Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1566222
  • 博文数量: 77
  • 博客积分: 1205
  • 博客等级: 少尉
  • 技术积分: 4476
  • 用 户 组: 普通用户
  • 注册时间: 2010-04-22 21:48
文章分类
文章存档

2018年(1)

2017年(1)

2015年(1)

2014年(18)

2013年(12)

2012年(44)

分类: LINUX

2012-02-27 22:04:37

这里所谓的长延时,是指其实现时间延时的粒度可以在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将指向无意义的空间。

阅读(6728) | 评论(9) | 转发(5) |
给主人留下些什么吧!~~

GFree_Wind2012-02-29 10:43:12

MagicBoy2010: 第一个例子和cpu_idle本质上是一样的,如果用这个例子做延迟,你会发现调用delay_30s的进程是处于R+状态的,它一直在运行队列中,但是不一定总是被调度器所调用.....
不好意思。是我弄混了。。。

我把第三个例子的结果给弄混了。看到最后的时候,以为第一个例子也是这个结果。所以想不明白了。多谢

MagicBoy20102012-02-29 10:33:50

GFree_Wind: 我问的情况肯定是有就绪任务,同样是调用schedule,同样cpu_idle会被调度出去,然后运行另外一个进程。

这是第一个例子:
void delay_30s(void){
    unsigned.....
第一个例子和cpu_idle本质上是一样的,如果用这个例子做延迟,你会发现调用delay_30s的进程是处于R+状态的,它一直在运行队列中,但是不一定总是被调度器所调用。你说结果不同是指什么?

GFree_Wind2012-02-29 10:28:52

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很类似

MagicBoy20102012-02-29 10:17:19

GFree_Wind: 不好意思,我对内核不熟悉,所以问题有点多。

调度器不管这个事情,那么就需要明确的将current放到某个等待队列中吗?
那为什么其它内核的线程,如cpu_idle可以.....
cpu_idle在调用schedule前又没有调用TASK_(UN)INTERRUPTIBLE,它依然在处理器的运行队列中,如果运行队列中没有别的就绪任务,那么调度器就选择它。。。

GFree_Wind2012-02-29 09:51:45

MagicBoy2010: 不是current状态不对,而是调度器不会去管等待队列这些事情。。。.....
不好意思,我对内核不熟悉,所以问题有点多。

调度器不管这个事情,那么就需要明确的将current放到某个等待队列中吗?
那为什么其它内核的线程,如cpu_idle可以直接调用schedule而没有其它操作呢?cpu_idle会在schedule后,仍然有运行的机会呢。