[TOC]

在这里插入图片描述

include/linux/ktime.h

ktime_set 从秒/纳秒值设置ktime_t变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/**
* ktime_set - 从秒/纳秒值设置ktime_t变量
* @secs:秒设置
* @nsecs:纳秒设置
*
* 返回:值的ktime_t表示形式。
*/
static inline ktime_t ktime_set(const s64 secs, const unsigned long nsecs)
{
if (unlikely(secs >= KTIME_SEC_MAX))
return KTIME_MAX;

return secs * NSEC_PER_SEC + (s64)nsecs;
}

/* Subtract two ktime_t variables. rem = lhs -rhs: */
#define ktime_sub(lhs, rhs) ((lhs) - (rhs))

/* Add two ktime_t variables. res = lhs + rhs: */
#define ktime_add(lhs, rhs) ((lhs) + (rhs))


/* 将 TimeSpec64 转换为 ktime_t 格式: */
static inline ktime_t timespec64_to_ktime(struct timespec64 ts)
{
return ktime_set(ts.tv_sec, ts.tv_nsec);
}

include/linux/time64.h

timespec64_sub sub = lhs - rhs,以标准化形式

1
2
3
4
5
6
7
8
9
10
11
/*
* sub = lhs - rhs,以标准化形式
*/
static inline struct timespec64 timespec64_sub(struct timespec64 lhs,
struct timespec64 rhs)
{
struct timespec64 ts_delta;
set_normalized_timespec64(&ts_delta, lhs.tv_sec - rhs.tv_sec,
lhs.tv_nsec - rhs.tv_nsec);
return ts_delta;
}

timespec64_valid Timespec64结构体 有效性验证

1
2
3
4
5
6
7
8
9
10
11
12
13
/*
*如果 timespec64 为 norm,则返回 true,如果 denorm,则返回 false:
*/
static inline bool timespec64_valid(const struct timespec64 *ts)
{
/* Dates before 1970 are bogus */
if (ts->tv_sec < 0)
return false;
/* Can't have more nanoseconds then a second */
if ((unsigned long)ts->tv_nsec >= NSEC_PER_SEC)
return false;
return true;
}

timespec64_valid_settod Timespec64结构体 有效性 和 溢出验证

1
2
3
4
5
6
7
8
9
static inline bool timespec64_valid_settod(const struct timespec64 *ts)
{
if (!timespec64_valid(ts))
return false;
/* 不允许导致溢出问题的值 vs. CLOCK_REALTIME */
if ((unsigned long long)ts->tv_sec >= TIME_SETTOD_SEC_MAX)
return false;
return true;
}

timespec64_compare Timespec64 比较

1
2
3
4
5
6
7
8
9
10
11
12
13
/*
* lhs < rhs: return <0
* lhs == rhs: return 0
* lhs > rhs: return >0
*/
static inline int timespec64_compare(const struct timespec64 *lhs, const struct timespec64 *rhs)
{
if (lhs->tv_sec < rhs->tv_sec)
return -1;
if (lhs->tv_sec > rhs->tv_sec)
return 1;
return lhs->tv_nsec - rhs->tv_nsec;
}

include/linux/timerqueue.h

timerqueue_getnext 返回具有最早到期时间的定时器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* timerqueue_getnext - 返回具有最早到期时间的定时器
*
* @head: 定时器队列的头部
*
* 返回指向具有最早到期时间的定时器节点的指针。
*/
static inline
struct timerqueue_node *timerqueue_getnext(struct timerqueue_head *head)
{
struct rb_node *leftmost = rb_first_cached(&head->rb_root);

return rb_entry_safe(leftmost, struct timerqueue_node, node);
}

kernel/sched/cputime.c

account_process_tick 统计一个tick的CPU时间

  • 它的核心作用是:将一个时钟节拍(tick)所代表的CPU时间(TICK_NSEC纳秒),根据当前上下文(用户态、内核态、或空闲态),精确地累加到目标任务p或系统的相应时间统计中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
