内核空间几种长延时函数实现策略的优劣讨论

转载 2012年03月22日 16:39:46

这里所谓的长延时,是指其实现时间延时的粒度可以在HZ这一水准上。《深入Linux设备驱动程序内核机制》第8章"时间管理"中提到了好几种实现延时功能的机制,包括长延时短延时等,对每一种延时机制的优劣都有理论上的分析。

本帖旨在从另一个角度探讨一下其中所提到的“长延时”的三个实现方式,这些延时方式都试图让出处理器,换句话说都是基于非忙等待的实现(因为长延时若是忙等待,将极大浪费处理器的资源)与书中的原理分析不同之处在于,我希望在本帖中以实际的代码验证的方式来使那种理论上的分析更加直观化:我们在一个内核模块,比如demodev.ko的初始化函数中调用这些延时函数,然后通过ps命令来查看延时期间insmod进程的状态。

首先我们看第一种实现(为了有足够的时间让我们去查看进程的状态,我将这种“长延时”延时间隔设定30S,这样足够我们在此延时的窗口期间观察insmod进程的状态):

  1. void delay_30s(void){
  2.     unsigned long j = jiffies + 30 * HZ;
  3.     while(time_before(jiffies, j))
  4.          schedule();
  5. }
这种实现采用了schedule的方式,貌似会重新调度一个新进程。其实因为当前进程状态没有改变,所以调用delay_30s的当前进程仍然处在CPU的运行队列中,进程状态为TASK_RUNNING.30s延时的目的肯定能达到了,但是该进程随时可能被调度器所调度,若是处理器的运行队列中只有这一个进程,CPU无法进入idle状态,而后者可以被电源管理模块所利用。如果我们insmod demodev.ko,那么通过ps命令来查看该进程状态时,会发现如下的输出信息:

root@build-server:/home/dennis/book_2nd_edition/book_sourcecode# ps aux | grep insmod
root      6491  100  0.0   4420   576 pts/2    R+   21:55   0:28 insmod demodev.ko
root      6514  0.0  0.0  13448   872 pts/3    S+   21:56   0:00 grep --color=auto insmod

insmod进程的R+状态意味着当前进程处在后台一个运行的状态下。

再来看一个改进版:

  1. void delay_30s(void){
  2.     set_current_state(TASK_INTERRUPTIBLE);
  3.     schedule_timeout(30 * HZ);
  4. }
这次在schedule_timeout调用之前,先把进程状态改成TASK_INTERRUPTIBLE。schedule_timeout()函数内部会启用一个内核timer,同时会调用schedule()。一旦schedule()函数被调用,当前进程将从处理器的运行队列中移走,移走的current进程会被放到内核定时队列的一个结点中。当30S的时间到期时,定时器上的函数会唤醒current,delay_30s函数将从schedule_timeout函数中返回。如果我们insmod demodev.ko,那么通过ps命令来查看该进程状态时,会发现如下的输出信息:

root@build-server:/home/dennis/book_2nd_edition/book_sourcecode# ps aux | grep insmod
root      6300  0.0  0.0   4420   580 pts/2    S+   21:53   0:00 insmod demodev.ko
root      6306  0.0  0.0  13448   868 pts/3    S+   21:53   0:00 grep --color=auto insmod

insmod进程的S+状态意味着当前进程处在后台一个睡眠的状态下。如果你的调用上下文允许当前进程进入睡眠,那么这是一个非常完美的实现:睡眠的进程不占用处理器资源,同时延时的目的也达到了。

