关于linux:Linux系统编程训练营9Linux-进程层次分析

1次阅读

共计 7626 个字符,预计需要花费 20 分钟才能阅读完成。

详解 Linux 过程组

  • 每个过程都有一个过程组号 (PGID)

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

  • 过程组可不便过程治理(如:同时杀死多个过程,发送一个信号给多个过程)

    • 每个过程必然属于一个过程组,也只能属于一个过程组
    • 过程除了有 PID 外,还有 PGID(惟一,可变,即某一个过程能够切换过程组)
    • 每个过程组有一个过程组长,过程组长的 PID 和 PGID 雷同
> ps -o pgid 19843
PGID 977

> kill -- -977

  • pid_t getpgrp(void); // 获取以后过程的组标识
  • pid_t getpgid(pid_t pid); // 获取指定过程的组标识
  • int setpgid(pid_t pid, pid_t pgid); // 设置过程的组标识

    • pid == pgid, 将 pid 指定的过程设为组长
    • pid == 0, 设置以后过程的组标识为 pgid
    • pid == 0, 将 pid 设置为组标识(行将 pid 所代表的过程设置为过程组长)

过程组示例程序

默认状况下,子过程与父过程属于同一过程组,是 fork 工作机制的产物(子过程复制以后过程自身)

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>

int main(void)
{
    int pid = 0;
    int i = 0;

    printf("parent = %d, ppid = %d, pgid = %d\n", getpid(), getppid(), getpgrp());

    while (i < 5) {if ((pid = fork()) > 0) {printf("new : %d\n", pid);
        }
        else if (pid == 0) {sleep(1);
            printf("child = %d, ppid = %d, pgid = %d\n", getpid(), getppid(), getpgrp());
            sleep(60);
            printf("last == pgid = %d\n", getpgrp());
            break;
        }
        else {printf("fork error...\n");
        }
        ++i;
    }

    if (pid) {sleep(60);
    }

    return 0;
}
tiansong@tiansong:~/Desktop/linux$ ./a.out &
[1] 3022
parent = 3022, ppid = 2125, pgid = 3022
new : 3024
new : 3025
new : 3026
new : 3027
new : 3028
tiansong@tiansong:~/Desktop/linux$ child = 3025, ppid = 3022, pgid = 3022
child = 3026, ppid = 3022, pgid = 3022
child = 3027, ppid = 3022, pgid = 3022
child = 3024, ppid = 3022, pgid = 3022
child = 3028, ppid = 3022, pgid = 3022

tiansong@tiansong:~/Desktop/linux$ ps
    PID TTY          TIME CMD
   2125 pts/3    00:00:00 bash
   3022 pts/3    00:00:00 a.out
   3024 pts/3    00:00:00 a.out
   3025 pts/3    00:00:00 a.out
   3026 pts/3    00:00:00 a.out
   3027 pts/3    00:00:00 a.out
   3028 pts/3    00:00:00 a.out
   3061 pts/3    00:00:00 ps
tiansong@tiansong:~/Desktop/linux$ kill 3022  // kill 过程组长
[1]+  Terminated              ./a.out
tiansong@tiansong:~/Desktop/linux$ ps
    PID TTY          TIME CMD
   2125 pts/3    00:00:00 bash
   3024 pts/3    00:00:00 a.out
   3025 pts/3    00:00:00 a.out
   3026 pts/3    00:00:00 a.out
   3027 pts/3    00:00:00 a.out
   3028 pts/3    00:00:00 a.out
   3149 pts/3    00:00:00 ps
tiansong@tiansong:~/Desktop/linux$ kill -- -3022  // kill 过程组
tiansong@tiansong:~/Desktop/linux$ ps
    PID TTY          TIME CMD
   2125 pts/3    00:00:00 bash
   3163 pts/3    00:00:00 ps

过程组深度分析

深刻了解过程组

  • 过程组长终止,过程组仍然存在(过程组长仅用于创立新过程组)
  • 父过程创立子过程后立刻通过 setpgid() 扭转其组标识(PGID)【当须要将子过程设置到其它过程组时】
  • 同时,子过程也须要通过 setpgid() 扭转本身组标识(PGID)【当须要将子过程设置到其它过程组时】
  • 当子过程调用 exec()

    • 父过程无奈通过 setpgid() 扭转子过程组标识(PGID)
    • 只能本身通过 setpgid() 扭转其组标识(PGID)

过程组标识设置技巧

试验 1:设置子过程为过程组长 👇 为例

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>

