前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >理解进程的退出

理解进程的退出

作者头像
theanarkh
发布2020-03-12 00:37:18
7950
发布2020-03-12 00:37:18
举报
文章被收录于专栏:原创分享原创分享

当一个进程调用exit的时候,就意味着他退出了。我们看一下他退出的时候,都做了什么操作。

代码语言:javascript
复制
int sys_exit(int error_code)
{
     return do_exit((error_code&0xff)<<8);
}

 int do_exit(long code)
 {
     int i;
     // 释放代码段和数据段页表,页目录,物理地址
     free_page_tables(get_base(current->ldt[1]),get_limit(0x0f));
     free_page_tables(get_base(current->ldt[2]),get_limit(0x17));
     for (i=0 ; i<NR_TASKS ; i++)
         // 找出当前进程的子进程
         if (task[i] && task[i]->father == current->pid) {
             // 子进程的新父进程是进程id为1的进程
             task[i]->father = 1;
             /*
             父进程没有调wait,子进程退出了,然后父进程也退出了。没人回收子进程的pcb,给init进程发
            */
             if (task[i]->state == TASK_ZOMBIE)
                 /* assumption task[1] is always init */
                 (void) send_sig(SIGCHLD, task[1], 1);
         }
     // 关闭文件
     for (i=0 ; i<NR_OPEN ; i++)
         if (current->filp[i])
             sys_close(i);
     // 回写inode到硬盘
     iput(current->pwd);
     current->pwd=NULL;
     iput(current->root);
     current->root=NULL;
     iput(current->executable);
     current->executable=NULL;
     // 是会话首进程并打开了终端
     if (current->leader && current->tty >= 0)
         tty_table[current->tty].pgrp = 0;
     if (last_task_used_math == current)
         last_task_used_math = NULL;
     // 是会话首进程,则通知会话里的所有进程会话结束
     if (current->leader)
         kill_session();
     // 更新状态
     current->state = TASK_ZOMBIE;
     current->exit_code = code;
     // 通知父进程
     tell_father(current->father);
     // 重新调度进程
     schedule();
     return (-1);    /* just to suppress warnings */
 }

释放地址

代码语言:javascript
复制
// from是线性地址。释放from开始,连续的n个大小为4MB的页面对应的物理地址。最后释放页表、页目录项
    int free_page_tables(unsigned long from,unsigned long size)
{
        unsigned long *pg_table;
        unsigned long * dir, nr;
        // 判断是否按4MB对齐
        if (from & 0x3fffff)
            panic("free_page_tables called with wrong alignment");
        if (!from)
            panic("Trying to free up swapper memory space");
        // 算出size包含多少个MB,比如size是0 - 1>>22,则计算机后是1
        size = (size + 0x3fffff) >> 22;
        /*
            页目录在地址0开始的地方,首先右移得到页目录索引,
            根据索引得到页目录项内容,因为页目录项的内容占4个字节,
            其中高20位是页表地址,低12位是标记位,,所以要乘以4得到
            from对应的页目录项的地址。即dir = from >> 22 << 2 = from >> 20,
            但是代码里是直接右移20位,所以需要和0xffc与,把低两位置0,最后得到from
            对应的页目录项的地址
        */
        dir = (unsigned long *) ((from>>20) & 0xffc); /* _pg_dir = 0 */
        for ( ; size-->0 ; dir++) {
            // 低位是1说明该页目录项有效
            if (!(1 & *dir))
                continue;
            // *dir为页表首地址,与0xfffff000是因为高二十位是有效地址,低12位是标记位
            pg_table = (unsigned long *) (0xfffff000 & *dir);
            // 释放每个页表指向的物理地址
            for (nr=0 ; nr<1024 ; nr++) {
                // 页表是否有效,有效则释放*pg_table指向物理地址,以4kb对齐
                if (1 & *pg_table)
                    // 与0xfffff000是因为高二十位是有效地址,低12位是标记位 
                    free_page(0xfffff000 & *pg_table);
                // 置页表无效
                *pg_table = 0;
                // 下一个页表
                pg_table++;
            }
            // 释放页表占据的物理地址
            free_page(0xfffff000 & *dir);
            // 置页目录项为无效
            *dir = 0;
        }
        invalidate();
        return 0;
    }

结束会话

