一、概念
Linux系统中,应用程序以进程的方式存在,调度也以进程为单位,有关进程的概念就不多说了,参考教科书。
本文主要关注进程状态、偶然会见到的僵尸进程(Z状态)、以及很少见过的X状态进程。
每个进程都有相应的状态,如平常常见的R、S和D状态,也有在出现问题时见到的Z状态,即僵尸状态,还有极少见到的X状态,这也是本文重点分析和关注的。
首先需要简单介绍下几种基本的进程状态的相关概念:
1、R状态
R即Running状态为可运行状态,但并不代表该进程正在运行,只有处于R状态的进程才可以被调度器选中运行,也就是说R状态的进程有机会得到调度运行,其它状态进程不行,当R状态进程被调度器选中运行时,其才正在开始运行。
2、S状态
S即Sleeping状态为可中断睡眠状态,对应于内核中的INTERRUPTIBLE状态,可中断的意思时,处于S状态的进程可以处理信号,可以被信号中断和唤醒。这是系统中最常见的进程状态了。
3、D状态
D为不可中断睡眠状态,对应于内核中的UNINTERRUPTIBLE状态,不可中断的意思是,处于D状态的额进程不能处理信号,不能被信号中断和唤醒,处于该状态的进程大多在等待IO完成后将其唤醒,其它方式不能唤醒,处于D状态的进程由于不处理信号,所以无法被kill,这也是平常遇到的比较头疼的问题。在处理问题时经常见D状态进程,无法kill,也没有其它办法处理,有人想把它强制kill掉,但没有办法,及时有办法(比如内核模块),但其实这样做也不妥。首先如果进程长期处于D状态不退出的话,那此时该进程或系统肯定有问题了,D状态通常等待IO完成,完成后会自动唤醒退出,不应该长期处于D状态,如果是这样,要么是系统IO挂住了(通常scsi等层都有超时机制的,所以通常不会导致长期D),要么是内核中产生死锁了(这种可能性比较大),要么内核出其它问题了。内核中针对长期处于D状态的进程也有相应的检测手段,俗称hungtask检测机制,基本原理是定期检测处于D状态的进程,如果D状态持续时间超过120s,就打印相应的堆栈及错误信息,也可以通过配置使内核直接panic,这样可以通过kdump搜集vmcore做详细分析。好像说太多了,说来话太长,这里就不继续了。
4、Z状态(僵尸进程)
僵尸进程产生的原理为:当进程退出时,默认会向其父进程发送SIGCLD信号,同时将自己设置为Z状态(僵尸状态),父进程在收到SIGCLD信号后,标准做法需要在SIGCLD信号的处理中,调用wait(或类似接口)函数对其子进程占用的剩余资源(如进程描述符)进行回收,回收后,进程彻底退出并消失。
也就是说Z状态(僵尸状态),其实是进程退出过程中的一个正常的中间状态,正常情况下该状态持续的时间应该比较短,应该会很快被回收并退出。当发现一个进程长期处于僵尸状态时,可能的原因有:
1)父进程的SIGCLD信号处理函数中没有调用wait。
2)父进程SIGCLD信号处理函数中调用wait执行过程中阻塞,可能由于资源回收过程中发生阻塞,见过的案例有:wait过程中,需要等待进程的所有子线程退出,而子线程处于D状态不返回,导致wait无法继续。
3)内核出问题了。
那如果父进程在收到SIGCLD信号之前先退出了,是否会导致僵尸进程呢?
答案是不会。因为父进程退出后,子进程会变成孤儿进程,孤儿进程会由init进程自动接管,而init进程会定期通过wait回收其正在退出过程中处于僵尸状态的进程,所以正常情况下,是不会出现这样的情况的。
5、X状态(Dead状态)
X即Dead状态,跟Z状态是密切相关的。如前面所述,进程退出时,默认会向父进程发送SIGCLD信号,但在发送之前,会先对父进程的sighand进行检查,当父进程忽略了SIGCLD信号时,就不会发送信号了,此时会将进程的退出状态设置为EXIT_DEAD,即X状态,此时进程的资源不由父进程回收,进程也不会进入僵尸状态。这种情况下,进程的资源需要自己回收,实际上是在内核调度到下一个进程开始执行时进行回收,回收后,进程消失,X状态也随之消失。
所以,X状态其实也是退出过程中的一个正常的中间状态,正常情况下该状态持续的时间应该比较短,应该会很快被回收并退出。当发现一个进程长期处于X状态时,那应该也有问题了,但这种情况很少见。
二、实现
1、内核中定义的进程状态
在3.10内核中,定义了如下进程状态:
-
/*进程状态*/
-
#define TASK_RUNNING 0 /*可运行状态,被调度的对象*/
-
#define TASK_INTERRUPTIBLE 1 /*可中断睡眠状态,即平时见的S状态*/
-
#define TASK_UNINTERRUPTIBLE 2 /*不可中断睡眠状态,即平时见的D状态*/
-
#define __TASK_STOPPED 4
-
#define __TASK_TRACED 8 /*调试使用状态,比如gdb attach时*/
-
#define TASK_DEAD 64 /*进程退出后,等待资源回收时的状态*/
-
#define TASK_WAKEKILL 128
-
#define TASK_WAKING 256
-
#define TASK_PARKED 512
-
#define TASK_STATE_MAX 1024
组合状态:
-
/* Convenience macros for the sake of set_task_state */
-
#define TASK_KILLABLE (TASK_WAKEKILL | TASK_UNINTERRUPTIBLE)
-
#define TASK_STOPPED (TASK_WAKEKILL | __TASK_STOPPED)
-
#define TASK_TRACED (TASK_WAKEKILL | __TASK_TRACED)
-
-
/* Convenience macros for the sake of wake_up */
-
#define TASK_NORMAL (TASK_INTERRUPTIBLE | TASK_UNINTERRUPTIBLE)
-
#define TASK_ALL (TASK_NORMAL | __TASK_STOPPED | __TASK_TRACED)
-
-
/* get_task_state() */
-
#define TASK_REPORT (TASK_RUNNING | TASK_INTERRUPTIBLE | \
-
TASK_UNINTERRUPTIBLE | __TASK_STOPPED | \
-
__TASK_TRACED)
为何没有我们熟知的ZOMBIE(僵尸)状态?不是常见(或偶见)僵尸进程么?也没有X状态?
答案:进程状态中确实没有ZOMBIE(僵尸)状态,ZOMBIE(僵尸)在内核中只是一种exit_status,即退出状态,也就是进程退出时的一种状态,具体定义如下:
/* in tsk->exit_state */
#define EXIT_ZOMBIE 16
#define EXIT_DEAD 32
可见,退出状态中,除了ZOMBIE,还有另一种叫DEAD的状态,该状态其实就是X状态。
2、用户看到的进程状态
那我们在ps、top或cat /proc//status中看到的Z状态()的进程是如何产生的呢?
ps、top或cat /proc//status中看到进程状态来源于内核中如下的定义
-
/*
-
* The task state array is a strange "bitmap" of
-
* reasons to sleep. Thus "running" is zero, and
-
* you can test for combinations of others with
-
* simple bit tests.
-
*/
-
static const char * const task_state_array[] = {
-
"R (running)", /* 0 */
-
"S (sleeping)", /* 1 */
-
"D (disk sleep)", /* 2 */
-
"T (stopped)", /* 4 */
-
"t (tracing stop)", /* 8 */
-
"Z (zombie)", /* 16 */
-
"X (dead)", /* 32 */
-
"x (dead)", /* 64 */
-
"K (wakekill)", /* 128 */
-
"W (waking)", /* 256 */
-
"P (parked)", /* 512 */
-
};
以/proc//status中显示的状态为例,看看这个状态是如何获取并显示的。
cat /proc//status示例:
-
[root@A10097139 ~]# cat /proc/115/status
-
Name: crypto/7
-
State: S (sleeping)
-
Tgid: 115
-
Pid: 115
-
PPid: 2
-
TracerPid: 0
-
Uid: 0 0 0 0
-
Gid: 0 0 0 0
-
Utrace: 0
-
FDSize: 64
-
Groups:
-
Threads: 1
-
SigQ: 2/30472
-
SigPnd: 0000000000000000
-
ShdPnd: 0000000000000000
-
SigBlk: 0000000000000000
-
SigIgn: ffffffffffffffff
-
SigCgt: 0000000000000000
-
CapInh: 0000000000000000
-
CapPrm: ffffffffffffffff
-
CapEff: fffffffffffffeff
-
CapBnd: ffffffffffffffff
-
Cpus_allowed: 80
-
Cpus_allowed_list: 7
-
Mems_allowed: 00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000001
-
Mems_allowed_list: 0
-
voluntary_ctxt_switches: 2
-
nonvoluntary_ctxt_switches: 0
/proc//status其中的进程状态获取是通过如下调用路径:
proc_pid_status()
->task_state()
->get_task_state()
-
/*获取进程状态,实际是根据根据task_struct->state和exit_state来确认的*/
-
static inline const char *get_task_state(struct task_struct *tsk)
-
{
-
/*将tsk->state通过TASK_REPORT过滤后,再组合exit_state,形成最终的状态*/
-
unsigned int state = (tsk->state & TASK_REPORT) | tsk->exit_state;
-
/*获取进程状态列表中的第一种状态*/
-
const char * const *p = &task_state_array[0];
-
-
BUILD_BUG_ON(1 + ilog2(TASK_STATE_MAX) != ARRAY_SIZE(task_state_array));
-
/*取进程状态中的最低位作为返回的状态,比如如果Z(Zombie)位为1,那就不管后面的DEAD位了*/
-
while (state) {
-
p++;
-
state >>= 1;
-
}
-
return *p;
-
}
可见,/proc中反应的进程状态实际为tsk->status和exit_state的组合。那需要继续分析这两种状态的设置情况。
3、Z(僵尸)状态和X(Dead)状态的形成
Z(僵尸)状态和X(Dead)状态的形成原理前面已经描述,这里主要关注Z(Zombie)和X状态形成的相关流程。基本流程为:
进程退出必经do_exit入口,其中调用exit_notify通知父进程,如果父进程未忽略SIGCLD信号,则设置进程的退出状态(exit_state)为EXIT_ZOMBIE(即为Z状态);如果父进程忽略了SIGCLD信号,则设置进程的退出状态(exit_state)为EXIT_DEAD(即为X状态)。
do_exit():
-
/*进程退出时必经入口,完成相应处理*/
-
void do_exit(long code)
-
{
-
struct task_struct *tsk = current;
-
int group_dead;
-
-
profile_task_exit(tsk);
-
-
WARN_ON(blk_needs_flush_plug(tsk));
-
/*不能在中断上下文中退出进程。*/
-
if (unlikely(in_interrupt()))
-
panic("Aiee, killing interrupt handler!");
-
/*不能kill idle(pid=0)进程*/
-
if (unlikely(!tsk->pid))
-
panic("Attempted to kill the idle task!");
-
...
-
/*退出通知,其中完成向父进程发SIGCLD信号*/
-
exit_notify(tsk, group_dead);
-
...
-
/* causes final put_task_struct in finish_task_switch(). */
-
/*设置进程状态为DEAD.Fixme:跟僵尸进程和ZOMBIE状态有何关系?*/
-
tsk->state = TASK_DEAD;
-
tsk->flags |= PF_NOFREEZE; /* tell freezer to ignore us */
-
schedule();
-
/*不应该再回来了。Fixme:task_struct会在finish_task_switch中清理?那SIGCLD信号谁来发?*/
-
BUG();
-
/* Avoid "noreturn function does return". */
-
for (;;)
-
cpu_relax(); /* For when BUG is null */
-
}
exit_notify():
-
static void exit_notify(struct task_struct *tsk, int group_dead)
-
{
-
bool autoreap;
-
-
/*
-
* This does two things:
-
*
-
* A. Make init inherit all the child processes
-
* B. Check to see if any process groups have become orphaned
-
* as a result of our exiting, and if they have any stopped
-
* jobs, send them a SIGHUP and then a SIGCONT. (POSIX 3.2.2.2)
-
*/
-
forget_original_parent(tsk);
-
-
write_lock_irq(&tasklist_lock);
-
if (group_dead)
-
kill_orphaned_pgrp(tsk->group_leader, NULL);
-
-
if (unlikely(tsk->ptrace)) {
-
int sig = thread_group_leader(tsk) &&
-
thread_group_empty(tsk) &&
-
!ptrace_reparented(tsk) ?
-
tsk->exit_signal : SIGCHLD;
-
autoreap = do_notify_parent(tsk, sig);
-
} else if (thread_group_leader(tsk)) {
-
/*autoreap表示当父进程忽略了SIGCLD信号时,需要进程self-reap相应的资源*/
-
autoreap = thread_group_empty(tsk) &&
-
do_notify_parent(tsk, tsk->exit_signal); /*通过信号(通常是SIGCLD)通知父进程*/
-
} else {
-
autoreap = true;
-
}
-
/*
-
* 设置进程退出状态,父进程忽略了SIGCLD信号时,需要进程self-reap,
-
* 此时autoreap==1,则退出状态为EXIT_DEAD,否则为EXIT_ZOMBIE。
-
* 父进程只会负责EXIT_ZOMBIE状态的子进程的资源回收,EXIT_DEAD的进程
-
* 自行处理。
-
*/
-
tsk->exit_state = autoreap ? EXIT_DEAD : EXIT_ZOMBIE;
-
-
/* mt-exec, de_thread() is waiting for group leader */
-
if (unlikely(tsk->signal->notify_count < 0))
-
wake_up_process(tsk->signal->group_exit_task);
-
write_unlock_irq(&tasklist_lock);
-
-
/* If the process is dead, release it - nobody will wait for it */
-
if (autoreap)
-
release_task(tsk);
-
}
do_notify_parent():
-
/*
-
* 通知父进程自己要退出了,其实就是向父进程发送SIGCLD信号,
-
* 如果父进程处理SIGCLD信号,则通常会在信号处理函数中调用wait()相关接口,
-
* 回收子进程最后的资源(比如task_struct?);如果父进程忽略该信号,则子进程
-
* 需要自行回收(self-reaping)。Fixme:可能会变僵尸进程?
-
*/
-
bool do_notify_parent(struct task_struct *tsk, int sig)
-
{
-
struct siginfo info;
-
unsigned long flags;
-
struct sighand_struct *psig;
-
bool autoreap = false;
-
cputime_t utime, stime;
-
-
BUG_ON(sig == -1);
-
-
/* do_notify_parent_cldstop should have been called instead. */
-
BUG_ON(task_is_stopped_or_traced(tsk));
-
-
BUG_ON(!tsk->ptrace &&
-
(tsk->group_leader != tsk || !thread_group_empty(tsk)));
-
-
if (sig != SIGCHLD) {
-
/*
-
* This is only possible if parent == real_parent.
-
* Check if it has changed security domain.
-
*/
-
if (tsk->parent_exec_id != tsk->parent->self_exec_id)
-
sig = SIGCHLD;
-
}
-
-
info.si_signo = sig;
-
info.si_errno = 0;
-
/*
-
* We are under tasklist_lock here so our parent is tied to
-
* us and cannot change.
-
*
-
* task_active_pid_ns will always return the same pid namespace
-
* until a task passes through release_task.
-
*
-
* write_lock() currently calls preempt_disable() which is the
-
* same as rcu_read_lock(), but according to Oleg, this is not
-
* correct to rely on this
-
*/
-
rcu_read_lock();
-
info.si_pid = task_pid_nr_ns(tsk, task_active_pid_ns(tsk->parent));
-
info.si_uid = from_kuid_munged(task_cred_xxx(tsk->parent, user_ns),
-
task_uid(tsk));
-
rcu_read_unlock();
-
-
task_cputime(tsk, &utime, &stime);
-
info.si_utime = cputime_to_clock_t(utime + tsk->signal->utime);
-
info.si_stime = cputime_to_clock_t(stime + tsk->signal->stime);
-
-
info.si_status = tsk->exit_code & 0x7f;
-
if (tsk->exit_code & 0x80)
-
info.si_code = CLD_DUMPED;
-
else if (tsk->exit_code & 0x7f)
-
info.si_code = CLD_KILLED;
-
else {
-
info.si_code = CLD_EXITED;
-
info.si_status = tsk->exit_code >> 8;
-
}
-
/*获取父进程的sighand*/
-
psig = tsk->parent->sighand;
-
spin_lock_irqsave(&psig->siglock, flags);
-
/*
-
* 如果发送信号为SIGCHLD且父进程忽略了SIGCHLD信号或者设置了SA_NOCLDWAIT标记,则设置autoreap,
-
* 即子进程自己回收资源,不由父进程通过wait来回收。
-
*/
-
if (!tsk->ptrace && sig == SIGCHLD &&
-
(psig->action[SIGCHLD-1].sa.sa_handler == SIG_IGN ||
-
(psig->action[SIGCHLD-1].sa.sa_flags & SA_NOCLDWAIT))) {
-
/*
-
* We are exiting and our parent doesn't care. POSIX.1
-
* defines special semantics for setting SIGCHLD to SIG_IGN
-
* or setting the SA_NOCLDWAIT flag: we should be reaped
-
* automatically and not left for our parent's wait4 call.
-
* Rather than having the parent do it as a magic kind of
-
* signal handler, we just set this to tell do_exit that we
-
* can be cleaned up without becoming a zombie. Note that
-
* we still call __wake_up_parent in this case, because a
-
* blocked sys_wait4 might now return -ECHILD.
-
*
-
* Whether we send SIGCHLD or not for SA_NOCLDWAIT
-
* is implementation-defined: we do (if you don't want
-
* it, just use SIG_IGN instead).
-
*/
-
autoreap = true;
-
if (psig->action[SIGCHLD-1].sa.sa_handler == SIG_IGN)
-
sig = 0;
-
}
-
if (valid_signal(sig) && sig)
-
/*向父进程发送信号(SIGCLD)*/
-
__group_send_sig_info(sig, &info, tsk->parent);
-
/*
-
* 唤醒父进程。
-
* Fixme:如果上面的if不成立,不发送信号,此时还唤醒父进程来干嘛?
-
* 答案:见上面注释:(?)
-
* Note that we still call __wake_up_parent in this case, because a
-
* blocked sys_wait4 might now return -ECHILD.
-
*/
-
__wake_up_parent(tsk, tsk->parent);
-
spin_unlock_irqrestore(&psig->siglock, flags);
-
-
return autoreap;
-
}
4、Z(僵尸)状态进程回收
如之前所说,僵尸进程的资源由父进程,在SIGCLD信号处理中,通过wait接口回收,回收代码流程如下:
sys_wait4()
->do_wait()
->do_wait_thread()
->wait_consider_task()
->wait_task_zombie()
->release_task()
5、X(Dead)状态进程回收
X状态进程资源不由父进程回收,需要自己回收(autoreap==1),其回收是在内核调度到下一个进程开始运行时进行的。
这里就涉及到进程上下文切换的问题,调度产生后必然会进行进程上下文切换,上下文切换后问题变得相对复杂一些,有关进程上下文切换相关的知识请参见另一篇blog:http://blog.chinaunix.net/uid-14528823-id-4740294.html
内核调度的代码路径如下:
schedule()
->__schedule()
->context_switch()
->switch_to(宏)
实际的上下文切换发生在switch_to宏中。
这里需要分两种情况:
1)当调度时,被选中的next进程已经经历过调度时,上下文切换后其会继续从switch_to宏的“标号1”处继续执行:
-
/*
-
* 上下文切换,在schedule中调用,current进程调度出去,当该进程被再次调度到时,重新从__switch_to后面开始执行
-
* prev:被替换的进程
-
* next:被调度的新进程
-
* last:当切换回原来的进程(prev)后,被替换的另外一个进程。
-
*/
-
#define switch_to(prev, next, last) \
-
do { \
-
/* \
-
* Context-switching clobbers all registers, so we clobber \
-
* them explicitly, via unused output variables. \
-
* (EAX and EBP is not listed because EBP is saved/restored \
-
* explicitly for wchan access and EAX is the return value of \
-
* __switch_to()) \
-
*/ \
-
unsigned long ebx, ecx, edx, esi, edi; \
-
\
-
asm volatile("pushfl\n\t" /* save flags */ /*将eflags寄存器值压栈*/\
-
"pushl %%ebp\n\t" /* save EBP */ /*将EBP压栈*/\
-
/*将当前栈指针(内核态)保存到prev进程的thread.sp中*/
-
"movl %%esp,%[prev_sp]\n\t" /* save ESP */ \
-
/*将next进程的栈指针(内核态)装载到ESP寄存器中*/
-
"movl %[next_sp],%%esp\n\t" /* restore ESP */ \
-
/*保存"标号1"的地址到prev进程的thread.ip,以便当prev进程重新被调度运行时,可以从"标号1处"重新开始执行*/
-
"movl $1f,%[prev_ip]\n\t" /* save EIP */ \
-
/*
-
* 将next进程的IP(通常都是"标号1"的地址,因为通常都是经历过这里的调度过程的,上一行代码中即保存了这个IP)
-
* 压入当前的(即next进程的)堆栈中。结合后面的jmp指令(注意:不是call指令)一起理解,当__switch_to执行完ret返回时,
-
* 会自动从当前的堆栈中弹出该地址作为函数的返回地址接着执行,如此即可实现新进程的运行。
-
*/
-
"pushl %[next_ip]\n\t" /* restore EIP */ \
-
__switch_canary \
-
/*
-
*jmp到__switch_to函数执行,当此函数返回时,自动跳转到[next_ip]开始执行,实现新进程的调度。注意不是call,jmp指令
-
* 不会自动将当前地址压栈,call会自动压栈
-
*/
-
"jmp __switch_to\n" /* regparm call */ \
-
/*当prev进程再次被调度到时,从这里开始执行*/
-
"1:\t" \
-
/*恢复EBP*/
-
"popl %%ebp\n\t" /* restore EBP */ \
-
/*恢复eflags*/
-
"popfl\n" /* restore flags */ \
-
\
-
/* output parameters */ \
-
/*输出参数*/
-
: [prev_sp] "=m" (prev->thread.sp), \
-
[prev_ip] "=m" (prev->thread.ip), \
-
"=a" (last), \
-
\
-
/* clobbered output registers: */ \
-
"=b" (ebx), "=c" (ecx), "=d" (edx), \
-
"=S" (esi), "=D" (edi) \
-
\
-
__switch_canary_oparam \
-
\
-
/* input parameters: */ \
-
/*输入参数*/
-
: [next_sp] "m" (next->thread.sp), \
-
[next_ip] "m" (next->thread.ip), \
-
\
-
/* regparm parameters for __switch_to(): */ \
-
/*将prev和next分别存入ecx和edx,然后作为参数传入到__switch_to函数中*/
-
[prev] "a" (prev), \
-
[next] "d" (next) \
-
\
-
__switch_canary_iparam \
-
\
-
: /* reloaded segment registers */ \
-
"memory"); \
-
} while (0)
退出switch_to宏后,会返回到context_switch函数中继续执行:
-
/*
-
* context_switch - switch to the new MM and the new
-
* thread's register state.
-
*/
-
static inline void
-
context_switch(struct rq *rq, struct task_struct *prev,
-
struct task_struct *next)
-
{
-
...
-
/* Here we just switch the register state and the stack. */
-
/*切换到新的进程上下文*/
-
switch_to(prev, next, prev);
-
/*屏障,防止乱序*/
-
barrier();
-
/*
-
* this_rq must be evaluated again because prev may have moved
-
* CPUs since it called schedule(), thus the 'rq' on its stack
-
* frame will be invalid.
-
*/
-
/*
-
* 上下文切换后,会继续到这里执行,但这里已经是新的进程上下文了
-
* 在新的上下文中,清理掉上一个被调度进程prev的相关资源(比如DEAD状态的进程占用的资源)。
-
*/
-
finish_task_switch(this_rq(), prev);
-
/*
-
* Fixme:本函数执行完成后,返回到哪里? 这里已经是新的进程上下文了,
-
* 进程的内核栈已经切换,所以,内核栈中该函数的返回地址也已经
-
* 切换了,因此,不可能再返回上一个进程的上下文中的__schedule函数了。
-
* 但是新的进程上下文该函数的上级函数(该返回的函数)也必然是__schedule函数,
-
* 因为每个进程的调度都需要经历相同的过程和函数调用,所以实际上,
-
* 这里还是返回__schedule函数,只是在新的进程上下文中运行而已。
-
*/
-
}
X状态(EXIT_DEAD)的进程在finish_task_switch函数中被回收:
-
static void finish_task_switch(struct rq *rq, struct task_struct *prev)
-
__releases(rq->lock)
-
{
-
struct mm_struct *mm = rq->prev_mm;
-
long prev_state;
-
-
rq->prev_mm = NULL;
-
-
/*
-
* A task struct has one reference for the use as "current".
-
* If a task dies, then it sets TASK_DEAD in tsk->state and calls
-
* schedule one last time. The schedule call will never return, and
-
* the scheduled task must drop that reference.
-
* The test for TASK_DEAD must occur while the runqueue locks are
-
* still held, otherwise prev could be scheduled on another cpu, die
-
* there before we look at prev->state, and then the reference would
-
* be dropped twice.
-
* Manfred Spraul <manfred@colorfullife.com>
-
*/
-
prev_state = prev->state;
-
vtime_task_switch(prev);
-
finish_arch_switch(prev);
-
perf_event_task_sched_in(prev, current);
-
finish_lock_switch(rq, prev);
-
finish_arch_post_lock_switch();
-
-
fire_sched_in_preempt_notifiers(current);
-
if (mm)
-
mmdrop(mm);
-
/*判断DEAD状态(即X状态)的进程,如果是的话,需要对齐占用的资源(比如进程描述符)进行回收*/
-
if (unlikely(prev_state == TASK_DEAD)) {
-
task_numa_free(prev);
-
-
/*
-
* Remove function-return probe instances associated with this
-
* task and put them back on the free list.
-
*/
-
kprobe_flush_task(prev);
-
put_task_struct(prev);
-
}
-
-
tick_nohz_task_switch(current);
-
}
2)当进程被fork创建后首次运行
当进程被fork创建后首次运行时,在进程上下文切换后,switch_to宏中应该返回到ret_from_fork(entry_32.S汇编代码中定义)处开始执行(具体原理参见另一篇blog)
-
/*fork返回,单独处理*/
-
ENTRY(ret_from_fork)
-
CFI_STARTPROC
-
pushl_cfi %eax
-
/*进行调度收尾处理,包括回收DEAD状态(X状态)的进程*/
-
call schedule_tail
-
GET_THREAD_INFO(%ebp)
-
popl_cfi %eax
-
pushl_cfi $0x0202 # Reset kernel eflags
-
popfl_cfi
-
jmp syscall_exit
-
CFI_ENDPROC
-
END(ret_from_fork)
其中,schedule_tail会调用finish_task_switch回收X状态进程。
阅读(775) | 评论(0) | 转发(0) |