POSIX 进程组

发布于 2024-07-25 16:51:22 字数 744 浏览 1 评论 0原文

我目前正在将进程组实现到我的操作系统项目的 POSIX 子系统中。 但是,我对 POSIX 规范 (setsid)(以及维基百科有关进程组的页面)。

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

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

它是否正确? 有什么我应该检查或做的真正晦涩的事情吗?

作为一个后续问题,我相信我已经知道了,分叉是否需要转移组成员资格? 或者是否必须在每次 fork 调用后使用 setpgid 来完成? 我收集到进程组是通过 forkfork 的 POSIX 定义传输的。

提前致谢。

I'm currently implementing process groups into my operating system project's POSIX subsystem. However, I've become a little confused at the POSIX specification (setsid) (along by Wikipedia's page on Process groups).

Our terminal layer sends SIGINT to the foreground process (group, whose id should equal the group leader's PID). In this case, that foreground process (our "login" application) becomes a group leader by calling setsid. When the user logs in, the program forks and executes the user's shell. At this stage, my understanding is that I call setpgid from the forked child before calling exec*. This means the executed program will be a part of the process group from the outset.

If I wanted to run the newly forked child outside the process group I would merely call setsid in the forked child before calling exec*.

Is this correct? Are there any really obscure things I should be checking or doing?

As a follow-on question, which I believe I already know, is it a requirement for fork to transfer group membership? Or is it something that must be done using setpgid after every fork call? I gather process groups are transferred by fork from the POSIX definition of fork.

Thanks in advance.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

陌路终见情 2024-08-01 16:51:22

有趣的问题——尤其是因为它在很长一段时间内都没有得到部分答案。

POSIX 基本定义

POSIX 定义部分的一些引用:

3.290 进程组

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

3.291进程组ID

代表进程组在其生命周期内的唯一正整数标识符。

注意:
另请参阅进程 ID 重用中定义的进程组 ID 重用。

3.292 流程组组长

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

3.293 进程组生命周期

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

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

[...]

3.337 会话

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

注意:
setid() 函数在 POSIX.1-2008 的系统接口卷中有详细定义。

3.338 会议主持人

已创建会话的进程。

注意:
有关详细信息,请参阅 POSIX.1-2008 系统接口卷中定义的 setsid() 函数。

3.339 会话生命周期

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


POSIX 系统接口

姓名

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

概要

 #include ; 

     pid_t 设置id(void); 
  

描述

如果调用进程不是进程组领导者,setsid() 函数将创建一个新会话。 返回后,调用进程应是该新会话的会话领导者,应是新进程组的进程组领导者,并且不应具有控制终端。 调用进程的进程组ID 应设置为等于调用进程的进程ID。 调用进程应该是新进程组中的唯一进程,并且是新会话中的唯一进程。

和:

姓名

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

概要

 #include ; 

     int setpgid(pid_t pid, pid_t pgid); 
  

描述

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

会话领导者的进程组 ID 不应更改。

成功完成后,进程ID与pid匹配的进程的进程组ID将被设置为pgid。

作为一种特殊情况,如果 pid 为 0,则应使用调用进程的进程 ID。 另外,如果 pgid 为 0,则应使用指定进程的进程 ID。


解释

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

复制问题的部分内容:

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

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

注意,前台进程通过调用setsid()成为会话领导者,同时也成为进程组领导也是。 我希望登录程序在分叉之后但在执行 shell 之前将用户的 shell 设置为进程组领导者(也可能是会话领导者)。 所有子进程自动继承父进程的进程组和会话; 如果你希望它有所不同,你必须覆盖它。

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

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

这是正确的吗? 有什么我应该检查或做的真正晦涩的事情吗?

是的,这基本上是正确的。 是的,可能还有一些晦涩的事情需要跟踪。 例如,您可能需要考虑控制 TTY。

作为一个后续问题,我相信我已经知道了,分叉是否需要转移组成员资格? 或者是否必须在每次 fork 调用后使用 setpgid 来完成? 我从 POSIX 的 fork 定义中了解到,进程组是通过 fork 传输的。

fork() 之后的子进程属于同一组组(如 /etc/group 中),并且也属于同一会话和同一进程组 - 但是它不是会话领导者,也不是进程组领导者。

Interesting question - not least because it stayed without even a partial answer for so long.

POSIX Base Definitions

Some quotes from the definitions part of POSIX:

3.290 Process Group

A collection of processes that permits the signaling of related processes. Each process in the system is a member of a process group that is identified by a process group ID. A newly created process joins the process group of its creator.

3.291 Process Group ID

The unique positive integer identifier representing a process group during its lifetime.

Note:
See also Process Group ID Reuse defined in Process ID Reuse .

3.292 Process Group Leader

A process whose process ID is the same as its process group ID.

3.293 Process Group Lifetime

The period of time that begins when a process group is created and ends when the last remaining process in the group leaves the group, due either to the end of the lifetime of the last process or to the last remaining process calling the setsid() or setpgid() functions.

Note:
The setsid() and setpgid() functions are defined in detail in the System Interfaces volume of POSIX.1-2008.

[...]

3.337 Session

A collection of process groups established for job control purposes. Each process group is a member of a session. A process is considered to be a member of the session of which its process group is a member. A newly created process joins the session of its creator. A process can alter its session membership; see setsid(). There can be multiple process groups in the same session.

Note:
The setsid() function is defined in detail in the System Interfaces volume of POSIX.1-2008.

3.338 Session Leader

A process that has created a session.

Note:
For further information, see the setsid() function defined in the System Interfaces volume of POSIX.1-2008.

3.339 Session Lifetime

The period between when a session is created and the end of the lifetime of all the process groups that remain as members of the session.


POSIX System Interfaces

NAME

setsid - create session and set process group ID

SYNOPSIS

   #include <unistd.h>

   pid_t setsid(void);

DESCRIPTION

The setsid() function shall create a new session, if the calling process is not a process group leader. Upon return the calling process shall be the session leader of this new session, shall be the process group leader of a new process group, and shall have no controlling terminal. The process group ID of the calling process shall be set equal to the process ID of the calling process. The calling process shall be the only process in the new process group and the only process in the new session.

And:

NAME

setpgid - set process group ID for job control

SYNOPSIS

   #include <unistd.h>

   int setpgid(pid_t pid, pid_t pgid);

DESCRIPTION

The setpgid() function shall either join an existing process group or create a new process group within the session of the calling process.

The process group ID of a session leader shall not change.

Upon successful completion, the process group ID of the process with a process ID that matches pid shall be set to pgid.

As a special case, if pid is 0, the process ID of the calling process shall be used. Also, if pgid is 0, the process ID of the indicated process shall be used.


Interpretation

As the definitions make clear, a session may consist of multiple process groups. Within broad limits, a process may change process groups (though it belongs to just one process group at any time). The options for session handling are more limited; basically, a process either remains a member of its original session, or it can make itself the leader of a new session.

Copying parts of the question:

Our terminal layer sends SIGINT to the foreground process (group, whose id should equal the group leader's PID). In this case, that foreground process (our "login" application) becomes a group leader by calling setsid. When the user logs in, the program forks and executes the user's shell. At this stage, my understanding is that I call setpgid from the forked child before calling exec*. This means the executed program will be a part of the process group from the outset.

I suspect the parentheses should be 'the foreground process group (whose id should equal the group leader's PID)'. By definition (3.292), the process group leader is the process whose PID is the same as the process group ID. I haven't quote the relevant material, but I believe that dispatching the signal to the process group leader is correct

Note that the foreground process becomes a session leader by calling setsid()and also becomes the process group leader too. I would expect that the login program would set up the user's shell as a process group leader (and probably a session leader) after forking but before executing the shell. All child processes inherit process group and session from their parent processes automatically; you have to override that if you want it to be different.

If I wanted to run the newly forked child outside the process group I would merely call setsid in the forked child before calling exec*.

You could do that, but it would also create a new session. You probably want to use setpgid() (modern standard; possibly setpgrp() which is an older standard from SVID) rather than setsid().

Is this correct? Are there any really obscure things I should be checking or doing?

Yes, this is mostly correct. Yes, there probably are some obscure things to keep track of too. For example, you might need to think about the controlling TTY.

As a follow-on question, which I believe I already know, is it a requirement for fork to transfer group membership? Or is it something that must be done using setpgid after every fork call? I gather process groups are transferred by fork from the POSIX definition of fork.

The child process after a fork() belongs to the same set of groups (as in /etc/group), and also to the same session and same process group - but it is not a session leader nor is it a process group leader.

∝单色的世界 2024-08-01 16:51:22

setpgid POSIX C 进程组最小示例

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

这说明了如果子进程没有使用 setpgid 更改其进程组,信号如何发送到子进程。

main.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 上游

编译:

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 向整个进程发送终止信号默认组

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

使用 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 上测试。

setpgid POSIX C process group minimal example

I believe that playing around with the base APIs is often the best way to learn new concepts, so let's give that a try.

This illustrates how the signal does get sent to the child, if the child didn't change its process group with setpgid.

main.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.

Compile with:

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

Run without setpgid

Without any CLI arguments, setpgid is not done:

./setpgid

Possible outcome:

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

and the program hangs.

As we can see, the pgid of both processes is the same, as it gets inherited across fork.

Then whenever you hit:

Ctrl + C

It outputs again:

sigint parent
sigint child

This shows how:

  • to send a signal to an entire process group with kill(-pgid, SIGINT)
  • Ctrl + C on the terminal sends a kill to the entire process group by default

Quit the program by sending a different signal to both processes, e.g. SIGQUIT with Ctrl + \.

Run with setpgid

If you run with an argument, e.g.:

./setpgid 1

then the child changes its pgid, and now only a single sigint gets printed every time from the parent only:

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

And now, whenever you hit:

Ctrl + C

only the parent receives the signal as well:

sigint parent

You can still kill the parent as before with a SIGQUIT:

Ctrl + \

however the child now has a different PGID, and does not receive that signal! This can seen from:

ps aux | grep setpgid

You will have to kill it explicitly with:

kill -9 16470

This makes it clear why signal groups exist: otherwise we would get a bunch of processes left over to be cleaned manually all the time.

Tested on Ubuntu 18.04.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文