深入理解Linux体系:进程组、会话、作业、控制终端与守护进程的关系解析
一、进程组Linux把进程分属一些组,用进程的组标识符赖知道进程所属的组。进程最初是通过fork()和exec调用来继承其进程组标识符。
进程组是一个或者多个进程的聚集, 一个进程组可以包含多个进程。每一个进程组也有一个唯一的进程组 ID(PGID), 并且这个 PGID 类似于进程ID,同样是一个正整数, 可以存放在 pid_t 数据类型中。PGID:process group id
https://i-blog.csdnimg.cn/direct/2ee35ba3f847446bb6204543ee2d4506.png#pic_center
[*]所有sleep下令的进程都拥有雷同的PGID(110118),表明它们属于同一个进程组。
[*]这些进程的SID(会话ID)也是雷同的(110087),表明它们属于同一个会话。
[*]PPID列表现这些进程都是由同一个父进程(PID 110087)创建的,这通常是创建该进程组的bash进程。
在Linux操作体系中,当下令行中利用管道(|)连接多个下令时,第一个下令(在管道之前的部分)将成为一个进程组的组长,同时也会创建一个新的进程组。这是因为管道操作符创建了一个管道,并且第一个下令作为管道的写入端,而后续的下令作为读取端。
在我的下令中:
zyb@myserver:~$ sleep 10000 | sleep 20000 | sleep 30000
这里发生了两次管道操作,因此会创建三个进程:
[*]第一个 sleep 10000 下令会创建一个进程。
[*]第二个 sleep 20000 下令会创建一个进程。
[*]第三个 sleep 30000 下令会创建一个进程。
每个 sleep 下令都在其自己的进程中运行,并且通过管道连接。第一个 sleep 下令的输出(只管现实上 sleep 下令不产生输出)会传递给第二个 sleep 下令,第二个 sleep 下令的输出(同样,现实上没有输出)会传递给第三个 sleep 下令。这里的第一个下令 sleep 10000 将成为新创建的进程组的组长。该进程组的进程组ID(PGID)将与第一个 sleep 下令的进程ID(PID)雷同。后续的下令 sleep 20000 和 sleep 30000 将是这个进程组的成员,并且它们的PGID将与第一个下令的PID雷同。
但是,进程可以利用体系调用setpgrp(),自己形成一个新的组。
https://i-blog.csdnimg.cn/direct/be23a944f7fd4b84904a895c7b0f4f97.png#pic_center
setpgrp() 体系调用用于将一个进程设置为新的进程组的组长,从而创建一个新的进程组。该体系调用的返回值是调用进程的进程组标识符(PGID),该标识符与调用进程的进程标识符(PID)雷同。调用进程成为该新进程组的组长进程(process group leader)。通常 setpgrp() 等价于 setpgid(0, 0)。
一个进程可以用体系调用 getpgrp()来获得其当前的进程组标识符。函数的返回值就是进程组的标识符。
每一个进程组都有一个组长进程,其 ID(PGID)与其进程 ID(PID)雷同。
固然组长进程有权创建新的进程组或将进程加入现有的组中,但它并不特别管理组内的其他进程。组长进程的主要角色是在体系中标识该进程组的一个唯一标识符。
进程组的生命周期
进程组的生命周期从创建时开始,到组内末了一个进程离开为止。在此期间,纵然组长进程终止,只要组内还有其他进程存在,进程组依然继续存在。
[*] 组长进程终止的影响: 组长进程的终止并不会导致进程组的终止。体系将继续利用原组长进程的 PID 作为进程组 ID,直到组内末了一个进程终止。
[*] 进程组的终止: 当进程组内所有进程都终止或离开该进程组时,进程组也就天然遣散。此时,体系不再保存该进程组的干系信息。
当某个用户退出体系时,则相应的shell进程所启动的全部进程都要被强行终止。体系是根据进程的组标识符来选定应该终止的进程的。
假如一个进程具有与其先人shell进程雷同的组标识符(PGID),那么在用户退出登录shell时,这个进程通常会接收到挂起信号(SIGHUP),并且默认情况下会被终止。这是因为登录shell在退出时会发送SIGHUP信号给其所属进程组中的所有进程。因此假如一个进程具有与其先人shell进程不同的组标识符,那么它的生命期将可超出用户的注册期。这对于需要长时间运行的背景任务是非常有用的。
[*]当用户登录体系时,登录shell会成为会话领头进程,并创建一个新的进程组,其PGID与shell的PID雷同。
[*]默认情况下,shell启动的所有进程都会继承shell的PGID。
[*]当用户退出体系时,其登录会话竣事。这通常是通过关闭终端会话或实行注销下令来触发的。shell会向其进程组内的所有进程发送SIGHUP(挂起信号),导致这些进程默认情况下会被终止。
[*]SIGHUP 信号首先发送给进程组的组长进程,然后组长进程通常会将其传播给进程组内的所有成员进程。
[*]假如进程的 PGID 被改变,那么它不会因为用户退出 shell而直接收到 SIGHUP 信号。这是因为 SIGHUP 信号通常会发送给整个进程组,而该进程已经不在那个进程组中了。
[*]假如进程忽略了 SIGHUP 信号,那么纵然它仍旧在原来的进程组中,它也不会因为 SIGHUP 信号而终止。
这就是为什么将背景任务放在一个新的进程组中(或者让它们忽略SIGHUP信号)对于需要长时间运行的背景任务很有用的缘故原由。如许做可以包管任务在用户注销后不会因为接收到SIGHUP信号而被终止。
单进程程序是一个进程组吗?
#include <iostream>
#include <string>
#include <unistd.h>
int main()
{
while (true)
{
std::cout << "I am a process :" << getpid() << std::endl;
sleep(1);
}
return 0;
}
https://i-blog.csdnimg.cn/direct/6feadebc3f34450392441ad7467d0a13.png#pic_center
多进程程序内的进程属于一个进程组吗?
#include <iostream>
#include <string>
#include <unistd.h>
int main()
{
pid_t id = fork();
if (id == 0)
{
while (true)
{
std::cout << "I am a child process :" << getpid() << std::endl;
sleep(1);
}
}
sleep(3);
std::cout << "I am a parent process :" << getpid() << std::endl;
sleep(100);
return 0;
}
https://i-blog.csdnimg.cn/direct/e193bbe25eeb4d06ba75b0814f115ea5.png#pic_center
只要某个进程组中有一个进程存在, 则该进程组就存在, 这与其组长进程是否已经终止无关。验证代码:
#include <iostream>
#include <string>
#include <unistd.h>
int main()
{
pid_t id = fork();
if (id == 0)
{
while (true)
{
std::cout << "I am a child process :" << getpid() << std::endl;
sleep(1);
}
}
sleep(3);
std::cout << "I am a parent process :" << getpid() << std::endl;
exit(0);
return 0;
}
https://i-blog.csdnimg.cn/direct/3284dda3328c449b856da2f13e6b67b3.png#pic_center
进程的标识符
每个进程都有一个现实用户标识符(Real User ID,RUID)和一个现实组标识符(Real Group ID,RGID)。
进程的有效用户标识符(Effective User ID,EUID) 和 有效组标识符(Effective Group ID,EGID) 也许更重要些,它们被用来确定一个用户能否访问某个确定的文件。在通常情况下,它们与现实用户标识符和现实组标识符是同等的。但是,一个进程或其先人进程可以设置程序文件的置用户标识符权限或置组标识符权限。如许,当通过 exec 调用实行该程序时,其进程的有效用户标识符就取自该文件的文件主的有效用户标识符,而不是启动该进程的用户的有效用户标识符。
在Unix/Linux操作体系中,进程的身份验证和权限检查主要基于以下四个标识符:
[*]现实用户标识符(Real User ID, RUID):标识启动进程的现实用户。
[*]现实组标识符(Real Group ID, RGID):标识启动进程的现实用户所属的组。
[*]有效用户标识符(Effective User ID, EUID):用于权限检查,确定用户对文件和其他资源的访问权限。
[*]有效组标识符(Effective Group ID, EGID):与EUID类似,用于权限检查,确定用户所属组对文件和其他资源的访问权限。
以下是关于这些标识符的详细解释:
[*]现实用户标识符和现实组标识符:当一个用户启动一个进程时,该进程的RUID和RGID被设置为用户的UID和GID。这些标识符不会在进程实行期间改变,它们标识了启动进程的用户。
[*]有效用户标识符和有效组标识符:通常情况下,EUID和EGID与RUID和RGID雷同。但是,在某些情况下,进程可能需要以不同的用户身份实行操作。这时,EUID和EGID可以被设置为不同于RUID和RGID的值。
设置用户标识符(Set-User-ID, SUID)和设置组标识符(Set-Group-ID, SGID)位是文件权限位,它们可以改变进程的EUID和EGID:
[*]设置用户标识符(SUID):当一个可实行文件设置了SUID位时,任何用户实行这个程序时,进程的EUID会被设置为该文件所有者的UID,而不是实行者的UID。如许,进程就可以访问文件所有者有权限访问的资源。
[*]设置组标识符(SGID):类似地,当一个可实行文件设置了SGID位时,实行这个程序时,进程的EGID会被设置为该文件所有组的GID,而不是实行者所属组的GID。
https://i-blog.csdnimg.cn/direct/04dff05b7d1b40eeb971a97c44b669b1.png#pic_center
例如,考虑/bin/passwd下令,它用于更改用户密码。为了允许普通用户更改自己的密码(这通常需要root权限,因为密码文件/etc/shadow只有root可以写),/bin/passwd被设置了SUID位。因此,当普通用户实行/bin/passwd时,只管进程的现实用户是普通用户,但有效用户是root,这使得进程可以修改/etc/shadow文件。
总结来说,SUID和SGID位允许进程以不同于启动它的用户的权限运行,这在某些情况下非常有用,尤其是在需要特定权限才能实行操作的程序中。然而,它们也可能引入安全风险,因为假如设置了SUID或SGID位的程序存在毛病,那么攻击者可能会利用这些权限实行恣意代码。
下面几个体系调用可以用来得到进程的用户标识符和组标识符:
https://i-blog.csdnimg.cn/direct/308cdc01f49d4754968f22cce5469bae.png#pic_center
https://i-blog.csdnimg.cn/direct/510d374dcfba4080a74cb84b9753607b.png#pic_center
另外还有两个体系调用可以用来设置进程的有效用户标识符和有效组标识符:
https://i-blog.csdnimg.cn/direct/fb525a532d6c438a8909d6590ed45c8c.png#pic_center
https://i-blog.csdnimg.cn/direct/49e6c4ecbb004adfbf0bd6f548cfa529.png#pic_center
普通用户进程的标识符设置
普通用户启动的进程只能将有效用户表示符(EUID)和有效组标识符(EGID)重新设置回其现实用户标识符(RUID)和现实组标识符(RGID)。这意味着,假如一个进程是由普通用户启动的,那么它不能通过setuid()和setgid()体系调用来提升其权限。以下是对这两个体系调用的简朴阐明:
[*]setuid(uid_t uid):尝试将进程的EUID设置为参数uid指定的值。假如进程由非root用户启动,则只能将EUID设置回RUID。
[*]setgid(uid_t gid):尝试将进程的EGID设置为参数gid指定的值。假如进程由非root用户启动,则只能将EGID设置回RGID。
假如调用成功,这些函数返回0;假如调用失败,它们返回-1。
超级用户进程的标识符设置
root用户启动的进程可以自由地设置其EUID和EGID为恣意值。这意味着root进程可以完全控制其权限,包括降低权限。
放弃和规复root权限:一个由root启动的进程可以通过setuid()和setgid()放弃其root权限,但假如它厥后需要规复这些权限,它不能再次利用setuid()和setgid()来实现,因为一旦进程的EUID被设置为一个非root值,它就不能再将其EUID设置回root。
为相识决这个问题,Linux提供了以下体系调用:
[*]seteuid(uid_t euid):设置进程的EUID。纵然进程的EUID已经被设置为非root值,只要进程的RUID是root,它仍旧可以利用seteuid()将EUID重新设置回root。
[*]setegid(gid_t egid):设置进程的EGID。与seteuid()类似,它允许进程根据需要改变其EGID。
这些调用允许root进程在须要时安全地降低和规复权限。
利用setuid()和setgid()时,必须非常小心,因为不正确的权限管理可能导致安全毛病。通常,一个进程只有在绝对须要时才会放弃root权限,并且在实行不需要特权的操作后立即规复它们。
通过这种方式,可以提高体系的安全性,因为纵然进程被攻破,攻击者也无法获得完备的root权限,只能获得进程当时所拥有的权限。
二、会话
在Linux操作体系中,会话(Session)和进程组(Process Group)密切干系。会话可以被看作是一个或多个进程组的聚集。每个会话都有一个唯一的会话ID(SID)。会话可以由一个进程(通常是一个终端控制进程)创建,并且可以包含多个进程组。进程组是会话的一部分,每个进程组都有自己的进程组ID(PGID),并且所有属于同一会话的进程共享同一个SID。
会话的主要作用
会话的主要作用是管理和控制与终端干系的进程。例如,一个用户在终端上登录后,登录进程创建一个新的会话,并启动一个shell进程。这个shell进程可以启动其他进程,它们通常共享雷同的会话ID。会话还用于处理终端信号,如挂断(SIGHUP)信号,当终端关闭时,会话内的所有进程都会收到这个信号。
因此,会话和进程组之间的关系可以总结如下:
[*]会话可以包含一个或多个进程组。
[*]每个进程组都有一个唯一的进程组ID(PGID)。
[*]每个进程组都属于一个唯一的会话,会话有一个会话ID(SID)。
[*]进程组的组长进程通常会创建一个新的进程组,并且成为该进程组的组长进程。
[*]进程组的组长进程通常也是会话的组长进程。
会话的组长进程通常会创建一个新的会话。会话的组长进程通常也是会话中第一个进程组的组长进程。
总的来说,会话和进程组是Unix/Linux操作体系中用于管理和控制一组干系进程的概念。它们之间的关系允许体系以有效的方式组织和管理进程。
#include <iostream>
#include <string>
#include <unistd.h>
int main()
{
pid_t id = fork();
if (id == 0)
{
while (true)
{
std::cout << "I am a child process :" << getpid() << std::endl;
sleep(1);
}
}
sleep(3);
std::cout << "I am a parent process :" << getpid() << std::endl;
exit(0);
return 0;
}
https://i-blog.csdnimg.cn/direct/551bb10e398c4d45ae2dfef412dced25.png#pic_center
当用户登录到Linux体系时,体系会在 /dev/pts/ 目录下为该用户创建一个或多个伪终端(pseudo-terminal, pty)装备文件。这些装备文件是用于实现会话的逻辑终端。
伪终端是一种特殊的终端,它允许一个进程与另一个进程之间的通信,就像它们是通过一个真实的物理终端进行通信一样。当用户登录时,体系会为该用户创建一个会话,并在这个会话中创建一个伪终端装备文件。这个伪终端装备文件通常位于 /dev/pts/ 目录下,其名称通常与用户的登录名有关。
例如,假如用户名为 user1,体系可能会在 /dev/pts/ 目录下创建一个名为 /dev/pts/1 的伪终端装备文件,用于 user1 的登录会话。这个伪终端装备文件通常与用户的登录终端(例如,终端窗口或终端仿真器)干系联,用于接收用户输入和表现输出。
每个用户登录时,体系都会创建一个伪终端装备文件,用于该用户的会话。这些伪终端装备文件是用于实现会话的逻辑终端,它们允许体系有效地管理多个用户的登录会话。
在一个会话中,可以同时存在多个进程组,但在任何时刻只能有一个前台进程组,而可以有多个背景进程组。
前台进程组: 前台进程组是当前与终端直接交互的进程组。前台进程组中的进程可以从终端获取输入(尺度输入)并接收来自终端的信号(如中断信号 SIGINT 和终端挂断信号 SIGHUP)。
背景进程组: 背景进程组是在不与终端直接交互的情况下运行的进程组。这些进程组不从终端接收输入,并且在尝试从终端读取时会收到SIGTTIN信号。这些信号的默认举动是将进程挂起(阻塞),直到它们被移动到前台。
也就是说:谁是前台进程,谁就可以从尺度输入中获取数据。
我们可以通过setsid()函数创建一个新的会话,并将调用进程设置为该会话的会话组长和进程组组长。调用该函数的进程将是新进程组和新会话中的唯一进程。该函数返回调用进程的新会话ID(也就是其进程ID),假如出现错误则返回-1。该函数常用于创建守护进程。
https://i-blog.csdnimg.cn/direct/c9cba43fc75d45748fc471ed24b356bb.png#pic_center
⚠️调用setsid()创建新会话时,要求调用进程不能是进程组组长。
由于进程组的组长不能调用setsid()来创建新会话,所以假如当前进程是进程组的组长(比如,它是通过fork()创建的,且父进程已经退出),它必须首先创建一个新的子进程,然退却出,如许子进程就不再是进程组的组长,可以调用setsid()成功。
三、控制终端
控制终端(Controlling Terminal)指的是一个进程(特别是前台进程组中的进程)所关联的终端装备。控制终端提供了用户与进程之间交互的接口,通常是文本界面或下令行界面,用于输入下令和接收输出。
主要功能:
[*]用户交互: 控制终端允许用户输入下令和数据,并接收体系或程序的输出。通常情况下,终端程序(如bash或sh)充当中介,担当用户的输入下令,实行相应的程序,并将输出返回给用户。
[*]当用户通过终端登录体系后,获得一个 shell进程,这个终端装备就成为 shell进程的控制终端。控制终端的信息生存在进程控制块(PCB)中,因此由 shell进程启动的所有子进程都会继承这个控制终端。
[*]信号发送: 控制终端可以发送特定的信号给前台进程组中的所有进程。例如,用户可以利用Ctrl+C来发送SIGINT信号以中断前台进程,或利用Ctrl+Z发送SIGTSTP信号以挂起前台进程。
[*]输入/输出控制: 终端装备通常与尺度输入(stdin)、尺度输出(stdout)和尺度错误输出(stderr)流干系联。前台进程可以从尺度输入读取数据,并将输出写入尺度输出和尺度错误。
控制终端的工作原理:
[*] 进程关联: 当用户在终端上登录时,登录进程通常会创建一个新的会话和控制终端。随后启动的shell(如bash)会继承这个终端,并成为控制终端的前台进程组的成员。
[*] 会话和进程组: 控制终端与会话和进程组密切干系。每个会话可以有一个控制终端,而控制终端在任何时刻只能关联一个前台进程组。背景进程组不会与控制终端直接交互,因此无法从中获取输入或接收来自终端的信号。
[*] 失去控制终端: 进程可以通过调用setsid()来离开当前控制终端并创建新的会话。如许,进程将不再与任何终端关联,从而不会受到终端信号的影响,也不会再从终端读取输入。这对于守护进程(daemon)尤其重要,因为它们需要在背景独立于用户终端运行。
一个典范的例子是用户在终端中运行下令:
[*]用户登录到体系,启动一个shell(如bash)。
[*]用户在shell中输入下令,shell将用户输入传递给相应的程序。
[*]程序实行,输出结果通过尺度输出返回到终端,用户可以看到结果。
[*]用户可以利用终端快捷键发送信号,如Ctrl+C中断前台进程。
终端的角色: 终端在会话和进程组之间起着桥梁作用。当用户在终端上运行下令时,终端与前台进程组交互,允许这些进程从尺度输入中读取数据和接收信号。在一个会话中,可以有多个进程组,但只有一个前台进程组与控制终端直接交互。前台进程组中的进程可以从控制终端获取输入和接收信号(如SIGINT),而背景进程组不能。
四、作业
在 shell中,作业控制是通过进程组实现的。用户可以将一组进程作为一个团体进行暂停、规复或终止操作。
作业(Job)是指一个或多个进程的聚集,由用户在交互式shell中启动。作业管理是shell提供的功能,利用户能够控制和监视作业的实行状态。作业管理允许用户在前台和背景之间切换作业、挂起和规复作业以及终止作业。
zyb@myserver:~$ sleep 10000 | sleep 20000 | sleep 30000
其中 & 下面那行是 [作业号]进程组中末了一个进程的pid,我们可以通过jobs下令查看当前会话内的作业
利用jobs下令,可以查看当前会话当中有哪些作业。
https://i-blog.csdnimg.cn/direct/5ae6600e857f4f42ad62ca183a1ce00d.png#pic_center
利用fg下令(foreground),可以将某个作业提至前台运行,假如该作业正在背景运行则直接提至前台运行,假如该作业处于停止状态,则给进程组的每个进程发SIGCONT信号使它继续运行并提至前台。
例如,利用fg 1下令将1号作业提到前台运行。
将一个前台进程放到背景运行可以利用Ctrl+Z,但利用Ctrl+Z后该进程就会处于停止状态(Stopped)。
利用bg下令,可以让某个停止的作业在背景继续运行(Running),本质就是给该作业的进程组的每个进程发SIGCONT信号。
例如,利用bg 1下令让1号作业在背景继续运行。
https://i-blog.csdnimg.cn/direct/205515fcbc324df687dbc426ce790683.png#pic_center
五、会话 作业 进程组 控制终端的关系
会话和控制终端: 一个会话可以与一个控制终端干系联。控制终端为会话中的前台进程组提供用户交互接口。
会话和进程组: 一个会话可以包含多个进程组。进程组可以是前台进程组或背景进程组。
进程组和作业: 在一个交互式shell中,作业通常对应于一个进程组。前台作业与控制终端交互,背景作业则不直接与控制终端交互。
控制终端和作业: 控制终端与前台作业干系联,允许用户通过终端输入控制前台作业。背景作业不能直接从控制终端读取输入,也不能直接担当终端信号。
Session (SID)
├── Control Terminal
├── Process Group 1 (PGID1)
│ ├── Job 1 (Foreground)
│ └── Job 2 (Background)
├── Process Group 2 (PGID2)
│ └── Job 3 (Background)
└── ...
https://i-blog.csdnimg.cn/direct/f06ea998731f4bb8bbcc893ec9bb7060.png#pic_center
六、守护进程
守护进程(Daemon) 是指在背景运行并且独立于所有终端控制之外的进程,UNIX/Linux体系通常由有许多的守护进程,它们实行着各种体系服务和管理的任务。
守护进程通常不与任何用户直接交互,也没有控制终端。它们独立运行,不受用户会话的影响。
守护进程也称为精灵进程。也一定是孤儿进程。
为什么需要有独立于终端之外的进程呢?首先,处于安全性的考虑我们不盼望如许写进程在实行中的信息在任何一个终端上表现。其次,我们也不盼望这些进程被中断所产生的中断信号所打断。末了,固然我们可以通过&将程序转为背景实行,我们偶然也会需要程序能够自动将其转入背景实行,因此,我们需要守护进程。
[*]安全性和隐私:
[*]当进程独立于终端运行时,制止了进程输出信息在终端上表现。这对于处理敏感信息的进程尤其重要,因为不盼望这些信息在任何终端上可见,以防止埋伏的安全毛病或未经授权的访问。
[*]制止中断信号:
[*]终端信号,如SIGINT(由Ctrl+C产生)和SIGHUP(挂断信号),通常会发送给前台进程组。假如一个进程与控制终端断开连接,并以守护进程的情势运行,它就不会收到这些信号,从而制止被用户意外地中断。这对需要长期稳定运行的进程非常重要,比如体系监控工具或服务器程序。
[*]背景运行的需求:
[*]固然用户可以通过在下令后加上&符号来手动将进程转为背景运行,但偶然我们需要进程自动进入背景。这种情况下,进程可以在启动时自行转为背景模式,成为守护进程。这种计划使得应用程序的启动和运行更加自动化和可靠,而不需要用户的手动干预。
[*]资源管理和体系稳定性:
[*]守护进程通常在体系启动时自动启动,并在体系运行期间持续存在。它们可以用于管理体系资源,监控体系状态,处理网络哀求等。这种计划有助于体系的稳定性和可靠性,因为守护进程能够在背景持续实行任务,而不受用户登录会话的影响。
[*]特定任务的持续实行:
[*]某些任务需要持续实行,如日志记录、定期备份、网络服务的监听等。守护进程得当实行这些任务,因为它们可以在背景长时间运行而不间断。
[*]与控制终端无关的操作:
[*]守护进程独立于任何控制终端,可以在体系启动后立即运行,纵然没有用户登录也可以实行任务。这在服务器环境中尤为重要,因为许多服务器任务需要在无人值守的情况下自动运行。
也就是说,守护进程为体系提供了一种机制,使得背景任务能够独立于用户会话和终端环境运行,从而确保体系的安全性、稳定性和可靠性。这种计划对于操作体系和应用程序的平稳运行至关重要。
那么创建守护进程需要进行哪些操作呢?
[*]终止与父进程的关系:
[*]调用fork()创建一个子进程。父进程可以立即退出,如许子进程就成为了孤儿进程,使其在背景运行。通常由init进程(进程号为1)接管。
[*]在新会话中运行:
[*]在子进程中调用setsid()来创建一个新的会话,这将使子进程成为新会话的首进程,同时也是新进程组的组长进程,并与控制终端断开连接。
[*]重设文件权限掩码:
[*]调用umask(0)来重设文件权限掩码,确保守护进程创建文件和目录时不会继承父进程的权限掩码限定。
[*]更改工作目录:
[*]调用chdir("/")将当前工作目录更改为根目录,防止守护进程持有任何特定目录的文件体系。
[*]关闭不需要的文件形貌符:
[*]关闭所有从父进程继承来的不需要的文件形貌符。通常,这涉及到关闭尺度输入、尺度输出和尺度错误(文件形貌符0、1和2)。或将尺度输入、输出和错误重定向到/dev/null,这是一个特殊的装备文件,用于抛弃所有写入它的数据。因为守护进程通常在背景运行,不与用户直接交互,所以不需要这些尺度流。
[*]实行守护进程的任务:
[*]实行守护进程的现实任务。这可能包括日志记录、监控或其他背景任务。
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <signal.h>
int main() {
pid_t pid;
// 1. 创建子进程
pid = fork();
if (pid < 0) {
return -1; // 创建失败
} else if (pid > 0) {
exit(0); // 父进程退出
}
// 2. 创建新的会话
setsid();
// 3. 改变工作目录
chdir("/");
// 4. 重设文件权限掩码
umask(0);
// 5. 关闭不需要的文件描述符
close(0);
close(1);
close(2);
// 6. 执行守护进程的主功能
while (1) {
// 守护进程的主要功能代码
}
return 0;
}
daemon() 函数是操作体系提供的用于将程序变化为守护进程(daemon),从而在背景运行而无需控制终端。
https://i-blog.csdnimg.cn/direct/02225a8dd36443908908a3e71fe82b23.png#pic_center
[*]假如 nochdir 为零,daemon() 将更改进程的当前工作目录到根目录("/");否则,当前工作目录保持稳定。
[*]假如 noclose 为零,daemon() 将重定向尺度输入、尺度输出和尺度错误到 /dev/null;否则,这些文件形貌符保持稳定。
int main()
{
std::cout << "Pid is: " << getpid() << std::endl;
sleep(1);
daemon(0, 0);
// 0: 要更改工作目录
// 0: 输入输出要进行重定向
// daemon(1, 1); // fork father exit
// 执行下面的代码的不是当前进程,而是当前进程的子进程
while (true)
{
std::cout << "hello world" << std::endl;
sleep(1);
}
return 0;
}
https://i-blog.csdnimg.cn/direct/d11e5213684b4229bccedc695bfc5fba.png#pic_center
守护进程是一个独立的会话,且pid与之前输出的不同。
https://i-blog.csdnimg.cn/direct/a2260871854143e390094422e4db0411.png#pic_center
任何进程启动时默认打开0 1 2 文件形貌符。
https://i-blog.csdnimg.cn/direct/492f650e19f24b10aee79298b935d1de.png#pic_center
守护进程与背景进程的区别
[*] 启动方式和目的
[*] 守护进程:
[*]通常由体系初始化脚本启动,如 /etc/init.d/ 或利用现代的 systemd。
[*]计划用于实行体系任务,如网络服务、日志记录、定时任务等,通常在体系启动时开始运行,并在体系关闭时停止。
[*]通常是长期运行的。
[*] 背景进程:
[*]可以通过在下令后添加 & 符号来启动,使得进程在背景运行。
[*]通常用于实行用户任务,如运行脚本、编译程序等,不需要特别的体系资源管理。
[*]可能是暂时的,也可能长期运行,但通常不作为体系服务的一部分。
[*] 会话和进程组
[*] 守护进程:会创建一个新的会话(调用 setsid()),从而成为会话的首进程,不再是任何进程组的成员。如许做是为了确保守护进程不受终端控制。
[*] 背景进程:仍旧是当前会话的一部分,并且属于一个进程组。它只是不再与终端关联,但仍旧可以通过作业控制下令(如 fg、bg)进行管理。
[*] 终端控制
[*] 守护进程:不再与任何终端关联,无法接收来自终端的输入,也不会将输出打印到终端。通常会将尺度输入、尺度输出和尺度错误重定向到 /dev/null 或日志文件。
[*] 背景进程:仍旧可以与终端交互,只是交互被隐藏了。输出可以通过文件形貌符重定向到文件或其他地方,但默认情况下,输出仍旧可以出现在终端。
[*] 独立性
[*] 守护进程:计划上更加独立,不依赖于启动它的终端会话。纵然登录用户退出,守护进程仍旧可以继续运行。
[*] 背景进程:假如启动它的终端会话关闭,背景进程可能会被发送 SIGHUP 信号,导致其终止。背景进程通常与特定的用户会话关联。
总的来说,守护进程是专门为体系服务计划的,它们在体系启动时启动,在体系关闭时停止,并且通常与体系紧麋集成。而背景进程更多是用户层面的操作,用于在不阻塞终端的情况下运行任务。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页:
[1]