/*
* 核算一整个tick的CPU时间。
* @p: CPU时间被记到其头上的那个进程。
* @user_tick: 指示这个tick是用户时间还是系统时间。
*/
void account_process_tick(struct task_struct *p, int user_tick)
{
/* cputime: 存储一个tick代表的总纳秒数。*/
u64 cputime;
/* steal: 存储从虚拟机宿主机那里被“窃取”的CPU时间。*/
u64 steal;

/* 如果当前CPU开启了虚拟时间记账(vtime),则不使用基于tick的记账,直接返回。*/
// if (vtime_accounting_enabled_this_cpu())
// return;

/* 如果开启了精确中断时间记账(irqtime)... */
// if (irqtime_enabled()) {
/* ...则将记账工作委托给专门的irqtime处理函数并返回。*/
// irqtime_account_process_tick(p, user_tick, 1);
// return;
// }

/* 获取一个tick周期的标准纳秒数。*/
cputime = TICK_NSEC;
/*
* 计算并核算从上次记账以来,被hypervisor窃走的CPU时间。
* ULONG_MAX表示我们不关心具体的cycle数,只关心时间。
*/
// steal = steal_account_process_time(ULONG_MAX);

/* 如果被窃取的时间大于等于整个tick周期,说明本tick内没有有效时间,直接返回。*/
// if (steal >= cputime)
// return;

/* 从总时间中减去被窃取的时间,得到任务实际可用的CPU时间。*/
// cputime -= steal;

/* 如果这个tick是在用户态发生的...*/
if (user_tick)
/* ...则调用account_user_time,将cputime记为任务p的用户态时间。*/
account_user_time(p, cputime);
/* 否则,如果是在内核态发生的,并且当前任务p不是空闲线程(idle)...
* (irq_count() != HARDIRQ_OFFSET这个检查用于排除从idle循环中
* 发生的、但不在硬中断上下文中的tick)*/
else if ((p != this_rq()->idle) || (irq_count() != HARDIRQ_OFFSET))
/* ...则调用account_system_time,将cputime记为任务p的内核态时间。
* HARDIRQ_OFFSET用于区分是纯内核态还是硬中断上下文。*/
account_system_time(p, HARDIRQ_OFFSET, cputime);
else
/* 否则,如果是在内核态,并且任务是idle线程,则调用account_idle_time,
* 将cputime记为该CPU的空闲时间。*/
account_idle_time(cputime);
}

arch/arm/kernel/time.c

time_init

1
2
3
4
5
6
7
8
9
10
11
12
void __init time_init(void)
{
if (machine_desc->init_time) {
machine_desc->init_time();
} else {
#ifdef CONFIG_COMMON_CLK
of_clk_init(NULL);
#endif
timer_probe();
tick_setup_hrtimer_broadcast();
}
}

kernel/time/time.c

set_normalized_timespec64 设置并规范化 timespec64 结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/**
* set_normalized_timespec64 - 设置 TimeSpec 秒和 NSec 部分并归一化
*
* @ts:指向要设置的 timespec 变量的指针
* @sec:秒设置
* @nsec:纳秒设置
*
* 设置 timespec 变量的 seconds 和 náns 字段,并规范化为 timespec 存储格式
*
* 注意:tv_nsec部分始终在 0 <= tv_nsec < NSEC_PER_SEC 的范围内。对于负值,只有 tv_sec 字段为负数!
*/
void set_normalized_timespec64(struct timespec64 *ts, time64_t sec, s64 nsec)
{
/* 如果 nsec 的值大于或等于 NSEC_PER_SEC(每秒的纳秒数,通常为 10^9),则通过循环将多余的纳秒转换为秒 */
while (nsec >= NSEC_PER_SEC) {
/*
*以下 asm() 可防止编译器将此循环优化为模运算。
* 另请参见 include/linux/time.h 中的 __iter_div_u64_rem()
*/
/* 每次循环减少 NSEC_PER_SEC 纳秒,同时将对应的秒数加到 sec 中 */
asm("" : "+rm"(nsec));
nsec -= NSEC_PER_SEC;
++sec;
}
/* 如果 nsec 的值小于 0,则通过循环将负的纳秒值转换为秒 */
while (nsec < 0) {
asm("" : "+rm"(nsec));
nsec += NSEC_PER_SEC;
--sec;
}
ts->tv_sec = sec;
ts->tv_nsec = nsec;
}
EXPORT_SYMBOL(set_normalized_timespec64);