代码语言:javascript
复制
// 结束会话,给该会话的所有进程发SIGHUP信号,因为子进程会继承父进程的sessionid,所以if可能会多次成立
    static void kill_session(void)
{
        struct task_struct **p = NR_TASKS + task;

        while (--p > &FIRST_TASK) {
            if (*p && (*p)->session == current->session)
                (*p)->signal |= 1<<(SIGHUP-1);
        }
    }

通知父进程

代码语言:javascript
复制
// 子进程退出,通知进程id是pid的父进程
    static void tell_father(int pid)
    {
        int i;

        if (pid)
            for (i=0;i<NR_TASKS;i++) {
                if (!task[i])
                    continue;
                if (task[i]->pid != pid)
                    continue;
                // 根据pid找到父进程,设置子进程退出的信号
                task[i]->signal |= (1<<(SIGCHLD-1));
                return;
            }
    /* if we don't find any fathers, we just release ourselves */
    /* This is not really OK. Must change it to make father 1 */
        printk("BAD BAD - no father found\n\r");
        // 释放pcb结构
        release(current);
    }

我们发生子进程释放了一系列的资源,但是没有释放pcb。即task_struct结构体。这时候子进程是僵尸进程。需要等待父进程处理。

代码语言:javascript
复制
// 等待pid进程退出,并且把退出码写到stat_addr变量
int sys_waitpid(pid_t pid,unsigned long * stat_addr, int options)
{
    int flag, code;
    struct task_struct ** p;

    verify_area(stat_addr,4);
repeat:
    flag=0;
    for(p = &LAST_TASK ; p > &FIRST_TASK ; --p) {
        // 过滤不符合条件的
        if (!*p || *p == current)
            continue;
        // 不是当前进程的子进程则跳过
        if ((*p)->father != current->pid)
            continue;
        // pid大于0说明等待某一个子进程
        if (pid>0) {
            // 不是等待的子进程则跳过
            if ((*p)->pid != pid)
                continue;
        } else if (!pid) {
            // pid等于0则等待进程组中的进程,不是当前进程组的进程则跳过
            if ((*p)->pgrp != current->pgrp)
                continue;
        } else if (pid != -1) {
            // 不等于-1说明是等待某一个组的,但不是当前进程的组,组id是-pid的组,不是该组则跳过
            if ((*p)->pgrp != -pid)
                continue;
        } 
        // else {
        //  等待所有进程
        // }
        // 找到了一个符合条件的进程
        switch ((*p)->state) {
            // 子进程已经退出,这个版本没有这个状态
            case TASK_STOPPED:
                if (!(options & WUNTRACED))
                    continue;
                put_fs_long(0x7f,stat_addr);
                return (*p)->pid;
            case TASK_ZOMBIE:
                // 子进程已经退出,则返回父进程
                current->cutime += (*p)->utime;
                current->cstime += (*p)->stime;
                flag = (*p)->pid;
                code = (*p)->exit_code;
                release(*p);
                put_fs_long(code,stat_addr);
                return flag;
            default:
                // flag等于1说明子进程还没有退出
                flag=1;
                continue;
        }
    }
    // 还没有退出的进程
    if (flag) {
        // 设置了非阻塞则返回
        if (options & WNOHANG)
            return 0;
        // 否则父进程挂起
        current->state=TASK_INTERRUPTIBLE;
        // 重新调度
        schedule();
        /*
            在schedule函数里,如果当前进程收到了信号,会变成running状态,
            如果current->signal &= ~(1<<(SIGCHLD-1)))为0,即...0000000100000... & ...111111110111111...
            说明当前需要处理的信号是SIGCHLD,因为signal不可能为全0,否则进程不可能被唤醒,
            即有子进程退出,跳到repeat找到该退出的进程,否则说明是其他信号导致了进程变成可执行状态,
            阻塞的进程被信号唤醒,返回EINTR
        */
        if (!(current->signal &= ~(1<<(SIGCHLD-1))))
            goto repeat;
        else
            return -EINTR;
    }
    return -ECHILD;
}

  //父进程在wait_pid调用时释放pcb的一页内存,重新调度进程
    void release(struct task_struct * p)
    {
        int i;

        if (!p)
            return;
        for (i=1 ; i<NR_TASKS ; i++)
            if (task[i]==p) {
                task[i]=NULL;
                free_page((long)p);
                schedule();
                return;
            }
        panic("trying to release non-existent task");
    }
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-03-02,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 编程杂技 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 释放地址
  • 通知父进程
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档