守护进程(Daemon)是一种在后台运行的特殊进程,通常用于执行系统服务、管理任务或处理请求,它们具有以下几个显著的特征:
1、长期运行:守护进程通常在系统启动时启动,并会持续运行,直至系统关闭或被强行终止,与普通进程不同,普通进程在用户登录或运行程序时创建,并在任务完成后或用户注销时终止,守护进程不受用户登录状态的影响,这使得它们能够在不需要用户交互的情况下稳定地执行任务。
2、与控制终端脱离:在Linux系统中,每个从终端启动的进程都与该终端绑定,这意味着当终端关闭时,与之关联的所有进程都会被终止,守护进程通过脱离控制终端来避免这种限制,使其能够在后台独立运行,这种脱离的目的是防止进程在运行过程中受到终端输出的干扰,并确保它们能够持续工作。
3、独立性:守护进程与用户的登录会话是独立的,它在后台静静运行,执行系统级任务,如日志记录、定时任务、文件清理等。
4、父进程为init进程:守护进程在系统启动时由父进程(通常是init进程)启动,运行时不会退出。
5、PID(进程ID):守护进程的PID是由操作系统分配的,它通常会被写入到某个文件中以供后续管理和终止。
6、资源管理:守护进程需要妥善管理资源,包括文件描述符、内存分配等,以确保系统资源的高效利用和避免泄漏。
7、错误处理与日志记录:守护进程需要能够处理运行时可能出现的错误,并将相关信息记录到日志文件中,以便于问题的诊断和追踪。
创建守护进程的步骤
创建一个守护进程通常涉及以下关键步骤,以确保其能够在后台独立运行,并完成预定的任务:
1、创建子进程并终止父进程:使用fork()系统调用创建子进程后,父进程应调用exit()终止自身,这一过程实现了以下几点:如果守护进程是通过简单的shell命令启动,父进程的退出将使shell认为命令已执行完毕;子进程继承了父进程的进程组ID,但它有自己独立的进程ID,确保子进程不是进程组的组长,为后续调用setsid()准备条件。
2、子进程调用setsid()创建会话:在子进程中调用setsid()是关键步骤,这将:创建一个新的会话,子进程成为新会话的首领;创建新的进程组,子进程成为组长;摆脱原有会话、进程组和控制终端的控制,实现完全独立。
3、更改工作目录为根目录:子进程会继承父进程的当前工作目录,而该目录可能会导致文件系统无法卸载,守护进程会将工作目录更改为根目录(/),以避免这种问题,也可以根据需要选择其他目录。
4、重设文件权限掩码(umask):文件权限掩码umask控制新建文件的默认权限,由于子进程继承了父进程的umask,建议将其设置为0,以确保子进程拥有最大权限,增强守护进程的灵活性,设置umask的方法是调用umask(0)。
5、关闭不再需要的文件描述符:子进程会继承父进程打开的所有文件描述符,这可能导致不必要的资源消耗,应关闭不再需要的文件描述符,以确保守护进程不再持有任何继承自父进程的描述符,从而减少资源浪费。
6、将文件描述符0、1、2定位到/dev/null:守护进程的标准输入、标准输出和标准错误通常会重定向到/dev/null,这样守护进程的输出就不会显示在任何地方,同时也不会试图从交互式用户那里接收输入。
7、其他处理:例如忽略SIGCHLD信号,对于某些并发服务器进程尤其重要,通过将SIGCHLD信号的处理方式设置为SIG_IGN,可以避免僵尸进程的产生,这样,当子进程结束时,内核将其交给init进程处理,减少了父进程的负担,从而提高了服务器的并发性能。
守护进程的使用和案例设计
为了深入理解如何创建和使用守护进程,可以设计一个多功能的守护进程示例,具备以下功能:
1、资源监控功能:守护进程每隔30秒获取系统的CPU、内存和磁盘使用信息,并将其写入/var/log/resource_monitor.log。
2、定时清理功能:每隔10分钟,清理/tmp目录下的所有文件。
3、信号处理功能:守护进程能够捕获SIGTERM信号,安全退出,并能够处理SIGHUP信号重新加载配置文件。
守护进程代码结构
daemonize():负责将进程变为守护进程的常规步骤。
monitor_resources():负责监控系统资源并将其写入日志。
cleanup_tmp():每隔10分钟清理一次/tmp目录中的文件。
handle_signal():处理SIGTERM和SIGHUP信号。
reload_config():当捕获SIGHUP时,重新加载配置文件。
C++实现守护进程示例
以下是一个C++实现守护进程的简单示例代码:
#include <iostream> #include <cstdlib> #include <unistd.h> #include <signal.h> #include <string> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> const std::string nullfile = "/dev/null"; void Daemon(const std::string &cwd = "") { // 1. 忽略其他异常信号 signal(SIGCLD, SIG_IGN); signal(SIGPIPE, SIG_IGN); signal(SIGSTOP, SIG_IGN); // 2. 将自己变成独立的会话 if (fork() > 0) // 0说明是父进程,让父进程直接退出 exit(0); setsid(); //子进程 // 3. 更改当前调用进程的工作目录 if (!cwd.empty()) chdir(cwd.c_str()); // 4. 标准输入,标准输出,标准错误重定向至/dev/null 垃圾桶 int fd = open(nullfile.c_str(), O_RDWR); if (fd > 0) { dup2(fd, 0); dup2(fd, 1); dup2(fd, 2); close(fd); } }
各位小伙伴们,我刚刚为大家分享了有关“服务器 进程守护”的知识,希望对你们有所帮助。如果您还有其他相关问题需要解决,欢迎随时提出哦!
原创文章,作者:K-seo,如若转载,请注明出处:https://www.kdun.cn/ask/743989.html