Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1242024
  • 博文数量: 122
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 4004
  • 用 户 组: 普通用户
  • 注册时间: 2014-02-20 08:27
文章分类
文章存档

2016年(1)

2015年(21)

2014年(100)

分类: LINUX

2015-01-06 13:00:03

一、概念
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内核中,定义了如下进程状态:

点击(此处)折叠或打开

  1. /*进程状态*/
  2. #define TASK_RUNNING    0 /*可运行状态,被调度的对象*/
  3. #define TASK_INTERRUPTIBLE    1 /*可中断睡眠状态,即平时见的S状态*/
  4. #define TASK_UNINTERRUPTIBLE    2 /*不可中断睡眠状态,即平时见的D状态*/
  5. #define __TASK_STOPPED    4
  6. #define __TASK_TRACED    8 /*调试使用状态,比如gdb attach时*/
  7. #define TASK_DEAD    64 /*进程退出后,等待资源回收时的状态*/
  8. #define TASK_WAKEKILL    128
  9. #define TASK_WAKING    256
  10. #define TASK_PARKED    512
  11. #define TASK_STATE_MAX    1024

组合状态:

点击(此处)折叠或打开

  1. /* Convenience macros for the sake of set_task_state */
  2. #define TASK_KILLABLE    (TASK_WAKEKILL | TASK_UNINTERRUPTIBLE)
  3. #define TASK_STOPPED    (TASK_WAKEKILL | __TASK_STOPPED)
  4. #define TASK_TRACED    (TASK_WAKEKILL | __TASK_TRACED)

  5. /* Convenience macros for the sake of wake_up */
  6. #define TASK_NORMAL    (TASK_INTERRUPTIBLE | TASK_UNINTERRUPTIBLE)
  7. #define TASK_ALL    (TASK_NORMAL | __TASK_STOPPED | __TASK_TRACED)

  8. /* get_task_state() */
  9. #define TASK_REPORT    (TASK_RUNNING | TASK_INTERRUPTIBLE | \
  10. TASK_UNINTERRUPTIBLE | __TASK_STOPPED | \
  11. __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中看到进程状态来源于内核中如下的定义

点击(此处)折叠或打开

  1. /*
  2.  * The task state array is a strange "bitmap" of
  3.  * reasons to sleep. Thus "running" is zero, and
  4.  * you can test for combinations of others with
  5.  * simple bit tests.
  6.  */
  7. static const char * const task_state_array[] = {
  8. "R (running)",    /* 0 */
  9. "S (sleeping)",    /* 1 */
  10. "D (disk sleep)",    /* 2 */
  11. "T (stopped)",    /* 4 */
  12. "t (tracing stop)",    /* 8 */
  13. "Z (zombie)",    /* 16 */
  14. "X (dead)",    /* 32 */
  15. "x (dead)",    /* 64 */
  16. "K (wakekill)",    /* 128 */
  17. "W (waking)",    /* 256 */
  18. "P (parked)",    /* 512 */
  19. };

以/proc//status中显示的状态为例,看看这个状态是如何获取并显示的。
cat /proc//status示例:

点击(此处)折叠或打开

  1. [root@A10097139 ~]# cat /proc/115/status
  2. Name:    crypto/7
  3. State:    S (sleeping)
  4. Tgid:    115
  5. Pid:    115
  6. PPid:    2
  7. TracerPid:    0
  8. Uid:    0    0    0    0
  9. Gid:    0    0    0    0
  10. Utrace:    0
  11. FDSize:    64
  12. Groups:
  13. Threads:    1
  14. SigQ:    2/30472
  15. SigPnd:    0000000000000000
  16. ShdPnd:    0000000000000000
  17. SigBlk:    0000000000000000
  18. SigIgn:    ffffffffffffffff
  19. SigCgt:    0000000000000000
  20. CapInh:    0000000000000000
  21. CapPrm:    ffffffffffffffff
  22. CapEff:    fffffffffffffeff
  23. CapBnd:    ffffffffffffffff
  24. Cpus_allowed:    80
  25. Cpus_allowed_list:    7
  26. Mems_allowed: 00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000001
  27. Mems_allowed_list:    0
  28. voluntary_ctxt_switches:    2
  29. nonvoluntary_ctxt_switches:    0

/proc//status其中的进程状态获取是通过如下调用路径:
proc_pid_status()
    ->task_state()
        ->get_task_state()


