古老的task机制

唯一的疑问是TASK_RUNNING后能schedule么

经常看到一些代码,比如

set_current_state(TASK_UNINTERRUPTABLE)

等等

实际上关键有这么几点

1. schedule的时候cpu会判断进程的状态,如果进程是主动放弃cpu,且状态不为TASK_RUNNING,就会被永远踢出运行队列

2.在进程从schedule中被wake_up,状态肯定被设置成TASK_RUNNING.

3. 如果是TASK_UNINTERRUPTABLT,那么只能被wakeup唤醒,如果是INTERRUPTABLE那么还能被信号量唤醒。

4. 睡眠往往通过 3 个步骤进行:
1. 将进程加入等待队列中。
2. 然后使用 set_current_state() 来设置进程的状态,设置的状态为 TASK_INTERRUPTIBLE 或 TASK_UNINTERRUTIBLE 。
3. 上面的设置完后,我们就要放弃处理器了。但在放弃处理器之前,还有一件重要的事情需要做:检查睡眠等待的条件。如果不检查,如果此时条件正好变为真,那么就漏掉了继续运行的机会,从而会睡眠更长的时间。就好比如,你在等一辆车,你觉得车还没来,你很困并就打算先睡一会儿,此时有一辆车刚好过来了,你睡眼朦胧的并没打算睁开眼睛去看一下,结果得花更长的时间来等下一趟。所以,一般在睡前需要类似的动作:

看此人的情况

A: A是一个等待进程.等待condition 满足过后退出死循环.
A:
......
while(1)
{
  if ( condition )  //条件成立了
   goto: OUT
  else{
   //1:----------------------
   set_current_state(TASK_UNINTERRUPTIBLE);
   schedule();
   set_current_state(TASK_RUNNING);
  } 
}
OUT:
......

B进程会唤醒A进程:
B:

......
wake_up_process(A)

写完之后,虽然测试通过了```但是老感觉有什么问题.所下:

在A进程中,首先它判断条件不满足,然后进入到else中.如果运行到1:------的时候.B进程被抢占进来了`并将A唤醒```
之后.A进程被调度回CPU.然后沿着1:------以下的部份运行,它设置当前状态,自己调用schedule().错过了这次条件````此后.A就会被移出运行队列,永远都睡眠着```

我开始怀疑这段代码,搜索了内核代码,发现内核中很多地方也有相类似的用法.无奈之下,向"中文邮件列表"中的众位高手请教,终于发现了问题所在.正确的写法应该是这样的:

                    //1:----------------------//
                     set_current_state(TASK_UNINTERRUPTIBLE);
                     if (!condition)

                               schedule();
                         set_current_state(TASK_RUNNING);

真是差之毫里,失之千里.现把这两段代码分析一下.

在第一段代码中.如果在标号处被抢占,B调用 wake_up_process(A)将A唤醒,此时A的运行状态会设为TASK_RUNNING.如切换到A进程运行时,之后的代码又会将其设为TASK_UNINTERRUPTIBLE.

在调用schdule()的时候,会判断进程是自愿放弃CPU还是被抢占出来的,如果是自愿放弃且进程不为运行状态.就会将其从运行队列中删除.

这样.A就永远失去了被调度的机会.

在第二种情况中:
1:进程A在set_current_state(TASK_UNINTERRUPTIBLE);之前被抢占,B将其唤配,切换回A后,判断condition条件时,会是一个真值,之后又会被调会TASK_RUNNING.这是正常的.

2:进程A在set_current_state(TASK_UNINTERRUPTIBLE)之后被抢占,假设抢占地在判断完条件之后.这样在B中会将A设为RUNING状态,A切换回来的时候,进入到schdule()后不会被踢出运行队列,只是等待下次调度而已,这也是正常的.

 

另外关于这方面的内核资料及翻译:

    Two states are associated with sleeping, TASK_INTERRUPTIBLE and TASK_UNINTERRUPTIBLE. They differ only in that tasks in the TASK_UNINTERRUPTIBLE state ignore signals, whereas tasks in the TASK_INTERRUPTIBLE state wake up prematurely and respond to a signal if one is issued. Both types of sleeping tasks sit on a wait queue, waiting for an event to occur, and are not runnable.
    休眠有两种相关的进程状态:TASK_INTERRUPTIBLE and TASK_UNINTERRUPTIBLE。它们的惟一却不是处于TASK_UNINTERRUPTIBLE状态的进程会忽略信号,而处于TASK_INTERRUPTIBLE状态的进程如果收到信号会被唤醒并处理信号(然后再次进入等待睡眠状态)。两种状态的进程位于同一个等待队列上,等待某些事件,不能够运行。
因为等待事件而进入睡眠状态的方法:
    the recommended method for sleeping in the kernel is a bit more complicated.
    在内核中进行休眠的推荐操作相对复杂一些.
