【在Linux世界中追寻伟大的One Piece】进程间关系与守护进程 ...

打印 上一主题 下一主题

主题 863|帖子 863|积分 2589

目录
1 -> 进程组
1.1 -> 什么是进程组
1.2 -> 组长进程
2 -> 会话
2.1 -> 什么是会话
2.2 -> 如何创建会话
2.3 -> 会话ID(SID)
3 -> 控制终端
4 -> 作业控制
4.1 -> 什么是作业(job)和作业控制(Job Control)
4.2 -> 作业号
4.3 -> 作业状态
4.4 -> 作业的挂起与切回
4.4.1 -> 作业挂起
4.4.2 -> 作业切回
4.5 -> 检察后台实行或挂起的作业
4.6 -> 作业控制相干的信号
5 -> 守护进程
6 -> 如何将服务守护进程化


1 -> 进程组

1.1 -> 什么是进程组

着实每一个进程除了有一个进程ID(PID)之外,还属于一个进程组。进程组是一个或者多个进程的聚集, 一个进程组可以包含多个进程。 每一个进程组也有一个唯一的进程组ID(PGID), 并且这个 PGID类似于进程ID, 同样是一个正整数, 可以存放在pid_t数据类型中。
      $ ps -eo pid,pgid,ppid,comm | grep test          #结果如下        PID        PGID        PPID COMMAND        2830        2830 2259 test          # -e 选项体现 every 的意思,体现输出每一个进程信息        # -o 选项以逗号操纵符(,)作为定界符,可以指定要输出的列    1.2 -> 组长进程

每一个进程组都有一个组长进程。 组长进程的ID便是其进程ID。我们可以通过ps命令看到组长进程的现象:
      [node@localhost code]$ ps -o pid,pgid,ppid,comm | cat          # 输出结果        PID        PGID        PPID COMMAND        2806        2806        2805 bash        2880        2880        2806 ps        2881        2880        2806 cat    从结果上看ps进程的PID和PGID雷同, 那也就是说明ps进程是该进程组的组长进程, 该进程组包罗ps和cat两个进程。


  • 进程组组长的作用: 进程组组长可以创建一个进程组或者创建该组中的进程。
  • 进程组的生命周期: 从进程组创建开始到此中末了一个进程离开为止。注意:主要某个进程组中有一个进程存在, 则该进程组就存在, 这与其组长进程是否已经停止无关。
2 -> 会话

2.1 -> 什么是会话

会话着实和进程组息息相干,会话可以当作是一个或多个进程组的聚集, 一个会话可以包含多个进程组。每一个会话也有一个会话ID(SID)。

通常我们都是使用管道将几个进程编成一个进程组。 如上图的进程组2和进程组3可能是由下列命令形成的:
      [node@localhost code]$ proc2 | proc3 &        [node@localhost code]$ proc4 | proc5 | proc6 &        # &体现将进程组放在后台实行    举一个例子观察一下这个现象:
      # 用管道和 sleep 构成一个进程组放在后台运行        [node@localhost code]$ sleep 100 | sleep 200 | sleep 300 &          # 检察 ps 命令打出来的列形貌信息        [node@localhost code]$ ps axj | head -n1             # 过滤 sleep 相干的进程信息           [node@localhost code]$ ps axj | grep sleep | grep -v grep              # a 选项体现不仅列当前⽤户的进程,也列出全部其他⽤户的进程           # x 选项体现不仅列有控制终端的进程,也列出全部⽆控制终端的进程           # j 选项体现列出与作业控制相干的信息, 作业控制后续会讲           # grep 的-v 选项体现反向过滤, 即不外滤带有 grep 字段相干的进程              # 结果如下           PPID           PID           PGID           SID TTY           TPGID STAT           UID           TIME           COMMAND           2806           4223           4223           2780 pts/2           4229 S           1000           0:00 sleep           100           2806           4224           4223           2780 pts/2           4229 S           1000           0:00 sleep           200           2806           4225           4223           2780 pts/2           4229 S           1000           0:00 sleep           300       从上述结果来看3个进程对应的PGID雷同, 即属于同一个进程组。
2.2 -> 如何创建会话

可以调用setseid函数来创建一个会话, 条件是调用进程不能是一个进程组的组长。
  1. #define _CRT_SECURE_NO_WARNINGS 1
  2. #include <unistd.h>
  3. /*
  4. *功能:创建会话
  5. *返回值:创建成功返回 SID, 失败返回-1
  6. */
  7. pid_t setsid(void);
复制代码
该接口调用之后会发生:


  • 调用进程会变成新会话的会话首进程。 此时, 新会话中只有唯一的一个进程。
  • 调用进程会变成进程组组长。 新进程组ID就是当前调用进程ID。
  • 该进程没有控制终端。 如果在调用setsid之前该进程存在控制终端, 则调用之后会切断接洽。
需要注意的是: 这个接口如果调用进程原来是进程组组长, 则会报错, 为了克制这种情况, 我们通常的使用方法是先调用fork创建子进程, 父进程停止, 子进程继续实行, 由于子进程会继承父进程的进程组ID, 而进程ID则是新分配的, 就不会出现错误的情况。
2.3 -> 会话ID(SID)

