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.源码如下
https://gitee.com/solo-king/linux-kernel-base-usage/blob/master/flagstaff/delayTest.c