ns_to_timespec64 - 将纳秒转换为 timespec64

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/**
* ns_to_timespec64 - 将纳秒转换为 timespec64
* @nsec:要转换的纳秒值
*
* 返回:nsec 参数的 timespec64 表示形式。
*/
struct timespec64 ns_to_timespec64(s64 nsec)
{
struct timespec64 ts = { 0, 0 };
s32 rem;

if (likely(nsec > 0)) {
ts.tv_sec = div_u64_rem(nsec, NSEC_PER_SEC, &rem);
ts.tv_nsec = rem;
} else if (nsec < 0) {
/*
* With negative times, tv_sec points to the earlier
* second, and tv_nsec counts the nanoseconds since
* then, so tv_nsec is always a positive number.
*/
ts.tv_sec = -div_u64_rem(-nsec - 1, NSEC_PER_SEC, &rem) - 1;
ts.tv_nsec = NSEC_PER_SEC - rem - 1;
}

return ts;
}
EXPORT_SYMBOL(ns_to_timespec64);

kernel/time/ntp.c

__ntp_clear

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
static void __ntp_clear(struct ntp_data *ntpdata)
{
/* Stop active adjtime() */
ntpdata->time_adjust = 0;
ntpdata->time_status |= STA_UNSYNC;
ntpdata->time_maxerror = NTP_PHASE_LIMIT;
ntpdata->time_esterror = NTP_PHASE_LIMIT;

ntp_update_frequency(ntpdata);

ntpdata->tick_length = ntpdata->tick_length_base;
ntpdata->time_offset = 0;

ntpdata->ntp_next_leap_sec = TIME64_MAX;
/* Clear PPS state variables */
pps_clear(ntpdata);
}

ntp_clear

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
static struct ntp_data tk_ntp_data = {
.tick_usec = USER_TICK_USEC,
.time_state = TIME_OK,
.time_status = STA_UNSYNC,
.time_constant = 2,
.time_maxerror = NTP_PHASE_LIMIT,
.time_esterror = NTP_PHASE_LIMIT,
.ntp_next_leap_sec = TIME64_MAX,
};

/**
* ntp_clear - Clears the NTP state variables
*/
void ntp_clear(void)
{
__ntp_clear(&tk_ntp_data);
}

ntp_init

1
2
3
4
5
6
7
8
9
10
static void __init ntp_init_cmos_sync(void)
{
hrtimer_setup(&sync_hrtimer, sync_timer_callback, CLOCK_REALTIME, HRTIMER_MODE_ABS);
}

void __init ntp_init(void)
{
ntp_clear();
ntp_init_cmos_sync();
}

kernel/time/sleep_timeout.c

msleep & schedule_timeout: 任务睡眠与延时调度

本代码片段展示了Linux内核中任务(线程)进行延时操作的核心实现。其主要功能由schedule_timeout函数族提供,它允许当前任务进入不同类型的睡眠状态(可中断、不可中断等),并设置一个定时器在指定时间后将其唤醒。msleep则是在此基础上封装的一个更易于使用的、以毫秒为单位的不可中断睡眠函数。

实现原理分析