最后,我们来试图改进第一种的那个方案,代码如下:

  1. void delay_30s(void){
  2.      unsigned long j = jiffies + 30 * HZ;
  3.      while(time_before(jiffies, j)){
  4.          set_current_state(TASK_UNINTERRUPTIBLE);
  5.          schedule();
  6. }
如果你在demodev.ko的初始化函数中调用了上面这个函数,insmod demodev.ko将回不到shell状态下了,ctrl+c也不行,如果换成TASK_INTERRUPTIBLE的话还可以用CTRL+C来杀死当前进程。这里的问题在于在schedule()前调用set_current_state(TASK_UNINTERRUPTIBLE),将导致当前进程从处理器运行队列中移开,一旦移开,current进程将处于流浪状态,谁会去收留它呢?它将永远失去被处理器调度的机会,不过此时若用ps aux来看一下insmod进程状态的话,会发现它处于S+状态,因为在schedule函数之前那个set_current_state的调用。

最后我想提一下那个schedule_timeout函数,如果调用该函数前没有用改变进程的状态,那么这个函数基本上是瞬间返回(其实取决于当前处理器运行队列及调度器行为),虽然它初始化并提交了一个定时器结点,但是它调用schedule时基本上是很快会被调度器再次调度,然后它有个del_singleshot_timer_sync(&timer)调用,后者将把之前提交的定时器结点从定时器管理队列中摘除,因为如果当前进程一旦结束,放到此前提交的定时器结点中的current将指向无意义的空间。

博客推荐文章
[发评论] 评论 重要提示:警惕虚假中奖信息!
GFree_Wind 2012-02-29 10:43
MagicBoy2010: 第一个例子和cpu_idle本质上是一样的,如果用这个例子做延迟,你会发现调用delay_30s的进程是处于R+状态的,它一直在运行队列中,但是不一定总是被调度器所调用.....
不好意思。是我弄混了。。。

我把第三个例子的结果给弄混了。看到最后的时候,以为第一个例子也是这个结果。所以想不明白了。多谢
MagicBoy2010 2012-02-29 10:33
GFree_Wind: 我问的情况肯定是有就绪任务,同样是调用schedule,同样cpu_idle会被调度出去,然后运行另外一个进程。

这是第一个例子:
void delay_30s(void){
    unsigned.....
第一个例子和cpu_idle本质上是一样的,如果用这个例子做延迟,你会发现调用delay_30s的进程是处于R+状态的,它一直在运行队列中,但是不一定总是被调度器所调用。你说结果不同是指什么?
GFree_Wind 2012-02-29 10:28
MagicBoy2010: cpu_idle在调用schedule前又没有调用TASK_(UN)INTERRUPTIBLE,它依然在处理器的运行队列中,如果运行队列中没有别的就绪任务,那么调度器就选择它。。。.....
我问的情况肯定是有就绪任务,同样是调用schedule,同样cpu_idle会被调度出去,然后运行另外一个进程。

这是第一个例子:
void delay_30s(void){
    unsigned long j = jiffies + 30 * HZ;
    while(time_before(jiffies, j))
         schedule();
}
这里也没有设置TASK_(UN)INTERRUPTIBLE,与cpu_idle很类似啊。为什么结果不同呢?
MagicBoy2010 2012-02-29 10:17
GFree_Wind: 不好意思,我对内核不熟悉,所以问题有点多。

调度器不管这个事情,那么就需要明确的将current放到某个等待队列中吗?
那为什么其它内核的线程,如cpu_idle可以.....
cpu_idle在调用schedule前又没有调用TASK_(UN)INTERRUPTIBLE,它依然在处理器的运行队列中,如果运行队列中没有别的就绪任务,那么调度器就选择它。。。
GFree_Wind 2012-02-29 09:51
MagicBoy2010: 不是current状态不对,而是调度器不会去管等待队列这些事情。。。.....
不好意思,我对内核不熟悉,所以问题有点多。

调度器不管这个事情,那么就需要明确的将current放到某个等待队列中吗?
那为什么其它内核的线程,如cpu_idle可以直接调用schedule而没有其它操作呢?cpu_idle会在schedule后,仍然有运行的机会呢。
MagicBoy2010 2012-02-29 09:39
GFree_Wind: 为什么不会移到等待队列呢?是因为current的状态不对吗?.....
不是current状态不对,而是调度器不会去管等待队列这些事情。。。
GFree_Wind 2012-02-28 16:15
MagicBoy2010: current被移除运行队列后,没有等待队列或者是定时器结点之类的慈善机构予以收留,一个无家可归的孩子没有人会把它再送到运行队列中(TASK_RUNNING),当然内核还.....
为什么不会移到等待队列呢?是因为current的状态不对吗?
MagicBoy2010 2012-02-28 15:49
GFree_Wind: 我一个问题请教一下:
当前进程从处理器运行队列中移开,一旦移开,current进程将处于流浪状态,谁会去收留它呢?它将永远失去被处理器调度的机会。
被调度出去.....
current被移除运行队列后,没有等待队列或者是定时器结点之类的慈善机构予以收留,一个无家可归的孩子没有人会把它再送到运行队列中(TASK_RUNNING),当然内核还是有链表管理所有的task的,这也是为什么你可以用ps命令看到它。
GFree_Wind 2012-02-28 11:57
我一个问题请教一下:
当前进程从处理器运行队列中移开,一旦移开,current进程将处于流浪状态,谁会去收留它呢?它将永远失去被处理器调度的机会。
被调度出去以后,为什么永远不会被执行呢?

linux驱动开发--copy_to_user 、copy_from_user函数实现内核空间数据与用户空间数据的相互访问

设备读操作 如果该操作为空,将使得read系统调用返回负EINVAL失败,正常返回实际读取的字节数 ssize_t (*read)(struct file *filp, char __user *bu...
  • waldmer
  • waldmer
  • 2013年12月30日 10:33
  • 3701

从普通的函数调用到操作系统内核空间的访问

从普通的函数调用到操作系统内核空间的访问   首先通过了解认识函数的原理来体会函数的调用   1)函数 首先什么是函数,函数是用于完成特定任务的程序代码自包含单元 (这里的自包含单元听着可...
  • CJ_Kano
  • CJ_Kano
  • 2014年12月29日 00:20
  • 625

Linux内核空间内存申请函数kmalloc、kzalloc、vmalloc的区别

我们都知道在用户空间动态申请内存用的函数是 malloc(),这个函数在各种操作系统上的使用是一致的,对应的用户空间内存释放函数是 free()。注意:动态申请的内存使用完后必须要释放,否则会造成...

回收内核空间资源 wait函数 waitpid函数

摘要:本文主要讲述内核空间资源的收回,分析了wait和waitid函数的基本使用方法,以及它们之间的差异....

linux内核空间 打开、读、写等文件操作实现

#include #include #include #include #include #include #include   /*   file I/...

linux内核空间访问文件系统代码实现

本文代码是从互联网收集而来,我记录下来主要为学习方便,版权归原作者所有。 以下代码实现了在linux内核空间打开、读、写文件等相关函数,有兴趣的可以研究一下并在此基础上扩展。   #includ...
  • fh400
  • fh400
  • 2011年11月17日 11:33
  • 520

mmap内核空间映射结合kfifo,poll机制的实现例子

平台: 全志A20 Android4.2.2 Linux4.4#include #include #include #include #include #include #include ...

Driver:LED灯操作、内核空间和用户空间的数据交互、ioctl函数、设备文件安装与销毁

1、LED灯操作 用户空间执行open时,led1亮;执行close时,led1灭。 电路原理图:     led1 ---> GPIOC12 如何控制:     cpu datasheet 特殊功能...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:内核空间几种长延时函数实现策略的优劣讨论
举报原因:
原因补充:

(最多只允许输入30个字)