如何在 Linux 上创建守护进程

守护进程是不直接在用户控制下运行而是在后台服务的进程。 通常,它们在系统启动时启动并持续运行,直到系统关闭。 这些和普通进程之间的唯一区别是它们不会以任何方式将消息发送到控制台或屏幕。

下面介绍如何在 Linux 机器上创建守护进程。

守护进程是如何创建的简介

系统上运行着很多守护进程,一些熟悉的守护进程示例如下:

  • crond:使命令在指定时间运行
  • sshd:允许从远程机器登录系统
  • httpd: 提供网页
  • nfsd:允许通过网络共享文件

此外,守护进程通常以字母结尾 d,虽然它不是强制性的。

对于作为守护进程运行的进程,遵循以下路径:

  • 必须在进程成为守护进程之前执行初始操作,例如读取配置文件或获取必要的系统资源。 这样,系统可以将接收到的错误报告给用户,并且该过程将以适当的错误代码终止。
  • 创建一个后台运行的进程,init 作为其父进程。 为此,首先从init进程中fork出一个子进程,然后以exit终止上层进程。
  • 应该通过调用 setsid 函数打开一个新会话,并且应该断开该进程与终端的连接。
  • 从父进程继承的所有打开的文件描述符都被关闭。
  • 标准输入、输出和错误消息被重定向到 /dev/null.
  • 进程的工作目录必须改变。

什么是守护进程会话?

通过终端登录系统后,用户可以通过shell程序运行很多应用程序。 这些过程应该 close 当用户退出系统时。 操作系统将这些进程分为会话组和进程组。

每个会话由进程组组成。 您可以将这种情况描述如下:

进程接收输入并发送输出的终端称为控制终端。 控制终端一次仅与一个会话相关联。

会话及其中的进程组具有标识 (ID) 编号; 这些标识号是会话和进程组领导的进程标识号 (PID)。 子进程与其父进程共享同一个组。 当多个进程与管道机制通信时,第一个进程成为进程组的领导者。

在 Linux 上创建守护进程

在这里,您将看到如何创建守护程序函数。 为此,您将创建一个名为 _守护进程. 您可以首先将作为守护进程运行的应用程序代码命名为 测试.c,以及您将创建守护程序函数的代码 守护进程.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 的后台进程。 在上面的代码中, _守护进程 创建一个子进程,然后杀死父进程。 在这种情况下,您的新进程将是 init 的子进程,并将继续在后台运行。

现在使用以下命令编译应用程序并检查进程前后的状态 _deamon 叫做:

gcc -o test test.c daemon.c

运行应用程序并切换到不同的终端,而无需按任何其他键:

./test

您可以看到与您的流程相关的值如下。 在这里,您必须使用 ps 命令来获取与进程相关的信息。 在这种情况下, _守护进程 函数还没有被调用。

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

当你看 统计数据 字段,您会看到您的进程正在运行,但正在等待发生计划外事件,这将导致它在前台运行。

缩写 意义
小号 睡着等待事件发生
应用程序停止
s 会话负责人
+ 应用程序在前台运行

您可以看到您的应用程序的父进程是预期的shell。

ps -jp 5119                                 
# Output
PID PGID SID TTY TIME CMD
5119 5119 5117 pts/2 00:00:02 zsh

现在返回运行应用程序的终端并按 Enter 调用 _守护进程 功能。 然后再次查看另一个终端上的进程信息。

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

首先,您可以说新的子进程正在后台运行,因为您看不到 + 中的人物 统计数据 场地。 现在使用以下命令检查谁是进程的父进程:

ps -jp 1                                      
​​​​​​​# Output
PID PGID SID TTY TIME CMD
1 1 1 ? 00:00:01 systemd

您现在可以看到您的进程的父进程是 系统 过程。 上面提到,对于下一步,应该打开一个新会话,并且应该断开该进程与控制终端的连接。 为此,您使用 setsid 函数。 将此呼叫添加到您的 _守护进程 功能。

要添加的代码如下:

if (setsid() == -1) 
return -1;

现在您已经检查过之前的状态 _守护进程 调用,您现在可以删除第一个 获取字符 中的功能 测试.c 代码。

//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 您的过程的值是相同的。 您的进程现在是会话负责人。

在下一步中,根据您传递的参数的值将工作目录更改为根目录。 您可以将以下代码段添加到 _守护进程 功能:

if (!nochdir) {
if (chdir("/") == -1)
return -1;
}

现在,根据传递的参数,可以关闭所有文件描述符。 将以下代码添加到 _守护进程 功能:

#define NR_OPEN 1024
if (!noclose) {
for (i = 0; i < NR_OPEN; i++)
close(i);
open("/dev/null", O_RDWR);
dup(0);
dup(0);
}

关闭所有文件描述符后,由 daemon 打开的新文件将分别显示为描述符 0、1 和 2。 在这种情况下,例如, 打印 代码中的命令将被定向到第二个打开的文件。 为了避免这种情况,前三个标识符指向 /dev/null 设备。

在这种情况下,最终状态 _守护进程 功能如下:

#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 操作系统的内核结构和了解各种系统架构的工作原理非常重要。