tcgetpgrp() 的返回值意义是什么?

3

问题

tcgetpgrp的man页面说:

当fd指向调用进程的控制终端时,如果该终端有前台进程组ID,则函数tcgetpgrp()将返回该终端的前台进程组ID;否则将返回大于1且目前不是进程组ID的某些值。

  1. “某些值”的含义是什么?它似乎有歧义。
  2. 我如何检查它是否为有效的进程组ID或其他值?这是否意味着我必须获取系统中所有进程组的列表以确定返回值是否引用有效进程组?

背景

我尝试使用它并发现它似乎返回当前会话ID。我尝试了很多次,它总是返回当前会话ID。

enter image description here

“某些值”是否意味着当前会话ID?还是这是一个特殊情况?或者我的代码有缺陷?

环境和代码

环境:libc 2.1.2,Linux 2.6.32

代码:

int main(int argc, char *argv[]) 
{
    return getgroup(argc, argv);
}

int getgroup(int argc, char *argv[])
{
    if (fork()) {
        return OK;
    }
    sleep(5);
    printf("process %d fork, ppid %d, pgid %d, psid %d \n", getpid(), getppid(), getpgid(getpid()), getsid(getpid()));
    pid_t gid = tcgetpgrp(STDIN_FILENO);
    printf("group id %d \n", gid);
    return OK;
}

手册上说,“进程的会话ID是会话领导者的进程组ID。”这意味着终端的进程组是会话领导者进程组。也就是说,会话ID始终也是一个有效的进程组ID。 - Ben
2
你的代码示例不完整(main在哪里?),而且你运行它的方式也不清楚(为什么输出出现在shell提示符后面?)。后者尤其可能会影响输出结果。 - davmac
当父进程退出时,没有前台进程,并且终端控制返回到shell,是否有可能返回bash进程的pgid?@Ben - lilin
“some value larger than 1 that is not presently a process group ID” 表示一个大于1的数字(可能是有效的进程组ID),但不是进程组ID,因为系统中没有该ID的进程组。这是这个表面上模棱两可的句子的明确含义。 - Luis Colorado
2个回答

6
  1. 一些值的含义是什么,它似乎有歧义。

我认为它就是字面意思:一些(任意的)不是进程组ID的值。(实际上它返回的很可能是前台组的进程组ID,在其终止之前;在实践中这不太可能可见,因为shell通常会立即将另一个进程设置为前台进程,当其先前在前台的子进程终止时)。

  1. 如何检查它是否为有效的组ID或其他值,这是否意味着我必须获取系统中所有进程组的列表,以确定返回值是否指向有效的进程组?

您可以使用带有负参数的kill来信号化进程组(信号化的进程组将是参数的绝对值),并使用信号编号0。如果进程组不存在,则此操作将返回-1并设置errno为ESRCH,如果存在,则不执行任何操作(并返回0)。

(也可能可以使用killpg,但手册页面没有记录使用信号编号0的可能性,因此我不确定)。

然而,存在竞争条件:进程组可能存在于调用tcgetgrp时,但自那时以来已终止;反之亦然,它可能不存在,但具有相同ID的新进程组可能已经存在。这使得它实际上只适用于检查当前进程控制的进程组(即可以防止收割的进程组) - 具体来说,是由当前进程的子进程领导的组(或由当前进程本身领导的组)。

如果这似乎有限,请考虑:您实际上需要知道哪个进程组在前台,以及为什么需要知道?


@LuisColorado 我知道了。我不明白你的观点是什么? - davmac
@davmac,我的评论是关于您对终端竞争条件的解释,即当它看到它的控制组死亡,并且相同的ID在一段时间后被重新分配时...请阅读我的答案,这补充了您的解释。 - Luis Colorado
@LuisColorado 你是在说如果一个进程组是某个终端的前台进程组,并且其中的所有进程都终止(因此进程组本身终止),那么在另一个进程组成为该终端的前台进程之前,不会创建具有相同ID的其他进程组吗?有趣。你有这方面的信息来源吗?啊,没关系。我现在明白了。你是说很少会重新使用PID。我同意,但如果安全性受到威胁,最好考虑一下。 - davmac
不是的... process group ID 仅从活动进程中创建... 并且在进程组中的所有进程都 exit() 后,才会重新分配 PID。因此,在短时间内看到相同的进程组 ID 的机会很小(与内核重新分配相同 PID 的顺序相同)。 - Luis Colorado
让我们在聊天中继续这个讨论 - Luis Colorado
显示剩余4条评论

1

专注于问题标题“tcgetpgrp()返回值的含义是什么?”,它指的是控制请求该数字的终端的进程组ID。

