美文网首页
为什么调用fork后需要调用wait

为什么调用fork后需要调用wait

作者: CODERLIHAO | 来源:发表于2020-12-23 13:17 被阅读0次

    就拿linux0.11源码分析,从进程2的创建与销毁举例子。在这里贴出的代码看不懂的不要紧,我会尽量把流程说清楚,看的懂的那是最好,程序员的语言不就是代码吗~ 😂

    linux内核被加载到内存后,就会执行main.c中的main函数,创建进程0,进程0创建进程1,进程1创建进程2

    void main(void)     
    {           
        ...
        if (!fork()) {      
            init();  // 在新建的子进程(任务1)中执行。
        }
        ...
    }
    

    fokk后就会创建进程1,在子进程中(进程1)fork返回0,为什么返回0,在这里说的比较清楚了
    init()由进程1开始执行。

    void init(void)
    {
        ...
        if (!(pid=fork())) {
           ...进程2执行块···
        }
            ...进程1执行块···
        if (pid>0)
            while (pid != wait(&i))
    
            ...
    }
    

    进程1里面又要执行fork,创建进程2,pid等于0代表的是子进程,大于0代表父进程,所以父进程(进程1)调用wait

    wait的定义在lib/wait.c文件中,

    #define __LIBRARY__
    #include <unistd.h>
    #include <sys/wait.h>
    
    _syscall3(pid_t,waitpid,pid_t,pid,int *,wait_stat,int,options)
    pid_t wait(int * wait_stat)
    {
        return waitpid(-1,wait_stat,0);
    }
    

    _syscall3声明在unistd.h头文件中。调用wait最终调用的是sys_waitpid

    #define _syscall3(type,name,atype,a,btype,b,ctype,c) \
    type name(atype a,btype b,ctype c) \
    { \
    long __res; \
    __asm__ volatile ("int $0x80" \
        : "=a" (__res) \
        : "0" (__NR_##name),"b" ((long)(a)),"c" ((long)(b)),"d" ((long)(c))); \
    if (__res>=0) \
        return (type) __res; \
    errno=-__res; \
    return -1; \
    }
    

    sys_waitpid找到进程后,就会把进程的状态设置为中断等待态,并且重新调度schedule()
    进程调度发现进程的状态时中断等待状态,就不会执行该进程,需要等待下一次调度。

    int sys_waitpid(pid_t pid,unsigned long * stat_addr, int options)
    {
        int flag, code;             // flag标志用于后面表示所选出的子进程处于就绪或睡眠态。
        struct task_struct ** p;
    
        verify_area(stat_addr,4);
    repeat:
        flag=0;
        
        ...省略部分代码···
        
        // 在上面对任务数组扫描结束后,如果flag被置位,说明有符合等待要求的子进程并没有处于退出或
        // 僵死状态。如果此时已设置WNOHANG选项(表示若没有子进程处于退出或终止态就立刻返回),就
        // 立刻返回0,退出。否则把当前进程置为可中断等待状态并重新执行调度。当又开始执行本进程时,
        // 如果本进程没有收到除SIGCHLD以外的信号,则还是重复处理。否则,返回出错码‘中断系统调用’
        // 并退出。针对这个出错号用户程序应该再继续调用本函数等待子进程。
        if (flag) {
            if (options & WNOHANG)                  // options = WNOHANG,则立刻返回。
                return 0;
            current->state=TASK_INTERRUPTIBLE;      // 置当前进程为可中断等待态
            schedule();                             // 重新调度。
            if (!(current->signal &= ~(1<<(SIGCHLD-1))))
                goto repeat;
            else
                return -EINTR;                      // 返回出错码(中断的系统调用)
        }
        // 若没有找到符合要求的子进程,则返回出错码(子进程不存在)。
        return -ECHILD;
    }
    

    调用wait后,进程1就不再CPU上执行了,此时进程2的状态时可运行状态,进程调度后,进程2开始执行,进程2做完自己的事后,就会执行_exit方法,该方法对应就是 sys_exit

    int sys_exit(int error_code)
    {
        return do_exit((error_code&0xff)<<8);
    }
    

    do_exit主要事释放进程占用的页,然后把自己的状态改为TASK_ZOMBIE僵死状态,
    tell_father(current->father);通知父进程,给父进程发送信号task[i]->signal |= (1<<(SIGCHLD-1));,不是说给父进程发信号,父进程就会醒来,需要等待下一次进程调度schedule();

    int do_exit(long code)
    {
        ...释放进程自己的页等数据···
        current->state = TASK_ZOMBIE;
        current->exit_code = code;
        // 通知父进程,也即向父进程发送信号SIGCHLD - 子进程将停止或终止。
        tell_father(current->father);
        schedule();  // 重新调度进程运行,以让父进程处理僵死其他的善后事宜。
     
        return (-1);
    }
    
    static void tell_father(int pid)
    {
        int i;
    
        if (pid)
            // 扫描进城数组表,寻找指定进程pid,并向其发送子进程将停止或终止信号SIGCHLD。
            for (i=0;i<NR_TASKS;i++) {
                if (!task[i])
                    continue;
                if (task[i]->pid != pid)
                    continue;
                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");
        release(current);  // 如果没有找到父进程,则自己释放
    }
    

    到这里子进程释放了自己的占用的内存页数据,但是在进程表里还占用进程结构,为什么不释放完呢,因为父进程还需要知道子进程的返回值,该返回值就存在进程结构体里,子进程已经是僵死状态了,这再也不会运行了,等下一次进程调度后,父进程发现收到SIGCHLD信号,并且是可中断等待状态,父进程开始苏醒,接着上次wait代码处执行,也就是上面提到的sys_waitpid,该方法找到子进程是TASK_ZOMBIE的进程,释放子进程占用的进程项。

    如果父进程不调用wait,子进程结束后,父进程不知道子进程有没有死,就没有办法释放子进程,虽然子进程释放了自己占用内存页,但是没有办法释放占用的进程结构体,子进程就会变成僵尸进程。

    case TASK_ZOMBIE:
        current->cutime += (*p)->utime;
        current->cstime += (*p)->stime;
        flag = (*p)->pid;                   // 临时保存子进程pid
        code = (*p)->exit_code;             // 取子进程的退出码
        release(*p);                        // 释放该子进程
        put_fs_long(code,stat_addr);        // 置状态信息为退出码值
        return flag;                        // 返回子进程的pid
    

    相关文章

      网友评论

          本文标题:为什么调用fork后需要调用wait

          本文链接:https://www.haomeiwen.com/subject/dbbynktx.html