该机制是内核调度器与内核定时器子系统协同工作的经典范例。它通过组合任务状态变更、定时器设置和调度器调用,实现了精确的、可配置的延时功能。

  1. 核心引擎 (schedule_timeout):

    • 这是所有延时功能的基础。其核心思想是在当前任务的栈上创建一个临时的内核定时器(struct timer_list)。
    • 定时器设置: 该函数计算出未来的超时时刻(expire = timeout + jiffies),并设置一个定时器。当定时器到期时,内核会调用其回调函数process_timeout
    • 唤醒机制: process_timeout回调函数的唯一作用就是调用wake_up_process,将创建该定时器的任务(timer.task)的状态从睡眠态(如 TASK_UNINTERRUPTIBLE)变更为就绪态(TASK_RUNNING),使其能够被调度器再次选中执行。
    • 放弃CPU: 设置好定时器后,函数会调用schedule()。这个调用会主动触发调度器,让出CPU给其他处于就緒态的任务。此时,由于当前任务处于睡眠状态,它不会被调度器选中,从而实现了“睡眠”。
    • 同步与清理: 当任务被唤醒并重新获得CPU后,它会从schedule()调用处继续执行。timer_delete_sync()是一个关键的同步函数,它确保在函数返回、栈上的定时器变量被销毁之前,该定时器已经被完全停用,并且其回调函数(process_timeout)没有也绝不会在任何CPU上运行。
  2. 睡眠状态的控制 (schedule_timeout_* 变体):

    • 内核中的睡眠分为多种状态,主要区别在于能否被信号唤醒。
    • schedule_timeout_uninterruptible: 将任务状态设置为TASK_UNINTERRUPTIBLE。在这种状态下,任务只能被显式的wake_up_process调用唤醒,完全忽略信号。这是最“深”的睡眠,用于等待必要资源且不能被用户干预的场景。
    • schedule_timeout_interruptible: 将任务状态设置为TASK_INTERRUPTIBLE。任务既可以被定时器到期唤醒,也可以被未屏蔽的信号提前唤醒。
    • schedule_timeout_killable: 任务状态为TASK_KILLABLE,是前两者的折中。它会忽略普通信号,但可以被致命信号(如SIGKILL)中断。
    • 这些函数本质上都是在调用核心的schedule_timeout之前,先通过__set_current_state()设置好任务状态的简单封装。
  3. 高层封装 (msleep):

    • 这是一个对驱动开发者更友好的API,它接受毫秒作为参数。
    • 它首先将毫秒转换为内核内部的时间单位jiffies
    • 它在一个while循环中调用schedule_timeout_uninterruptible。这种循环结构非常健壮,即使schedule_timeout因为某些罕见原因(虽然在uninterruptible模式下几乎不可能)提前返回,它也会重新计算剩余的超时时间,并继续睡眠,直到总的睡眠时间达到要求。

代码分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
/**
* @brief 以毫秒为单位进行不可中断的睡眠。
* @param msecs 要睡眠的毫秒数。
*
* @details 该函数使用基于jiffies的超时机制。由于内核定时器轮的设计,
* 实际的睡眠时间可能会有一定的正向偏差(slack)。
* 它会安全地处理睡眠过程,即使被(理论上不可能的)唤醒信号中断,
* 也会继续睡完剩余的时间。
*/
void msleep(unsigned int msecs)
{
/* 将毫秒转换为内核时间单位 jiffies。 */
unsigned long timeout = msecs_to_jiffies(msecs);

/* 循环调用 schedule_timeout_uninterruptible 直到超时用尽。*/
/* 这确保了即使被意外唤醒,也能睡足指定的总时长。*/
while (timeout)
timeout = schedule_timeout_uninterruptible(timeout);
}
EXPORT_SYMBOL(msleep);

/**
* @struct process_timer
* @brief 一个在栈上创建的辅助结构,用于将定时器与特定任务关联。
*/
struct process_timer {
struct timer_list timer; /*!< 内核定时器对象。 */
struct task_struct *task; /*!< 定时器到期时需要唤醒的任务。 */
};

/**
* @brief schedule_timeout 所使用的定时器回调函数。
* @param t 指向触发此回调的 timer_list 结构的指针。
*/
static void process_timeout(struct timer_list *t)
{
/* 从 timer_list 指针找到其容器 process_timer 结构的指针。 */
struct process_timer *timeout = timer_container_of(timeout, t, timer);

/* 唤醒与此定时器关联的任务。 */
wake_up_process(timeout->task);
}