点击(此处)折叠或打开

  1. /*获取进程状态,实际是根据根据task_struct->state和exit_state来确认的*/
  2. static inline const char *get_task_state(struct task_struct *tsk)
  3. {
  4. /*将tsk->state通过TASK_REPORT过滤后,再组合exit_state,形成最终的状态*/
  5. unsigned int state = (tsk->state & TASK_REPORT) | tsk->exit_state;
  6. /*获取进程状态列表中的第一种状态*/
  7. const char * const *p = &task_state_array[0];

  8. BUILD_BUG_ON(1 + ilog2(TASK_STATE_MAX) != ARRAY_SIZE(task_state_array));
  9. /*取进程状态中的最低位作为返回的状态,比如如果Z(Zombie)位为1,那就不管后面的DEAD位了*/
  10. while (state) {
  11. p++;
  12. state >>= 1;
  13. }
  14. return *p;
  15. }

可见,/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():

点击(此处)折叠或打开

  1. /*进程退出时必经入口,完成相应处理*/
  2. void do_exit(long code)
  3. {
  4. struct task_struct *tsk = current;
  5. int group_dead;

  6. profile_task_exit(tsk);

  7. WARN_ON(blk_needs_flush_plug(tsk));
  8. /*不能在中断上下文中退出进程。*/
  9. if (unlikely(in_interrupt()))
  10. panic("Aiee, killing interrupt handler!");
  11. /*不能kill idle(pid=0)进程*/
  12. if (unlikely(!tsk->pid))
  13. panic("Attempted to kill the idle task!");
  14. ...
  15. /*退出通知,其中完成向父进程发SIGCLD信号*/
  16. exit_notify(tsk, group_dead);
  17. ...
  18. /* causes final put_task_struct in finish_task_switch(). */
  19. /*设置进程状态为DEAD.Fixme:跟僵尸进程和ZOMBIE状态有何关系?*/
  20. tsk->state = TASK_DEAD;
  21. tsk->flags |= PF_NOFREEZE;    /* tell freezer to ignore us */
  22. schedule();
  23. /*不应该再回来了。Fixme:task_struct会在finish_task_switch中清理?那SIGCLD信号谁来发?*/
  24. BUG();
  25. /* Avoid "noreturn function does return". */
  26. for (;;)
  27. cpu_relax();    /* For when BUG is null */
  28. }

exit_notify():

点击(此处)折叠或打开

  1. static void exit_notify(struct task_struct *tsk, int group_dead)
  2. {
  3. bool autoreap;

  4. /*
  5. * This does two things:
  6. *
  7.   * A. Make init inherit all the child processes
  8. * B. Check to see if any process groups have become orphaned
  9. *    as a result of our exiting, and if they have any stopped
  10. *    jobs, send them a SIGHUP and then a SIGCONT. (POSIX 3.2.2.2)
  11. */
  12. forget_original_parent(tsk);

  13. write_lock_irq(&tasklist_lock);
  14. if (group_dead)
  15. kill_orphaned_pgrp(tsk->group_leader, NULL);

  16. if (unlikely(tsk->ptrace)) {
  17. int sig = thread_group_leader(tsk) &&
  18. thread_group_empty(tsk) &&
  19. !ptrace_reparented(tsk) ?
  20. tsk->exit_signal : SIGCHLD;
  21. autoreap = do_notify_parent(tsk, sig);
  22. } else if (thread_group_leader(tsk)) {
  23. /*autoreap表示当父进程忽略了SIGCLD信号时,需要进程self-reap相应的资源*/
  24. autoreap = thread_group_empty(tsk) &&
  25. do_notify_parent(tsk, tsk->exit_signal); /*通过信号(通常是SIGCLD)通知父进程*/
  26. } else {
  27. autoreap = true;
  28. }
  29. /*
  30.         * 设置进程退出状态,父进程忽略了SIGCLD信号时,需要进程self-reap,
  31.         * 此时autoreap==1,则退出状态为EXIT_DEAD,否则为EXIT_ZOMBIE。
  32.         * 父进程只会负责EXIT_ZOMBIE状态的子进程的资源回收,EXIT_DEAD的进程
  33.         * 自行处理。
  34. */
  35. tsk->exit_state = autoreap ? EXIT_DEAD : EXIT_ZOMBIE;

  36. /* mt-exec, de_thread() is waiting for group leader */
  37. if (unlikely(tsk->signal->notify_count < 0))
  38. wake_up_process(tsk->signal->group_exit_task);
  39. write_unlock_irq(&tasklist_lock);

  40. /* If the process is dead, release it - nobody will wait for it */
  41. if (autoreap)
  42. release_task(tsk);
  43. }

do_notify_parent():


