代码之家  ›  专栏  ›  技术社区  ›  Matthew Iselin

POSIX进程组

  •  12
  • Matthew Iselin  · 技术社区  · 15 年前

    我目前正在将流程组实现到我的操作系统项目的POSIX子系统中。但是,我对 POSIX specification (setsid) (在维基百科关于过程组的页面上)。

    我们的终端层将sigint发送到前台进程(group,其id应该等于group leader的pid)。在这种情况下,前台进程(我们的“登录”应用程序)通过调用 setsid . 当用户登录时,程序分叉并执行用户的shell。在这个阶段,我的理解是 setpgid 在打电话之前从分叉的孩子那里 exec* . 这意味着执行的程序从一开始就是过程组的一部分。

    如果我想在进程组之外运行新分叉的孩子,我只会打电话给 塞西德 在被叉子叉着的孩子打电话之前 执行* .

    这是正确的吗?有什么我应该检查或做的非常模糊的事情吗?

    作为一个后续问题,我相信我已经知道,它是一个要求 fork 是否要转移组成员身份?还是必须用 SETPGID 每一次之后 打电话?我收集过程组通过 从posix定义 .

    事先谢谢。

    2 回复  |  直到 6 年前
        1
  •  14
  •   Jonathan Leffler    15 年前

    有趣的问题——尤其是因为它在这么长时间里都没有部分答案。

    POSIX基本定义

    一些来自posix定义部分的引用:

    3.290工艺组

    允许相关进程发出信号的进程集合。系统中的每个进程都是由进程组ID标识的进程组的成员。新创建的进程将加入其创建者的进程组。

    3.291过程组ID

    表示进程组在其生存期内的唯一正整数标识符。

    注: 另请参见流程ID重用中定义的流程组ID重用。

    3.292工艺组长

    进程ID与其进程组ID相同的进程。

    3.293工艺组寿命

    由于最后一个进程的生存期结束或调用setsid()或setpgid()函数的最后一个剩余进程,从创建进程组开始到该组中最后一个剩余进程离开该组结束的时间段。

    注: setsid()和setpgid()函数在posix.1-2008的系统接口卷中详细定义。

    […]

    3.337届

    为作业控制目的而建立的过程组的集合。每个进程组都是会话的成员。进程被视为其进程组所属会话的成员。新创建的进程加入其创建者的会话。进程可以更改其会话成员身份;请参阅setsid()。同一会话中可以有多个进程组。

    注: setsid()函数在posix.1-2008的系统接口卷中详细定义。

    3.338会议主持人

    已创建会话的进程。

    注: 有关更多信息,请参见posix.1-2008的系统接口卷中定义的setsid()函数。

    3.339会话寿命

    从创建会话到保留为会话成员的所有进程组生命周期结束之间的时间段。


    POSIX系统接口

    名字

    setsid-创建会话并设置进程组ID

    简介

       #include <unistd.h>
    
       pid_t setsid(void);
    

    描述

    如果调用进程不是进程组领导人,setsid()函数将创建一个新的会话。返回时,呼叫流程应为本次新会议的会议主持人,应为新工艺组的工艺组组长,且无控制终端。调用进程的进程组ID应设置为调用进程的进程ID。调用进程应是新进程组中的唯一进程,也是新会话中的唯一进程。

    还有:

    名字

    setpgid-为作业控制设置进程组ID

    简介

       #include <unistd.h>
    
       int setpgid(pid_t pid, pid_t pgid);
    

    描述

    setpgid()函数应加入现有进程组或在调用进程的会话中创建新的进程组。

    会议主持人的过程组ID不得更改。

    成功完成后,进程ID与PID匹配的进程的进程组ID应设置为PGID。

    特殊情况下,如果pid为0,则使用调用进程的进程ID。此外,如果pgid为0,则应使用所示工艺的工艺ID。


    解释

    正如定义所表明的那样,会话可能由多个进程组组成。在广泛的范围内,一个流程可能会改变流程组(尽管它在任何时候只属于一个流程组)。会话处理的选项更加有限;基本上,一个进程要么仍然是其原始会话的成员,要么可以使自己成为新会话的领导者。

    复制问题的部分内容:

    我们的终端层将sigint发送到前台进程(group,其id应该等于group leader的pid)。在这种情况下,前台进程(我们的“登录”应用程序)通过调用setsid成为一个组长。当用户登录时,程序分叉并执行用户的shell。在这个阶段,我的理解是,在调用exec*之前,我从分叉的子级调用setpgid。这意味着执行的程序从一开始就是过程组的一部分。

    我怀疑括号应该是“前台进程组(其ID应该等于组长的PID)”。根据定义(3.292),过程组组长是PID与过程组ID相同的过程,我没有引用相关资料,但我相信将信号发送给过程组组长是正确的。

    注意,前台进程通过调用 setsid() 同时也成为工艺组组长。我希望登录程序在分叉之后,但在执行shell之前,将用户的shell设置为进程组领导人(可能是会话领导人)。所有子进程都会自动从其父进程继承进程组和会话;如果希望进程组和会话不同,则必须重写该进程组和会话。

    如果我想在进程组之外运行新分叉的子进程,那么在调用exec*之前,我只需要在分叉的子进程中调用setsid。

    您可以这样做,但它也会创建一个新的会话。你可能想用 setpgid() (现代标准;可能 setpgrp() 这是SVID的旧标准),而不是 设置() .

    这是正确的吗?有什么我应该检查或做的非常模糊的事情吗?

    是的,这基本上是正确的。是的,可能还有一些模糊的东西需要跟踪。例如,您可能需要考虑控制tty。

    作为一个后续问题,我相信我已经知道了,fork是否需要转移集团成员?或者在每次fork调用之后都必须使用setpgid来完成?我从fork的posix定义收集fork传递的进程组。

    a之后的子进程 fork() 属于同一组(如 /etc/group ,也适用于同一个会议和同一个过程组,但它既不是会议领导人,也不是过程组领导人。

        2
  •  0
  •   Ciro Santilli OurBigBook.com    6 年前

    setpgid POSIX C流程组最小示例

    我相信使用基本API通常是学习新概念的最佳方法,所以让我们尝试一下。

    这说明了如果子进程组不随 SETPGID .

    主C

    #define _XOPEN_SOURCE 700
    #include <assert.h>
    #include <signal.h>
    #include <stdbool.h>
    #include <stdint.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    
    volatile sig_atomic_t is_child = 0;
    
    void signal_handler(int sig) {
        char parent_str[] = "sigint parent\n";
        char child_str[] = "sigint child\n";
        signal(sig, signal_handler);
        if (sig == SIGINT) {
            if (is_child) {
                write(STDOUT_FILENO, child_str, sizeof(child_str) - 1);
            } else {
                write(STDOUT_FILENO, parent_str, sizeof(parent_str) - 1);
            }
        }
    }
    
    int main(int argc, char **argv) {
        pid_t pid, pgid;
    
        (void)argv;
        signal(SIGINT, signal_handler);
        signal(SIGUSR1, signal_handler);
        pid = fork();
        assert(pid != -1);
        if (pid == 0) {
            /* Change the pgid.
             * The new one is guaranteed to be different than the previous, which was equal to the parent's,
             * because `man setpgid` says:
             * > the child has its own unique process ID, and this PID does not match
             * > the ID of any existing process group (setpgid(2)) or session.
             */
            is_child = 1;
            if (argc > 1) {
                setpgid(0, 0);
            }
            printf("child pid, pgid = %ju, %ju\n", (uintmax_t)getpid(), (uintmax_t)getpgid(0));
            assert(kill(getppid(), SIGUSR1) == 0);
            while (1);
            exit(EXIT_SUCCESS);
        }
        /* Wait until the child sends a SIGUSR1. */
        pause();
        pgid = getpgid(0);
        printf("parent pid, pgid = %ju, %ju\n", (uintmax_t)getpid(), (uintmax_t)pgid);
        /* man kill explains that negative first argument means to send a signal to a process group. */
        kill(-pgid, SIGINT);
        while (1);
    }
    

    GitHub upstream .

    编译:

    gcc -ggdb3 -O0 -std=c99 -Wall -Wextra -Wpedantic -o setpgid setpgid.c
    

    没有运行 SETPGID

    没有任何cli参数, SETPGID 不做:

    ./setpgid
    

    可能的结果:

    child pid, pgid = 28250, 28249
    parent pid, pgid = 28249, 28249
    sigint parent
    sigint child
    

    程序挂起。

    如我们所见,两个进程的pgid是相同的,因为它是通过 fork .

    然后每当你点击:

    Ctrl + C
    

    它再次输出:

    sigint parent
    sigint child
    

    这说明了:

    • 向整个过程组发送信号 kill(-pgid, SIGINT)
    • 默认情况下,终端上的ctrl+c向整个进程组发送一个kill

    通过向两个进程发送不同的信号来退出程序,例如使用 Ctrl + \ .

    奔跑 SETPGID

    如果使用参数运行,例如:

    ./setpgid 1
    

    然后子级更改其pgid,现在每次只从父级打印一个sigint:

    child pid, pgid = 16470, 16470
    parent pid, pgid = 16469, 16469
    sigint parent
    

    现在,每当你点击:

    CTRL+C
    

    只有父级也接收到信号:

    sigint parent
    

    您仍然可以像以前一样使用sigquit杀死父级:

    Ctrl + \
    

    然而,孩子现在有一个不同的pgid,并且不接收该信号!这可以从以下方面看出:

    ps aux | grep setpgid
    

    你必须明确地杀死它:

    kill -9 16470
    

    这就清楚了为什么存在信号组:否则,我们将得到一组剩余的进程,以便随时手动清除。

    在Ubuntu 18.04上测试。