linux中父进程退出时如何通知子进程

时间:2021-08-03 23:48:15

根据posix约定和linux的实现,linux中子进程退出的时候要发送信号给父进程,这其实只是一种约定,没有什么大不了的,但是这个特性给编程的人带来了一种方便,使得可是得知子进程何时退出,那么子进程在不用心跳检测的前提下如何得知父进程何时退出的呢?
子进程在退出的时候发送信号给父进程并不是一种什么机制,只是歪了更加方便的管理操作系统进程而商量得到的一种约定而已,但是这种约定却是可以被利用的,从而父进程就没有必要在来什么心跳来检测子进程是否已经退出。在操作系统内核当中,进程是被作为一个实体来管理的,进程管理其实是操作系统的一部分,进程 要被管理就要一些数据结构,这些数据结构只在进程存在没有退出的时候有效,进程一旦退出,这些数据结构就要被回收,那么谁来回收这些数据结构就成了一个问题,让退出的进程自己来回收吗?这看来不是什么好主意,毕竟退出行为是自己的行为,而回收行为却不是自己的职责,因此为了自洽,谁分配的谁回收显然是一个 好主意,进程实际上没有回收自己的义务,它拥有的唯一义务就是运行自己,结婚,生子,和动物不同的是,它还要为孩子收尸(人类当然也有白发送黑发一说,呵呵),进程是什么,进程是一个执行绪,除此之外没有别的内涵了,进程在运行的时候并不知道太多关于自己被操作系统管理的进程结构的太多信息,它所知道的仅 仅是自己该如何运行,因此回收进程数据结构的任务就不应该来人进程本身来承担,那么为何不让操作系统内核来承担呢?要知道操作系统内核只是提供了一种机制来管理进程,管理并维持进程的状态机,比如运行,睡眠,挂起等等,它要管理进程的话,进程数据结构必须首先存在,要不它管理个鸟啊,创建进程是用户的事 情,因此何时为进程分配数据结构以及分配什么样的数据结构就是一种策略,再重申一遍,内核仅仅提供一种机制,该机制用来管理进程,而进程的行为多种多样,创建进程变成了用户实现的一种策略,用户进程依据用户提供的信息创建了另一个进程,然后操作系统就开始管理这个新进程,只有创建这个新进程的进程知道这个 进程的创建信息,那么理所当然创建新进程的进程有义务在这个新进程退出后回收这个由它创建的进程的一切数据结构,于是posix就有了一种约定,就是现在 linux实现的这样,进程退出的时候向父进程发信号,由父进程来负责回收之。
     linux中的进程都有一个父进程,而子进程和父进程的关系甚密,它们之间的互操作及其方便,那么是否应该把子进程拥有父进程这件事看得很很大不了呢?不是的,前面说过,父子进程的关系仅仅是为了操作系统进程管理子系统管理起来方便而不需要让操作系统内核知道太多用户进程的信息,为了操作系统内核和进程之 间解除强耦合,这样看来用户进程就没有必要知道太多关于父进程的东西,仅仅知道谁创建的它就是了,父进程对于子进程而言没有什么特殊性,于是linux中 就没有提供更改一个进程父进程的系统调用,这是合理的,如果父进程先于子进程退出了,那么父进程临死前会将所有子进程过继给另外一个进程,这完全不用用户自己操心,完全由操作系统内核来完成。
可是父子进程一说确实给用户编程带来了方便,仅举一例,子进程退出的时候可以通知父进程(当然涉及到信号操作的机制,父进程必须进行一些设置)。那么父进程退出时能否通知子进程呢?这显然是一个没有意义,甚至看来有点钻牛角尖的问题,可是我们能否解决它呢?这个问题延伸一点就是,能否设置一个进程的父进程 呢?既然父子进程一说没有什么大不了的,那么解决这个问题的方法当然不是规范的了,linux的特点就是,只要有一件很严重的事,那么必然有一个很规范的 解决办法。解决这个问题我们可以试一下ptrace系统调用,它可以“非常不规范”的设置一个进程的父进程:
asmlinkage long sys_ptrace(long request, long pid, long addr, long data)
{
         struct task_struct *child;
...
         if (request == PTRACE_ATTACH) {
                 ret = ptrace_attach(child);
                 goto out_put_task_struct;
         }
}
int ptrace_attach(struct task_struct *task)
{
...
         task->ptrace |= PT_PTRACED | ((task->real_parent != current) ? PT_ATTACHED : 0);
         if (capable(CAP_SYS_PTRACE))
                 task->ptrace |= PT_PTRACE_CAP;
         __ptrace_link(task, current);
         force_sig_specific(SIGSTOP, task);  //主意发送了SIGSTOP给需要调试的进程
...
}
void __ptrace_link(task_t *child, task_t *new_parent)
{
         BUG_ON(!list_empty(&child->ptrace_list));
         if (child->parent == new_parent)
                 return;
         list_add(&child->ptrace_list, &child->parent->ptrace_children);
         remove_parent(child);        //解除了原来的关系
         child->parent = new_parent;  //设置了新的父进程
         add_parent(child);
}
这样的话,在被调试的进程退出的时候,当然要通知调试进程了,这个不规范的方式显然可以满足要求,实际上通过linux的ptrace接口,可以设置任何进程之间的父子关系,想想看也是合理的,调试嘛,当然父亲才能调教孩子啦。最后看一个用户空间实现:
int ok = -1;
void handler(int signo)
{
    if( -1 != ok )
    {
        ...//子进程在得知父进程退出后的操作
    }
}
int main(int argc, char* argv[])
{
    pid_t pid;
    int i;
    pid_t pp = getpid();
    if( 0 == (pid = fork()) )
    {
        signal(SIGCHLD,handler);
        ptrace(PTRACE_ATTACH, pp, NULL, NULL);
        wait(NULL);
        ptrace(PTRACE_CONT, pp, NULL, NULL);  //主意在PTRACE_ATTACH为参数的ptrace调用下,调试进程发送了一个SIGSTOP信号,于是这里当然要唤醒被调试的进程了。
        ok = 1
        ...  //做子进程的事
    }
    ...  //做父进程的事
    return 0;
}
这个代码在父进程退出的时候可以通知子进程。

guosha 发表于2009-01-10 23:15:01  IP: 116.24.93.*

子进程退出要发信号给父进程是因为需要父进程进行wait以释放子进程退出后的一些遗留系统资源,或是一些感兴趣的信息做相应的处理. 这里的道理如你所说,很简单,父进程创造了子进程就得对子进程负责,如你进行了malloc就得相应进行free一样.
但反过来, 父进程退出的时候并不需要通知子进程做什么事情. 所以,系统内部没有在父进程退出时做给其子进程发信号的处理.
既然这是一个异步的事件, 就应该由系统提供的机制来处理,所以我觉得更好的方法是父进程在退出前主动去通知子进程. 注册atexit事件去通知进程,对于异常的退出,在相应的异常处理里进行通知。
这样做看上去麻烦一点,但更直观。通常我在有简单实现跟直观实现两种选择时我更愿意使用直观实现。