点击(此处)折叠或打开

  1. /*
  2.   * 通知父进程自己要退出了,其实就是向父进程发送SIGCLD信号,
  3.   * 如果父进程处理SIGCLD信号,则通常会在信号处理函数中调用wait()相关接口,
  4.   * 回收子进程最后的资源(比如task_struct?);如果父进程忽略该信号,则子进程
  5.   * 需要自行回收(self-reaping)。Fixme:可能会变僵尸进程?
  6.   */
  7. bool do_notify_parent(struct task_struct *tsk, int sig)
  8. {
  9. struct siginfo info;
  10. unsigned long flags;
  11. struct sighand_struct *psig;
  12. bool autoreap = false;
  13. cputime_t utime, stime;

  14. BUG_ON(sig == -1);

  15.      /* do_notify_parent_cldstop should have been called instead. */
  16.      BUG_ON(task_is_stopped_or_traced(tsk));

  17. BUG_ON(!tsk->ptrace &&
  18.       (tsk->group_leader != tsk || !thread_group_empty(tsk)));

  19. if (sig != SIGCHLD) {
  20. /*
  21. * This is only possible if parent == real_parent.
  22. * Check if it has changed security domain.
  23. */
  24. if (tsk->parent_exec_id != tsk->parent->self_exec_id)
  25. sig = SIGCHLD;
  26. }

  27. info.si_signo = sig;
  28. info.si_errno = 0;
  29. /*
  30. * We are under tasklist_lock here so our parent is tied to
  31. * us and cannot change.
  32. *
  33. * task_active_pid_ns will always return the same pid namespace
  34. * until a task passes through release_task.
  35. *
  36. * write_lock() currently calls preempt_disable() which is the
  37. * same as rcu_read_lock(), but according to Oleg, this is not
  38. * correct to rely on this
  39. */
  40. rcu_read_lock();
  41. info.si_pid = task_pid_nr_ns(tsk, task_active_pid_ns(tsk->parent));
  42. info.si_uid = from_kuid_munged(task_cred_xxx(tsk->parent, user_ns),
  43.       task_uid(tsk));
  44. rcu_read_unlock();

  45. task_cputime(tsk, &utime, &stime);
  46. info.si_utime = cputime_to_clock_t(utime + tsk->signal->utime);
  47. info.si_stime = cputime_to_clock_t(stime + tsk->signal->stime);

  48. info.si_status = tsk->exit_code & 0x7f;
  49. if (tsk->exit_code & 0x80)
  50. info.si_code = CLD_DUMPED;
  51. else if (tsk->exit_code & 0x7f)
  52. info.si_code = CLD_KILLED;
  53. else {
  54. info.si_code = CLD_EXITED;
  55. info.si_status = tsk->exit_code >> 8;
  56. }
  57. /*获取父进程的sighand*/
  58. psig = tsk->parent->sighand;
  59. spin_lock_irqsave(&psig->siglock, flags);
  60. /*
  61.  * 如果发送信号为SIGCHLD且父进程忽略了SIGCHLD信号或者设置了SA_NOCLDWAIT标记,则设置autoreap,
  62.  * 即子进程自己回收资源,不由父进程通过wait来回收。
  63.  */
  64. if (!tsk->ptrace && sig == SIGCHLD &&
  65.    (psig->action[SIGCHLD-1].sa.sa_handler == SIG_IGN ||
  66.     (psig->action[SIGCHLD-1].sa.sa_flags & SA_NOCLDWAIT))) {
  67. /*
  68. * We are exiting and our parent doesn't care. POSIX.1
  69. * defines special semantics for setting SIGCHLD to SIG_IGN
  70. * or setting the SA_NOCLDWAIT flag: we should be reaped
  71. * automatically and not left for our parent's wait4 call.
  72. * Rather than having the parent do it as a magic kind of
  73. * signal handler, we just set this to tell do_exit that we
  74. * can be cleaned up without becoming a zombie. Note that
  75. * we still call __wake_up_parent in this case, because a
  76. * blocked sys_wait4 might now return -ECHILD.
  77. *
  78. * Whether we send SIGCHLD or not for SA_NOCLDWAIT
  79. * is implementation-defined: we do (if you don't want
  80. * it, just use SIG_IGN instead).
  81. */
  82. autoreap = true;
  83. if (psig->action[SIGCHLD-1].sa.sa_handler == SIG_IGN)
  84. sig = 0;
  85. }
  86. if (valid_signal(sig) && sig)
  87. /*向父进程发送信号(SIGCLD)*/
  88. __group_send_sig_info(sig, &info, tsk->parent);
  89. /*
  90.  * 唤醒父进程。
  91.  * Fixme:如果上面的if不成立,不发送信号,此时还唤醒父进程来干嘛?
  92.  * 答案:见上面注释:(?)    
  93.  * Note that we still call __wake_up_parent in this case, because a
  94.  * blocked sys_wait4 might now return -ECHILD.
  95.  */
  96. __wake_up_parent(tsk, tsk->parent);
  97. spin_unlock_irqrestore(&psig->siglock, flags);

  98. return autoreap;
  99. }

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”处继续执行:

