Paw*_*dan 199 c unix linux fork process
假设我有一个只产生一个子进程的进程.现在,当父进程因任何原因(正常或异常,通过kill,^ C,断言失败或其他任何原因)退出时,我希望子进程死掉.如何正确地做到这一点?
stackoverflow上的一些类似问题:
关于Windows的 stackoverflow的一些类似问题:
qrd*_*rdl 180
SIGHUP
通过PR_SET_PDEATHSIG
在prctl()
syscall中指定选项,子进程可以让内核传递(或其他信号),如下所示:
prctl(PR_SET_PDEATHSIG, SIGHUP);
详情man 2 prctl
请见.
编辑:这只是Linux
Sch*_*hof 66
我正在尝试解决同样的问题,因为我的程序必须在OS X上运行,所以Linux专用解决方案对我不起作用.
我得出了与此页面上其他人相同的结论 - 当父母去世时,没有与POSIX兼容的方式通知孩子.因此,我完成了下一个最好的事情 - 让孩子进行民意调查.
当父进程死亡(由于任何原因)时,子进程的父进程变为进程1.如果子进程只是定期轮询,则可以检查其父进程是否为1.如果是,则该进程应退出.
这不是很好,但它可以工作,并且比本页其他地方建议的TCP套接字/锁定文件轮询解决方案更容易.
dmc*_*kee 34
我在过去通过运行"child"中的"原始"代码和"parent"中的"衍生"代码(即:你反转了通常的测试意识fork()
)来实现了这一点.然后在"生成"代码中捕获SIGCHLD ...
在你的情况下可能不可能,但它可行时很可爱.
小智 29
如果您无法修改子进程,可以尝试以下操作:
int pipes[2];
pipe(pipes)
if (fork() == 0) {
close(pipes[1]); /* Close the writer end in the child*/
dup2(0, pipes[0]); /* Use reader end as stdin */
exec("sh -c 'set -o monitor; child_process & read dummy; kill %1'")
}
close(pipes[0]); /* Close the reader end in the parent */
Run Code Online (Sandbox Code Playgroud)
这将在启用了作业控制的shell进程中运行子进程.子进程在后台生成.shell等待换行符(或EOF)然后杀死孩子.
当父母去世时 - 无论是什么原因 - 它将关闭管道的末端.子shell将从读取中获得EOF并继续杀死后台子进程.
neo*_*eye 14
为了完整起见.在macOS上你可以使用kqueue:
void noteProcDeath(
CFFileDescriptorRef fdref,
CFOptionFlags callBackTypes,
void* info)
{
// LOG_DEBUG(@"noteProcDeath... ");
struct kevent kev;
int fd = CFFileDescriptorGetNativeDescriptor(fdref);
kevent(fd, NULL, 0, &kev, 1, NULL);
// take action on death of process here
unsigned int dead_pid = (unsigned int)kev.ident;
CFFileDescriptorInvalidate(fdref);
CFRelease(fdref); // the CFFileDescriptorRef is no longer of any use in this example
int our_pid = getpid();
// when our parent dies we die as well..
LOG_INFO(@"exit! parent process (pid %u) died. no need for us (pid %i) to stick around", dead_pid, our_pid);
exit(EXIT_SUCCESS);
}
void suicide_if_we_become_a_zombie(int parent_pid) {
// int parent_pid = getppid();
// int our_pid = getpid();
// LOG_ERROR(@"suicide_if_we_become_a_zombie(). parent process (pid %u) that we monitor. our pid %i", parent_pid, our_pid);
int fd = kqueue();
struct kevent kev;
EV_SET(&kev, parent_pid, EVFILT_PROC, EV_ADD|EV_ENABLE, NOTE_EXIT, 0, NULL);
kevent(fd, &kev, 1, NULL, 0, NULL);
CFFileDescriptorRef fdref = CFFileDescriptorCreate(kCFAllocatorDefault, fd, true, noteProcDeath, NULL);
CFFileDescriptorEnableCallBacks(fdref, kCFFileDescriptorReadCallBack);
CFRunLoopSourceRef source = CFFileDescriptorCreateRunLoopSource(kCFAllocatorDefault, fdref, 0);
CFRunLoopAddSource(CFRunLoopGetMain(), source, kCFRunLoopDefaultMode);
CFRelease(source);
}
Run Code Online (Sandbox Code Playgroud)
max*_*zig 12
在Linux下,您可以在孩子中安装父死亡信号,例如:
#include <sys/prctl.h> // prctl(), PR_SET_PDEATHSIG
#include <signal.h> // signals
#include <unistd.h> // fork()
#include <stdio.h> // perror()
// ...
pid_t ppid_before_fork = getpid();
pid_t pid = fork();
if (pid == -1) { perror(0); exit(1); }
if (pid) {
; // continue parent execution
} else {
int r = prctl(PR_SET_PDEATHSIG, SIGTERM);
if (r == -1) { perror(0); exit(1); }
// test in case the original parent exited just
// before the prctl() call
if (getppid() != ppid_before_fork)
exit(1);
// continue child execution ...
Run Code Online (Sandbox Code Playgroud)
请注意,在fork之前存储父进程id并在子进程中对其进行测试后,可以prctl()
消除prctl()
调用子进程和进程的退出之间的竞争条件.
另请注意,孩子的父母死亡信号在新创建的孩子中被清除.它不受影响execve()
.
如果我们确定负责采用所有孤儿的系统进程具有PID 1 ,则可以简化该测试:
pid_t pid = fork();
if (pid == -1) { perror(0); exit(1); }
if (pid) {
; // continue parent execution
} else {
int r = prctl(PR_SET_PDEATHSIG, SIGTERM);
if (r == -1) { perror(0); exit(1); }
// test in case the original parent exited just
// before the prctl() call
if (getppid() == 1)
exit(1);
// continue child execution ...
Run Code Online (Sandbox Code Playgroud)
init
然而,依赖于系统进程并且具有PID 1是不可移植的.POSIX.1-2008规定:
调用进程的所有现有子进程和僵尸进程的父进程ID应设置为实现定义的系统进程的进程ID.也就是说,这些过程应由特殊的系统过程继承.
传统上,采用所有孤儿的系统过程是PID 1,即init - 它是所有过程的祖先.
在像Linux或FreeBSD这样的现代系统上,另一个进程可能具有该角色.例如,在Linux上,进程可以调用prctl(PR_SET_CHILD_SUBREAPER, 1)
将自身建立为继承其任何后代的所有孤儿的系统进程(参见Fedora 25上的示例).
Gre*_*ill 10
受到另一个答案的启发,我想出了以下全POSIX解决方案.一般的想法是在父级和子级之间创建一个中间过程,其目的有一个:当父级死亡时注意,并明确地杀死子级.
当无法修改子代码时,此类解决方案很有用.
int p[2];
pipe(p);
pid_t child = fork();
if (child == 0) {
close(p[1]); // close write end of pipe
setpgid(0, 0); // prevent ^C in parent from stopping this process
child = fork();
if (child == 0) {
close(p[0]); // close read end of pipe (don't need it here)
exec(...child process here...);
exit(1);
}
read(p[0], 1); // returns when parent exits for any reason
kill(child, 9);
exit(1);
}
Run Code Online (Sandbox Code Playgroud)
这种方法有两个小注意事项:
顺便说一句,我使用的实际代码是Python.这是为了完整性:
def run(*args):
(r, w) = os.pipe()
child = os.fork()
if child == 0:
os.close(w)
os.setpgid(0, 0)
child = os.fork()
if child == 0:
os.close(r)
os.execl(args[0], *args)
os._exit(1)
os.read(r, 1)
os.kill(child, 9)
os._exit(1)
os.close(r)
Run Code Online (Sandbox Code Playgroud)
我不相信可以保证只使用标准POSIX调用.就像现实生活一样,一旦孩子产生,它就有自己的生命.
这是可能的父进程捕捉最有可能终止事件,并试图在该点杀子过程,但总有一些不能被捕获.
例如,没有任何流程可以捕获SIGKILL
.当内核处理此信号时,它将终止指定的进程,而不会通知该进程.
扩展类比 - 唯一的另一种标准方法是让孩子在发现自己不再拥有父母时自杀.
有一种仅限Linux的方式prctl(2)
- 请参阅其他答案.
小智 6
正如其他人所指出的那样,当父母退出时,依靠父pid成为1是不可移植的.而不是等待特定的父进程ID,只需等待ID更改:
pit_t pid = getpid();
switch (fork())
{
case -1:
{
abort(); /* or whatever... */
}
default:
{
/* parent */
exit(0);
}
case 0:
{
/* child */
/* ... */
}
}
/* Wait for parent to exit */
while (getppid() != pid)
;
Run Code Online (Sandbox Code Playgroud)
如果您不想全速轮询,请根据需要添加微睡眠.
这个选项对我来说比使用管道或依赖信号更简单.
这个解决方案对我有用:
这是一个工人类型的进程,它的存在只有在父进程活着时才有意义。
安装一个陷阱处理程序来捕获 SIGINT,如果它仍然活着,它会杀死你的子进程,尽管其他海报是正确的,它不会捕获 SIGKILL。
打开具有独占访问权限的 .lockfile 并让子轮询尝试打开它 - 如果打开成功,子进程应该退出
一些海报已经提到了管道和kqueue
. 实际上,您还可以通过调用创建一对连接的Unix 域套接字socketpair()
。套接字类型应该是SOCK_STREAM
.
让我们假设您有两个套接字文件描述符 fd1、fd2。现在fork()
创建子进程,它将继承 fds。在父级中关闭 fd2,在子级中关闭 fd1。现在每个进程都可以poll()
在自己的一端为POLLIN
事件打开剩余的 fd 。只要每一方close()
在正常生命周期内没有明确地表明它的 fd,你就可以相当确定一个POLLHUP
标志应该表明对方的终止(无论是否干净)。收到此事件的通知后,孩子可以决定做什么(例如死亡)。
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <poll.h>
#include <stdio.h>
int main(int argc, char ** argv)
{
int sv[2]; /* sv[0] for parent, sv[1] for child */
socketpair(AF_UNIX, SOCK_STREAM, 0, sv);
pid_t pid = fork();
if ( pid > 0 ) { /* parent */
close(sv[1]);
fprintf(stderr, "parent: pid = %d\n", getpid());
sleep(100);
exit(0);
} else { /* child */
close(sv[0]);
fprintf(stderr, "child: pid = %d\n", getpid());
struct pollfd mon;
mon.fd = sv[1];
mon.events = POLLIN;
poll(&mon, 1, -1);
if ( mon.revents & POLLHUP )
fprintf(stderr, "child: parent hung up\n");
exit(0);
}
}
Run Code Online (Sandbox Code Playgroud)
您可以尝试编译上面的概念验证代码,然后在像./a.out &
. 您有大约 100 秒的时间来尝试通过各种信号杀死父 PID,否则它将直接退出。在任何一种情况下,您都应该看到消息“孩子:父母挂断电话”。
与使用SIGPIPE
处理程序的方法相比,此方法不需要尝试write()
调用。
这种方法也是对称的,即进程可以使用相同的通道来监视彼此的存在。
此解决方案仅调用 POSIX 函数。我在 Linux 和 FreeBSD 中尝试过这个。我认为它应该适用于其他 Unix,但我还没有真正测试过。
也可以看看:
unix(7)
的 Linux 手册页,unix(4)
用于 FreeBSD poll(2)
、、socketpair(2)
、socket(7)
在 Linux 上。 归档时间: |
|
查看次数: |
145616 次 |
最近记录: |