Linux僵尸进程堆栈分析,kernel 3.10内核源码分析--进程状态、ZOMBIE僵尸进程及X状态进程...

一、概念

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_ZOMBIE16

#define EXIT_DEAD32

可见,退出状态中,除了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

*/

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状态进程。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值