前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >linu0.11父进程等待子进程退出和进程退出原理分析

linu0.11父进程等待子进程退出和进程退出原理分析

作者头像
theanarkh
发布2019-04-24 11:03:34
1.9K0
发布2019-04-24 11:03:34
举报
文章被收录于专栏:原创分享
代码语言:javascript
复制
// 释放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");
}
/*
  发送信号给进程sig是发送的信号,p是接收信号的进程,priv是权限,
  1是代表可以直接设置,比如给自己租发信息,priv为0说明需要一定的权限
*/
static inline int send_sig(long sig,struct task_struct * p,int priv)
{
    if (!p || sig<1 || sig>32)
        return -EINVAL;
    // 这里使用euid,即进程设置了suid位的话,可以扩大权限,即拥有文件属主的权限
    if (priv || (current->euid==p->euid) || suser())
        p->signal |= (1<<(sig-1));
    else
        return -EPERM;
    return 0;
}
// 结束会话,给该会话的所有进程发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);
    }
}

/*
 * XXX need to check permissions needed to send signals to process
 * groups, etc. etc.  kill() permissions semantics are tricky!
 */
int sys_kill(int pid,int sig)
{
    struct task_struct **p = NR_TASKS + task;
    int err, retval = 0;
    // pid等于0则给当前进程的整个组发信号,大于0则给某个进程发信号,-1则给全部进程发,小于-1则给某个组发信号
    if (!pid) while (--p > &FIRST_TASK) {
        if (*p && (*p)->pgrp == current->pid) 
            if (err=send_sig(sig,*p,1))
                retval = err;
    } else if (pid>0) while (--p > &FIRST_TASK) {
        if (*p && (*p)->pid == pid) 
            if (err=send_sig(sig,*p,0))
                retval = err;
    } else if (pid == -1) while (--p > &FIRST_TASK)
        if (err = send_sig(sig,*p,0))
            retval = err;
    else while (--p > &FIRST_TASK)
        if (*p && (*p)->pgrp == -pid)
            if (err = send_sig(sig,*p,0))
                retval = err;
    return retval;
}

// 子进程退出,通知进程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);
}

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;
            // 如果子进程是僵尸进程,即已经退出,则给pid是1的进程发一个信号
            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 */
}

int sys_exit(int error_code)
{
    return do_exit((error_code&0xff)<<8);
}
// 等待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,否则进程不可能被唤醒,
            即有子进程退出,否则说明是其他信号导致了进程变成可执行状态,
            阻塞的进程被信号唤醒,返回EINTR
        */
        if (!(current->signal &= ~(1<<(SIGCHLD-1))))
            goto repeat;
        else
            return -EINTR;
    }
    return -ECHILD;
}

其中会话是进程组的集合。linux通过下面函数建立一个会话。

代码语言:javascript
复制
// 当前进程新建一个会话并成为会话的领头进程
int sys_setsid(void)
{    
    // 已经是领头进程或者不是超级用户
    if (current->leader && !suser())
        return -EPERM;
    // 标记该进程是领头进程
    current->leader = 1;
    /*
        sessionid是进程的id,因为会话是进程组的集合,
        所以当前进程的组id也需要更新,否则进程在其他组,但是其他组又不属于当前的会话,矛盾
    */
    current->session = current->pgrp = current->pid;
    // 重置终端,会话领头进程第一个打开终端的时候赋值,一个会话对应一个终端
    current->tty = -1;
    return current->pgrp;
}
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-04-10,如有侵权请联系 cloudcommunity@tencent.com 删除

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

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档