据我所知,实现终止一个subprocess,当其父母死亡的最佳途径是通过prctl(PR_SET_PDEATHSIG) (至less在Linux上): 如何让父进程后,subprocess死亡?
有一个告诫人们提到这个man prctl :
在执行set-user-ID或set-group-ID二进制文件或具有关联function的二进制文件时,该值将被fork(2)和(从Linux 2.4.36 / 2.6.23开始)的子节点清除能力(7))。 这个值保存在execve(2)中。
在这个信号处理程序中会发生什么?
发送信号到后台进程
发送信号从命令行进程
如何在处理信号时阻止相同的信号?
parent.c:
#include <unistd.h> int main(int argc,char **argv) { int f = fork(); if (fork() == 0) { execl("./child","child",NULL,NULL); } return 0; }
child.c:
#include <sys/prctl.h> #include <signal.h> int main(int argc,char **argv) { prctl(PR_SET_PDEATHSIG,SIGKILL); // ignore error checking for Now // ... return 0; }
也就是说,在prctl()之前死亡的父计数在孩子中执行(因此孩子将不会收到SIGKILL )。 解决这个问题的正确方法是在exec()之前的父级中的prctl() exec() :
parent.c:
#include <unistd.h> #include <sys/prctl.h> #include <signal.h> int main(int argc,char **argv) { int f = fork(); if (fork() == 0) { prctl(PR_SET_PDEATHSIG,SIGKILL); // ignore error checking for Now execl("./child",NULL); } return 0; }
child.c:
int main(int argc,char **argv) { // ... return 0; }
但是,如果./child是一个setuid / setgid二进制文件,那么避免竞争条件的这个技巧就不起作用了( exec() setuid / setgid二进制文件导致PDEATHSIG按照上面引用的手册页丢失),而且似乎你被迫采用第一个(活泼的)解决scheme。
有没有什么办法,如果child是一个setuid / setgid二进制prctl(PR_SET_PDEATH_SIG)以非racy的方式?
为什么我不在bash中获取kill -9命令的SIGKILL信号?
Linux:system()+ SIGCHLD处理+multithreading
试图在Linux上closures睡眠
将信号发送到调用exec()的分叉进程
让父进程建立一个管道更为常见。 父进程保持写入结束( pipefd[1] ),关闭读取结束( pipefd[0] )。 子进程关闭写入结束( pipefd[1] ),并设置读取结束( pipefd[1] )非阻塞。
这样,子进程可以使用read(pipefd[0],buffer,1)来检查父进程是否仍然活着。 如果父节点仍在运行,它将返回-1 errno == EAGAIN (或errno == EINTR )。
现在,在Linux中,子进程还可以将读取结束设置为异步,在这种情况下,父进程退出时将发送一个信号(缺省为SIGIO ):
fcntl(pipefd[0],F_SETSIG,desired_signal); fcntl(pipefd[0],F_SetoWN,getpid()); fcntl(pipefd[0],F_SETFL,O_NONBLOCK | O_ASYNC);
对于desired_signal使用siginfo处理程序。 如果info->si_code == POLL_IN && info->si_fd == pipefd[0] ,父进程退出或写入管道的东西。 因为read()是异步信号安全的,并且管道是非阻塞的,所以无论父节点是否写入内容,或者父节点是否退出(关闭管道),都可以在信号处理程序中使用read(pipefd[0],&buffer,sizeof buffer) ) 。 在后一种情况下, read()将返回0 。
就我所见,这种方法没有竞争条件(如果你使用实时信号,所以信号不会丢失,因为用户发送的信号已经挂起),尽管它是非常特定于Linux的。 设置完信号处理程序后,在子进程生命周期中的任何时刻,子进程总是可以明确地检查父进程是否仍然有效,而不会影响信号生成。
所以,用伪代码来回顾一下:
Construct pipe Fork child process Child process: Close write end of pipe Install pipe signal handler (say,SIGRTMIN+0) Set read end of pipe to generate pipe signal (F_SETSIG) Set own PID as read end owner (F_SetoWN) Set read end of pipe nonblocking and async (F_SETFL,O_NONBLOCK | O_ASYNC) If read(pipefd[0],sizeof buffer) == 0,the parent process has already exited. Continue with normal work. Child process pipe signal handler: If siginfo->si_code == POLL_IN and siginfo->si_fd == pipefd[0],parent process has exited. To immediately die,use eg raise(SIGKILL). Parent process: Close read end of pipe Continue with normal work.
我不指望你相信我的话。
下面是一个粗略的示例程序,您可以使用它自己检查此行为。 这是很长的,但只是因为我想让它很容易看到在运行时发生了什么。 要在一个正常的程序中实现这个,你只需要几十行代码。 example.c :
#define _GNU_SOURCE #define _POSIX_C_SOURCE 200809L #include <stdlib.h> #include <sys/types.h> #include <sys/wait.h> #include <unistd.h> #include <fcntl.h> #include <signal.h> #include <string.h> #include <stdio.h> #include <errno.h> static volatile sig_atomic_t done = 0; static void handle_done(int signum) { if (!done) done = signum; } static int install_done(const int signum) { struct sigaction act; memset(&act,sizeof act); sigemptyset(&act.sa_mask); act.sa_handler = handle_done; act.sa_flags = 0; if (sigaction(signum,&act,NULL) == -1) return errno; return 0; } static int deathfd = -1; static void death(int signum,siginfo_t *info,void *context) { if (info->si_code == POLL_IN && info->si_fd == deathfd) raise(SIGTERM); } static int install_death(const int signum) { struct sigaction act; memset(&act,sizeof act); sigemptyset(&act.sa_mask); act.sa_sigaction = death; act.sa_flags = SA_SIGINFO; if (sigaction(signum,NULL) == -1) return errno; return 0; } int main(void) { pid_t child,p; int pipefd[2],status; char buffer[8]; if (install_done(SIGINT)) { fprintf(stderr,"Cannot set SIGINT handler: %s.n",strerror(errno)); return EXIT_FAILURE; } if (pipe(pipefd) == -1) { fprintf(stderr,"Cannot create control pipe: %s.n",strerror(errno)); return EXIT_FAILURE; } child = fork(); if (child == (pid_t)-1) { fprintf(stderr,"Cannot fork child process: %s.n",strerror(errno)); return EXIT_FAILURE; } if (!child) { /* * Child process. */ /* Close write end of pipe. */ deathfd = pipefd[0]; close(pipefd[1]); /* Set a SIGHUP signal handler. */ if (install_death(SIGHUP)) { fprintf(stderr,"Child process: cannot set SIGHUP handler: %s.n",strerror(errno)); return EXIT_FAILURE; } /* Set SIGTERM signal handler. */ if (install_done(SIGTERM)) { fprintf(stderr,"Child process: cannot set SIGTERM handler: %s.n",strerror(errno)); return EXIT_FAILURE; } /* We want a SIGHUP instead of SIGIO. */ fcntl(deathfd,SIGHUP); /* We want the SIGHUP delivered when deathfd closes. */ fcntl(deathfd,getpid()); /* Make the deathfd (read end of pipe) nonblocking and async. */ fcntl(deathfd,O_NONBLOCK | O_ASYNC); /* Check if the parent process is dead. */ if (read(deathfd,sizeof buffer) == 0) { printf("Child process (%ld): Parent process is already dead.n",(long)getpid()); return EXIT_FAILURE; } while (1) { status = __atomic_fetch_and(&done,__ATOMIC_SEQ_CST); if (status == SIGINT) printf("Child process (%ld): SIGINT caught and ignored.n",(long)getpid()); else if (status) break; printf("Child process (%ld): Tick.n",(long)getpid()); fflush(stdout); sleep(1); status = __atomic_fetch_and(&done,(long)getpid()); else if (status) break; printf("Child process (%ld): Tock.n",(long)getpid()); fflush(stdout); sleep(1); } printf("Child process (%ld): Exited due to %s.n",(long)getpid(),(status == SIGINT) ? "SIGINT" : (status == SIGHUP) ? "SIGHUP" : (status == SIGTERM) ? "SIGTERM" : "UnkNown signal.n"); fflush(stdout); return EXIT_SUCCESS; } /* * Parent process. */ /* Close read end of pipe. */ close(pipefd[0]); while (!done) { fprintf(stderr,"Parent process (%ld): Tick.n",(long)getpid()); fflush(stderr); sleep(1); fprintf(stderr,"Parent process (%ld): Tock.n",(long)getpid()); fflush(stderr); sleep(1); /* Try reaping the child process. */ p = waitpid(child,&status,WNOHANG); if (p == child || (p == (pid_t)-1 && errno == ECHILD)) { if (p == child && WIFSIGNALED(status)) fprintf(stderr,"Child process died from %s. Parent will Now exit,too.n",(WTERMSIG(status) == SIGINT) ? "SIGINT" : (WTERMSIG(status) == SIGHUP) ? "SIGHUP" : (WTERMSIG(status) == SIGTERM) ? "SIGTERM" : "an unkNown signal"); else fprintf(stderr,"Child process has exited,so the parent will too.n"); fflush(stderr); break; } } if (done) { fprintf(stderr,"Parent process (%ld): Exited due to %s.n",(done == SIGINT) ? "SIGINT" : (done == SIGHUP) ? "SIGHUP" : "UnkNown signal.n"); fflush(stderr); } /* Never reached! */ return EXIT_SUCCESS; }
编译并运行上面的例如
gcc -Wall -O2 example.c -o example ./example
父进程将打印到标准输出,并将子进程打印到标准错误。 如果按Ctrl + C ,则父进程将退出; 子进程将忽略该信号。 子进程使用SIGHUP而不是SIGIO (尽管实时信号,比如说SIGRTMIN+0会更安全)。 如果由父进程退出生成, SIGHUP信号处理程序将在子进程中引发SIGTERM 。
为了使终止的原因容易看到,孩子捕获SIGTERM ,并退出下一个迭代(一秒钟后)。 如果需要,处理程序可以使用例如raise(SIGKILL)立即终止它自己。
父进程和子进程都显示进程ID,因此您可以轻松地从另一个终端窗口发送SIGINT / SIGHUP / SIGTERM信号。 (子进程忽略从进程外部发送的SIGINT和SIGHUP 。)
我不知道这是肯定的,但是当调用set-id二进制文件时,清除execve上的execve信号看起来像是出于安全原因的有意限制。 我不确定为什么,考虑到你可以使用kill来发送信号给setuid共享你的真实用户ID的程序,但是如果没有理由拒绝它,他们不会在2.6.23中做这个改变。
既然你控制了set-id子代码,这里就是一个混乱的问题:先调用prctl ,然后立即调用getppid ,看它是否返回1.如果是,那么这个过程直接由init这并不像以前那么少见),或者在有机会调用prctl之前将进程重新init ,这意味着它的原始父项已经死了,应该退出。
(这是一个kludge,因为我知道没有办法排除这个过程是由init直接启动的可能性, init永远不会退出,所以你有一个应该退出的情况,以及一个不应该和不应该告诉哪一个,但是如果你从更大的设计中知道这个过程不会被init直接init ,那么它应该是可靠的。)
(你必须在 prctl 之后调用getppid ,否则你只能缩小比赛窗口,不能消除。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 [email protected] 举报,一经查实,本站将立刻删除。