请看这段代码。它运行在CentOS6 64位。
#include<stdio.h>
int main(int argc, char **argv)
{
fprintf(stderr, "output 1n");
printf("output 2n");
fflush(stdout);
system("echo abc");
fprintf(stderr, "output 3n ");
printf("output 4n");
fflush(stdout);
daemon(0, 1);
fprintf(stderr, "output 5n");
printf("output 6n");
fflush(stdout);
system("echo abc");
fprintf(stderr, "output 7n");
printf("output 8n");
fflush(stdout);
}
如果我运行它,我将看到这些消息:
output 1
output 2
abc
output 3
output 4
output 5
output 6
abc
output 7
output 8
如果我使用ssh登录并运行它,我将看到相同的结果。
但是,如果我使用二进制名称作为ssh的参数并运行它,在调用守护进程(0,1)后,程序将在向stderr写入数据时退出。我运行
ssh localhost myapp
我将只看到这些消息:
output 1
output 2
abc
output 3
output 4
output 5
output 6
abc
有人知道为什么吗?根据调试,程序只有在做了三件事后才会退出:
- 调用守护进程(0,1).
- 调用系统运行另一个应用程序或bash命令。
- 写点东西给stderr
非常感谢!
如果您在shell中运行这个命令,您可能会在输出4和输出5之间看到一个新的shell提示符(如果在输出行之间有睡眠,这将更加明显)。
这是因为daemon()
系统调用导致程序分裂为两个独立的进程。这被称为"分叉",可以使用fork()
系统调用更紧密地控制它。fork之后,两个进程都保留指向打开的文件描述符的指针:stdin、stdout和stderr。根据"man 3 daemon",父进程在fork后调用exit()
。
当您从SSH调用可执行文件时,SSH会话将运行一个进程。它派生出一个子进程,主进程退出。SSH看到您发出的命令已经完成,因此它关闭SSH连接。这将关闭stdout和stderr。不幸的是,子进程仍有一些工作要做,但它无法写入共享标准错误,因为该文件描述符已关闭。如果您打印更多的调试信息,如printf()
和fprintf()
调用的返回值,您将看到它无法写入已关闭的文件描述符。
如果不是打印到stderr,而是打印到日志文件(大多数守护进程都是这样做的),那么您将看到子进程将继续在后台运行并按照您的期望进行写入。
如果您选择使用fork()
而不是daemon()
,您可以让父进程等待直到子进程完成。您可以使用pid_t waitpid(pid_t pid, int *stat_loc, int options);
。
您可能还想查看在父节点和子节点之间发送的信号。当子进程死亡时,它将向父进程发送SIGCHILD。如果您想要反向通知,您可以使用prctl(PR_SET_PDEATHSIG, SIGHUP);
设置一个(仅在Linux上),以便父进程向子进程发送SIGHUP。