当前位置: 首页 > news >正文

Linux守护进程

目录

1、编写守护进程的步骤

2、守护进程的使用和案例设计

2.1、案例功能分析

2.2、守护进程代码结构

2.3、代码实现

2.4、代码详解

3、编译和运行守护进程

4、检查守护进程

5、停止守护进程


守护进程(Daemon)是一种在后台运行的特殊进程,通常用于执行系统服务、管理任务或处理请求。它们具有几个显著的特征,使其在系统中扮演重要角色。

主要特征:

  • 长期运行:守护进程通常在系统启动时启动,并会持续运行,直至系统关闭或被强行终止。与普通进程不同,普通进程在用户登录或运行程序时创建,并在任务完成后或用户注销时终止。守护进程不受用户登录状态的影响,这使得它们能够在不需要用户交互的情况下稳定地执行任务。
  • 与控制终端脱离:在 Linux 系统中,每个从终端启动的进程都与该终端绑定。这意味着当终端关闭时,与之关联的所有进程都会被终止。守护进程通过脱离控制终端来避免这种限制,使其能够在后台独立运行。这种脱离的目的是防止进程在运行过程中受到终端输出的干扰,并确保它们能够持续工作。

守护进程在 Linux 系统中极为重要,它们是许多服务器的核心组成部分,例如 Internet 服务器 inetd 和 Web 服务器 httpd。这些进程不仅负责提供网络服务,还执行各种系统任务,例如作业调度进程 crond

守护进程通常以 “d” 结尾,表明其身份,例如 sshdhttpd。这些进程独立于终端,用户的登录和注销不会影响它们的运行。每个守护进程自成进程组和会话,确保其独立性(即 pid=gid=sid)。要查看系统中所有进程,可以使用命令 ps -ajx,其输出将显示所有进程的详细信息。

在命令输出中,如果 TTY 一栏显示为问号(?),这表示该进程没有控制终端,通常意味着它是一个守护进程。同时,COMMAND 一栏中用中括号([])括起来的进程表示内核线程。这些线程是在内核空间中创建的,没有对应的用户空间代码,因此不具备程序文件名和命令行信息,通常以字母 k 开头,表示它们是内核线程(Kernel)。

1、编写守护进程的步骤

编写守护进程通常包括以下几个关键步骤,以确保其能够在后台独立运行,并完成预定的任务。

1、创建子进程并终止父进程

使用 fork() 创建子进程后,父进程应调用 exit() 终止自身。这一过程实现了以下几点:

  • 如果守护进程是通过简单的 shell 命令启动,父进程的退出将使 shell 认为命令已执行完毕。
  • 子进程继承了父进程的进程组 ID,但它有自己独立的进程 ID,确保子进程不是进程组的组长,为后续调用 setsid() 准备条件。

2、子进程调用 setsid() 创建会话

在子进程中调用 setsid() 是关键步骤。这将:

  • 创建一个新的会话,子进程成为新会话的首领。
  • 创建新的进程组,子进程成为组长。
  • 摆脱原有会话、进程组和控制终端的控制,实现完全独立。尽管子进程在 fork() 时继承了父进程的控制权,但 setsid() 能确保其完全脱离。

3、更改工作目录为根目录

子进程会继承父进程的当前工作目录,而该目录可能会导致文件系统无法卸载。通常,守护进程会将工作目录更改为根目录(/),以避免这种问题。也可以根据需要选择其他目录。

4、重设文件权限掩码(umask)

文件权限掩码 umask 控制新建文件的默认权限。由于子进程继承了父进程的 umask,建议将其设置为 0,以确保子进程拥有最大权限,增强守护进程的灵活性。设置 umask 的方法是调用 umask(0)

5、关闭不再需要的文件描述符

子进程会继承父进程打开的所有文件描述符,这可能导致不必要的资源消耗。应关闭不再需要的文件描述符,以确保守护进程不再持有任何继承自父进程的描述符,从而减少资源浪费。

6、将文件描述符 0、1、2 定位到 /dev/null

守护进程的标准输入、标准输出和标准错误通常会重定向到 /dev/null,这样守护进程的输出就不会显示在任何地方,同时也不会试图从交互式用户那里接收输入。

7、其他处理:忽略 SIGCHLD 信号

处理 SIGCHLD 信号不是绝对必要的,但对于某些并发服务器进程尤其重要。通过将 SIGCHLD 信号的处理方式设置为 SIG_IGN,可以避免僵尸进程的产生。这样,当子进程结束时,内核将其交给 init 进程处理,减少了父进程的负担,从而提高了服务器的并发性能。

2、守护进程的使用和案例设计

