进程,shell,shell进程,终端,控制终端,前台进程,后台进程,控制进程,前台进程组,后台进程组,会话,守护进程,init进程,用户进程,系统进程
它们之间的联系与区别- 系列文章第一篇传送门:全面理解shell进程、终端、控制终端的概念,以及它们之间有什么区别与联系?(系列文章第一篇)
- 系列文章第二篇传送门:全面理解进程组,会话的基础概念,以及进程组,会话,控制终端,前台进程组与后台进程组之间的联系(系列文章第二篇)
什么是守护进程
-
守护进程通常在系统引导装载时启动,并且在系统关闭之前一直运行。
-
守护进程的名称通常以 "d" 结尾,以便于区分
。例如,sshd 是 Secure Shell 守护进程,httpd 是 HTTP 守护进程。 -
这些进程在后台运行,提供各种服务
,例如处理网络请求(如 web 服务器)、处理系统日志、处理电子邮件和其他各种任务。 -
守护进程通常不直接与用户交互,但它们工作起来非常重要,为其他程序和用户提供关键服务。
-
守护进程在创建时通常会进行“孤儿化”操作,使其成为 init 进程(进程ID为1)的子进程。这样,它们就可以在后台运行,不受任何特定用户或会话的影响。此外,守护进程通常会更改其工作目录到根目录 (“/”),关闭所有已打开的文件描述符(包括输入、输出和错误输出),并重新打开标准输入、标准输出和标准错误到/dev/null,这样就可以防止它们不小心读取或写入任何用户文件或终端。
总的来说,守护进程是一种在后台运行,为系统或其他程序提供服务的进程。
如何创建一个守护进程
下面我来解释一下每个步骤的含义:
-
执行一个 fork(),之后父进程退出,子进程继续执行。
- 这是创建守护进程的第一步,目的是让守护进程在后台运行。在fork()之后,父进程退出,子进程成为孤儿进程,并被 init 进程(进程ID为1)接管。
-
子进程调用 setsid() 开启一个新会话。
- setsid()函数会创建一个新的会话,并且让子进程成为这个新会话的首进程。这样子进程就与其原来的会话、进程组和控制终端脱离,成为一个新的会话的首进程,可以独立运行。
-
清除进程的 umask 以确保当守护进程创建文件和目录时拥有所需的权限。
- umask是一个权限掩码,它决定了新建文件或目录的默认权限。清除umask是为了让守护进程能够有更大的权限控制它创建的文件或目录。
-
修改进程的当前工作目录,通常会改为根目录(/)。
- 这是为了防止守护进程阻止文件系统被卸载。如果守护进程的工作目录在一个挂载的文件系统中,那么这个文件系统就不能被卸载。
-
关闭守护进程从其父进程继承而来的所有打开着的文件描述符。
- 这是为了避免守护进程继续使用这些文件描述符,可能会导致不可预料的问题。
-
在关闭了文件描述符0、1、2之后,守护进程通常会打开/dev/null 并使用dup2() 使所有这些描述符指向这个设备。
- 这是为了让守护进程的标准输入、输出和错误输出被重定向到/dev/null,避免它们产生任何输出,因为守护进程通常不需要和用户交互。
核心业务逻辑 在完成了以上所有的步骤后,守护进程开始执行其核心的业务逻辑,为系统或其他程序提供服务。
示例:创建一个守护进程
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
void daemonize()
{
// 执行一个 fork(),之后父进程退出,子进程继续执行。
pid_t pid = fork();
if (pid > 0)
exit(0);
else if (pid < 0)
{
perror("fork");
exit(0);
}
// 子进程调用 setsid() 开启一个新会话。
if (setsid() == -1)
{
perror("setsid");
exit(0);
}
// 清除进程的 umask 以确保当守护进程创建文件和目录时拥有所需的权限。
umask(0);
// 修改进程的当前工作目录,通常会改为根目录(/)。
chdir("/home/nowcoder/Linux/lession28");
// 关闭守护进程从其父进程继承而来的所有打开着的文件描述符。
close(STDIN_FILENO);
close(STDOUT_FILENO);
close(STDERR_FILENO);
// 在关闭了文件描述符0、1、2之后,守护进程通常会打开/dev/null 并使用dup2() 使所有这些描述符指向这个设备。
}
// 代码主体逻辑部分
int main()
{
daemonize();
FILE *file = fopen("/home/nowcoder/Linux/lession28/time.txt", "a+");
if (file == NULL)
{
perror("fopen");
exit(0);
}
while (1)
{
time_t tt = time(NULL);
struct tm t = *localtime(&tt);
fprintf(file, "现在是北京时间: %d-%d-%d %d:%d:%d\n", t.tm_year + 1900, t.tm_mon + 1, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec);
fflush(file); // 保证数据立即写入文件
sleep(2);
}
return 0;
}
- 编译和运行这个程序之后,你可以使用ps -ef | grep a.out(假设你的程序名是a.out)来查看守护进程。你会看到它没有控制终端,而且会话ID是1,这都是守护进程的特征。
- 你可以用
kill - 9 守护进程的进程号
去杀死一个守护进程
注意,这是一个基础的守护进程,一个真实的守护进程可能需要处理更多的细节,对于实际环境中的守护进程,需要处理的问题可能包括但不限于:
-
日志记录:当守护进程运行时,可能需要记录或报告它的行为。通常,它将这些信息写入日志文件或系统日志。
-
配置文件:守护进程通常有一些可配置的设置,这些设置通常存储在配置文件中。当守护进程启动时,它会读取这个配置文件。
-
信号处理:守护进程需要响应系统发送给它的信号。例如,当系统关闭时,它可能会收到一个SIGTERM信号,通知它应当进行清理并优雅地终止。
-
错误处理:守护进程需要处理可能在运行时发生的各种错误,例如文件读取失败、网络连接中断等。
-
权限管理:守护进程可能需要以特定的用户身份运行,以限制其对系统资源的访问。
-
资源管理:如果守护进程创建了子进程,那么它可能需要跟踪这些进程,确保它们被正确地关闭和清理。
以上就是在设计守护进程时,可能需要考虑的一些问题。这些问题的处理方式会因应用的具体需求而异。
总结
-
守护进程(Daemon)是一类在 Unix 和 Unix-like 操作系统中运行的后台进程。这些进程通常在系统启动时开始,然后在系统关闭时结束。守护进程通常不与用户直接交互,它们为其他程序和用户提供服务,例如网络服务、系统日志服务、打印服务等。
-
守护进程的名称通常以 “d” 结尾,这表示它是一个守护进程。例如,httpd 是一个处理 HTTP 请求的守护进程,sshd 是一个提供安全 shell 服务的守护进程。
总的来说,守护进程就是在后台运行,为系统或用户提供各种服务的进程。与普通的进程一样,只要知道进程号,就可以用kill信号去杀死它。
最后的最后,如果你觉得我的这篇文章写的不错的话,请给我一个赞与收藏,关注我,我会继续给大家带来更多更优质的干货内容
。