int main(void)
{
    int pid = 0;

    printf("parent = %d, ppid = %d, pgid = %d\n", getpid(), getppid(), getpgrp());

    if((pid = fork()) > 0) {int r = setpgid(pid, pid);    // ① 子过程设置新的过程组
        printf("new: %d, r = %d\n", pid, r);
    }
    else if (pid == 0) {setpgid(pid, pid);   // ② -> setpgid(0,0) -> setpgid(子过程 pid, 用子过程 id 作为过程组 id)
        sleep(1);
        printf("child = %d, ppid = %d, pgid =%d\n", getpid(), getppid(), getpgrp());
    }
    else {printf("fork error ...\n");
    }

    return 0;
}
tiansong@tiansong:~/Desktop/linux$ ./a.out 
parent = 3434, ppid = 2125, pgid = 3434
new: 3435, r = 0
child = 3435, ppid = 1, pgid =3435

问:为什么在父子过程都须要调用 setpgid 呢?
答:为了双保险。

fork 实现之后,无奈确认是父过程先执行还是子过程限制性(古代操作系统个别子过程先执行)。
为了确保不让子过程与父过程在“短暂的工夫内”仍呈现在雷同的过程组中,须要在子过程创立进去之后立刻对“子过程”进行过程组设置。


试验 2:当子过程调用 exec() 后, 父过程无奈通过 setpgid() 扭转子过程组标识(PGID)

main.c

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>

int main(void)
{
    int pid = 0;

    printf("parent = %d, ppid = %d, pgid = %d\n", getpid(), getppid(), getpgrp());

    if((pid = fork()) > 0) {int r = setpgid(pid, pid);
        printf("new: %d, r = %d\n", pid, r);
    }
    else if (pid == 0) {
        char *out = "./helloword.out";
        char *const ps_argv[] = {out, NULL};
        char *const ps_envp[] = {"PATH=/bin:/usr/bin", NULL};
        execve(out, ps_argv, ps_envp);
    }
    else {printf("fork error ...\n");
    }

    sleep(60);

    return 0;
}

helloword.c


#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>

int main(void)
{printf("child = %d, ppid = %d, pgid = %d\n", getpid(), getppid(), getpgrp());

    printf("hello world\n");

    sleep(30);

    return 0;
}
tiansong@tiansong:~/Desktop/linux$ ./a.out &
[1] 5660
parent = 5660, ppid = 2125, pgid = 5660
new: 5662, r = 0  // r 等于 0, 示意父过程 setpgid 调用胜利
child = 5662, ppid = 5660, pgid = 5662
hello world

批改 main.c

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>

int main(void)
{
    int pid = 0;

    printf("parent = %d, ppid = %d, pgid = %d\n", getpid(), getppid(), getpgrp());

    if((pid = fork()) > 0) {
        int r = 0;
        sleep(1);  // 确保子过程先执行并调用了 execve
        r = setpgid(pid, pid);
        printf("new: %d, r = %d\n", pid, r);
    }
    else if (pid == 0) {
        char *out = "./helloword.out";
        char *const ps_argv[] = {out, NULL};
        char *const ps_envp[] = {"PATH=/bin:/usr/bin", NULL};
        execve(out, ps_argv, ps_envp);
    }
    else {printf("fork error ...\n");
    }

    sleep(60);

    return 0;
}

批改 helloword.c


#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>

int main(void)
{sleep(5);  // 期待父过程 setpgid 后再进行后续打印

    printf("child = %d, ppid = %d, pgid = %d\n", getpid(), getppid(), getpgrp());

    printf("hello world\n");

    sleep(30);

    return 0;
}
tiansong@tiansong:~/Desktop/linux$ ./a.out &
parent = 5917, ppid = 2125, pgid = 5917
tiansong@tiansong:~/Desktop/linux$ new: 5919, r = -1  // r 等于 -1, 示意 setpgid 执行失败
child = 5919, ppid = 5917, pgid = 5917  // pgid 也未发生变化
hello world

会话与终端的关系

