技术开发 频道

Linux驱动开发必看:详解神秘内核

  2.4.2 长延时

  在内核中,以jiffies为单位进行的延迟通常被认为是长延时。一种可能但非非常好的的实现长延时的方法是忙等待。实现忙等待的函数有“占着茅坑不拉屎”之嫌,它本身不利用CPU进行有用的工作,同时还不让其他程序使用CPU。如下代码将占用CPU 1秒:

  unsigned long timeout = jiffies + HZ;

  while (time_before(jiffies, timeout)) continue;

  实现长延时的更好方法是睡眠等待而不是忙等待,在这种方式中,本进程会在等待时将处理器出让给其他进程。schedule_timeout()完成此功能:

  unsigned long timeout = HZ;

  schedule_timeout(timeout); /* Allow other parts of the kernel to run */

  这种延时仅仅确保超时较低时的精度。由于只有在时钟节拍引发的内核调度才会更新jiffies,所以无论是在内核空间还是在用户空间,都很难使超时的精度比HZ更大了。另外,即使你的进程已经超时并可被调度,但是调度器仍然可能基于优先级策略选择运行队列的其他进程[1]。

  用于睡眠等待的另2个函数是wait_event_timeout()和msleep(),它们的实现都基于schedule_timeout()。wait_event_timeout()的使用场合是:在一个特定的条件满足或者超时发生后,希望代码继续运行。msleep()表示睡眠指定的时间(以毫秒为单位)。

  这种长延时技术仅仅适用于进程上下文。睡眠等待不能用于中断上下文,因为中断上下文不允许执行schedule()或睡眠(4.2节给出了中断上下文可以做和不能做的事情)。在中断中进行短时间的忙等待是可行的,但是进行长时间的忙等则被认为不可赦免的罪行。在中断禁止时,进行长时间的忙等待也被看作禁忌。

  为了支持在将来的某时刻进行某项工作,内核也提供了定时器API。可以通过init_timer()动态定义一个定时器,也可以通过DEFINE_TIMER()静态创建定时器。然后,将处理函数的地址和参数绑定给一个timer_list,并使用add_timer()注册它即可:

#include <linux/timer.h>

struct timer_list my_timer;

init_timer(
&my_timer);            /* Also see setup_timer() */
my_timer.expire
= jiffies + n*HZ; /* n is the timeout in number of seconds */
my_timer.function
= timer_func;   /* Function to execute after n seconds */
my_timer.data
= func_parameter;   /* Parameter to be passed to timer_func */
add_timer(
&my_timer);             /* Start the timer */

  上述代码只会让定时器运行一次。如果想让timer_func()函数周期性地执行,需要在timer_func()加上相关代码,指定其在下次超时后调度自身:

static void timer_func(unsigned long func_parameter)
{
  
/* Do work to be done periodically */
  
/* ... */

  init_timer(
&my_timer);
  my_timer.expire  
= jiffies + n*HZ;
  my_timer.data    
= func_parameter;
  my_timer.function
= timer_func;
  add_timer(
&my_timer);
}

  你可以使用mod_timer()修改my_timer的到期时间,使用del_timer()取消定时器,或使用timer_pending()以查看my_timer当前是否处于等待状态。查看kernel/timer.c源代码,会发现schedule_timeout()内部就使用了这些API。

  clock_settime()和clock_gettime()等用户空间函数可用于获得内核定时器服务。用户应用程序可以使用setitimer()和getitimer()来控制一个报警信号在特定的超时后发生。

0
相关文章