The task performs the following steps to add itself to a wait queue:
进程通过执行下面几步将自己加入到一个等待队列中:
1. Creates a wait queue entry via DECLARE_WAITQUEUE().
调用DECLARE_WAITQUEUE()创建一个等待队列的项
|------------------------------------------------|
|/* 'q' is the wait queue we wish to sleep on */ |
|DECLARE_WAITQUEUE(wait, current);               |
|------------------------------------------------|
2. Adds itself to a wait queue via add_wait_queue(). This wait queue awakens the process when the condition for which it is waiting occurs. Of course, there needs to be code elsewhere that calls wake_up() on the queue when the event actually does occur.
调用add_wait_queue()把自己加入到队列中。该队列在进程等待的条件满足时唤醒它。当然我们必须在其他地方撰写相关代码,在事件发生时,对等待队列执行wake_up()操作
|-----------------------------|
|add_wait_queue(q, &wait);    |
|-----------------------------|
while (!condition) {     /* condition is the event that we are waiting for */
3. Changes the process state to TASK_INTERRUPTIBLE or TASK_UNINTERRUPTIBLE.
将进程的状态变更为TASK_INTERRUPTIBLE or TASK_UNINTERRUPTIBLE
|----------------------------------------------|
|       /* or TASK_UNINTERRUPTIBLE */          |
|       set_current_state(TASK_INTERRUPTIBLE); |
|----------------------------------------------|
4. If the state is set to TASK_INTERRUPTIBLE, a signal wakes the process up. This is called a spurious wake up (a wake-up not caused by the occurrence of the event). So check and handle signals.
如果状态被设置为TASK_INTERRUPTIBLE,则信号可以唤醒进程(信号和事件都可以唤醒该进程)。这就是所谓的伪唤醒(唤醒不是因为事件的发生,而是由信号唤醒的),因此检查并处理信号。
注: 信号和等待事件都可以唤醒处于 TASK_INTERRUPTIBLE 状态的进程,信号唤醒该进程为伪唤醒; 该进程被唤醒后,如果 (!condition) 结果为真,则说明该进程不是由等待事件唤醒的, 而是由信号唤醒的。 所以该进程处理信号后将再次让出CPU控制权
|----------------------------------------------|
|       if (signal_pending(current))           |
|               /* handle signal */            |
|----------------------------------------------|
5. Tests whether the condition is true. If it is, there is no need to sleep. If it is not true, the task calls schedule().
本进程在此处交出CPU控制权,如果该进程再次被唤醒,将从while循环结尾处继续执行,因而将回到while循环的开始处while (!condition),进测等待事件是否真正发生.
|----------------------------------------------|
|       schedule();                            |
|----------------------------------------------|
}
6. Now that the condition is true, the task can set itself to TASK_RUNNING and remove itself from the wait queue via remove_wait_queue().
|----------------------------------------------|
|set_current_state(TASK_RUNNING);              |
|remove_wait_queue(q, &wait);                  |
|----------------------------------------------|
    If the condition occurs before the task goes to sleep, the loop terminates, and the task does not erroneously go to sleep. Note that kernel code often has to perform various other tasks in the body of the loop. For example, it might need to release locks before calling schedule() and reacquire them after or react to other events.
    如果在进程开始睡眠之前条件就已经达成了,那么循环会退出,进程不会存在错误的进入休眠的倾向。需要注意的是,内核代码在循环体内常常需要完成一些其他的任务,比如,它可能在调用schedule()之前需要释放掉锁,而在这以后再重新获取它们,或者响应其他的事件。
    Waking is handled via wake_up(), which wakes up all the tasks waiting on the given wait queue. It calls try_to_wake_up(), which sets the task's state to TASK_RUNNING, calls activate_task() to add the task to a runqueue, and sets need_resched if the awakened task's priority is higher than the priority of the current task. The code that causes the event to occur typically calls wake_up() afterward.
    唤醒操作通过函数wake_up进行,它会唤醒指定的等待队列上的所有进程。它调用函数try_to_wake_up,该函数负责将进程设置为TASK_RUNNING状态,调用activate_task将此进程放入可执行队列,如果被唤醒的进程优先级比当前正在运行的进程的优先级高,还有设置need_resched标志。通常哪段代码促使等待条件达成,它就负责随后调用wake_up()函数。
    An important note about sleeping is that there are spurious wake-ups. Just because a task is awakened does not mean that the event for which the task is waiting has occurred; sleeping should always be handled in a loop that ensures that the condition for which the task is waiting has indeed occurred.
    关于休眠有一点需要注意,存在虚假的唤醒。有时候进程被唤醒并不是因为它所等待的条件达成了(而是接受到了信号),所以才需要用一个循环处理来保证它等待的条件真正达成。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值