Linux中延迟相关函数的实现
一、调度超时函数schedule_timeout
fastcall signed long __sched schedule_timeout(signed long timeout)
{struct timer_list timer;unsigned long expire;switch (timeout){case MAX_SCHEDULE_TIMEOUT:/** These two special cases are useful to be comfortable* in the caller. Nothing more. We could take* MAX_SCHEDULE_TIMEOUT from one of the negative value* but I' d like to return a valid offset (>=0) to allow* the caller to do everything it want with the retval.*/schedule();goto out;default:/** Another bit of PARANOID. Note that the retval will be* 0 since no piece of kernel is supposed to do a check* for a negative retval of schedule_timeout() (since it* should never happens anyway). You just have the printk()* that will tell you if something is gone wrong and where.*/if (timeout < 0){printk(KERN_ERR "schedule_timeout: wrong timeout ""value %lx from %p\n", timeout,__builtin_return_address(0));current->state = TASK_RUNNING;goto out;}}expire = timeout + jiffies;init_timer(&timer);timer.expires = expire;timer.data = (unsigned long) current;timer.function = process_timeout;add_timer(&timer);schedule();del_singleshot_timer_sync(&timer);timeout = expire - jiffies;out:return timeout < 0 ? 0 : timeout;
}
1. 函数定义
fastcall signed long __sched schedule_timeout(signed long timeout)
fastcall
:x86架构的调用约定,参数通过寄存器传递__sched
:告诉编译器这是调度相关函数- 参数:
timeout
- 超时时间(以jiffies为单位) - 返回值:剩余的超时时间
2. 变量声明
struct timer_list timer;unsigned long expire;
timer
:内核定时器结构,用于实现超时expire
:超时到期的时间点(jiffies值)
3. 特殊情况处理
switch (timeout){case MAX_SCHEDULE_TIMEOUT:schedule();goto out;
MAX_SCHEDULE_TIMEOUT:表示无限等待
- 直接调用
schedule()
让出CPU,不设置定时器 - 需要其他事件(如信号)来唤醒进程
4. 参数验证
default:if (timeout < 0){printk(KERN_ERR "schedule_timeout: wrong timeout ""value %lx from %p\n", timeout,__builtin_return_address(0));current->state = TASK_RUNNING;goto out;}}
- 检查超时值是否为负
- 如果为负,打印错误信息,显示错误值和调用者地址
- 确保进程状态为运行状态后返回
5. 计算超时到期时间
expire = timeout + jiffies;
- 将相对超时时间转换为绝对到期时间
- 例如:
timeout=100
,当前jiffies=1000
,则expire=1100
6. 初始化定时器
init_timer(&timer);timer.expires = expire;timer.data = (unsigned long) current;timer.function = process_timeout;
init_timer()
:初始化定时器结构expires
:设置到期时间data
:传递当前进程的指针(转换为unsigned long)function
:超时回调函数process_timeout
7. 启动定时器和调度
add_timer(&timer);schedule();del_singleshot_timer_sync(&timer);
关键的三步操作:
add_timer(&timer)
:将定时器加入内核定时器队列schedule()
:让出CPU,进程进入睡眠状态- 这是核心的睡眠点
- 进程状态会在调用前被设置为TASK_INTERRUPTIBLE等,需要在调用
schedule_timeout
函数之前设置
del_singleshot_timer_sync(&timer)
:确保定时器被移除- 防止定时器在超时后误触发
8. 计算剩余时间
timeout = expire - jiffies;
- 计算剩余的超时时间
- 如果超时已到,结果为负;如果提前唤醒,结果为正
9. 返回处理
out:return timeout < 0 ? 0 : timeout;
- 如果超时已到(剩余时间<0),返回0
- 如果提前唤醒(剩余时间>0),返回剩余时间
10. 超时回调函数 process_timeout
static void process_timeout(unsigned long __data)
{wake_up_process((task_t *)__data);
}
唤醒进程
我来详细解释这个Linux内核延迟头文件的每一行代码:
二、毫秒延迟宏 mdelay
#ifdef notdef
#define mdelay(n) (\{unsigned long __ms=(n); while (__ms--) udelay(1000);})
#else
#define mdelay(n) (\(__builtin_constant_p(n) && (n)<=MAX_UDELAY_MS) ? udelay((n)*1000) : \({unsigned long __ms=(n); while (__ms--) udelay(1000);}))
#endif
情况1:编译时常量且小延迟(优化路径)
(__builtin_constant_p(n) && (n)<=MAX_UDELAY_MS) ? udelay((n)*1000) :
__builtin_constant_p(n)
:GCC内置函数,检查n是否为编译时常量- 如果是常量且≤5ms,直接调用
udelay(n*1000)
情况2:变量或大延迟(通用路径)
({unsigned long __ms=(n); while (__ms--) udelay(1000);})
- 循环调用
udelay(1000)
来实现毫秒延迟 - 适用于运行时变量或大于5ms的延迟
示例:
mdelay(3); // 编译时常量,≤5ms → 直接调用 udelay(3000)
mdelay(10); // 编译时常量,>5ms → 循环10次 udelay(1000)
mdelay(var); // 运行时变量 → 循环var次 udelay(1000)
三、纳秒延迟宏 ndelay
#ifndef ndelay
#define ndelay(x) udelay(((x)+999)/1000)
#endif
- 将纳秒延迟转换为微秒延迟
(x+999)/1000
:向上取整的整数除法- 例如:
ndelay(1500)
→udelay(2)
- 架构可以提供更精确的
ndelay
实现来覆盖这个默认定义
四、秒级睡眠函数 ssleep
static inline void ssleep(unsigned int seconds)
{msleep(seconds * 1000);
}
- 将秒转换为毫秒,然后调用
msleep()
五、微秒延迟宏 udelay
void __delay(unsigned long loops)
{cur_timer->delay(loops);
}
inline void __const_udelay(unsigned long xloops)
{int d0;xloops *= 4;__asm__("mull %0":"=d" (xloops), "=&a" (d0):"1" (xloops),"0" (current_cpu_data.loops_per_jiffy * (HZ/4)));__delay(++xloops);
}void __udelay(unsigned long usecs)
{__const_udelay(usecs * 0x000010c7); /* 2**32 / 1000000 (rounded up) */
}
#define udelay(n) (__builtin_constant_p(n) ? \((n) > 20000 ? __bad_udelay() : __const_udelay((n) * 0x10c7ul)) : \__udelay(n))
1. __const_udelay
函数
inline void __const_udelay(unsigned long xloops)
{int d0;xloops *= 4;
操作:xloops *= 4
- 将循环次数放大4倍,防止current_cpu_data.loops_per_jiffy * (HZ/4)
溢出32位
__asm__("mull %0":"=d" (xloops), "=&a" (d0):"1" (xloops),"0" (current_cpu_data.loops_per_jiffy * (HZ/4)));
汇编指令:
mull %0 # 无符号32位乘法
输出操作数:
"=d" (xloops)
:EDX寄存器输出到xloops
(结果的高32位)"=&a" (d0)
:EAX寄存器输出到d0(结果的低32位),&
表示早期破坏,输入输出使用同一个寄存器
输入操作数:
"1" (xloops)
:约束"1"表示使用与第二个输出操作数相同的寄存器(EAX)"0" (current_cpu_data.loops_per_jiffy * (HZ/4))
:约束"0"表示使用与第一个输出操作数相同的寄存器(EDX)
0x000010c7
-
相当于
2^32 / 1000000
-
xloops
计算的结果是取64位的高32位,相当于/ 2^32
,这里刚好抵消
循环次数
原始公式
xloops = usecs × (loops_per_jiffy × HZ) / 1,000,000当前公式
xloops = (usecs * 2^32 / 1000000 * 4 * loops_per_jiffy * (HZ/4)) / 2^32
↓
xloops = usecs * loops_per_jiffy * HZ / 1000000
↓
xloops的结果是当前微妙数要执行的空循环次数
__delay(++xloops);
__delay(++xloops)
:执行实际的延迟循环,根据loops的次数执行指定次数的空循环操作,比如指令rep;nop
++xloops
:先递增,确保至少循环1次
2. __udelay
函数
void __udelay(unsigned long usecs)
{__const_udelay(usecs * 0x000010c7); /* 2**32 / 1000000 (rounded up) */
}
关键魔法数字: 0x000010c7
计算原理:
2³² / 1,000,000 = 4,294,967,296 / 1,000,000 ≈ 4294.967296
向上取整:0x10c7 = 4295
为什么这样设计?
在__const_udelay
中,最终的循环次数计算为:
循环次数 = (usecs × 0x10c7 × loops_per_jiffy × HZ) / 2³²
由于 0x10c7 ≈ 2³² / 1,000,000
,所以:
循环次数 ≈ (usecs × loops_per_jiffy × HZ) / 1,000,000
而 loops_per_jiffy × HZ
就是每秒钟的循环次数(CPU频率相关),所以:
循环次数 ≈ usecs × (CPU频率) / 1,000,000
正好实现微秒延迟!
3. udelay
宏
#define udelay(n) (__builtin_constant_p(n) ? \((n) > 20000 ? __bad_udelay() : __const_udelay((n) * 0x10c7ul)) : \__udelay(n))
情况1:编译时常量且n ≤ 20000
__const_udelay((n) * 0x10c7ul)
- 编译器可以预先计算
n * 0x10c7
情况2:编译时常量但n > 20000
__bad_udelay()
- 20ms以上的延迟不应该用
udelay()
- 应该使用
msleep()
情况3:运行时变量
__udelay(n)
- 调用函数处理
六、毫秒级睡眠宏 msleep
static inline unsigned long msecs_to_jiffies(const unsigned int m)
{if (m > jiffies_to_msecs(MAX_JIFFY_OFFSET))return MAX_JIFFY_OFFSET;
#if HZ <= 1000 && !(1000 % HZ)return (m + (1000 / HZ) - 1) / (1000 / HZ);
#elif HZ > 1000 && !(HZ % 1000)return m * (HZ / 1000);
#elsereturn (m * HZ + 999) / 1000;
#endif
}
void msleep(unsigned int msecs)
{unsigned long timeout = msecs_to_jiffies(msecs) + 1;while (timeout) {set_current_state(TASK_UNINTERRUPTIBLE);timeout = schedule_timeout(timeout);}
}
1. msecs_to_jiffies 函数
static inline unsigned long msecs_to_jiffies(const unsigned int m)
- 作用:将毫秒转换为jiffies
- 参数:
m
- 毫秒数
1.1. 边界检查
if (m > jiffies_to_msecs(MAX_JIFFY_OFFSET))return MAX_JIFFY_OFFSET;
- 检查毫秒数是否超过jiffies能表示的最大值
MAX_JIFFY_OFFSET
:jiffies的最大安全值- 如果超过,返回最大值,防止溢出
1.2. 情况1:HZ是1000的约数
#if HZ <= 1000 && !(1000 % HZ)return (m + (1000 / HZ) - 1) / (1000 / HZ);
适用条件:
HZ <= 1000
且1000 % HZ == 0
(HZ能整除1000)- 例如:HZ = 100, 125, 200, 250, 500, 1000
计算公式:
(m + (1000/HZ) - 1) / (1000/HZ)
为什么这样计算?
1000/HZ
:每个jiffy的毫秒数+ (1000/HZ) - 1
:向上取整,防止为0- 除法:将毫秒转换为jiffies
(1000/HZ)
是编译时常量,可以将除法优化为乘法加移位
1.3. 情况2:HZ是1000的倍数
#elif HZ > 1000 && !(HZ % 1000)return m * (HZ / 1000);
适用条件:
HZ > 1000
且HZ % 1000 == 0
(1000能整除HZ)- 例如:HZ = 2000, 4000, 8000
计算公式:
m * (HZ / 1000)
- 将除法优化为乘法
1.4. 情况3:通用情况
#elsereturn (m * HZ + 999) / 1000;
#endif
适用条件: 其他所有情况
计算公式:
(m * HZ + 999) / 1000
为什么这样计算?
m * HZ
:将毫秒转换为"毫jiffies"+ 999
:向上取整/ 1000
:转换为真正的jiffies
2. msleep
函数
void msleep(unsigned int msecs)
{unsigned long timeout = msecs_to_jiffies(msecs) + 1;
- 作用:不可中断的毫秒睡眠
- 参数:
msecs
- 要睡眠的毫秒数 + 1
:确保至少睡眠1个jiffy,避免为0时立即返回
2.1. 睡眠循环
while (timeout) {set_current_state(TASK_UNINTERRUPTIBLE);timeout = schedule_timeout(timeout);}
}
2.1.1. 步骤1:设置进程状态
set_current_state(TASK_UNINTERRUPTIBLE);
- 将当前进程状态设置为不可中断睡眠
- 进程不会响应信号,会一直睡眠直到超时
2.1.2. 步骤2:调度超时
timeout = schedule_timeout(timeout);
- 调用
schedule_timeout()
让出CPU - 返回值:剩余的jiffies数
0
:超时已到>0
:提前唤醒,剩余时间
2.1.3. 步骤3:循环检查
- 如果
timeout > 0
,继续睡眠剩余时间 - 如果
timeout == 0
,退出循环
2.2. 为什么需要循环?
schedule_timeout()
可能提前返回:
- 内核调度器可能因为其他原因唤醒进程
- 循环确保睡眠足够的时间
2.3. 为什么 +1?
timeout = msecs_to_jiffies(msecs) + 1;
- 防止转换截断导致睡眠时间不足
- 确保至少睡眠1个jiffy
2.4. TASK_UNINTERRUPTIBLE 的意义
- 不可中断:不会被信号唤醒
- 保证睡眠时间:确保进程睡眠指定的毫秒数
- 适用场景:设备驱动、硬件操作等不能被打断的情况
七、毫秒级可中断睡眠宏 msleep_interruptible
unsigned long msleep_interruptible(unsigned int msecs)
{unsigned long timeout = msecs_to_jiffies(msecs) + 1;while (timeout && !signal_pending(current)) {set_current_state(TASK_INTERRUPTIBLE);timeout = schedule_timeout(timeout);}return jiffies_to_msecs(timeout);
}
1. 函数定义
unsigned long msleep_interruptible(unsigned int msecs)
- 作用:可中断的毫秒睡眠,可以被信号提前唤醒
- 参数:
msecs
- 要睡眠的毫秒数 - 返回值:剩余的毫秒数(如果被信号中断)
2. 变量初始化
unsigned long timeout = msecs_to_jiffies(msecs) + 1;
- 将毫秒转换为jiffies
+ 1
:确保至少睡眠1个jiffy,防止转换截断导致立即返回
3. 睡眠循环
while (timeout && !signal_pending(current)) {
timeout
:剩余的超时时间
> 0
:还有时间需要睡眠= 0
:超时已到,退出循环
!signal_pending(current)
:检查是否有待处理信号
signal_pending(current)
:检查当前进程是否有未处理的信号!
取反:没有待处理信号时才继续睡眠- 如果有信号,立即退出循环
4. 循环体
set_current_state(TASK_INTERRUPTIBLE);timeout = schedule_timeout(timeout);}
4.1. 步骤1:设置进程状态
set_current_state(TASK_INTERRUPTIBLE);
- 将当前进程状态设置为可中断睡眠
- 进程可以响应信号而被唤醒
- 与
msleep()
的TASK_UNINTERRUPTIBLE
关键区别
4.2. 步骤2:调度超时
timeout = schedule_timeout(timeout);
- 让出CPU,进入睡眠状态
- 返回值:
0
:超时已到> 0
:提前唤醒,剩余时间- 可能被信号或调度器唤醒
5. 返回值处理
return jiffies_to_msecs(timeout);
}
- 将剩余的jiffies转换回毫秒
- 返回剩余的毫秒数
- 返回值含义:
0
:完整睡眠了指定时间> 0
:被信号中断,剩余的时间