点击(此处)折叠或打开

  1. /*
  2.   * 上下文切换,在schedule中调用,current进程调度出去,当该进程被再次调度到时,重新从__switch_to后面开始执行
  3.   * prev:被替换的进程
  4.   * next:被调度的新进程
  5.   * last:当切换回原来的进程(prev)后,被替换的另外一个进程。
  6.   */
  7. #define switch_to(prev, next, last)    \
  8. do {    \
  9. /*    \
  10. * Context-switching clobbers all registers, so we clobber    \
  11. * them explicitly, via unused output variables.    \
  12. * (EAX and EBP is not listed because EBP is saved/restored    \
  13. * explicitly for wchan access and EAX is the return value of    \
  14. * __switch_to())    \
  15. */    \
  16. unsigned long ebx, ecx, edx, esi, edi;    \
  17. \
  18. asm volatile("pushfl\n\t"    /* save flags */    /*将eflags寄存器值压栈*/\
  19.     "pushl %%ebp\n\t"    /* save EBP */    /*将EBP压栈*/\
  20. /*将当前栈指针(内核态)保存到prev进程的thread.sp中*/
  21.     "movl %%esp,%[prev_sp]\n\t"    /* save ESP */ \
  22.     /*将next进程的栈指针(内核态)装载到ESP寄存器中*/
  23.     "movl %[next_sp],%%esp\n\t"    /* restore ESP */ \
  24.     /*保存"标号1"的地址到prev进程的thread.ip,以便当prev进程重新被调度运行时,可以从"标号1处"重新开始执行*/
  25.     "movl $1f,%[prev_ip]\n\t"    /* save EIP */    \
  26.     /*
  27.          * 将next进程的IP(通常都是"标号1"的地址,因为通常都是经历过这里的调度过程的,上一行代码中即保存了这个IP)
  28.    * 压入当前的(即next进程的)堆栈中。结合后面的jmp指令(注意:不是call指令)一起理解,当__switch_to执行完ret返回时,
  29.    * 会自动从当前的堆栈中弹出该地址作为函数的返回地址接着执行,如此即可实现新进程的运行。
  30.                        */
  31.     "pushl %[next_ip]\n\t"    /* restore EIP */    \
  32.     __switch_canary    \
  33.     /*
  34.          *jmp到__switch_to函数执行,当此函数返回时,自动跳转到[next_ip]开始执行,实现新进程的调度。注意不是call,jmp指令
  35.          * 不会自动将当前地址压栈,call会自动压栈
  36.          */
  37.     "jmp __switch_to\n"    /* regparm call */    \
  38.     /*当prev进程再次被调度到时,从这里开始执行*/
  39.     "1:\t"    \
  40.     /*恢复EBP*/
  41.     "popl %%ebp\n\t"    /* restore EBP */    \
  42.     /*恢复eflags*/
  43.     "popfl\n"    /* restore flags */    \
  44. \
  45.     /* output parameters */    \
  46.     /*输出参数*/
  47.     : [prev_sp] "=m" (prev->thread.sp),    \
  48.       [prev_ip] "=m" (prev->thread.ip),    \
  49.       "=a" (last),    \
  50. \
  51.       /* clobbered output registers: */    \
  52.       "=b" (ebx), "=c" (ecx), "=d" (edx),    \
  53.       "=S" (esi), "=D" (edi)    \
  54.       \
  55.       __switch_canary_oparam    \
  56. \
  57.       /* input parameters: */    \
  58.       /*输入参数*/
  59.     : [next_sp] "m" (next->thread.sp),    \
  60.       [next_ip] "m" (next->thread.ip),    \
  61.       \
  62.       /* regparm parameters for __switch_to(): */    \
  63.       /*将prev和next分别存入ecx和edx,然后作为参数传入到__switch_to函数中*/
  64.       [prev] "a" (prev),    \
  65.       [next] "d" (next)    \
  66. \
  67.       __switch_canary_iparam    \
  68. \
  69.     : /* reloaded segment registers */    \
  70. "memory");    \
  71. } while (0)