为了稍微解释一下发生的事情,有必要知道系统如何处理pid和进程组ID以及终端需要知道哪个进程组控制该终端。

当用户登录时,登录shell会交互地成为进程组领导者,并使用户终端知道该进程组是登录shell。

对于作业控制,对于shell构建并执行的每个管道,在分叉第一个进程后,它成为进程组领导者(通过setpgrp()调用)并在该进程组中生成所有管道中的命令。然后,它使该进程组ID成为控制该终端的进程组ID,终端知道哪个进程组是该终端的控制进程组。

这意味着两件事:

  • 终端每次只能处理一个进程组。其他以该终端为控制终端的进程组与其不相关(虽然它们有一个指针指向tty,但由于tty没有指向这个进程组,所以这些进程的 read(2) 操作是不可能的)。一旦它们再次成为前台进程组(通过使 tty 指向该进程组,通过 tcsetpgrp termios 调用),它们将能够再次进行 read(2) 操作。

  • Shell 只对交互式作业执行此操作。更改交互式作业意味着将进程组设置为即将转为前台的进程组。然后,它必须更改等待方式,以等待该组的进程领导者。这排除了在后台生成的所有作业(或使用命令 bgfg 转换为后台作业),因为 tty 现在指向不同的进程组。Shell 仅维护一个前台进程组和许多后台进程,只有前台进程组才能输入。

终端需要知道它连接到哪个进程组,原因有两个:

  • 发送信号SIGINT(Ctrl-C)、SIGHUP(Ctrl-D)、SIGQUIT(Ctrl-])和SIGSTOP(Ctrl-X)必须发送到前台进程组,因此它被发送到控制进程组。信号SIGHUP被发送到更广泛的一组进程,即会话组。

  • tty读取函数需要知道读取终端的进程是否在指向的进程组中,因此它检查进行读取调用的进程是否具有与终端驱动程序结构中存储的进程组相同的进程组ID。如果不匹配,则返回错误(errno等于ENOTTY)。

另一方面,成为进程组长(创建进程组的唯一方法)将创建一个进程组,其ID等于发出调用的进程的PID,因此进程组长的PID和进程组ID相同。

一旦说了这个……终端不需要更新控制进程组ID。当进程exit(2)时,应该搜索终端,并且在其中删除任何可能留在那里的控制终端ID会很繁琐。
但是,当进程组长打开tty时,该tty的进程组将更改为该组长的进程组......因此,在进程死亡时,无需浏览系统中的所有终端,仅需使其控制进程组ID无效即可。
正如您从@davmac的答案中所告知的那样,有其他方法可以知道返回的数字是否为有效的进程组ID,只需发送0信号(用于判断进程或进程组是否存在),就可以获得它。
当然,关于@davmac指出的竞争条件,请考虑重新使用pid的可能性,但由于内核以最近最少使用(或接近)的方式管理pid,因此需要非常高的负载才能产生这样的pid冲突。
你总是获取当前进程的进程组ID的原因是你总是以交互方式启动程序。尝试在后台启动它,你就会看到一个进程组与请求进程的进程组不匹配(很可能是shell的进程组)。

你测试过吗?因为这是古老的用法,我最近没有测试过。但在UNIX中,当你打开一个tty时,如果该tty没有附加进程组(这很难实现),并且该进程是进程组的领导者(这意味着它的pgid与其pid相匹配),并且该进程没有打开另一个tty,则该tty已将其进程组设置为调用进程的进程组。我很久以前就测试过了(当时还没有tcsetpgrp调用),而且它也起作用。 - Luis Colorado
@LuisColorado 我已经测试过了,是的(在Linux上)。对于您描述的行为,进程在打开终端之前需要没有控制终端。这个要求以及终端没有关联进程组的要求,在您的答案中没有提到。 “这意味着tcsetpgrp(3)调用未用于将终端设置为进程组控制终端” - 是的,它用于设置终端的前台进程组。 - davmac
@lilin GNU C库手册提供了相当不错的信息(虽然有时候不是100%清晰):https://www.gnu.org/software/libc/manual/html_node/Job-Control.html - davmac
1
@davmac,我并没有说反话。许多组可以有相同的tty作为控制终端。一个进程不能拥有多个控制终端,而一个终端只能有一个控制进程组。我所说的是,为了能够从tty读取,进程到终端和终端到进程组的指针必须互相指向。句子“其他进程组与该终端未连接”是糟糕的错别字。我的意思就是上面所说的。终端始终将键盘信号发送到进程组(除了发送到进程会话组的SIGHUP)。 - Luis Colorado
1
进程组被发明用于管理作业控制...和进程组会话用于管理交互式登录会话或守护进程组的会话。 - Luis Colorado
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接