从事Linux内核开发特别是驱动开发的小伙伴,肯定需要经常使用到定时器,比如,按键的去抖、LED屏幕显存buffer的刷新等。同时,在控制硬件时,可能会用到十分精确地短延时,这时,定时器的精度就不能满足这种需求了,这时就会使用到高精度定时器和忙等延时。今天就来简要说一下如何正确的使用内核提供的delay和sleep函数。
这篇文章面对的读者是从事与驱动程序开发,但是,对于内核delay和sleep实现机制不是很熟悉的开发人员。
如何插入delays
首先,你需要回答一个问题,“需要使用delay的代码存在于原子性的上下文中吗?”或者“是否真的需要在原子性的上下文中插入delay吗?”。对于初学者来说,可能对于原子性的上下文不是很理解,下面简要解释一下。
对于任何Linux程序来说,无非运行于以下几种上下文之中:1)进程-用户空间上下文; 2)进程-内核空间上下文;3)硬件中断上下文;4)软件中断上下文;其中,3)和4)为原子性的上下文,其使用遵循以下几种原则:
- 不允许访问用户空间。因为其没有处于任何进程上下文之中,而用户空间都是依附于进程上下文的,所以,原子性上下文中,无法将任何进程与用户空间关联;
- 原子性的上下文中,使用current指针是没有任何意义的。current指针指向当前的任务(进程或者线程),而原子性的上下文不与任何进程有关系;
- 不能执行任何休眠或者调度。原子性的上下文中,不能调用任何可能会引起进程切换、调度、休眠的函数,比如schedul、wait_event、kmalloc、copy_from_user、msleep、del_timer_sync等等。
原子性上下文
好了,解释了原子性的上下文之后,我们言归正传,继续说一下,原子性的上下文中延时函数都有哪些。
ndelay(unsigned long nsecs) udelay(unsigned long usecs) mdelay(unsigned long msecs)
这三个函数原理,都是通过使CPU处于忙等状态,直到指定的指令周期执行完毕。其中,mdelay函数是udelay函数的简单封装。ndelay不是在所有平台上都能达到ns的延时精度。
这三个函数一般用在需要延时很短时间的硬件驱动程序中,比如,等待网卡寄存器初始化完成,等待直流电机启动完成等。
一般情况下,mdelay是不推荐使用的,msleep是较好的替代方式。
非原子性上下文
非原子性上下文,应该使用*sleep[_range]函数族,该函数族中的任何函数都能正确执行,“合理的”使用这些函数,可以帮助调度器、电源管理模块、驱动程序更好的工作。
-- 基于忙等: udelay(unsigned long usecs) -- 基于hrtimers高分辨率定时器: usleep_range(unsigned long min, unsigned long max) -- 基于jiffies / legacy_timers msleep(unsigned long msecs) msleep_interruptible(unsigned long msecs)
除了udelay函数,其他函数都会使调用进程睡眠,从而被调度出去,这是合理的,因为简单的忙等会浪费大量的CPU时间,从而严重的降低系统性能。
使用惯例
对于不同的延时时间,使用何种延时函数,内核提供了一些惯例,下面一一说明一下。
延时几微妙(< 10us)
使用udelay
- 为何选用udelay? 对于嵌入式系统或者speed-steped PC,使用基于hrtimer实现的usleep,往往得不偿失,不过,这依赖于具体的使用环境而定,不过,你需要注意到这一点。
延时几毫秒(10us~20ms)
使用usleep_range
- 为何不使用msleep(1ms
20ms)?原始的解析,一般情况下,msleep(1ms20ms)往往不会达到使用者预期,其延时时长一般会稍微长于1ms~20ms,这对精度十分敏感的应用来说是不可接受的。
- 为何不存在usleep函数? 因为usleep_range基于hrtimer实现,所以其有很好的精度,而usleep会引起大量的中断,从而降低系统性能。
- 什么是合理的range?
通过引入范围,调度程序可以自由地将您的唤醒与由于其他原因而发生的任何其他唤醒结合在一起,或者在最坏的情况下,为您的上限触发中断。
您提供的范围越大,则不会触发中断的机会就越大; 这应该与特定代码路径在延迟/性能上可接受的上限之间取得平衡。 此处的精确公差是非常具体的情况,因此,由调用者确定一个合理的范围。
延时较大的毫秒(> 10ms)
使用msleep或者msleep_interruptible
- msleep和msleep_interruptible的区别?
msleep将当前任务设置为TASK_UNINTERRUPTIBLE,而msleep_interruptible将当前任务设置为TASK_INTERRUPTIBLE,然后调度睡眠。 简而言之,区别在于睡眠是否可以通过信号提前结束。 通常,除非知道需要使用可中断的变体,否则请使用msleep。