linux kernel中的延时(二)

本文详细介绍了Linux设备驱动中两种主要的延时等待技术:忙等待和休眠等待。忙等待包括mdelay和使用time_before接口实现的延时,虽然精度高但消耗CPU资源。休眠等待如usleep_range、msleep和wait_event_interruptible_timeout则在减少CPU占用的同时可能精度稍逊。文章通过实例展示了各种延时函数的使用和效果,并提供了源代码供参考。

1.概览

  在设备驱动中,有时候在驱动中延迟执行一些指令以等待硬件完成。延时一般分为两大类,第一类为忙等待即CPU在此期间执行的指令目的地仅仅是为了消耗时间。第二类为休眠等待唤醒,即在延时点进入休眠,然后被唤醒,这也就实现了驱动的等待功能。

2.CPU忙等待延时

2.1 mdelay

  代码过于简单,下面则是示例代码

time_test_drv_init
    DELAY_TEST_INFO("start:%lld ns", ktime_get_boottime_ns());
    mdelay(1000);
    DELAY_TEST_INFO("after mdelay:%lld ns", ktime_get_boottime_ns());

  执行结果如下

[  121.908834] delayTest:delay_test_drv_init,27:start:121905834566 ns
[  122.931042] delayTest:delay_test_drv_init,33:after mdelay:122928044142 ns

2.2 使用 time_before 接口实现忙等待

  使用之前提到过的接口 time_before 再配合循环也是可以实现忙等待的,示例代码如下

unsigned long delayTime = jiffies + HZ;//delay 1s
while(time_before(jiffies, delayTime));

  实现的逻辑很简单,在每一个循环中都判断下当前时间是否超期预设值(delayTime),如果超期了那么就不成立退出循环了。
  使用这类的等待实际上非常浪费CPU的,所以尽量能不使用则不使用吧。

3.休眠等待延时

  这一类的等待在精度上是不如忙等待的,因为这需要一个休眠唤醒的过程,但对CPU的消耗则会小的多。不过对于这类接口是不能在原子和中断上下文中调用的,在使用这些接口时一定要确认当前所在的上下文。

3.1 usleep_range

  使用该接口来设置延时,内核会根据所设置的范围来决定能否复用已有的唤醒中断,因此相较于usleep这样更加节省系统资源。下面是使用示例

usleep_range(1000000,1500000);
DELAY_TEST_INFO("after usleep_range:%lld ns", ktime_get_boottime_ns());

  usleep_range的入参单位都是微妙,所以在示例代码中设置了本次延时可以接收的范围是1s~1.5s。
  下面是执行结果

[  122.931042] delayTest:delay_test_drv_init,33:after mdelay:122928044142 ns
[  124.256229] delayTest:delay_test_drv_init,38:after usleep_range:124253207756 ns

  124253207756 - 122928044142 = 1,325,163,614ns,可见还是满足期望值的。

3.2 msleep

  调用该接口后会在设置的毫秒之后被唤醒,但是接口msleep_interruptible则是可以被消耗所打断的,例如在调用msleep_interruptible进入休眠后,有信号发送到当前进入休眠等待的进程,那么该进程也会被唤醒,那么此时的休眠时间就不够所设了,下面时示例代码

msleep(1000);//http://lkml.org/lkml/2007/8/3/250
DELAY_TEST_INFO("after msleep:%lld ns", ktime_get_boottime_ns());
msleep_interruptible(1000);
DELAY_TEST_INFO("after msleep_interruptible:%lld ns", ktime_get_boottime_ns());

  如下则为执行结果

[  124.256229] delayTest:delay_test_drv_init,38:after usleep_range:124253207756 ns
[  125.280232] delayTest:delay_test_drv_init,40:after msleep:125277210986 ns
[  126.303898] delayTest:delay_test_drv_init,42:after msleep_interruptible:126300866101 ns

3.5 wait_event_interruptible_timeout

  这种延迟的实现是有点取巧的,但是好处就是延迟时间单位可以和jiffies的精度一致。本接口只要满足下面的任意一种条件则会返回,
   a)条件为真时
   c)当timeout时间用完时
  所以这里将条件设置为0,那么也就实现了延时的功能,下面是示例代码

struct wait_queue_head wait_head;
init_waitqueue_head(&wait_head);
wait_event_timeout(wait_head, 0 , HZ);//1s delay
DELAY_TEST_INFO("after wait_event_timeout:%lld ns", ktime_get_boottime_ns());

  执行结果如下