退出switch_to宏后,会返回到context_switch函数中继续执行:

点击(此处)折叠或打开

  1. /*
  2.  * context_switch - switch to the new MM and the new
  3.  * thread's register state.
  4.  */
  5. static inline void
  6. context_switch(struct rq *rq, struct task_struct *prev,
  7.       struct task_struct *next)
  8. {
  9. ...
  10. /* Here we just switch the register state and the stack. */
  11. /*切换到新的进程上下文*/
  12. switch_to(prev, next, prev);
  13. /*屏障,防止乱序*/
  14. barrier();
  15. /*
  16. * this_rq must be evaluated again because prev may have moved
  17. * CPUs since it called schedule(), thus the 'rq' on its stack
  18. * frame will be invalid.
  19. */
  20. /*
  21.  * 上下文切换后,会继续到这里执行,但这里已经是新的进程上下文了
  22.  * 在新的上下文中,清理掉上一个被调度进程prev的相关资源(比如DEAD状态的进程占用的资源)
  23.  */
  24. finish_task_switch(this_rq(), prev);
  25. /*
  26.  * Fixme:本函数执行完成后,返回到哪里? 这里已经是新的进程上下文了,
  27.  * 进程的内核栈已经切换,所以,内核栈中该函数的返回地址也已经
  28.  * 切换了,因此,不可能再返回上一个进程的上下文中的__schedule函数了。
  29.  * 但是新的进程上下文该函数的上级函数(该返回的函数)也必然是__schedule函数,
  30.  * 因为每个进程的调度都需要经历相同的过程和函数调用,所以实际上,
  31.  * 这里还是返回__schedule函数,只是在新的进程上下文中运行而已。
  32.  */
  33. }

X状态(EXIT_DEAD)的进程在finish_task_switch函数中被回收:

点击(此处)折叠或打开

  1. static void finish_task_switch(struct rq *rq, struct task_struct *prev)
  2. __releases(rq->lock)
  3. {
  4. struct mm_struct *mm = rq->prev_mm;
  5. long prev_state;

  6. rq->prev_mm = NULL;

  7. /*
  8. * A task struct has one reference for the use as "current".
  9. * If a task dies, then it sets TASK_DEAD in tsk->state and calls
  10. * schedule one last time. The schedule call will never return, and
  11. * the scheduled task must drop that reference.
  12. * The test for TASK_DEAD must occur while the runqueue locks are
  13. * still held, otherwise prev could be scheduled on another cpu, die
  14. * there before we look at prev->state, and then the reference would
  15. * be dropped twice.
  16. *    Manfred Spraul <manfred@colorfullife.com>
  17. */
  18. prev_state = prev->state;
  19. vtime_task_switch(prev);
  20. finish_arch_switch(prev);
  21. perf_event_task_sched_in(prev, current);
  22. finish_lock_switch(rq, prev);
  23. finish_arch_post_lock_switch();

  24. fire_sched_in_preempt_notifiers(current);
  25. if (mm)
  26. mmdrop(mm);
  27. /*判断DEAD状态(即X状态)的进程,如果是的话,需要对齐占用的资源(比如进程描述符)进行回收*/
  28. if (unlikely(prev_state == TASK_DEAD)) {
  29. task_numa_free(prev);

  30. /*
  31. * Remove function-return probe instances associated with this
  32. * task and put them back on the free list.
  33. */
  34. kprobe_flush_task(prev);
  35. put_task_struct(prev);
  36. }

  37. tick_nohz_task_switch(current);
  38. }

2)当进程被fork创建后首次运行
当进程被fork创建后首次运行时,在进程上下文切换后,switch_to宏中应该返回到ret_from_fork(entry_32.S汇编代码中定义)处开始执行(具体原理参见另一篇blog)

点击(此处)折叠或打开

  1. /*fork返回,单独处理*/
  2. ENTRY(ret_from_fork)
  3. CFI_STARTPROC
  4. pushl_cfi %eax
  5. /*进行调度收尾处理,包括回收DEAD状态(X状态)的进程*/
  6. call schedule_tail
  7. GET_THREAD_INFO(%ebp)
  8. popl_cfi %eax
  9. pushl_cfi $0x0202    # Reset kernel eflags
  10. popfl_cfi
  11. jmp syscall_exit
  12. CFI_ENDPROC
  13. END(ret_from_fork)
其中,schedule_tail会调用finish_task_switch回收X状态进程。


阅读(6276) | 评论(0) | 转发(3) |
给主人留下些什么吧!~~