Home Page
Search
\begin{md} # 守护进程的创建 ## 守护进程概述 Linux Daemon(守护进程)是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。它不需要用户输入就能运行而且提供某种服务,不是对整个系统就是对某个用户程序提供服务。Linux 系统的大多数服务器就是通过守护进程实现的。常见的守护进程包括系统日志进程 syslogd、 web 服务器 httpd、邮件服务器 sendmail 和数据库服务器 mysqld 等。 守护进程一般在系统启动时开始运行,除非强行终止,否则直到系统关机都保持运行。守护进程经常以超级用户(root)权限运行,因为它们要使用特殊的端口(1-1024)或访问某些特殊的资源。 一个守护进程的父进程是 init 进程,因为它真正的父进程在 fork 出子进程后就先于子进程 exit 退出了,所以它是一个由 init 继承的孤儿进程。守护进程是非交互式程序,没有控制终端,所以任何输出,无论是向标准输出设备 stdout 还是标准出错设备 stderr 的输出都需要特殊处理。守护进程的名称通常以 d 结尾,比如 sshd、xinetd、crond 等。 ## 创建守护进程的步骤 首先我们要了解一些基本概念: **进程组 :** * 每个进程属于一个进程组 * 每个进程都有一个进程组号,等于该进程组组长的 PID 号 * 一个进程只能为它自己或子进程设置 GPID 号 **会话:** ```c #include
pid_t setsid(void); ``` 会话(session)是一个或多个 **进程组** 的集合。 `setsid()` 函数可以建立一个会话。该函数不能由进程组首领调用,否则将产生一个错误。对于非组首领的进程,调用该函数不仅创建新会话,还有如下额外效果: 1. 调用进程成为会话的首领,此时该进程是新会话的唯一成员 2. 新建一个进程组,其 PGID 就是调用进程的 PID,调用进程成为该组的首领 3. 调用进程将脱离终端(如果有的话) 该函数成功时返回新的进程组的 PGID,失败则返回 -1 并设置 errno。 **Linux 进程并未提供所谓会话 ID(SID)的概念,但 Linux 系统认为它等于会话首领所在的进程组的 PGID,并提供了如下函数来读取 SID** ```c #include
pid_t getsid(pid_t pid); ``` 现在我们来给出创建守护进程的一般步骤: 1. 在父进程中执行 `fork()` 并 `exit()` 退出 2. 在子进程中调用 `setsid()` 函数创建新的会话 3. 在子进程中调用 `chdir()` 函数,让根目录 ”/” 成为子进程的工作目录 4. 在子进程中调用 `umask()` 函数,设置进程的 umask 为 0 5. 在子进程中关闭任何不需要的文件描述符 **说明:** 1. **在后台运行。** 为避免挂起控制终端将 Daemon 放入后台执行。方法是在进程中调用 fork 使父进程终止,让 Daemon 在子进程中后台执行。 2. **脱离控制终端,登录会话和进程组。** 有必要先介绍一下 Linux 中的进程与控制终端,登录会话和进程组之间的关系:进程属于一个进程组,进程组号 GID 就是进程组长的进程号 PID。登录会话可以包含多个进程组。这些进程组共享一个控制终端。这个控制终端通常是创建进程的登录终端。 控制终端,登录会话和进程组通常是从父进程继承下来的。我们的目的就是要摆脱它们,使之不受它们的影响。方法是在第 1 点的基础上,调用 `setsid()` 使进程成为会话组长。 `setsid()` 调用成功后,进程成为新的会话组长和新的进程组长,并与原来的登录会话和进程组脱离。由于会话对控制终端的独占性,进程同时与控制终端脱离。 3. **禁止进程重新打开控制终端。** 现在,进程已经成为无终端的会话组长。但它可以重新申请打开一个控制终端。可以通过使进程不再成为会话组长(再 fork 一次)来禁止进程重新打开控制终端。 4. **关闭打开的文件描述符。** 进程从创建它的父进程那里继承了打开的文件描述符。如不关闭,将会浪费系统资源,造成进程所在的文件系统无法卸下以及引起无法预料的错误。 5. **改变当前工作目录。** 进程活动时,其工作目录所在的文件系统不能卸下。一般需要将工作目录改变到根目录。对于需要转储核心,写运行日志的进程将工作目录改变到特定目录如/tmpchdir("/") 6. **重设文件创建掩模。** 进程从创建它的父进程那里继承了文件创建掩模。它可能修改守护进程所创建的文件的存取位。为防止这一点,将文件创建掩模清除:umask(0); 7. **处理 SIGCHLD 信号。** 处理 SIGCHLD 信号并不是必须的。但对于某些进程,特别是服务器进程往往在请求到来时生成子进程处理请求。如果父进程不等待子进程结束,子进程将成为僵尸进程(zombie)从而占用系统资源。如果父进程等待子进程结束,将增加父进程的负担,影响服务器进程的并发性能。 实例: ```c bool daemonize() { // 创建子进程,关闭父进程,使程序在后台运行 pid_t pid = fork(); if ( pid < 0 ) { return false; } else if ( pid > 0 ) { exit( 0 ); } // 设置文件权限掩码,当使用mode模式创建新文件时,新文件的权限将是mode & 0777 umask( 0 ); // 创建新会话,设置本进程为会话组的首领 pid_t sid = setsid(); if ( sid < 0 ) { return false; } // 切换工作目录 if ( ( chdir( "/" ) ) < 0 ) { /* Log the failure */ return false; } // 关闭标准输入,标准输出,标准错误设备 close( STDIN_FILENO ); close( STDOUT_FILENO ); close( STDERR_FILENO ); // 关闭其他打开的文件描述符,代码省略 // 将标准输入,标准输出,标准错误输出都重定向到 /dev/null // 代码省略 return true; } ``` ## daemon() 其实,Linux 提供了完成同样功能的库函数 ```c #include
int daemon(int nochdir, int noclose); ``` The **daemon**() function is for programs wishing to detach themselves from the controlling terminal and run in the background as system daemons. If nochdir is zero, **daemon**() changes the process's current working directory to the root directory ("/"); otherwise, the current working directory is left unchanged. If noclose is zero, **daemon**() redirects standard input, standard output and standard error to /dev/null; otherwise, no changes are made to these file descriptors. ## 参考 《Linux 高性能服务器编程》 https://www.cnblogs.com/mickole/p/3188321.html \end{md}
Home Page