我想在我的驱动程序代码中实现 8 毫秒的延迟。我使用了
msleep
函数,但是我发现我只循环了两次。 dmesg
中两次打印的时间差其实是10ms,不应该是2ms吗?有什么问题吗?
dmesg
的一部分:
[ 386.199343] this is ioctl
[ 386.199359] ioctl value = 0
[ 386.210085] ioctl value = 1
驱动程序代码中的ioctl
功能:
static long spiio_drv_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
int i = 0;
int value = 0;
int *param = (int *)arg;
printk("this is ioctl\n");
switch (cmd)
{
case 0: /*no sleep*/
*param = 0;
case 1: /*SPIIO_IOCTL_CMD*/
do {
value = gpio_get_value(spiio_gpio);
printk("ioctl value = %d\n", value);
if (value == 1) {
return 1;
} else {
if (*param) {
msleep(1);
}
i ++;
}
} while(i < *param);
break;
default:
printk("spiio ioctrl cmd %d is error", cmd);
return -1;
}
return 0;
}
有什么问题吗?
有2个问题。
第一个问题是,当无法保证任何延迟或睡眠不会比请求的时间长得多时,您期望它是精确的(例如,在请求的时间到期之后但在函数之前可能会发生中断或任务切换)将控制权返回给您的代码)。在循环中,最好的替代方案是某种带有
sleep_until(when)
的 when += period;
,这样如果前一个睡眠需要更长的时间,那么下一个睡眠会自动补偿(通过更短),并且额外不需要的时间不能/不会累积并且随着循环的每次迭代而变得更糟。此外,msleep()
可能不是最好使用的函数(例如与 usleep_range()
相比)。
另一个问题是
msleep()
在Linux中的实现很糟糕。具体来说,Linux 有两种不同的计时系统。对于第一个/最旧的系统,一个计时器被配置为以固定频率(例如 100 Hz 或每 10 毫秒)生成一个中断,然后所有内容(所有时间延迟、所有调度等)都建立在其之上。当没有多核 CPU 和电源管理的时候,这被认为是“可以接受的”。第二个/较新的系统称为“无滴答”,并涉及设置底层(硬件)计时器以在“尽可能精确”的最快到期时间创建中断。因为你的时间延迟正好是 10 毫秒,所以 Linux 很可能正在使用旧的/坏的“10 毫秒滴答”系统,并且尚未更新为使用更新的/更好的“无滴答”系统。
当然,您希望的“理论上最好的”是一种分割方法,其中内核确定它可以安全睡眠的最长时间(运行其他任务或节省电量),然后执行一个小的最终“忙等待”循环以获得最大限度。精确。 AFAIK Linux 从来没有为任何类型的延迟或睡眠的“ticks”或“tickless”这样做过;但您也许可以自己实现它(例如,使用
schedule_timeout_uninterruptible()
和 ktime_get()
的组合,实现某种 wait_until(when)
)。
注意:您还可以通过重新配置内核(启用“CONFIG_HZ=1000”或无滴答)来使 Linux 不那么糟糕;但如果您需要确保您的代码对其他人有效,您可能会想做相反的事情。