gpt4 book ai didi

c - 如何在C中创建自定义多个进程?

转载 作者:行者123 更新时间:2023-11-30 14:43:31 25 4
gpt4 key购买 nike

我想问你们一些C编程的帮助。基本上,我在fork()系统调用方面遇到问题。
这是我的问题:
我们有一个经理流程,该流程必须创建POP_SIZE学生流程。在创建所有学生流程之前,经理流程和学生流程本身无法执行任何其他操作。
每个学生进程的标识是:
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。然后,

#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);运行该功能的子进程。请注意,如何使用函数名称来指定函数指针。标准的C qsort()函数使用完全相同的机制来允许对任何内容进行快速排序。调用者只需指定一个函数即可比较要排序的数组中的两个元素。

我们将创建几个孩子,并立即收割它们。这意味着编写一个可以收割所有子进程(实际上是 blocking)直到所有子进程退出的函数是有意义的。我们可能对其中至少一些的退出状态感兴趣,因此让我们将有趣的子进程PID,将状态保存到的int以及这些数组中的进程数作为参数传递给参数:

/* 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也可以在 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作为命令行参数。对于每个进程,它都会创建一个子进程,并使用指定的ID和等级运行 child_process()函数。然后,主程序将全部收获它们,并描述每个子进程的退出状态。

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,则可以使用例如

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,蓝色箭头显示哪个进程创建了哪个进程,红色箭头显示哪个进程获得了哪个进程。

关于c - 如何在C中创建自定义多个进程?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53887577/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com