当前位置: 首页 > news >正文

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);

关键的三步操作:

  1. add_timer(&timer):将定时器加入内核定时器队列
  2. schedule():让出CPU,进程进入睡眠状态
    • 这是核心的睡眠点
    • 进程状态会在调用前被设置为TASK_INTERRUPTIBLE等,需要在调用schedule_timeout函数之前设置
  3. 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 <= 10001000 % 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 > 1000HZ % 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:被信号中断,剩余的时间
http://www.dtcms.com/a/460925.html

相关文章:

  • 企业制作网站一般多少钱如何选择网站关键词
  • 记录一下Unity的BUG,Trial Version
  • 【bug日记】python找不到包
  • 23大数据 数据挖掘复习1
  • 微信小程序反编译教程
  • 使用AWS IAM和Python自动化权限策略分析与导出
  • 网站建设的总体目标是什么编程代码大全
  • AWS WAF 防护目录列表漏洞:完整实施指南
  • 【pycharm】识别uv路径
  • 当数据仓库遇见AI:金融风控的「认知大脑」正在觉醒
  • 【markdown】win11部署微软markitdown支持格式转换markdown
  • 微软发布Azure容器存储v2.0.0国际版
  • 建一个展示网站下班多少钱企业网站建设 cms
  • Canvas 图形绘制与高级功能
  • ML-Agents 学习笔记
  • 建设微信营销网站一站式网站建设哪家专业
  • Java 大视界 -- Java 大数据在智慧交通自动驾驶仿真与测试数据处理中的应用
  • 广东省省考备考(第一百一十九天10.9)——言语、判断推理(强化训练)
  • 字段行居中(HTML基础语法)
  • [特殊字符] 基于 Qt + OpenGL 实现的入门级打砖块游戏
  • 【MySQL】库与表的基础操作
  • Vue前端开发学习的简单记录
  • 营销型建设网站实训总结wordpress jsp
  • Qt MVC架构及其应用
  • 头歌Educator机器学习与数据挖掘-逻辑回归
  • 安装kafka-2.5.1
  • 基于KAN融合的混合CNN-Transformer模型应用于皮肤癌分类
  • 【rabbitmq】RabbitMQ 全面详解:从核心概念到高级应用
  • Kafka 面试题及详细答案100道(91-95)-- 问题排查与解决方案1
  • CentOS 7 升级perl版本到5.40.3 —— 筑梦之路