上边我们提到了会话ID, 那么会话ID是什么呢? 我们可以先说一下会话首进程, 会话首进程是具有唯一进程ID的单个进程, 那么我们可以将会话首进程的进程ID当做是会话ID。注意:会话ID在有些地方也被称为 会话首进程的进程组ID, 由于会话首进程总是一个进程组的组长进程, 所以两者是等价的。
3 -> 控制终端

在UNIX系统中,用户通过终端登录系统后得到一个Shell进程,这个终端成为Shell进程的控制终端。控制终端是保存在PCB中的信息,我们知道fork进程会复制PCB中的信息,因此由Shell进程启动的其它进程的控制终端也是这个终端。默认情况下没有重定向,每个进程的标准输入、标准输出和标准错误都指向控制终端,进程从标准输入读也就是读用户的键盘输入,进程往标准输出或标准错误输出写也就是输出到显示器上。另外会话、进程组以及控制终端还有一些其他的关系,我们在下边具体介绍一下:


  • 一个会话可以有一个控制终端,通常会话首进程打开一个终端(终端设备或伪终端设备)后,该终端就成为该会话的控制终端。
  • 建立与控制终端毗连的会话首进程被称为控制进程
  • 一个会话中的几个进程组可被分成一个前台进程组以及一个或者多个后台进程组
  • 如果一个会话有一个控制终端,则它有一个前台进程组,会话中的其他进程组则为后台进程组。
  • 无论何时进入终端的中断键(ctrl+c)退出键(ctrl+\),就会将中断信号发送给前台进程组的全部进程。
  • 如果终端接口检测到调制解调器(或网络)已经断开,则将挂断信号发送给控制进程(会话首进程)。
这些特性的关系如下图所示:

4 -> 作业控制

4.1 -> 什么是作业(job)和作业控制(Job Control)

作业是针对用户来讲,用户完成某项任务而启动的进程,一个作业既可以只包含一个进程,也可以包含多个进程,进程之间相互协作完成任务, 通常是一个进程管道。
Shell分前后台来控制的不是进程而是作业 或者进程组。一个前台作业可以由多个进程构成,一个后台作业也可以由多个进程构成,Shell可以同时运⾏一个前台作业和任意多个后台作业,这称为作业控制
比方下列命令就是一个作业,它包罗两个命令,在执⾏时Shell将在前台启动由两个进程构成的作业。
   [node@localhost code]$ cat /etc/filesystems | head -n 5
  运行结果如下:
   xfs
ext4
ext3
ext2
nodev proc

  4.2 -> 作业号

放在后台执⾏的步伐或命令称为后台命令,可以在命令的背面加上&符号从而让Shell识别这是一个后台命令,后台命令不用等待该命令执⾏完成,就可立即接收新的命令,另外后台进程实行完后会返回一个作业号以及一个进程号(PID)。
比方下面的命令在后台启动了一个作业, 该作业由两个进程构成, 两个进程都在后台运⾏:
      [node@localhost code]$ cat /etc/filesystems | grep ext &    运行结果如下:
      [1] 2202        ext4        ext3        ext2        # 按下回车        [1]+ 完成        cat /etc/filesystems | grep --        color=auto ext   

  • 第一⾏体现作业号和进程ID, 可以看到作业号是1, 进程ID是2202。
  • 第3-4⾏体现该步伐运⾏的结果, 过滤/etc/filesystems有关ext的内容。
  • 第6行分别体现作业号、默认作业、作业状态以及所执⾏的命令关于默认作业:对于一个用户来说,只能有一个默认作业(+),同时也只能有一个即将成为默认作业的作业(-),当默认作业退出后,该作业会成为默认作业。

    • + : 体现该作业号是默认作业。
    • -:体现该作业即将成为默认作业。
    • 无符号: 体现其他作业。

4.3 -> 作业状态

常见的作业状态如下表:
作业状态含义
正在运行【Running】后台作业(&),体现正在实行
完成【Done】作业已完成,返回的状态码为0
完成并退出【Done(code)】作业已完成并退出,返回的状态码为非0
已克制【Stopped】前台作业,当前被Ctrl+Z挂起
已停止【Terminated】作业被停止
4.4 -> 作业的挂起与切回

4.4.1 -> 作业挂起

我们在执⾏某个作业时,可以通过Ctrl+Z键将该作业挂起,然后Shell会显示相干的作业号、状态以及所执⾏的命令信息。
比方我们运⾏一个死循环的步伐, 通过Ctrl+Z将该作业挂起, 观察一下对应的作业状态:
  1. #define _CRT_SECURE_NO_WARNINGS 1
  2. #include <stdio.h>
  3. int main()
  4. {
  5.         while (1)
  6.         {
  7.                 printf("hello\n");
  8.         }
  9.         return 0;
  10. }
