我想请教各位C 编程方面的帮助。基本上我有fork()
系统调用问题。这是我的问题:我们有一个管理器进程,它必须创建POP_SIZE
学生进程。在创建所有学生进程之前,经理进程和学生进程本身不能做任何其他事情。每个 Student Process 都通过以下方式标识:1) 其标识号(6 位整数) 2)在特定考试中获得的成绩(整数)
这是我设法编写的代码:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#define POP_SIZE 10
int main(int argc, char *argv[]){
pid_t firstFork;
int *status;
int numStudents = 0;
pid_t managerChild, managerParent;
pid_t students[POP_SIZE];
int studentStatus[POP_SIZE];
switch(firstFork = fork()){
case -1:
perror("Something wrong with fork()\n");
break;
case 0:
managerChild = getpid();
printf("Manager Child Process %d started\n", managerChild);
printf("I have to create %d Student Processes\n", POP_SIZE);
for(int i = 0; i < POP_SIZE; i++){
switch(students[i] = fork()){
case -1:
perror("Something wrong with FORK in Manager Child Process\n");
break;
case 0:
printf("Created first Student Process PID: %d\n", getpid());
numStudents++;
break;
default:
printf("Haven't created all Student Processes\n");
waitpid(managerChild, status, WUNTRACED | WNOHANG);
printf("%d Student Processes succesfully created\n", numStudents);
break;
}
}
break;
default:
for(int i = 0; i < POP_SIZE; i++)
wait(NULL);
}
}
我需要一些帮助来理解在我的代码中放置的位置wait(*status)
或waitpid(pid, *status, __options)
函数以实现我上面指定的要求?此外,如何为每个进程分配和保持变量的存储?非常感谢你
由于您将创建许多子进程,因此最好首先创建一个创建子进程的函数,并让它执行调用者指定的函数。让我们假设 ID 号和等级都是int
s。然后,
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>
/* Run func(id, grade) in a child process.
Returns the child process PID if success,
or -1 with errno set in case an error occurs.
*/
pid_t run_child(int id, int grade,
int (*func)(int id, int grade))
{
pid_t p;
p = fork();
if (p == -1) {
/* fork() failed; it set errno to indicate the error. */
return -1;
} else
if (!p) {
/* Run child process function. When it returns,
have the child exit with that exit status. */
exit(func(id, grade));
} else {
/* Parent process. p is positive. */
return p;
}
}
注意第三个参数是一个函数指针。我们使用函数名来指定它。该函数必须采用两个int
参数(分别为 ID 和等级),并返回一个int
. 例如:
/* Each child process runs this function.
*/
int child_process(int id, int grade)
{
printf("Child: id = %d, grade = %d, PID = %d.\n", id, grade, (int)getpid());
return EXIT_SUCCESS;
}
我们可以创建一个运行该函数的子进程child_pid = run_child(123456, 5, child_process);
。请注意如何使用函数名称来指定函数指针。标准 Cqsort()
函数使用完全相同的机制来允许快速排序任何内容;调用者只需要指定一个函数,该函数可以比较要排序的数组中的两个元素。
我们将创造几个孩子,并同时收获他们。这意味着编写一个获取所有子进程的函数是有意义的,基本上是阻塞直到它们都退出。我们可能对其中至少一些的退出状态感兴趣,所以让我们将有趣的子进程 PID、保存状态的整数以及这些数组中的进程数作为参数传递:
/* Reap all child processes.
If child_count > 0, child processes with PID in child_pid[]
will have child_pid[] negated when reaped, with exit status saved
in child_status.
The function returns the number of child processes reaped.
*/
size_t reap_children(pid_t *child_pid, int *child_status, size_t child_count)
{
size_t reaped = 0;
size_t i;
int status;
pid_t p;
while (1) {
/* Reap a child process, if any. */
p = wait(&status);
if (p == -1) {
/* errno == EINTR is not an error; it occurs when a
signal is delivered to a hander installed without
SA_RESTART flag. This will not occur in this program,
but it is good practice to handle that case gracefully. */
if (errno == EINTR)
continue;
/* errno set by wait(). */
return reaped;
}
/* Another child process was reaped. */
reaped++;
/* If the reaped child was one of the interesting ones,
negate its pid and save the exit status. */
for (i = 0; i < child_count; i++) {
if (child_pid[i] == p) {
child_pid[i] = -p;
child_status[i] = status;
break;
}
}
}
}
请注意,p = wait(&status)
收割子进程。这意味着如果一个或多个子进程已经退出,它会选择其中一个,并返回其 PID,退出状态保存到&status
. 如果剩下的所有子进程仍在运行,则调用将等到其中至少一个退出。如果没有更多的子进程,它返回-1
与errno
设置为ECHILD
。
如果使用了信号处理程序,wait()
也可以-1
使用errno
set to返回EINTR
,如果信号被传递到安装时没有带SA_RESTART
标志的信号处理程序sigaction()
。许多程序员放弃了这个检查(因为“它永远不会发生”),但我确实喜欢包含那个检查,因为它很容易,并且确保在我的代码中添加信号处理不会在以后咬我。我也经常这样做。(我的意思是添加信号处理。)
当相应的子进程被收割时,我们否定 pid 的原因很简单:它允许我们轻松检测哪些子进程被收割。(POSIX 表示所有进程 ID 都是正数,并且pid_t
是有符号类型。否定 PID 也是一种常用技术;请参见例如waitpid()
。)
如果我们想获得一个特定的子进程,我们会使用waitpid()
. 例如,
pid_t child, p; /* wait for 'child'. */
int status;
do {
p = waitpid(child, &status, 0);
if (p == -1) {
if (errno == EINTR)
continue;
break;
}
} while (p != child);
if (p == child) {
/* Reaped 'child', status in 'status'. */
} else {
/* Error: failed to reap 'child'. See 'strerror(errno)'. */
}
请注意,在 POSIX/Unix 术语中,“子进程”仅指由该进程创建的进程;不是“孙子”,由子进程创建的进程。
我更喜欢编写我的进程来从命令行接收参数。如果没有参数被指定,或-h
或--help
指定,将显示一个简短的帮助(“用法”); 这在 POSIX 和 Unix 命令行工具中极为常见,因此非常直观。
以下main()
将一个或多个ID:grade
作为命令行参数。对于每一个,它创建一个子进程,并让它运行child_process()
具有指定 ID 和等级的函数。主程序然后将它们全部收割,并描述每个子进程的退出状态。
int main(int argc, char *argv[])
{
pid_t child_pid[argc];
int child_status[argc];
int count, i, n, arg, id, grade, status;
char dummy;
if (argc < 2 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
fprintf(stderr, "\n");
fprintf(stderr, "Usage: %s [ -h | --help ]\n", argv[0]);
fprintf(stderr, " %s ID:GRADE [ ID:GRADE ]*\n", argv[0]);
fprintf(stderr, "\n");
return EXIT_SUCCESS;
}
status = EXIT_SUCCESS;
count = 0;
for (arg = 1; arg < argc; arg++) {
if (sscanf(argv[arg], "%d:%d %c", &id, &grade, &dummy) == 2) {
child_pid[count] = run_child(id, grade, child_process);
if (child_pid[count] == -1) {
fprintf(stderr, "Cannot fork a child process: %s.\n", strerror(errno));
status = EXIT_FAILURE;
} else
count++;
} else {
fprintf(stderr, "%s: Not a valid ID:GRADE specification.\n", argv[arg]);
status = EXIT_FAILURE;
}
}
if (count < 0) {
fprintf(stderr, "No running child processes.\n");
return EXIT_FAILURE;
}
n = reap_children(child_pid, child_status, count);
printf("Reaped %d child processes.\n", n);
for (i = 0; i < count; i++) {
if (child_pid[i] < 0) {
printf("Child process %d (%d of %d)", (int)(-child_pid[i]), i + 1, count);
if (WIFEXITED(child_status[i])) {
if (WEXITSTATUS(child_status[i]) == EXIT_SUCCESS)
printf(" exited with success (EXIT_SUCCESS), %d.\n", EXIT_SUCCESS);
else
if (WEXITSTATUS(child_status[i]) == EXIT_FAILURE)
printf(" exited with failure (EXIT_FAILURE), %d.\n", EXIT_FAILURE);
else
printf(" exited with status %d.\n", WEXITSTATUS(child_status[i]));
} else
if (WIFSIGNALED(child_status[i])) {
printf(" died from signal %d.\n", WTERMSIG(child_status[i]));
} else {
printf(" died from unknown causes.\n");
}
} else {
printf("Child process %d (%d of %d) was lost!\n", (int)child_pid[i], i + 1, count);
}
}
return status;
}
如果将上述内容另存为example.c,则可以使用 eg 将其编译为example
gcc -Wall -O2 example.c -o example
如果你然后跑说
./example 100001:1 100002:5 100003:3 21532:4
输出将类似于
Child: id = 100002, grade = 5, PID = 1260.
Child: id = 100001, grade = 1, PID = 1259.
Child: id = 100003, grade = 3, PID = 1261.
Child: id = 21532, grade = 4, PID = 1262.
Reaped 4 child processes.
Child process 1259 (1 of 4) exited with success (EXIT_SUCCESS), 0.
Child process 1260 (2 of 4) exited with success (EXIT_SUCCESS), 0.
Child process 1261 (3 of 4) exited with success (EXIT_SUCCESS), 0.
Child process 1262 (4 of 4) exited with success (EXIT_SUCCESS), 0.
请注意,初始Child:
行可以按任何顺序排列,因为子进程基本上是并行运行的。每个子进程一启动就运行,所以这个例子不是对 OP 要求的复制和粘贴答案。
如果您想尝试复杂的流程层次结构,我建议使用 Graphviz 将它们可视化。例如,dot-kids.c:
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
static void reap_all(void)
{
pid_t p;
int status;
while (1) {
p = wait(&status);
if (p == -1) {
if (errno == EINTR)
continue;
if (errno == ECHILD)
return;
fprintf(stderr, "Process %d: reap_all(): %s.\n", (int)getpid(), strerror(errno));
return;
}
printf(" \"%d\" -> \"%d\" [ color=\"#ff0000\" ];\n", (int)p, (int)getpid());
if (WIFEXITED(status)) {
if (WEXITSTATUS(status) == EXIT_SUCCESS)
printf(" \"%d\" [ label=\"%d\" ];\n", (int)p, (int)p);
else
printf(" \"%d\" [ label=\"%d (exit %d)\" ];\n", (int)p, (int)p, WEXITSTATUS(status));
} else
if (WIFSIGNALED(status))
printf(" \"%d\" [ label=\"%d (signal %d)\" ];\n", (int)p, (int)p, WTERMSIG(status));
else
printf(" \"%d\" [ label=\"%d (lost)\" ];\n", (int)p, (int)p);
fflush(stdout);
}
}
static pid_t run_child(int (*child)(int depth, int width), int depth, int width)
{
pid_t p;
fflush(stdout);
fflush(stderr);
p = fork();
if (p == -1) {
fprintf(stderr, "Process %d: Cannot fork: %s.\n", (int)getpid(), strerror(errno));
return -1;
} else
if (!p) {
exit(child(depth, width));
} else {
printf(" \"%d\" -> \"%d\" [ color=\"#0000ff\" ];\n", (int)getpid(), (int)p);
fflush(stdout);
return p;
}
}
int child(int depth, int width)
{
if (depth > 0) {
while (width > 0)
run_child(child, depth - 1, width--);
reap_all();
}
return EXIT_SUCCESS;
}
int main(int argc, char *argv[])
{
int depth, width, i;
char dummy;
if (argc != 3 || !strcmp(argv[1], "-h") || !strcmp(argv[2], "--help")) {
fprintf(stderr, "\n");
fprintf(stderr, "Usage: %s [ -h | --help ]\n", argv[0]);
fprintf(stderr, " %s depth width | dot -Tx11\n", argv[0]);
fprintf(stderr, "\n");
return EXIT_SUCCESS;
}
if (sscanf(argv[1], " %d %c", &depth, &dummy) != 1 || depth < 0) {
fprintf(stderr, "%s: Invalid depth.\n", argv[1]);
return EXIT_FAILURE;
}
if (sscanf(argv[2], " %d %c", &width, &dummy) != 1 || width < 1) {
fprintf(stderr, "%s: Invalid width.\n", argv[2]);
return EXIT_FAILURE;
}
printf("digraph {\n");
printf(" \"%d\" [ shape=\"box\", label=\"%d\" ];\n", (int)getpid(), (int)getpid());
fflush(stdout);
for (i = 0; i < width; i++)
run_child(child, depth, width - 1);
reap_all();
printf("}\n");
return EXIT_SUCCESS;
}
使用例如编译它
gcc -Wall -O2 dot-kids.c -o dot-kids
并使用例如运行
./dot-kids 1 3 | dot -Tx11
要查看类似于其中数字是进程 ID的进程图,蓝色箭头显示哪个进程创建了哪个进程,红色箭头显示了哪个进程收获了哪个进程。
本文收集自互联网,转载请注明来源。
如有侵权,请联系[email protected] 删除。
我来说两句