Linux 会话(session)

  • 用户通过终端登录零碎后产生一个会话
  • 会话是一个或多个 过程组 的汇合
  • 每个会话有一个会话标识(SID

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

  • 通厂状况下,会话与一个终端(管制终端)相关联用于执行输入输出操作

    • 会话首过程建设与管制终端的连贯(会话首过程又叫做管制过程)
    • 会话中的过程组可分为:

      • 前台过程组:可接管管制终端中的输出,也可输入数据到管制终端
      • 后盾过程组:所有过程后盾运行,无奈接管终端中的输出,但可输入数据到终端
其中 getty 用于关联终端

会话与前后台过程组

会话中的前台过程组

问题

在终端中输出命令后,产生了什么?

  • 当命令行(shell)运行命令后 创立一个新的过程组
  • 如果运行的命令中有多个子命令则创立多个过程(新创建的过程处于新建的过程组中)
  • 命令不带 &

    • shell 将新见的过程组设置为前台过程组,并将本人临时设置为后盾过程组
  • 命令带 shell

    • shell 将新见的过程组设置为后盾过程组,本人仍旧是前台过程组

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

  • 标识过程是否处于一个和终端相干的过程组中
  • 前台过程:TPGID == PGID (因为前台过程组可能扭转,TPGID 用于标识以后的前台过程组)
  • 后盾过程:TPGID != PGID
  • 若过程和任何终端无关:TPGID == -1
通过比拟 TPGID 与 PGID 可判断:一个过程属于前台过程组,还是后盾过程组
如果过程组和终端相关联,那么当终端敞开断开连接,过程组的过程将全副完结
tiansong@tiansong:~/Desktop/linux$ ps -ajx | grep TPGID
   PPID     PID    PGID     SID TTY        TPGID STAT   UID   TIME COMMAND
   2125    6933    6932    2125 pts/3       6932 S+    1000   0:00 grep --color=auto TPGID

会话编程深度分析

Linux 会话接口

  • #include <unistd.h>
  • pid_t getsid(pd_t pid); // 获取指定过程的 SID, (pid == 0) → 以后过程
  • pid_t setpid(void); // 用于创立新会话,其中调用过程不能是过程组长,执行了如下动作:

    • 创立新会话,SID == PID,调用过程会成为会话首过程(在创立的会话中是惟一过程)
    • 创立新过程组,PGID == PID,调用过程成为过程组长(在创立的过程组中是惟一过程)
    • 调用过程没有管制终端,若调用前关联了管制终端,调用后与管制终端断联
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>

int main(void)
{
    int pid = 0;

    if((pid = fork()) > 0) {printf("parent = %d, ppid = %d, pgid = %d, sid = %d\n", getpid(), getppid(), getpgrp(), getsid(getpid()));
        printf("new: %d\n", pid);
    }
    else if (pid == 0) {setsid();  // 子过程脱离以后会话,创立新的过程会话,新的过程组
        sleep(180);
        printf("child = %d, ppid = %d, pgid = %d, sid = %d\n", getpid(), getppid(), getpgrp(), getsid(getpid()));
    }
    else {printf("fork error ...\n");
    }

    sleep(240);

    return 0;
}
tiansong@tiansong:~/Desktop/linux$ ./a.out   // 前台运行
parent = 7291, ppid = 2125, pgid = 7291, sid = 2125
new: 7292
^C                                          //  ctrl + c 终止前台过程组中的前台过程
tiansong@tiansong:~/Desktop/linux$ ps       // 查看以后终端窗口中运行的根本信息,发现没有 a.out
    PID TTY          TIME CMD
   2125 pts/3    00:00:00 bash
   7361 pts/3    00:00:00 ps
tiansong@tiansong:~/Desktop/linux$ ps -ajx | grep a.out  // 显示没有管制终端的过程(-x),搜寻 a.out
      1    7292    7292    7292 ?             -1 Ss    1000   0:00 ./a.out  // 7292 可知为创立的子过程。? 表明与任何一个终端都不关联,同时 TPGID 为 -1
   2125    7383    7382    2125 pts/3       7382 S+    1000   0:00 grep --color=auto a.out

tiansong@tiansong:~/Desktop/linux$ pstree -p -s -A 7292
systemd(1)---a.out(7292)                    // 父过程终止运行,被初始化过程接管

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>

int main(void)
{
    int pid = 0;

    if((pid = fork()) > 0) {printf("parent = %d, ppid = %d, pgid = %d, sid = %d\n", getpid(), getppid(), getpgrp(), getsid(getpid()));
        printf("new: %d\n", pid);
    }
    else if (pid == 0) {setsid();
        sleep(3);   // 批改此处,不便察看打印
        printf("child = %d, ppid = %d, pgid = %d, sid = %d\n", getpid(), getppid(), getpgrp(), getsid(getpid()));
    }
    else {printf("fork error ...\n");
    }

    sleep(240);

    return 0;
}
tiansong@tiansong:~/Desktop/linux$ ./a.out 
parent = 7947, ppid = 2125, pgid = 7947, sid = 2125
new: 7948
child = 7948, ppid = 7947, pgid = 7948, sid = 7948    // pgid(过程组长),sid(会话首过程)为以后过程,合乎预期
^C // ctrl + c 终止前台过程组中的前台过程

tiansong@tiansong:~/Desktop/linux$ ps -ajx | grep 7948
      1    7948    7948    7948 ?             -1 Ss    1000   0:00 ./a.out  // ? 表明与任何一个终端都不关联,同时 TPGID 为 -1
   2125    8028    8027    2125 pts/3       8027 S+    1000   0:00 grep --color=auto 7948

问题:在上述测试中,子过程创立新会话,与以后终端断开,与任何终端都不关联,那么为什么会在以后终端有输入呢?

只管 setsid 导致子过程再无相关联的终端,但因为 fork 的关系,子过程的 stdout 仍标记的以后所操作的终端上,因而子过程的打印会在以后终端输入

总结:规范输入输出与终端是“无关”的。只不过在默认状况下,规范输入输出和终端挂接到了一起(能够通过重定向使其断连)。(新会话能够没有管制终端,但还是能够有规范输入输出)

正文完
 0