/**
* @brief 使当前任务睡眠,直到指定的jiffies超时。
* @param timeout 以jiffies为单位的超时值。
* @return signed long 0表示定时器已到期,否则返回剩余的jiffies。
*
* @details 函数的行为取决于当前任务的状态:
* - TASK_RUNNING: 只调用调度器,任务不睡眠。
* - TASK_UNINTERRUPTIBLE: 任务会睡眠,直到超时或被显式唤醒。
* - TASK_INTERRUPTIBLE: 任务可能会因信号或显式唤醒而提前返回。
* 函数返回时,任务状态保证是 TASK_RUNNING。
* 若timeout为MAX_SCHEDULE_TIMEOUT,则任务会无限期睡眠。
*/
signed long __sched schedule_timeout(signed long timeout)
{
struct process_timer timer;
unsigned long expire;

switch (timeout) {
case MAX_SCHEDULE_TIMEOUT:
/* 对于无限期超时,直接调用调度器让出CPU。 */
schedule();
goto out;
default:
/* 检查并处理无效的负数超时值。 */
if (timeout < 0) {
pr_err("%s: 错误的超时值 %lx\n", __func__, timeout);
dump_stack();
__set_current_state(TASK_RUNNING);
goto out;
}
}

/* 计算定时器的绝对到期时间(jiffies)。 */
expire = timeout + jiffies;

/* 将定时器与当前任务关联。 */
timer.task = current;
/* 在栈上初始化定时器,并设置回调函数。 */
timer_setup_on_stack(&timer.timer, process_timeout, 0);
timer.timer.expires = expire;
/* 将定时器添加到内核的定时器列表中。 */
add_timer(&timer.timer);

/* 调用调度器,让出CPU,进入睡眠。 */
schedule();
/* 当任务被唤醒后,从这里继续执行。同步并删除定时器。*/
/* 这确保定时器回调不会在函数返回后被执行。*/
timer_delete_sync(&timer.timer);

/* 从对象跟踪器中移除定时器。 */
timer_destroy_on_stack(&timer.timer);

/* 计算剩余的超时时间。 */
timeout = expire - jiffies;

out:
/* 返回值确保为非负数。 */
return timeout < 0 ? 0 : timeout;
}
EXPORT_SYMBOL(schedule_timeout);

/**
* @brief 进行可被信号中断的睡眠,直到超时。
* @param timeout 以jiffies为单位的超时值。
* @return signed long 剩余的jiffies或0。
*/
signed long __sched schedule_timeout_interruptible(signed long timeout)
{
/* 将当前任务状态设置为可中断。 */
__set_current_state(TASK_INTERRUPTIBLE);
/* 调用核心的超时函数。 */
return schedule_timeout(timeout);
}
EXPORT_SYMBOL(schedule_timeout_interruptible);

/**
* @brief 进行可被致命信号中断的睡眠,直到超时。
* @param timeout 以jiffies为单位的超时值。
* @return signed long 剩余的jiffies或0。
*/
signed long __sched schedule_timeout_killable(signed long timeout)
{
/* 将当前任务状态设置为可被致命信号中断。 */
__set_current_state(TASK_KILLABLE);
/* 调用核心的超时函数。 */
return schedule_timeout(timeout);
}
EXPORT_SYMBOL(schedule_timeout_killable);

/**
* @brief 进行不可中断的睡眠,直到超时。
* @param timeout 以jiffies为单位的超时值。
* @return signed long 剩余的jiffies或0。
*/
signed long __sched schedule_timeout_uninterruptible(signed long timeout)
{
/* 将当前任务状态设置为不可中断。 */
__set_current_state(TASK_UNINTERRUPTIBLE);
/* 调用核心的超时函数。 */
return schedule_timeout(timeout);
}
EXPORT_SYMBOL(schedule_timeout_uninterruptible);