为啥在编写 linux 守护进程时必须与 tty 分离?

Posted

技术标签:

【中文标题】为啥在编写 linux 守护进程时必须与 tty 分离?【英文标题】:Why MUST detach from tty when writing a linux daemon?为什么在编写 linux 守护进程时必须与 tty 分离? 【发布时间】:2012-02-05 08:22:58 【问题描述】:

当我尝试在 linux 下使用 C 编写守护程序时,有人告诉我应该在 fork 代码块之后添加以下代码:

/* Preparations */
...

/* Fork a new process */
pid_t cpid = fork();
if (cpid == -1)perror("fork");exit(1);
if (cpid > 0)exit(0);

/* WHY detach from tty ? */
int fd = open("/dev/tty", O_RDWR);
ioctl(fd, TIOCNOTTY, NULL);

/* Why set PGID as current PID ? */
setpgid(getpid(), 0);

我的问题是: 以上操作是不是一定要做?

【问题讨论】:

我认为部分原因是守护进程不应该写入输出或读取输入。如果你要开始,例如SSH 会话上的 HTTP 服务器,您不会期望稍后在会话中输出随机警告。 @JohnChadwick 您所说的确实是您在转换为守护程序时想要做的事情之一,但是您可以通过关闭 stdin、stdout 和 stderr 来实现。您从终端分离以避免某些信号(请参阅下面的答案)。 你能“不接受”我的回答并接受@AdamZalcman 的回答吗?他做得比我好得多。他对 setsid() 的看法是完全正确的,你应该使用它。 @AdamZalcman 哦,对了,我实际上忘记了关闭 std*。我在考虑更改进程组 ID 会阻止所有相关信号,但这会变得不那么有意义,至少在 SIGHUP 的情况下。 【参考方案1】:

另一个答案是明确的并且在技术上是正确的(因此我相应地投了赞成票)。

另一个答案是:“不,不要编写自己守护进程的代码。”

请改为使用流程监督框架(如 daemontools 或 runit 或 launchd)来为您处理此问题。

传统的 UNIX 服务器是自守护的,因此会处理很多事情:当前工作目录、进程组和会话独立性、信号掩码和处置、文件系统根目录、权限、umask、打开文件描述符等。

但是,这些进程属性中的大部分或全部都是通过exec() 继承的,这意味着服务器进程通常可以“出生”与所需的进程组、工作目录、根等。几乎不需要做一切由您自己完成,尽管您通常仍需要自己管理特权操作和特权撤销。

(实际上,我认为编写自我守护程序存在长期风险。样板“背景”例程被复制和粘贴,并匆忙移植和扩展,程序员将时间花在辅助代码上而不是程序的主要目的。)

【讨论】:

很好的答案!守护进程应该是调用者的选择,而不是应用程序本身的强制。而对于调用者来说,现有的工具很多,无需在应用程序中重新发明。【参考方案2】:

您必须解除您的守护进程与终端的关联,以避免发送与终端操作相关的信号(例如终端会话结束时的 SIGHUP 以及潜在的 SIGTTIN 和 SIGTTOU)。

但是请注意,使用 TIOCNOTTY ioctl 与终端解除关联的方式在很大程度上已经过时。你应该改用setsid()

守护进程离开其原始进程组的原因是没有接收到发送到该组的信号。请注意,setsid() 也会将您的进程置于其自己的进程组中。

【讨论】:

+1。警告:即使在 setid(2) 之后,如果不小心(再次 fork() 或 O_NOCTTY),也可能获得控制终端。 你的实验室能解决这个问题吗?为什么后台进程不能忽略这些信号?

以上是关于为啥在编写 linux 守护进程时必须与 tty 分离?的主要内容,如果未能解决你的问题,请参考以下文章

为啥我必须 sudo 来自守护进程的命令?

守护进程设计

守护进程的作用

linux C守护进程编写

编写Linux/Unix守护进程

[C++]-Linux中创建Daemon程序