为了深入理解如何创建和使用守护进程,我们将创建一个多功能的守护进程,具备以下功能:

  • 资源监控功能:守护进程每隔 30 秒获取系统的 CPU、内存和磁盘使用信息,并将其写入 /var/log/resource_monitor.log
  • 定时清理功能:每隔 10 分钟,清理 /tmp 目录下的所有文件。
  • 信号处理功能:守护进程能够捕获 SIGTERM 信号,安全退出,并能够处理 SIGHUP 信号重新加载配置文件。

2.1、案例功能分析

系统资源监控

  • 使用系统命令 statvmstat 来获取 CPU 和内存信息。
  • 使用 df 命令获取磁盘使用情况。
  • 每次获取的信息都写入 /var/log/resource_monitor.log,便于运维人员检查系统的健康状态。

定时清理任务

  • 每隔 10 分钟调用一个函数清理 /tmp 目录下的文件。
  • 使用系统函数 unlink() 删除文件。

信号处理

  • 捕获 SIGTERM 信号,干净地终止守护进程并进行资源释放。
  • 捕获 SIGHUP 信号,重新加载配置文件(如改变日志文件的路径)。

2.2、守护进程代码结构

  • daemonize():负责将进程变为守护进程的常规步骤。
  • monitor_resources():负责监控系统资源并将其写入日志。
  • cleanup_tmp():每隔 10 分钟清理一次 /tmp 目录中的文件。
  • handle_signal():处理 SIGTERMSIGHUP 信号。
  • reload_config():当捕获 SIGHUP 时,重新加载配置文件。

