zoukankan      html  css  js  c++  java
  • Linux0.11内核进程的结束

    进程的结束

    结束一个进程,就是要释放该进程所有的结构和资源,让系统从此之后再也感觉不到它的存在。如前面所说的,一个进程的结构包括:

    1. task[]数组中一项,指向了该进程的task_struct和内核堆栈所在页面;

    2. GDT中两项,一项是TSS描述符,一项是LDT描述符;

    3. 若干页目录项和若干页表。

    一个进程拥有的资源包括:

    1. 进程拥有的所有物理页面(包括页表和task_struct所占页面);

    2. 进程打开的所有文件。

    GDT中的两项不用特意清除,以后别的进程要用时直接覆盖上去就了。因此,进程要结束就要做好如下几件事情

    1. 释放所有物理页面;

    2. 关闭所有打开的文件;

    3. 清除task[]数组中相关项。

    清除task[]数组项往往是最后一步工作。当该项被清除后,进程就不可能被调度函数schedule()再次选中了。同时,进程结束时还可能需要与父进程通信,所以子进程一般完成前面两个任务,然后通知父进程“子进程要结束了!!”,最后由父进程做最后的task[]数组项清除。子进程通过系统调用exit()完成前两项任务,把自己变成僵死状态(TASK_ZOMBIE)。父进程通过系统调用waitpit()完成最后的扫尾。

    1. /****************************************************************************/  
    2. /* 功能:通知进程号为pid的父进程,子进程结束                               */  
    3. /*      其实就是给父进程发送SIGCHLD信号                                 */  
    4. /* 参数:pid 父进程的进程号                                                   */  
    5. /* 返回:(无)                                                               */  
    6. /****************************************************************************/  
    7. static void tell_father(int pid)  
    8. {  
    9.     int i;  
    10. // 遍历task[]数组,寻找进程号为pid的进程   
    11.     if (pid)  
    12.         for (i=0;i<NR_TASKS;i++) {  
    13.             if (!task[i])   // 跳过空项   
    14.                 continue;  
    15.             if (task[i]->pid != pid) // 跳过进程号不是pid的项   
    16.                 continue;  
    17.             task[i]->signal |= (1<<(SIGCHLD-1));   // 向pid发送SIGCHLD信号   
    18.             return;  
    19.         }  
    20. /* if we don't find any fathers, we just release ourselves */  
    21. /* This is not really OK. Must change it to make father 1 */  
    22. // 到这里说明父进程找不到,这时释放task[]数组项的工作就要子进程自己完成。   
    23. // 正常情况下程序不会运行到这里,因为若父进程退出,子进程会由进程1接管。   
    24. // 到这里说明程序有bug了。   
    25.     printk("BAD BAD - no father found/n/r");  
    26.     release(current);   // 只能子进程自己完成扫尾工作   
    27. }  
     

    1. /****************************************************************************/  
    2. /* 功能:当前进程释放所有资源和结构,只保留进程控制块,同时                 */  
    3. /*       进入僵死状态,等待父进程做最后处理                                  */  
    4. /* 参数:出错码                                                               */  
    5. /* 返回:(无)                                                               */  
    6. /****************************************************************************/  
    7. int do_exit(long code)  
    8. {  
    9.     int i;  
    10. // 第一步工作,释放进程占用的所有物理页面,同时清空相应页目录项和页表   
    11.     free_page_tables(get_base(current->ldt[1]),get_limit(0x0f)); // 释放代码段   
    12.     free_page_tables(get_base(current->ldt[2]),get_limit(0x17)); // 释放数据段   
    13. // 如果当前要结束进程还有子进程,则需要让进程1变成所有进程的父进程   
    14. // 进程1保证调用waitpid(),处理所有子进程结束的扫尾工作。   
    15.     for (i=0 ; i<NR_TASKS ; i++)  
    16.         // 把当前进程的所有子进程交给进程1   
    17.         if (task[i] && task[i]->father == current->pid) {  
    18.             task[i]->father = 1;  
    19.         // 如果有的子进程已经是僵死状态,则给进程1发送SIGCHLD信号   
    20.             if (task[i]->state == TASK_ZOMBIE)  
    21.                 /* assumption task[1] is always init */  
    22.                 (void) send_sig(SIGCHLD, task[1], 1);  
    23.         }  
    24. // 第二步,关闭所有文件,同时释放占用的文件i节点。   
    25.     for (i=0 ; i<NR_OPEN ; i++)  
    26.         if (current->filp[i])  
    27.             sys_close(i);  
    28.     iput(current->pwd);  
    29.     current->pwd=NULL;  
    30.     iput(current->root);  
    31.     current->root=NULL;  
    32.     iput(current->executable);  
    33.     current->executable=NULL;  
    34.     if (current->leader && current->tty >= 0)  
    35.         tty_table[current->tty].pgrp = 0;  
    36.     if (last_task_used_math == current)  
    37.         last_task_used_math = NULL;  
    38. // 如果当前进程是一个会话的首领,则终止该会话的所有进程   
    39. // 即向进程发送SIGHUP信号   
    40.     if (current->leader)  
    41.         kill_session();  
    42. // 两步工作都完成了,当前进入僵死状态   
    43.     current->state = TASK_ZOMBIE;  
    44.     current->exit_code = code;   // 设置退出码,父进程会取   
    45. // 通知父进程,子要进程结束,即向父进程发送SIGCHLD信号   
    46.     tell_father(current->father);      
    47. // 重新调度,当内核调度到父进程时,让父进程处理最后事宜   
    48.     schedule();  
    49. // 因为当前进程的状态已经是僵死状态了,所以schedule()函数永远不会再次   
    50. // 选中当前进程。也就是说do_exit()是永远运行不到这里的。   
    51. // 当父进程最终把当前进程的task[]数组项清空后,当前进程完全消失。   
    52.     return (-1);    /* just to suppress warnings */  
    53. }  
    54. /****************************************************************************/  
    55. /* 功能:exit()系统调用,内部再调用真正的处理函数do_exit()                      */  
    56. /* 参数:出错码                                                               */  
    57. /* 返回:(无)                                                               */  
    58. /****************************************************************************/  
    59. int sys_exit(int error_code)  
    60. {  
    61.     return do_exit((error_code&0xff)<<8);  
    62. }  
     

    1. /****************************************************************************/  
    2. /* 功能:释放p指向的一页内存,并且清空task[]数组中存放p的项                 */  
    3. /* 参数:p 存放进程控制块所在页面的地址                                      */  
    4. /* 返回:(无)                                                               */  
    5. /****************************************************************************/  
    6. void release(struct task_struct * p)  
    7. {  
    8.     int i;  
    9.     if (!p)  
    10.         return;  
    11. // 遍历整个task[]数组,直到找到存放了p的数组项   
    12.     for (i=1 ; i<NR_TASKS ; i++)  
    13.         if (task[i]==p) {   // 找到了存放p的数组项   
    14.             task[i]=NULL;   // 清空数组项   
    15.             free_page((long)p);     // 是否p指向的一页物理内存   
    16.             schedule();         // 重新调度,似乎没有必要,但也没错   
    17.             return;  
    18.         }  
    19. // 如果task[]数组中没有一项值等于p,说明正在对一个并不存在的进程进行操作   
    20. // 这时内核有错误,死机   
    21.     panic("trying to release non-existent task");  
    22. }  
    23. /****************************************************************************/  
    24. /* 功能:waitpid系统调用,当前进程挂起,进入可中断等待状态。直到pid指定的子    */  
    25. /*       进程退出(即僵死装入)。然后释放子进程占用的最后资源(即进程控制块  */  
    26. /*       和内核堆栈所占用的一页物理内存)。如果子进程已经是僵死状态,当前进程*/  
    27. /*       无需挂起,直接释放子进程资源。                                        */  
    28. /* 参数:pid   指定子进程的进程标识号                                         */  
    29. /*          pid > 0  等待进程号为pid的子进程                                   */  
    30. /*          pid = 0 等待进程组号等于当前进程组号的任何一个子进程          */  
    31. /*          pid = -1    等待任何一个子进程                                       */  
    32. /*          pid < -1 等待进程组号等于-pid的任何一个子进程                        */  
    33. /*      stat_addr   指向long型的指针,存放状态信息                               */  
    34. /*      options                                                             */  
    35. /*          如果options中WNOHANG置位:表示如果没有满足pid标识的子进程   */  
    36. /*          进入僵死状态,则马上退出,当前进程不用挂起等待。                */  
    37. /*          如果options中WUNTRACED置位,表示如果满足pid标识的子进程   */  
    38. /*          是停止状态(TASK_STOPPED),当前进程马上退出,不用跟踪   ,同时 */  
    39. /*          如果stat_addr不是空,就将状态信息保存到那里。                 */  
    40. /* 返回:子进程号                                                          */  
    41. /*       负数 出错码                                                     */  
    42. /****************************************************************************/  
    43. int sys_waitpid(pid_t pid,unsigned long * stat_addr, int options)  
    44. {  
    45.     int flag, code;  
    46.     struct task_struct ** p;  
    47. // 验证stat_addr开始4个字节的内存是否可写,如果不能写需要分配新的内存页面   
    48.     verify_area(stat_addr,4);  
    49. repeat:  
    50.     flag=0;  
    51. // 遍历整个task[]数组,寻找满足pid条件的子进程   
    52.     for(p = &LAST_TASK ; p > &FIRST_TASK ; --p) {  
    53.         if (!*p || *p == current)   // 忽略task[]中的空项和当前进程   
    54.             continue;  
    55.         if ((*p)->father != current->pid) // 忽略非当前进程儿子的进程   
    56.             continue;  
    57. // 下面根据pid在值考察子进程的情况   
    58. // 如果下面三个情况都没有continue,则pid就是-1   
    59.         if (pid>0) { // 如果pid>0,忽略进程号不是pid的进程   
    60.             if ((*p)->pid != pid)  
    61.                 continue;  
    62.         } else if (!pid) {  // 如果pid=0,忽略组号不是当前进程组号的进程   
    63.             if ((*p)->pgrp != current->pgrp)  
    64.                 continue;  
    65.         } else if (pid != -1) { // 如果pid<-1,忽略组号不是-pid的进程   
    66.             if ((*p)->pgrp != -pid)  
    67.                 continue;  
    68.         }  
    69. // 到这里,说明找到了符合pid条件的子进程   
    70. // 分别考察子进程在状态   
    71.         switch ((*p)->state) {  
    72.             case TASK_STOPPED:      // 子进程是停止状态   
    73.                 if (!(options & WUNTRACED)) // 如果WUNTRACED没有置位,则继续   
    74.                                             // 扫描其他进程   
    75.                     continue;  
    76.                 put_fs_long(0x7f,stat_addr); // 如果WUNTRACED置位,则写入状态信息   
    77.                                         // 0x7f使得WIFSTOPPED()宏为真   
    78.                 return (*p)->pid;    // 返回子进程的pid   
    79.             case TASK_ZOMBIE:       // 如果子进程退出   
    80.                 current->cutime += (*p)->utime;   // 把子进程的用户态和内核态时间   
    81.                 current->cstime += (*p)->stime;   //  计入父进程中   
    82.                 flag = (*p)->pid;        // 临时保存子进程号,下面要返回   
    83.                 code = (*p)->exit_code;  // 取出子进程的退出码   
    84.                 release(*p);            // 是放子进程最后占用的资源,彻底消灭子进程   
    85.                 put_fs_long(code,stat_addr);    // 把子进程退出码放入stat_addr中   
    86.                 return flag;            // 返回子进程号   
    87.             default:  
    88.                 flag=1;         // 如果子进程是其他任何状态,把flag置为1   
    89.                 continue;  
    90.         }  
    91.     }  
    92.     if (flag) {  
    93. //程序会运行到这里,说明满足条件的子进程状态不是停止或退出。   
    94.         if (options & WNOHANG)  // 如果WNOHANG置位,马上退出   
    95.             return 0;  
    96.         current->state=TASK_INTERRUPTIBLE;   // 否则当前进程挂起   
    97.         schedule();     // 重新调度   
    98. // 当调度程序重新选择当前进程后,从这里开始运行   
    99.         // 检查当前进程是否仅仅因为SIGCHLD信号而被唤醒,如果是,从repeat   
    100.         // 重新开始运行。   
    101.         if (!(current->signal &= ~(1<<(SIGCHLD-1))))  
    102.             goto repeat;  
    103.         else        // 如果当前进程还收到其他信号,则返回出错码   
    104.             return -EINTR;  
    105.     }  
    106. // 程序到这里,说明找不到满足pid条件的子进程   
    107.     return -ECHILD; // 返回出错码   
    108. }  
     


    一个进程eixt()后并没有完全消失,它的物理页面全部释放了,页表页目录项也全部清除。只剩进程控制块(task_struct)和内核堆栈占用的一页内存还保留着,在task[]数组中还有它的一项。同时进程进入了僵死状态,在也不会被调度执行。一个进程永远不会再次被调度,但是它却还占有task[]数组中的一项,这让人很难忍受,所以这时的进程就像僵尸一样让人讨厌。

    最后一页内存以及task[]数组项的清除工作应该由父进程调用waitpid()完成,但是很有可能父进程无法完成,比如如下情况:

    1. 父进程早于子进程exit()

    2. 子进程僵死,但父进程没有调用waitpid()

    3. 父进程调用waitpid(),但因为种种原因没有释放子进程资源就退出了。

    我们必须要在这样的情况下仍然能消灭掉掉僵死进程,否则它们永远占用task[]数组项,会使得find_empty_process() 函数找不到空闲的task[]数组项,导致无法创建新的进程。

    解决方法很简单,如果父进程无法完成,就让让进程1来做。当一个父进程早于子进程exit()时,它把所有的子进程过继给进程1。当父进程没有调用waitpid(),或调用waitpid()但还是没能消灭僵死进程时,僵死的子进程永远存在,直到父进程exit(),这时子进程过继给进程1,同时父进程向进程1发送SIGCHLD信号。

    init()的源代码分析见系统初始化这章,这里我们主要看进程1如何接管父进程无法消灭的僵死进程。init()的伪代码如下:

    init()初始化应用环境后,进程1就运行在while(1)的死循环中。进程1创建进程2,进程2execve()系统调用运行shell。当用户与shell交互时,进程1运行在里层while(1)循环中,该循环的作用就是收拾所有的僵死进程。

    wait()函数的定义在wait.c中,它封装了waitpid()函数,等待任何子进程结束。


    wait()


    pid_t wait(int * wait_stat)

    {

    return waitpid(-1,wait_stat,0);

    }



    父进程调用waitpid()最多只能处理一个满足条件的子进程,除非它像进程1那样在死循环中调用waitpid()。进程1死循环调用wait(),直到当前shell退出,保证处理完所有僵死进程。

  • 相关阅读:
    通过Logstash由SQLServer向Elasticsearch同步数据
    ELK +Nlog 分布式日志系统的搭建 For Windows
    Gulp 给所有静态文件引用加版本号
    Fluentdata详解
    jQuery Easy UI (适应屏幕分辨率大小)布局(Layout)
    什么是数据结构---算法
    WCF的学习之旅
    程序员必学之精华----软件工程
    译 .NET Core 3.0 发布
    Vue+Element UI 实现视频上传
  • 原文地址:https://www.cnblogs.com/wangfengju/p/6173168.html
Copyright © 2011-2022 走看看