如何在 Linux 上创建守护进程
守护进程是在计算机后台静默运行的进程。下面介绍了如何在 Linux 上编写自己的守护程序。
守护进程是不直接在用户控制下运行而是在后台运行的进程。通常,它们在系统启动时启动并持续运行,直到系统关闭。这些进程与普通进程之间的唯一区别是它们不会以任何方式向控制台或屏幕发送消息。
下面介绍了如何在 Linux 计算机上创建守护程序。
守护进程是如何创建的简介
系统上运行着很多守护进程,一些常见的守护进程示例如下:
crond:使命令在指定时间运行
sshd:允许从远程计算机登录系统
httpd:提供网页服务
nfsd:允许通过网络共享文件
此外,守护进程通常命名为以字母 d 结尾,但这不是强制性的。
对于作为守护进程运行的进程,遵循以下路径:
初始操作,例如读取配置文件或获取必要的系统资源,必须在进程成为守护进程之前执行。这样,系统可以向用户报告收到的错误,并且进程将以适当的错误代码终止。
创建一个后台运行进程,以 init 作为其父进程。为此,首先从init进程中fork出一个子进程,然后通过exit终止上层进程。
应通过调用setsid函数打开一个新会话,并且该进程应与终端断开连接。
所有从父进程继承的打开的文件描述符都被关闭。
标准输入、输出和错误消息被重定向到/dev/null。
进程的工作目录必须更改。
什么是守护进程会话?
用户通过终端登录系统后,可以通过shell程序运行许多应用程序。当用户退出系统时,这些进程应该关闭。操作系统将这些进程分为会话组和进程组。
每个会话都由进程组组成。您可以将这种情况描述如下:
进程接收输入并发送输出的终端称为控制终端。控制终端一次仅与一个会话相关联。
会话和其中的进程组都有标识(ID)号;这些标识号是会话和进程组领导者的进程标识号 (PID)。子进程与其父进程共享同一组。当多个进程使用管道机制进行通信时,第一个进程成为进程组领导者。
在 Linux 上创建守护进程
在这里您将看到如何创建守护程序函数。为此,您将创建一个名为 _daemon 的函数。您可以首先将作为守护程序运行的应用程序代码命名为 test.c,并将创建守护程序函数的代码命名为 daemon.c。
//test.c
#include <stdio.h>
int _daemon(int, int);
int main()
{
getchar();
_daemon(0, 0);
getchar();
return 0;
}
//daemon.c
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/fs.h>
#include <linux/limits.h>
int _daemon(int nochdir, int noclose) {
pid_t pid;
pid = fork(); // Fork off the parent process
if (pid < 0) {
exit(EXIT_FAILURE);
}
if (pid > 0) {
exit(EXIT_SUCCESS);
}
return 0;
}
要创建守护进程,您需要一个父进程为 init 的后台进程。在上面的代码中,_daemon 创建一个子进程,然后终止父进程。在这种情况下,您的新进程将是 init 的子进程,并将继续在后台运行。
现在使用以下命令编译应用程序并检查调用 _deamon 之前和之后的进程状态:
gcc -o test test.c daemon.c
运行应用程序并切换到不同的终端,无需按任何其他键:
./test
您可以看到与您的流程相关的值如下。在这里,您必须使用 ps 命令来获取与进程相关的信息。在这种情况下,_daemon函数尚未被调用。
ps -C test -o "pid ppid pgid sid tty stat command"
# Output
PID PPID PGID SID TT STAT COMMAND
10296 5119 10296 5117 pts/2 S+ ./test
当您查看STAT字段时,您会看到您的进程正在运行,但正在等待计划外事件的发生,这将导致它在前台运行。
Abbreviation | Meaning |
S | Waiting asleep for an event to happen |
T | Application stopped |
s | Session leader |
+ | The application is running in the foreground |
您可以看到应用程序的父进程是预期的 shell。
ps -jp 5119
# Output
PID PGID SID TTY TIME CMD
5119 5119 5117 pts/2 00:00:02 zsh
现在返回到运行应用程序的终端,然后按 Enter 调用 _daemon 函数。然后再看另外一个终端上的进程信息。
ps -C test -o "pid ppid pgid sid tty stat command"
# Output
PID PPID PGID SID TT STAT COMMAND
22504 1 22481 5117 pts/2 S ./test
首先,您可以说新的子进程正在后台运行,因为您在 STAT 字段中看不到 + 字符。现在使用以下命令检查谁是该进程的父进程:
ps -jp 1
# Output
PID PGID SID TTY TIME CMD
1 1 1 ? 00:00:01 systemd
您现在可以看到您的进程的父进程是 systemd 进程。上面提到,下一步应该打开一个新会话,并且该进程应该与控制终端断开连接。为此,您可以使用setsid 函数。将此调用添加到您的 _daemon 函数中。
需要添加的代码如下:
if (setsid() == -1)
return -1;
现在您已经检查了 _daemon 调用之前的状态,现在可以删除 test.c 代码中的第一个 getchar 函数。
//test.c
#include <stdio.h>
int _daemon(int, int);
int main()
{
_daemon(0, 0);
getchar();
return 0;
}
再次编译并运行应用程序后,转到您进行评论的终端。您的流程的新状态如下:
ps -C test -o "pid ppid pgid sid tty stat command"
# Output
PID PPID PGID SID TT STAT COMMAND
25494 1 25494 25494 ? Ss ./test
TT 字段中的 ? 符号表示您的进程不再连接到终端。请注意,进程的 PID、PGID 和 SID 值是相同的。您的进程现在是会话领导者。
在下一步中,根据您传递的参数值将工作目录更改为根目录。为此,您可以将以下代码片段添加到 _daemon 函数中:
if (!nochdir) {
if (chdir("/") == -1)
return -1;
}
现在,根据传递的参数,可以关闭所有文件描述符。将以下代码添加到_daemon函数中:
#define NR_OPEN 1024
if (!noclose) {
for (i = 0; i < NR_OPEN; i++)
close(i);
open("/dev/null", O_RDWR);
dup(0);
dup(0);
}
所有文件描述符关闭后,守护进程打开的新文件将分别显示描述符 0、1 和 2。例如,在这种情况下,代码中的 printf 命令将定向到第二个打开的文件。为了避免这种情况,前三个标识符指向 /dev/null 设备。
在这种情况下,_daemon函数的最终状态将如下:
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <syslog.h>
#include <string.h>
int _daemon(void) {
// PID: Process ID
// SID: Session ID
pid_t pid, sid;
pid = fork(); // Fork off the parent process
if (pid < 0) {
exit(EXIT_FAILURE);
}
if (pid > 0) {
exit(EXIT_SUCCESS);
}
// Create a SID for child
sid = setsid();
if (sid < 0) {
// FAIL
exit(EXIT_FAILURE);
}
if ((chdir("/")) < 0) {
// FAIL
exit(EXIT_FAILURE);
}
close(STDIN_FILENO);
close(STDOUT_FILENO);
close(STDERR_FILENO);
while (1) {
// Some Tasks
sleep(30);
}
exit(EXIT_SUCCESS);
}
以下是将 sshd 应用程序作为 守护进程 运行的代码片段示例:
...
if (!(debug_flag || inetd_flag || no_daemon_flag)) {
int fd;
if (daemon(0, 0) < 0)
fatal("daemon() failed: %.200s", strerror(errno));
/* Disconnect from the controlling tty. */
fd = open(_PATH_TTY, O_RDWR | O_NOCTTY);
if (fd >= 0) {
(void) ioctl(fd, TIOCNOTTY, NULL);
close(fd);
}
}
...
守护进程对于 Linux 系统编程很重要
守护进程是按照响应某些事件而设置的预定义方式执行各种操作的程序。它们在您的 Linux 机器上静默运行。它们不受用户的直接控制,并且在后台运行的每个服务都有其守护进程。
掌握守护进程对于学习Linux操作系统的内核结构以及了解各种系统架构的工作方式非常重要。