• Linux 进程层次分析


    Linux 进程组

    每个进程都有一个进程组号 (PGID)

    • 进程组:一个或多个进程的集合 (集合中的进程并不孤立)
    • 进程组中的进程通常存在父子关系,兄弟关系,或功能相近

    进程组可方便进程管理 (如:同时杀死多个进程,发送一个信号给多个进程)

    • 每个进程必定属于一个进程组,也只能属于一个进程组
    • 进程除了 PID 外,还有 PGID (唯一,但可变)
    • 每个进程组有一个进程组长,进程组长的 PID 和 PGID 相同

    pid_t getpgrp(void);  // 获取当前进程的组标识

    pid_t getpgid(pid_t pid);  // 获取指定进程的组标识

    int setpgid(pid_t pid, pid_t pgid);  // 设置进程的组标识

    • pid == pgid,将 pid 指定的进程设为组长
    • pid == 0,设置当前进程的组标识
    • pgid == 0,则将 pid 作为组标识

    进程组示例程序

    pgid_j.c

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. int main(void)
    7. {
    8. int pid = 0;
    9. int i = 0;
    10. printf("parent = %d, ppid = %d, pgid = %d\n", getpid(), getppid(), getpgrp());
    11. while( i < 5 )
    12. {
    13. if( (pid = fork()) > 0 )
    14. {
    15. printf("new: %d\n", pid);
    16. }
    17. else if( pid == 0 )
    18. {
    19. sleep(1);
    20. printf("child = %d, ppid = %d, pgid = %d\n", getpid(), getppid(), getpgrp());
    21. sleep(60);
    22. printf("last -- pgid = %d\n", getpgrp());
    23. break;
    24. }
    25. else
    26. {
    27. printf("fork error...\n");
    28. }
    29. i++;
    30. }
    31. if( pid )
    32. {
    33. sleep(60);
    34. }
    35. return 0;
    36. }

    这个程序会创建出5个子进程,打印父进程和5个子进程的 pid 和 pgid

    通过打印可以看出父进程的 pid 和 pgid 相同,为 13297,说明父进程是进程组组长,5 个子进程的 pgid 也是 13297,说明父进程和 5 个子进程为同一个进程组

    我们使用 kill 命令将 结束信号发送给 13297 这个进程组,这个进程组的所有进程都被干掉了

    我们将 13471 这个父进程 kill 掉,发现 5 个子进程还在运行,说明进程组组长运行结束并不会影响其子进程的运行 

    深入理解进程组

    进程组长终止,进程组依然存在 (进程组长仅用于创建新进程组)

    父进程创建子进程后立即通过 setpgid() 改变其组标识 (PGID)

    同时,子进程也需要通过 setpgid() 改变自身组标识 (PGID)

    子进程调用 exec()

    • 父进程无法通过 setpgid() 改变其组标识 (PGID)
    • 只能自身通过 setpgid() 改变其组标识 (PGID)

    进程组标识设置技巧

    进程组实验

    pgid_a.c

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. int main(void)
    7. {
    8. int pid = 0;
    9. printf("parrent = %d, ppid = %d, pgid = %d\n", getpid(), getppid(), getpgrp());
    10. if( (pid = fork()) > 0 )
    11. {
    12. int r = setpgid(pid, pid);
    13. printf("new: %d, r = %d\n", pid, r);
    14. }
    15. else if( pid == 0 )
    16. {
    17. setpgid(pid, pid);
    18. sleep(1);
    19. printf("child = %d, ppid = %d, pgid = %d\n", getpid(), getppid(), getpgrp());
    20. }
    21. else
    22. {
    23. printf("fork error...\n");
    24. }
    25. sleep(60);
    26. return 0;
    27. }

    这个程序用于改变子进程的子进程的 pgid,将子进程改变为进程组组长

    第 16 行和第 21 行,在父进程和子进程中,都通过 setgid(...) 来改变子进程为进程组组长,这是因为在 fork() 之后,我们无法确定是父进程先运行还是子进程先运行,如果只在子进程中设置,而父进程先运行,则在父进程运行的某一小段时间内,子进程的 pgid 并未改变,所以需要在父进程和子进程中都通过 setpgid(...) 来改变子进程的 pgid

    程序运行结果如下图所示:

    可以看出我们成功通过 setpgid(...) 改变了子进程的 pgid,子进程成为了进程组组长

    下面我们验证下子进程调用 exec(...) 后,父进程能够通过 setgpid(...) 来改变子进程的 pgid 吗?

    pgid_a.c

    1. #include <stdio.h>
    2. #include <stdlib.h>
    3. #include <fcntl.h>
    4. #include <string.h>
    5. #include <unistd.h>
    6. int main(void)
    7. {
    8. int pid = 0;
    9. printf("parrent = %d, ppid = %d, pgid = %d\n", getpid(), getppid(), getpgrp());
    10. if( (pid = fork()) > 0 )
    11. {
    12. int r = 0;
    13. sleep(1);
    14. r = setpgid(pid, pid);
    15. printf("new: %d, r = %d\n", pid, r);
    16. }
    17. else if( pid == 0 )
    18. {
    19. char* out = "helloworld.out";
    20. char* const ps_argv[] = {out, NULL};
    21. execve("./helloworld.out", ps_argv, NULL);
    22. }
    23. else
    24. {
    25. printf("fork error...\n");
    26. }
    27. sleep(60);
    28. return 0;
    29. }

    helloworld.c

    1. #include
    2. #include
    3. #include
    4. #include
    5. int main(void)
    6. {
    7. sleep(5);
    8. printf("before change pid\n");
    9. printf("child = %d, ppid = %d, pgid = %d\n", getpid(), getppid(), getpgrp());
    10. printf("hello world\n");
    11. printf("after change pid\n");
    12. setpgid(getpid(), getpid());
    13. printf("child = %d, ppid = %d, pgid = %d\n", getpid(), getppid(), getpgrp());
    14. sleep(30);
    15. return 0;
    16. }

    我们在子进程调用 execve(...) 后,在父进程中调用 setpgid(...) 改变子进程的进程组

    程序运行结果如下图所示:

    通过打印可以看出,子进程在调用 execve(...) 后,父进程就无法改变子进程的 pgid 了,不过我们可以在子进程中改变子进程的 pgid,可以成功改变

    Linux 会话 (session)

    用户通过终端登录系统后会产生一个会话

    会话是一个或多个进程组的集合

    每个会话有一个会话标识 (SID)

    • 终端登录后的第一个进程成为会话首进程,通常是一个 shell/bash
    • 对于会话首进程 (session leader),其 PID 与 SID 相等

    通常情况下,会话与一个终端 (控制终端) 相关联用于执行输入输出操作

    • 会话首进程建立与控制终端的连接 (会话首进程又叫控制进程)
    • 会话中的进程可分为
      • 前台进程组:可接受控制终端中的输入,也可输出数据到控制终端
      • 后台进程组:所有进程后台运行,无法接收终端中的输入,但可以输出数据到终端

    问题:在终端中输入命令后,发生了什么?

    当命令行 (shell) 运行命令后创建一个新的进程组

    如果运行的命令中有多个子命令则创建多个进程 (处于新建的进程组中)

    命令不带 &

    • shell 将新建的进程组设置为前台进程组,并将自己暂时设置为后台进程组

    命令中带 &

    • shell 将新建的进程组设置为后台进程组,自己依旧是前台进程组

    我们将 pgid_j 程序放到前台运行,这时 shell 会将这个进程所在的进程组设置为前台进程组,然后我们按下 ^C,shell 会发送一个终止信号给前台进程组,前台进程组的所有进程都会终止运行

    什么是终端进程组标识 (TPGID) ?

    标识进程是否处于一个和终端相关的进程组中

    前台进程组:TPGID == PGID

    后台进程组:TPGID != PGID

    若进程和任何终端无关:TPGID == -1

    第一次前台运行, grep 这个进程的 PGID 和 TPGID 相同,说明这个进程当前处于前台进程组

    第二次后台运行, grep 这个进程的 PGID 和 TPGID 不同,并且 TPGID 不等于 -1,说明这个进程当前处于后台进程组

    Linux 会话接口

    #include

    pid_t getsid(pid_t pid);  // 获取指定进程的 SID,(pid == 0) => 当前进程

    pid_t setsid(void);  // 调用进程不能是进程组长

    • 创建新会话,SID == PID,调用进程成为会话首进程
    • 创建新进程组,PGID == PID,调用进程成为进程组长
    • 调用进程没有控制终端,若调用前关联了控制终端,调用后与控制终端断联

    下面的程序输出什么?为什么?

    会话实验

    session.c

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. int main(void)
    7. {
    8. int pid = 0;
    9. int i = 0;
    10. if( (pid = fork()) > 0 )
    11. {
    12. printf("parrent = %d, ppid = %d, pgid = %d, sid = %d\n", getpid(), getppid(), getpgrp(), getsid(getpid()));
    13. printf("new: %d\n", pid);
    14. }
    15. else if( pid == 0 )
    16. {
    17. setsid();
    18. sleep(3);
    19. printf("child = %d, ppid = %d, pgid = %d, sid = %d\n", getpid(), getppid(), getpgrp(), getsid(getpid()));
    20. }
    21. else
    22. {
    23. printf("fork error...\n");
    24. }
    25. sleep(120);
    26. return 0;
    27. }

    我们在子进程中调用 setsid(),让子进程成为会话首进程

    可以看出子进程的 pid 为 14182,它的 PID == PGID == SID,子进程成功的成为了会话首进程,并且它的 TPGID 为 -1,已经脱离了控制终端,但我们看到在当前的终端中还是可以看到子进程的打印,这是为什么呢?

    在子进程 setsid() 后,虽然与控制终端无关,但还是可以有标准输入输出的,这里标准输出 stdout 还是关联到之前的终端,所以在当前的终端中还是会有打印

    标准输入输出与控制终端没关系,只不过默认情况下标准输入输出是与终端关联到了一起

  • 相关阅读:
    [SWPU2019]Web1-1|SQL注入
    Python构建学生信息管理系统:需求分析与规划
    Jenkins自动化部署相关shell命令
    Spring Security(6)
    Aeron:Online Resources
    还未入职,这位将来的博导为学生规划了一条高效学习之路
    IIC 通信协议 (一)
    woocommerce对接paypal如何进行沙盒测试?
    操作系统性能参数调优
    我与阿里巴巴集团副总裁、阿里云智能数据库事业部总负责人在阿里云官网同框啦
  • 原文地址:https://blog.csdn.net/qq_52484093/article/details/133276829