复制代码
下面运⾏这个步伐, 通过Ctrl+Z将该作业挂起:
      # 运行可实行步伐        [node@localhost code]$ ./test        #键入 Ctrl + Z 观察现象    运行结果如下:
      # 结果依次对应作业号 默认作业 作业状态 运行步伐信息           [1]+ 已克制           ./test7       可以发现通过Ctrl+Z将作业挂起, 该作业状态已经变为了克制状态。
4.4.2 -> 作业切回

如果想将挂起的作业切回,可以通过fg命令,fg背面可以跟作业号或作业的命令名称。如果参数缺省则会默认将作业号为1的作业切到前台来执⾏,若当前系统只有一个作业在后台进⾏,则可以直接使用fg命令不带参数直接切回。 具体的参数参考如下:
参数含义
%nn为正整数,体现作业号
%string以字符串开头的命令所对应的作业
%?string包含字符串的命令所对应的作业
%+或%%迩来提交的一个作业
%-倒数第二个提交的作业
比方我们把刚刚挂起来的./test作业切回到前台:
   [node@localhost code]$ fg %%
  运⾏结果为开始无穷循环打印hello, 可以发现该作业已经切换到前台了。
注意: 当通过fg命令切回作业时,若没有指定作业参数,此时会将默认作业切到前台实行,即带有"+"的作业号的作业。
4.5 -> 检察后台实行或挂起的作业

我们可以直接通过输入jobs命令检察本用户当前后台执⾏或挂起的作业。


  • 参数-l 则显示作业的具体信息。
  • 参数-p 则只显示作业的PID。
比方, 我们先在后台及前台运⾏两个作业, 并将前台作业挂起, 来用jobs命令检察作业相干的信息:
   # 在后台运行一个作业 sleep
[node@localhost code]$ sleep 300 &

  
# 运行刚才的死循环可实行步伐
[node@localhost code]$ ./test

  
# 键入 Ctrl + Z 挂起作业
# 使用 jobs 命令检察后台及挂起的作业
[node@localhost code]$ jobs -l

  运行结果如下:
   # 结果依次对应作业号 默认作业 作业状态 运行步伐信息
[1]- 2265 运行中 sleep 300 &
[2]+ 2267 克制 ./test7

  4.6 -> 作业控制相干的信号

上面我们提到了键Ctrl + Z可以将前台作业挂起,现实上是将STGTSTP信号发送至前台进程组作业中的全部进程, 后台进程组中的作业不受影响。 在unix系统中, 存在3个特殊字符可以使得终端驱动步伐产生信号, 并将信号发送至前台进程组作业, 它们分别是:


  • Ctrl + C:中断字符,会产生SIGINT信号。
  • Ctrl + \:退出字符,会产生SIGQUIT信号。
  • Ctrl + Z:挂起字符,会产生STGTSTP信号。
终端的I/O(即标准输入和标准输出)和终端产生的信号总是从前台进程组作业毗连打破现实终端。可以通过下图看到作业控制的功能:

5 -> 守护进程

  1. #pragma once
  2. #include <iostream>
  3. #include <cstdlib>
  4. #include <signal.h>
  5. #include <unistd.h>
  6. #include <fcntl.h>
  7. #include <sys/types.h>
  8. #include <sys/stat.h>
  9. const char* root = "/";
  10. const char* dev_null = "/dev/null";
  11. void Daemon(bool ischdir, bool isclose)
  12. {
  13.         // 1. 忽略可能引起程序异常退出的信号
  14.         signal(SIGCHLD, SIG_IGN);
  15.         signal(SIGPIPE, SIG_IGN);
  16.         // 2. 让自己不要成为组长
  17.         if (fork() > 0)
  18.                 exit(0);
  19.         // 3. 设置让自己成为一个新的会话, 后面的代码其实是子进程在走setsid();
  20.         // 4. 每一个进程都有自己的 CWD,是否将当前进程的 CWD 更改成为 /根目录
  21.         if (ischdir)
  22.                 chdir(root);
  23.         // 5. 已经变成守护进程了,不需要和用户的输入输出,错误进行关联了
  24.         if (isclose)
  25.         {
  26.                 close(0);
  27.                 close(1);
  28.                 close(2);
  29.         }
  30.         else
  31.         {
  32.                 // 这里一般建议就用这种
  33.                 int fd = open(dev_null, O_RDWR);
  34.                 if (fd > 0)
  35.                 {
  36.                         dup2(fd, 0);
  37.                         dup2(fd, 1);
  38.                         dup2(fd, 2);
  39.                         close(fd);
  40.                 }
  41.         }
  42. }
复制代码
6 -> 如何将服务守护进程化

  1. // ./server port
  2. int main(int argc, char* argv[])
  3. {
  4.         if (argc != 2)
  5.         {
  6.                 std::cout << "Usage : " << argv[0] << " port" <<
  7.                         std::endl;
  8.                 return 0;
  9.         }
  10.         uint16_t localport = std::stoi(argv[1]);
  11.         Daemon(false, false);
  12.         std::unique_ptr<TcpServer> svr(new TcpServer(localport,
  13.                 HandlerRequest));
  14.         svr->Loop();
  15.         return 0;
  16. }
复制代码



感谢各位大佬支持!!!

互三啦!!!




免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

宝塔山

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表