2.3、代码实现

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <time.h>
#include <dirent.h>
#include <syslog.h>#define LOG_FILE "/var/log/resource_monitor.log"
#define CONFIG_FILE "/etc/daemon_config.conf"
#define TMP_DIR "/tmp"// 定义轮询时间
#define MONITOR_INTERVAL 30  // 资源监控间隔 30 秒
#define CLEANUP_INTERVAL 600 // 清理间隔 10 分钟int keep_running = 1;
FILE *log_fp = NULL;// 守护进程初始化函数
void daemonize() {pid_t pid;// 1. 创建子进程并终止父进程pid = fork();if (pid < 0) exit(EXIT_FAILURE);if (pid > 0) exit(EXIT_SUCCESS);  // 父进程退出// 2. 创建新的会话if (setsid() < 0) exit(EXIT_FAILURE);// 3. 忽略 SIGCHLD 信号signal(SIGCHLD, SIG_IGN);// 4. 再次 fork,防止守护进程重新获得终端pid = fork();if (pid < 0) exit(EXIT_FAILURE);if (pid > 0) exit(EXIT_SUCCESS);// 5. 更改工作目录到根目录chdir("/");// 6. 重设文件权限掩码umask(0);// 7. 关闭不再需要的文件描述符close(STDIN_FILENO);close(STDOUT_FILENO);close(STDERR_FILENO);// 8. 重定向标准输入、输出、错误到 /dev/nullopen("/dev/null", O_RDONLY);open("/dev/null", O_WRONLY);open("/dev/null", O_WRONLY);// 打开系统日志openlog("resource_daemon", LOG_PID, LOG_DAEMON);
}// 捕获信号的处理函数
void handle_signal(int signal) {switch (signal) {case SIGHUP:syslog(LOG_INFO, "Reloading configuration file...");// 重新加载配置文件if (log_fp) {fclose(log_fp);}log_fp = fopen(LOG_FILE, "a");if (log_fp == NULL) {syslog(LOG_ERR, "Failed to open log file");exit(EXIT_FAILURE);}break;case SIGTERM:syslog(LOG_INFO, "Daemon is shutting down...");if (log_fp) {fclose(log_fp);}closelog();keep_running = 0;  // 设置标志位,结束主循环break;}
}// 资源监控功能
void monitor_resources() {FILE *fp;char buffer[128];// 记录当前时间time_t now = time(NULL);fprintf(log_fp, "Timestamp: %s", ctime(&now));// 记录 CPU 和内存使用情况fp = popen("vmstat 1 2 | tail -1", "r");if (fp != NULL) {fgets(buffer, sizeof(buffer) - 1, fp);fprintf(log_fp, "CPU/Memory Usage: %s\n", buffer);pclose(fp);}// 记录磁盘使用情况fp = popen("df -h /", "r");if (fp != NULL) {while (fgets(buffer, sizeof(buffer) - 1, fp) != NULL) {fprintf(log_fp, "Disk Usage: %s", buffer);}pclose(fp);}fflush(log_fp);  // 确保日志刷新到文件
}// 定时清理 /tmp 目录
void cleanup_tmp() {DIR *dir;struct dirent *entry;char file_path[256];dir = opendir(TMP_DIR);if (dir == NULL) {syslog(LOG_ERR, "Failed to open /tmp directory");return;}while ((entry = readdir(dir)) != NULL) {if (entry->d_type == DT_REG) {  // 只删除常规文件snprintf(file_path, sizeof(file_path), "%s/%s", TMP_DIR, entry->d_name);if (unlink(file_path) == 0) {syslog(LOG_INFO, "Deleted file: %s", file_path);} else {syslog(LOG_ERR, "Failed to delete file: %s", file_path);}}}closedir(dir);
}int main() {daemonize();// 打开日志文件log_fp = fopen(LOG_FILE, "a");if (log_fp == NULL) {syslog(LOG_ERR, "Failed to open log file");exit(EXIT_FAILURE);}// 捕获信号处理signal(SIGTERM, handle_signal);  // 用于进程关闭signal(SIGHUP, handle_signal);   // 用于重新加载配置time_t last_cleanup = time(NULL);// 主循环while (keep_running) {monitor_resources();  // 监控系统资源// 检查是否需要清理 tmp 目录if (difftime(time(NULL), last_cleanup) >= CLEANUP_INTERVAL) {cleanup_tmp();last_cleanup = time(NULL);}// 等待 30 秒后继续sleep(MONITOR_INTERVAL);}// 清理资源并退出if (log_fp) {fclose(log_fp);}closelog();return 0;
}

2.4、代码详解

守护进程初始化 (daemonize)

  • 将进程变为守护进程,使用了双 fork() 技术,确保进程在后台运行并与终端脱离关系。
  • 使用 syslog 系统日志服务记录进程启动、关闭等信息。

信号处理 (handle_signal)

  • 通过 signal() 函数捕获 SIGTERMSIGHUP 信号。
  • SIGTERM 信号用于干净地终止守护进程。
  • SIGHUP 信号用于重新加载配置文件,这里模拟了重新打开日志文件的过程。

资源监控 (monitor_resources)

  • 使用 vmstat 命令监控 CPU 和内存使用情况,df 命令获取磁盘使用状态。
  • 每次监控结果都记录到日志文件中。

定时清理 (cleanup_tmp)

  • 每隔 10 分钟清理 /tmp 目录下的文件。
  • 仅删除常规文件,忽略目录等。

主循环

  • 守护进程每 30 秒调用监控和清理函数,保持持续运行状态。

3、编译和运行守护进程

将上述代码保存为 resource_monitor.c,使用以下命令进行编译和运行:

gcc resource_monitor.c -o resource_monitor
sudo ./resource_monitor

注意,守护进程需要写入 /var/log/resource_monitor.log 文件,因此需要使用 sudo 权限运行。

4、检查守护进程

查看日志文件内容:

cat /var/log/resource_monitor.log

查看守护进程状态:

ps -ef | grep resource_monitor

5、停止守护进程

可以使用 kill 命令根据守护进程的 PID 将其终止:

kill <PID>
http://www.dtcms.com/a/270454.html

相关文章:

  • 【ES实战】ES客户端线程量分析
  • java-网络编程
  • Java中数组与链表的性能对比:查询与增删效率分析
  • RabbitMQ第二章(RocketMQ的五大工作模式)
  • 【Linux服务器】-安装ftp与sftp服务
  • 数据结构:数组:合并数组(Merging Arrays)
  • 20 道 Node.js 高频面试题
  • Codeforces Round 868 (Div. 2) D. Unique Palindromes(1900,构造)
  • 深入企业内部的MCP知识(四):FastMCP装饰器与类方法:正确结合面向对象与MCP组件的实践指南
  • 4.权重衰减(weight decay)
  • MySQL-索引
  • SQL135 每个6/7级用户活跃情况
  • ${project.basedir}延申出来的Maven内置的一些常用属性
  • Python入门Day5
  • 嵌入式面试八股文100题(二)
  • 分库分表之实战-sharding-JDBC水平分库+水平分表配置实战
  • 【深度学习入门 鱼书学习笔记(1)感知机】
  • 7月8日学习笔记——统计决策方法
  • 基于springboot的物流配货系统
  • Nuxt.js 静态生成中的跨域问题解决方案
  • C++学习笔记之数组、指针和字符串
  • 【PyTorch】PyTorch中torch.nn模块的激活函数
  • 项目Win系统下可正常获取Header字段,但是到了linux、docker部署后无法获取
  • python基础day08
  • linux wsl2 docker 镜像复用快速方法
  • 【读代码】GLM-4.1V-Thinking:开源多模态推理模型的创新实践
  • 基于模板设计模式开发优惠券推送功能以及对过期优惠卷进行定时清理
  • C++ 遍历可变参数的几种方法
  • 数据库表设计:图片存储与自定义数据类型的实战指南
  • C语言宏替换比较练习