1. 进程组1.1 什么是进程组之前我们提到了进程的概念 其实每一个进程除了有一个进程ID(PID)之外还属于一个进程组。进程组是一个或者多个进程的集合一个进程组可以包含多个进程。 每一个进程组也有一个唯一的进程组ID(PGID) 并且这个PGID类似于进程ID同样是一个正整数可以存放在pid_t数据类型中。如下代码代码语言javascriptAI代码解释#include iostream #include unistd.h int main() { while(true) { std::cout hello server std::endl; sleep(1); } return 0; }编译运行后通过ps指令查看在这里插入图片描述注意代码语言javascriptAI代码解释# -e 选项表⽰every的意思 表示输出每一个进程信息 # -o 选项以逗号操作符,作为定界符, 可以指定要输出的列可以看到进程组pgid和进程pid相等那是因为单个进程自己独立成为进程组1.2 组长进程每一个进程组都有一个组长进程。 组长进程的ID等于其进程ID。我们可以通过ps命令看到组长进程的现象代码语言javascriptAI代码解释ltxMy-Xshell-8-Pro-Max-Ultra:~$ ps -o pid,pgid,ppid,comm | cat PID PGID PPID COMMAND 634309 634309 634308 bash 634381 634381 634309 ps 634382 634381 634309 cat从结果上看ps进程的PID和PGID相同 那也就是说明ps进程是该进程组的组长进程 该进程组包括ps和cat两个进程。进程组组长的作用 进程组组长可以创建一个进程组或者创建该组中的进程进程组的生命周期 从进程组创建开始到其中最后一个进程离开为止。注意 只要某个进程组中有一个进程存在 则该进程组就存在 这与其组长进程是否已经终止无关。此时的进程组被称为 ”孤儿进程组“2. 会话2.1 什么是会话刚刚我们谈到了进程组的概念 那么会话又是什么呢 会话其实和进程组息息相关 会话可以看成是一个或多个进程组的集合 一个会话可以包含多个进程组。每一个会话也有一个会话ID(SID)在这里插入图片描述通常我们都是使用管道将几个进程编成一个进程组。 如上图的进程组2和进程组3可能是由下列命令形成的代码语言javascriptAI代码解释ltxMy-Xshell-8-Pro-Max-Ultra:~$ sleep 1000 | sleep 2000 | sleep 3000 [1] 638455 # 表⽰将进程组放在后台执⾏我们通过ps指令查看代码语言javascriptAI代码解释# 过滤sleep相关的进程信息 ltxMy-Xshell-8-Pro-Max-Ultra:~$ ps axj | head -n1 ps axj | grep sleep | grep -v grep PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND 634309 638453 638453 634309 pts/0 638653 S 1004 0:00 sleep 1000 634309 638454 638453 634309 pts/0 638653 S 1004 0:00 sleep 2000 634309 638455 638453 634309 pts/0 638653 S 1004 0:00 sleep 3000 # a选项表⽰不仅列当前用户的进程也列出所有其他用户的进程 # x选项表⽰不仅列有控制终端的进程也列出所有⽆控制终端的进程 # j选项表⽰列出与作业控制相关的信息 作业控制后续会讲 # grep的-v选项表⽰反向过滤 即不过滤带有grep字段相关的进程从上述结果来看3个进程对应的PGID相同 即属于同一个进程组。同样3个进程的SID也相同因为属于同一个进程组肯定也属于同一个会话2.2 如何创建会话可以调用setseid函数来创建一个会话 前提是调用进程不能是一个进程组的组长。代码语言javascriptAI代码解释#include unistd.h /* *功能创建会话 *返回值创建成功返回SID, 失败返回-1 */ pid_t setsid(void);该接口调用之后会发生调用进程会变成新会话的会话首进程。此时新会话中只有唯一的一个进程调用进程会变成进程组组长。新进程组ID就是当前调用进程ID该进程没有控制终端。如果在调用setsid之前该进程存在控制终端 则调用之后会切断联系需要注意的是 这个接口如果调用进程原来是进程组组长则会报错为了避免这种情况 我们通常的使用方法是先调用fork创建子进程父进程终止子进程继续执行因为子进程会继承父进程的进程组ID而进程ID则是新分配的也就意味此时子进程不可能是进程组长就不会出现错误的情况。2.3 会话ID(SID)上边我们提到了会话ID 那么会话ID是什么呢 我们可以先说一下会话首进程 会话首进程是具有唯一进程ID的单个进程那么我们可以将会话首进程的进程ID当做是会话ID。注意会话ID在有些地方也被称为会话首进程的进程组ID 因为会话首进程总是一个进程组的组长进程 所以两者是等价的。下面我们来详细介绍一下会话首进程会话首进程(session leader)是创建该会话的进程或者更准确地说它是会话中第一个进程并且该进程的进程ID就是会话IDSID。在Unix/Linux系统中一个会话是一个或多个进程组的集合通常与一个终端控制终端相关联。会话首进程的关键特性创建会话通过调用setsid()一个进程会创建一个新会话并成为该会话的首进程。调用setsid()的进程不能是进程组组长因此通常先fork一个子进程然后让子进程调用setsid()。会话IDSID会话ID就是会话首进程的进程IDPID。因此会话首进程的PID和SID是相同的。进程组组长会话首进程同时也是一个进程组的组长其进程组IDPGID等于它的PID也就是SID。控制终端一个会话可以有一个控制终端通常是用户登录时使用的终端设备。会话首进程通常是打开控制终端的进程但并不是必须的。不过如果会话有控制终端那么会话首进程通常是与控制终端建立连接的进程。终端断开信号当控制终端断开连接例如终端窗口关闭时会话首进程会收到SIGHUP信号。通常会话首进程会处理这个信号比如终止会话中的所有进程。会话首进程的作用管理会话会话首进程通常用于管理整个会话的生命周期。例如当用户注销时系统会向会话首进程发送SIGHUP信号会话首进程通常会将这个信号转发给会话中的所有进程组然后终止会话。作业控制在shell中每个作业一个命令或管道序列通常是一个进程组而shell本身是会话首进程。shell使用会话和进程组来管理作业控制比如前后台作业的切换。示例说明在一个典型的登录环境中用户登录时系统启动一个登录shell该shell成为新会话的首进程。它的PID就是会话ID。当在这个shell中运行命令时每个命令可能是多个进程通过管道连接会被放入一个新的进程组。但所有这些进程组都属于同一个会话。如果shell退出会话首进程终止那么系统会向该会话中的所有进程发送SIGHUP信号导致它们终止除非它们已经忽略了SIGHUP信号或者已经变成了守护进程。3. 控制终端先说一下什么是控制终端在UNIX系统中用户通过终端登录系统后得到一个Shell进程这个终端成为Shell进程的控制终端。控制终端是保存在PCB中的信息我们知道fork进程会复制PCB中的信息因此由Shell进程启动的其它进程的控制终端也是这个终端。默认情况下没有重定向每个进程的标准输入、标准输出和标准错误都指向控制终端进程从标准输入读也就是读用户的键盘输入进程往标准输出或标准错误输出写也就是输出到显示器上。另外会话、进程组以及控制终端还有一些其他的关系我们在下边详细介绍 一下一个会话可以有一个控制终端通常会话首进程打开一个终端终端设备或伪终端设备后该终端就成为该会话的控制终端。建立与控制终端连接的会话首进程被称为控制进程。一个会话中的几个进程组可被分成一个前台进程组以及一个或者多个后台进程组。如果一个会话有一个控制终端则它有一个前台进程组会话中的其他进程组则为后台进程组。无论何时进入终端的中断键ctrlc或退出键ctrl\就会将中断信号发送给前台进程组的所有进程。如果终端接口检测到调制解调器或网络已经断开则将挂断信号发送给控制进程会话首进程。这些特性的关系如下图所示在这里插入图片描述控制终端与整个会话关联而不是仅与前台进程组关联。但是只有前台进程组中的进程可以从控制终端读取输入并向其输出同时接收终端产生的信号如SIGINT、SIGQUIT、SIGTSTP等。会话首进程是创建该会话的进程它通常是一个shell进程。会话首进程不一定在前台进程组中。实际上当我们在shell中启动一个前台作业时shell会话首进程会将自己设置为后台进程组而将新启动的进程组设置为前台进程组。所以当有一个前台进程时控制终端与前台进程组关联会话首进程通常是shell则在后台进程组中。但是会话首进程仍然与控制终端关联因为它是会话的一部分只是它不接收终端输入和终端产生的信号因为只有前台进程组接收这些。举个例子 假设我们有一个shell比如bash它是会话首进程。我们在shell中运行一个前台命令比如vim。此时shell会创建一个新的进程组将vim放入该进程组并将该进程组设置为前台进程组。然后shell自身所在的进程组就成为后台进程组。控制终端仍然与整个会话关联但只有vim前台进程组可以接收终端输入和信号。如果按下CtrlCSIGINT会发送给vim而不会发送给shell因为shell在后台进程组。但是如果控制终端断开比如网络断开则挂断信号SIGHUP会发送给会话首进程即shell然后shell通常会将SIGHUP发送给所有子进程包括vim然后自己退出。4. 作业控制4.1 什么是作业(job)和作业控制(Job Control)作业是针对用户来讲用户完成某项任务而启动的进程一个作业既可以只包含一个进程也可以包含多个进程进程之间互相协作完成任务 通常是一个进程管道。 Shell分前后台来控制的不是进程而是作业或者进程组。一个前台作业可以由多个进程组成一个后台作业也可以由多个进程组成Shell可以同时运行一个前台作业和任意多个后台作业这称为作业控制。例如下列命令就是一个作业它包括两个命令在执行时Shell将在前台启动由两个进程组成的作业代码语言javascriptAI代码解释ltxMy-Xshell-8-Pro-Max-Ultra:~$ cat /etc/filesystems | head -n 5 xfs ext4 ext3 ext2 nodev proc4.2 作业号放在后台执行的程序或命令称为后台命令可以在命令的后面加上 符号从而让Shell识别这是一个后台命令后台命令不用等待该命令执行完成就可立即接收新的命令另外后台进程执行完后会返回一个作业号以及一个进程号PID。例如下面的命令在后台启动了一个作业该作业由两个进程组成两个进程都在后台运行:代码语言javascriptAI代码解释ltxMy-Xshell-8-Pro-Max-Ultra:~$ cat /etc/filesystems | grep ext [1] 2202 ext4 ext3 ext2 # 按下回⻋ [1] 完成 cat /etc/filesystems | grep --colorauto ext第一行表示作业号和进程ID可以看到作业号是1进程ID是2202第3-4行表示该程序运行的结果过滤 /etc/filesystems 有关 ext 的内容第6行分别表示作业号、默认作业、作业状态以及所执行的命令关于默认作业对于一个用户来说只能有一个默认作业 同时也只能有一个即将成为默认作业的作业 - 当默认作业退出后该作业会成为默认作业。 : 表示该作业号是默认作业- 表示该作业即将成为默认作业无符号 表示其他作业在Shell中当我们启动多个后台作业时Shell会为每个作业分配一个作业号并标记其中一个为默认作业通常是最近启动的作业或最近被操作的作业。常见的作业状态如下表所示在这里插入图片描述4.3 作业的挂起与切回作业挂起我们在执行某个作业时可以通过 CtrlZ 键将该作业挂起然后Shell会显示相关的作业号、状态以及所执行的命令信息。例如我们运行一个死循环的程序 通过 CtrlZ 将该作业挂起 观察一下对应的作业状态代码语言javascriptAI代码解释#include iostream #include unistd.h int main() { while(true) { std::cout hello server std::endl; sleep(1); } return 0; }下面我运行这个程序 通过 CtrlZ 将该作业挂起代码语言javascriptAI代码解释ltxMy-Xshell-8-Pro-Max-Ultra:~/gitLinux/Linux_network/Daemon/test$ ./proc hello server hello server ^Z #键⼊Ctrl Z观察现象 # 结果依次对应作业号 默认作业 作业状态 运⾏程序信息 [1] Stopped ./proc可以发现通过 CtrlZ 将作业挂起 该作业状态已经变为了停止状态作业切回如果想将挂起的作业切回可以通过 fg 命令 fg 后面可以跟 作业号 或 作业的命令名称 。如果参数缺省则会默认将作业号为1的作业切到前台来执行若当前系统只有一个作业在后台进行则可以直接使用fg命令不带参数直接切回。 具体的参数参考如下在这里插入图片描述例如我们把刚刚挂起来的 ./test 作业切回到前台代码语言javascriptAI代码解释ltxMy-Xshell-8-Pro-Max-Ultra:~/gitLinux/Linux_network/Daemon/test$ fg %% ./proc hello server hello server hello server hello server hello server ^C运行结果为开始无限循环打印 hello server可以发现该作业已经切换到前台了。注意 当通过 fg 命令切回作业时若没有指定作业参数此时会将默认作业切到前台执行即带有“”的作业号的作业4.4 查看后台执行或挂起的作业我们可以直接通过输入 jobs 命令查看用户当前后台执行或挂起的作业参数 -l 则显示作业的详细信息参数 -p 则只显示作业的PID参数 -r 只显示运行中的作业参数 -s 只显示停止的作业参数 -n 显示状态变化的作业 (只显示自上次通知后状态发生变化的作业)例如 我们先在后台及前台运行两个作业并将前台作业挂起 来用 jobs 命令查看作业相关的信息代码语言javascriptAI代码解释ltxMy-Xshell-8-Pro-Max-Ultra:~/gitLinux/Linux_network/Daemon/test$ sleep 300 [1] 800147 ltxMy-Xshell-8-Pro-Max-Ultra:~/gitLinux/Linux_network/Daemon/test$ ./proc hello server hello server ^Z [2] Stopped ./proc ltxMy-Xshell-8-Pro-Max-Ultra:~/gitLinux/Linux_network/Daemon/test$ jobs -l [1]- 800147 Running sleep 300 [2] 800188 Stopped ./proc注意 jobs 命令是Shell内置的作业控制功能作用范围仅限于当前Shell会话。4.5 作业控制相关的信号上面我们提到了键入 Ctrl Z 可以将前台作业挂起实际上是将 STGTSTP 信号发送至前台进程组作业中的所有进程 后台进程组中的作业不受影响。 在unix系统中 存在3个特殊字符可以使得终端驱动程序产生信号 并将信号发送至前台进程组作业 它们分别是Ctrl C 中断字符 会产生 SIGINT 信号Ctrl \ 退出字符 会产生 SIGQUIT 信号Ctrl Z 挂起字符 会产生 STGTSTP 信号终端的I/O(即标准输入和标准输出)和终端产生的信号总是从前台进程组作业连接打破实际终端。我们可以通过下体来看到作业控制的功能在这里插入图片描述5. 守护进程守护进程Daemon Process是计算机操作系统中一种在后台运行的特殊进程它独立于控制终端通常用于执行周期性任务或长期运行的服务。这类进程在系统启动时自动运行直到系统关闭才终止。主要特点脱离终端控制守护进程在后台运行不与任何用户终端直接交互避免被终端信号中断。以 root 权限运行通常需要特殊权限如使用特定端口或资源因此多以 root 用户身份启动。孤儿进程特性其父进程在 fork() 创建子进程后立即退出使守护进程被 init 进程PID1接管成为孤儿进程。常驻内存生命周期长从系统启动持续运行到关机提供持续服务如日志记录、网络服务等。传统的守护进程创建代码语言javascriptAI代码解释#pragma once #include iostream #include cstdlib #include signal.h #include unistd.h #include fcntl.h #include sys/types.h #include sys/stat.h const char *root /; const char *dev_null /dev/null; void Daemon(bool ischdir, bool isclose) { // 1. 忽略可能引起程序异常退出的信号 signal(SIGCHLD, SIG_IGN); signal(SIGPIPE, SIG_IGN); // 2. 让自己不要成为组长 if (fork() 0) exit(0); // 3. 设置让自己成为一个新的会话 后面的代码其实是子进程在走 setsid(); // 4. 每一个进程都有自己的CWD是否将当前进程的CWD更改成为 / 根目录 if (ischdir) chdir(root); // 5. 已经变成守护进程啦不需要和用户的输入输出错误进行关联了 if (isclose) { close(0); close(1); close(2); } else { // 这里一般建议就用这种 int fd open(dev_null, O_RDWR); if (fd 0) { dup2(fd, 0); dup2(fd, 1); dup2(fd, 2); close(fd); } } }常见的系统守护进程守护进程功能管理命令sshdSSH服务器systemctl status sshdcrond定时任务systemctl status crondrsyslogd系统日志systemctl status rsysloghttpd/nginxWeb服务器systemctl status nginxmysqld数据库systemctl status mysql6. 守护进程 vs 后台进程守护进程Daemon Process和后台进程Background Process都是在后台运行的进程但它们在设计目标、运行方式和生命周期管理上存在本质区别。守护进程Daemon Process守护进程是运行在后台的一种特殊进程它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。守护进程通常随着系统的启动而启动并一直运行直到系统关闭。它们不受用户登录和注销的影响。守护进程的父进程是init进程PID1因为它是由init进程收养的。守护进程没有控制终端它的TTY是所以它不会与用户交互。常见的守护进程有sshd、httpd、mysqld等。后台进程Background Process后台进程也是运行在后台的进程但是它通常是由用户通过Shell如bash在终端中启动的并在后台运行。后台进程与终端有关联虽然它在后台运行但它仍然属于当前终端会话。如果终端关闭那么后台进程会收到SIGHUP信号默认会终止。后台进程的父进程是启动它的Shell进程。用户可以通过fg命令将后台进程切换到前台也可以通过bg命令将暂停的进程放到后台运行。后台进程通常用于让用户能够继续在同一个终端中工作而不必等待进程结束。关键差异详解与会话和终端的关系后台进程通过 或 CtrlZ bg 命令创建它仍然是当前登录会话Session‍ 和进程组Process Group‍ 的一部分。这意味着当用户注销或启动它的终端关闭时该进程会收到 SIGHUP 信号并被终止。守护进程通过调用 setsid() 等系统调用创建了一个新的独立会话并摆脱了与任何控制终端的关联。因此终端关闭或用户注销不会影响其运行它由 init 进程PID 1直接接管。