[  341.376510] delayTest:delay_test_drv_init,52:after msleep_interruptible:341373267986 ns new
[  342.400526] delayTest:delay_test_drv_init,59:after wait_event_timeout:342397288024 ns

4.源码如下

delayTest

https://gitee.com/solo-king/linux-kernel-base-usage/blob/master/flagstaff/delayTest.c
Linux 内核中使用多个定时器(multiple timers)是非常常见的需求,比如驱动程序需要周期性地轮询硬件状态、延时执行某些任务等。Linux 内核提供了基于 `timer_list` 结构的定时器机制,支持创建和管理多个独立的定时器。 下面是如何在 Linux 内核模块中使用多个定时器的完整示例。 --- ### ✅ 示例:使用多个内核定时器 ```c #include <linux/module.h> #include <linux/kernel.h> #include <linux/timer.h> #include <linux/jiffies.h> // 定义两个定时器结构体 static struct timer_list my_timer1; static struct timer_list my_timer2; // 定时器 1 的回调函数 void timer1_callback(struct timer_list *t) { pr_info("Timer 1: Hello from timer 1! jiffies = %lu\n", jiffies); // 如果你想让它重复运行,重新设置过期时间并添加回定时器系统 mod_timer(&my_timer1, jiffies + msecs_to_jiffies(1000)); // 1秒后再次触发 } // 定时器 2 的回调函数 void timer2_callback(struct timer_list *t) { pr_info("Timer 2: Hello from timer 2! jiffies = %lu\n", jiffies); mod_timer(&my_timer2, jiffies + msecs_to_jiffies(2000)); // 2秒后再次触发 } // 模块初始化函数 static int __init multiple_timers_init(void) { pr_info("Multiple timers module loaded.\n"); // 初始化定时器 1 timer_setup(&my_timer1, timer1_callback, 0); my_timer1.expires = jiffies + msecs_to_jiffies(1000); // 1秒后第一次触发 add_timer(&my_timer1); // 初始化定时器 2 timer_setup(&my_timer2, timer2_callback, 0); my_timer2.expires = jiffies + msecs_to_jiffies(2000); // 2秒后第一次触发 add_timer(&my_timer2); return 0; } // 模块退出函数 static void __exit multiple_timers_exit(void) { int ret; // 删除定时器(如果正在运行则返回1) ret = del_timer(&my_timer1); if (ret) pr_info("Timer 1 was still in use...\n"); ret = del_timer(&my_timer2); if (ret) pr_info("Timer 2 was still in use...\n"); pr_info("Multiple timers module unloaded.\n"); } module_init(multiple_timers_init); module_exit(multiple_timers_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Your Name"); MODULE_DESCRIPTION("A simple Linux kernel module demonstrating multiple timers"); ``` --- ### 🔍 解释说明: - **`struct timer_list`**:这是内核老版本就存在的定时器结构,在现代内核(>=4.15)中推荐配合 `timer_setup()` 使用。 - **`timer_setup()`**:用于安全初始化定时器,替代旧的直接赋值方式(避免竞争条件)。 - **`mod_timer()`**:修改或启动一个定时器,常用于实现周期性执行。 - **`jiffies` 和 `msecs_to_jiffies()`**:将毫秒转换为节拍数(jiffies),是内核中常用的时间单位。 - **`add_timer()`**:激活并添加定时器到内核调度系统。 - **`del_timer()`**:安全删除一个定时器,防止已删除的定时器被再次触发。 > ⚠️ 注意事项: > - 所有定时器回调函数运行在**中断上下文**中,不能调用可能睡眠的函数(如 `msleep`, `kmalloc(..., GFP_KERNEL)`, `printk` 除外)。 > - 若需执行耗时操作,请结合工作队列(workqueue)使用。 --- ### ✅ 可扩展性建议 你可以通过以下方式进一步优化多定时器管理: 1. **动态分配定时器**:使用 `kzalloc` 动态创建多个定时器对象。 2. **使用 `hrtimer` 高精度定时器**:如果你需要微秒级精度,应改用 `struct hrtimer`。 3. **封装结构体**:将定时器与其数据绑定在一个结构中,便于管理。 例如: ```c struct my_device { struct timer_list timer; int id; char name[16]; }; ``` 然后在 `timer_callback` 中通过 `container_of(t, struct my_device, timer)` 获取上下文。 --- ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值