在为我的OS课程编写程序时,我发现了一个有趣的案例,涉及一个涉及setpgid
的竞争条件。
分别编译以下每个程序。执行./test 3
(或任何数字> 2)后,ps jx
将显示所有infy
进程已放置在同一组中。 ./test 2
将显示setpgid
尝试移动最后一个进程失败的错误。取消注释“修复我”行将导致./test 2
按预期工作。
有人可以提供解释或解决方案吗?
// test.c
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
char* args[] = {
"./infy",
NULL
};
int main(int argc, char* argv[])
{
if (argc != 2)
{
fprintf(stderr, "Usage: %s [num]\n", argv[0]);
return 1;
}
int num = strtol(argv[1], NULL, 10);
if (num < 2)
{
fprintf(stderr, "Invalid number of processes\n");
return 1;
}
pid_t pid = fork();
if (pid > 0)
{
int s;
waitpid(pid, &s, 0);
fprintf(stderr, "Children done\n");
}
else
{
pid_t pgid = -1;
int i;
for (i = 1; i < num; i++)
{
pid_t pid2 = fork();
if (pid2 > 0)
{
if (pgid == -1)
{
pgid = pid2;
}
}
else
{
if (setpgid(0, pgid == -1 ? 0 : pgid) != 0)
{
perror("setpgid failed in non-last process");
}
execve(args[0], args, NULL);
perror("exec failed");
exit(1);
}
}
// uncomment me to fix
//fprintf(stderr, "pgid %d\n", pgid);
if (setpgid(0, pgid) != 0)
{
perror("setpgid failed in last process");
}
execve(args[0], args, NULL);
perror("exec failed");
exit(1);
}
}
“infy”是一个单独的程序:
// infy.c
#include <unistd.h>
int main()
{
while (1)
{
sleep(1);
}
}
答案 0 :(得分:1)
我终于明白了。 setpgid
失败时,errno
设置为EPERM
。 EPERM
手册页上可能出现的错误之一是:
pgid参数的值有效但与pid参数指示的进程的进程ID不匹配,并且没有进程的进程组ID与同一会话中的pgid参数的值匹配。呼叫过程。
在这种情况下,竞争条件是子进程是否可以在父进程之前设置其pgid。如果孩子赢得比赛,一切都很好。如果父母赢得比赛,它尝试设置的进程组尚未存在,并且setpgid
失败。
解决方案是父进程通过调用setpgid(pid2, pid2)
块中的if (pgid == -1)
,在第一个分支后立即设置子组ID。
同样相关,来自手册页:
为了提供更严格的安全性,setpgid()仅允许调用进程在其会话中加入已在使用的进程组,或者创建一个进程组ID等于其进程ID的新进程组。
答案 1 :(得分:1)
山姆是对的。我进行了测试,发现即使孩子没有呼叫setpgid()
,只要没有execvp
,父母对setpgid()
的呼叫也会成功。这是演示代码。
#include <iostream>
#include <sys/types.h>
#include <unistd.h>
using namespace std;
int main(int argc, char* argv[]) {
pid_t pid = fork();
if(pid == 0) {
char* argv[3];
argv[0] = strdup("sleep");
argv[1] = strdup("10");
argv[2] = NULL;
execvp(argv[0], argv);
cout << "execvp failed: " << strerror(errno) << endl;
exit(0);
}
sleep(5);
int result = setpgid(pid, pid);
cout << "setpgid return value: " << result << endl;
if(result == -1) {
cout << "setpgid failed: " << strerror(errno) << endl;
cout << "Errno: " << errno << endl;
if(errno == EACCES) {
cout << "yellow" << endl;
}
}
return 0;
}
如果按原样拨打睡眠电话,则在孩子执行任何操作并成功之前,对setpgid()
的父母呼叫会通过。如果将睡眠呼叫移至其他位置,则execvp()
首先经过,并且父呼叫失败,并出现errno 13,并显示“黄色”。
答案 2 :(得分:0)
您的问题的答案似乎在setpgid(2)的手册页中给出:
ERRORS
EACCES An attempt was made to change the process group ID of
one of the children of the calling process and the child
had already performed an execve(2) (setpgid(),
setpgrp()).
这是一场竞争条件。如果您的原始父进程(最终在您的注释下面运行setpgid()调用的进程设法在其子进程execve()s其他可执行文件之前执行它,它将成功。如果子进程在父进入setpgid()之前设法执行execve(),则父setpgid()将失败。
缺少fprintf()调用会导致更改父进程的执行配置文件,并更改它以使其最终影响父进程赢或输的可能性。
我觉得很有意思的是,额外的fprintf()调用似乎实际上让你的父进程赢得了比赛!但事实就是如此。