关于守护进程和后台进程, 一直以为是一个东西。然而并不是。
概念
先看下 chatGPT上对二者的描述:
如何创建守护进程
通常情况下,守护进程的父进程是init进程(PID为1)。在类Unix系统中,init进程是系统引导过程的第一个进程,是所有其他进程的祖先进程。当一个守护进程启动时,它会创建一个子进程并终止自己,使得子进程成为新的守护进程。这样的设计是为了确保守护进程不会成为孤儿进程(没有父进程),并且能够继续在系统中运行。由于init进程是系统中的根进程,它会接收并处理孤儿进程。当守护进程的父进程终止时(通常是init进程接收到子进程的终止信号),init进程会接管并处理守护进程的状态和资源释放。因此,init进程通常是守护进程的父进程,负责维护和管理守护进程的生命周期。
代码示例:
/* Daemonizing */pid = fork();if (pid < 0) {DEBUG_MSG("fork error");exit(1);}else if (pid > 0) {DEBUG_MSG("parent exits");exit(0);}/* Process Independency */if (setsid() < 0) {DEBUG_MSG("setsid error");exit(1);}/* Fork again, daemon process is re-parented to init(PID 1) */pid = fork();if (pid < 0) {DEBUG_MSG("fork error");exit(1);}else if (pid > 0) {DEBUG_MSG("parent exits");exit(0);}
这段代码展示了创建守护进程的过程。工作原理如下:
1.pid = fork();
:通过调用fork()
函数创建一个子进程。子进程的pid
为0,父进程的pid为子进程的进程ID。
2.检查pid
的值,如果pid
小于0,则表示fork()
调用失败,输出错误消息并退出程序。
3.如果pid
大于0,说明当前代码在父进程中执行。父进程通过输出调试消息表明自己正在退出,并调用exit(0)
来正常终止进程。
4.setsid()
:子进程调用setsid()函数创建一个新的会话,并成为该会话的首进程。这使得子进程独立于其父进程和终端会话。
5.检查setsid()
的返回值,如果小于0,则表示出现错误,输出错误消息并退出程序。
6.pid = fork();
:子进程再次调用fork()
函数创建一个新的子进程。这样做是为了确保守护进程不会成为会话首进程(因为会话首进程无法终止)。
7.检查pid
的值,如果pid
小于0,则表示fork()
调用失败,输出错误消息并退出程序。
8.如果pid
大于0,说明当前代码在第二次fork
后的子进程中执行。子进程通过输出调试消息表明自己正在退出,并调用exit(0)来正常终止进程。
最终,执行这段代码的进程将会变成一个守护进程。这个守护进程与终端会话分离,成为一个独立的进程,并且它的父进程是init
进程(PID
为1)。
会话、终端、进程之间的关系
会话(Session)、终端(Terminal)和进程(Process)之间有以下关系:
- 会话:会话是一个抽象的概念,代表一个用户与系统进行交互的时间段。它可以包含多个终端会话和相关联的进程。一个会话通常从用户登录开始,直到用户注销或系统关闭。每个会话都有一个唯一的会话ID(Session ID)。
- 终端:终端是用户与计算机系统进行交互的设备或应用程序。在传统的计算机系统中,终端通常是指物理终端设备,如计算机控制台、终端机或虚拟终端。在现代的操作系统中,终端也可以是指终端仿真器(Terminal Emulator)软件,如命令行终端、终端窗口或终端模拟器。一个终端可以关联一个会话。
- 进程:进程是正在执行的程序的实例。它是操作系统中进行资源分配和管理的基本单位。每个进程都有一个唯一的进程ID(Process ID),用于标识和管理进程。一个进程可以关联一个终端会话。
关系描述:
- 一个会话可以包含多个终端会话。例如,用户在登录后可能同时在多个终端上进行交互,这些终端会话都属于同一个会话。
- 一个终端会话通常关联一个终端设备或终端仿真器,它是用户与系统进行交互的接口。
- 一个终端会话可以关联一个或多个进程。这些进程可以是用户启动的应用程序、命令行工具或其他进程。
- 一个进程可以属于一个终端会话,从而与一个终端关联。这意味着进程可以通过关联的终端接收输入和发送输出。
总结来说,会话是用户与系统交互的时间段,终端是用户与系统交互的接口,而进程是正在执行的程序实例,可以与终端会话关联起来以接收输入和发送输出。
为什么要这样创建守护进程
守护进程的设计目标是在后台长期运行,独立于任何终端会话。
守护进程通常不会成为会话的首进程。当一个进程成为会话的首进程时,它会自动拥有控制终端。这意味着它能够接收来自终端的输入和发送输出到终端。但对于守护进程来说,与终端相关的交互是不必要的,而且可能会引起一些问题,例如无法完全脱离终端,或者因为接收到来自终端的信号而终止运行。
为了确保守护进程的独立性和稳定性,常见的做法是采用以下步骤:
1.调用setsid()函数:它会创建一个新的会话并使调用进程成为该会话的首进程。这样,守护进程就不再与原始终端会话相关联。
2. 第二次调用fork():通过再次调用fork(),创建一个新的子进程。这样做是为了确保守护进程不会成为会话的首进程,因为会话首进程无法终止。
通过这种方式,守护进程将成为新会话的成员,但不会成为会话的首进程。它将与终端完全分离,不再受终端会话的影响。这有助于确保守护进程能够在后台长期运行,并且不会因为与终端相关的问题而受到干扰。