【问题标题】:Passing information between child and parent process - linux (without `pipe()`)在子进程和父进程之间传递信息 - linux(没有`pipe()`)
【发布时间】:2021-10-19 03:58:42
【问题描述】:

作为我的任务的一部分,我必须实现子父进程。

“父进程不应等待子进程退出,而是应在元素到达共享缓冲区后立即打印元素”。

我知道父母必须等待孩子结束的情况,但是如何以异步方式实现从孩子到父母的消息通信?

附:确切的 Q 是

编写一个程序,其主例程从用户那里获得两个参数 n 和 d,即在从 shell 调用时传递给您的程序。然后,您的程序将创建一个共享内存和一个子进程。 子进程应该获取 n 和 d 的值(您有多种选择)并创建一个长度为 n 的等差数列,其第一个元素为 0,每个后续元素的值为 kd,其中 k 是元素编号(k= 0 到 n-1)。 子进程应一次创建一个元素,并在生成序列的元素之间等待一个随机的时间间隔(0 到 9.999 秒)。一旦生成了一个元素,孩子就会按照第 4 课的幻灯片 33-37 中的描述将元素组织到共享缓冲区中。 (例如:如果 n=5 和 d=2,则序列应为 0,2,4,6,8) 父进程不应等待子进程退出,而是应在元素到达共享缓冲区后立即打印元素(再次,以类似于第 4 课的幻灯片 33-37 的方式) 暗示;使用 fflush() 确保 printf 立即打印到屏幕上。

到目前为止我的代码 - gist

#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>

void printLab4(int n, int a, int *fibo)
{
    int i;
    for (i = 0; i < n; i++)
        printf("%d -> %d\n", i, fibo[i]);
}

void computeLab4(int n, int a, int *fibo)
{
    int i;

    for (i = 0; i < n; i++)
    {
        int sleepSec = rand() % 10;
        printf("sleeping for %d : ", sleepSec);
        sleep(sleepSec);
        fibo[i] = i * a;
        // randomly sleeping for 0-10 secs
        printf("Generated new element %d after %d seconds \n", fibo[i], sleepSec);
    }
}

int main(int argc, char *argv[])
{
    pid_t childPID;
    int status;
    int shm_fd;
    int *shared_memory;
    int msize; // the size (in bytes) of the shared memory segment
    const char *name = "Lab_4";
    int n, a;

    if (argc != 3)
    {
        fprintf(stderr, "usage: %s <Lab4 Seq to be generated>\n", argv[0]);
        return -1;
    }

    n = atoi(argv[1]);
    a = atoi(argv[2]);

    printf("%d \n", n);
    printf("%d \n", a);

    if (n < 0 || a < 0)
    {
        fprintf(stderr, "Illegal number: %s\n", argv[1]);
        return -2;
    }

    // calculating the array size based on the number of terms being passed from child to parent
    msize = (n + 2) * sizeof(int);

    // open the memory
    shm_fd = shm_open(name, O_CREAT | O_EXCL | O_RDWR, S_IRWXU | S_IRWXG);
    if (shm_fd < 0)
    {
        fprintf(stderr, "Error in shm_open()");
        return -3;
    }

    printf("Created shared memory object %s\n", name);

    // attach the shared memory segment
    ftruncate(shm_fd, msize);
    printf("shmat returned\n");

    // allocating the shared memory
    shared_memory = (int *)mmap(NULL, msize, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0);
    if (shared_memory == NULL)
    {
        fprintf(stderr, "Error in mmap()");
        return -3;
    }

    printf("Shared memory segment allocated correctly (%d bytes).\n", msize);

    shared_memory[0] = n;
    shared_memory[1] = a;

    childPID = fork();
    if (childPID == -1)
    {
        fprintf(stderr, "Cannot proceed. fork() error");
        return -4;
    }
    if (childPID == 0)
    {
        // then we're the child process
        computeLab4(shared_memory[0], shared_memory[1], shared_memory + 1);
        exit(0);
    }
    else
    {
        // parent will wait until the child finished
        wait(&status);

        // print the final results in the
        printLab4(shared_memory[0], shared_memory[1], shared_memory + 1);

        // now detach the shared memory segment
        shm_unlink(name);
    }
    return 0;
}

【问题讨论】:

  • 没有代码很难帮你。你有共享缓冲区的代码吗?有很多方法可以做到这一点,如果不理解“共享缓冲区”的分配意味着什么,我们很可能会把你引向正确的方向。
  • 我不确定这可以通过 100 种不同的方式完成,但它接近 100 种。需要更多详细信息。否则,它太宽泛,无法得到答案。
  • 在问题中发布您的代码——而不是在 GitHub gist 等临时存储位置。
  • 您的代码会休眠整秒。指定范围 0 到 9.999 秒的含义是您的孩子应该睡小数秒。您可能应该重新考虑您正在使用哪个睡眠功能。

标签: c linux


【解决方案1】:

由于您是在 shell 调用程序时从命令行获取值,因此当父调用 fork() 时,子将可以访问相同的变量。无需将值从父级“传递”给子级。

当孩子创建元素时,我假设父母正在从共享内存中读取元素。除非您在并行和实时系统上,否则“一旦到达”是一个有点可疑的要求。但是,模拟该需求可以通过父进程的繁忙等待循环来实现,以检查共享内存是否已获取新数据。

为了在父进程忙于等待循环时不让操作系统占用 CPU 周期,父进程可以在检查迭代之间调用 sched_yield(),其行为类似于立即唤醒的睡眠。

父子共享内存可以看成某种队列,父子的忙等待是检查队列是否为非空,如果是,则处理队列元素直到为空,此时点它忙等待。

for (;;) {
    if (q_is_empty(q)) {
        sched_yield();
        continue;
    }
    int v = q_dequeue(q);
    process(v);
}

您可以使用值为 1 的 usleep()nanosleep(),而不是 sched_yield()

孩子当然是按照分配的规定往队列中添加元素了。

for (;;) {
    if (q_is_full(q)) {
        sched_yield();
        continue;
    }
    v = next_value_in_sequence_after_delay();
    q_enqueue(v);
}

您可能想要添加一个指示(例如入队-1),表明孩子已完成。然后孩子可以退出,父母可以知道收割孩子是安全的。

完整检查和空检查在逻辑上可以看作是入队和出队操作本身的一部分。所以,一个可能的实现可能是:

void q_enqueue(struct queue_type *q, int v) {
    while (q_is_full(q)) sched_yield();
    q->q[q->tail % Q_MAX_ELEM] = v;
    q->tail += 1;
}

int q_dequeue(struct queue_type *q) {
    while (q_is_empty(q)) sched_yield();
    int v = q->q[q->head % Q_MAX_ELEM];
    q->head += 1;
    return v;
}

然后父函数和子函数可能如下所示:

void parent(struct queue_type *q) {
    for (;;) {
        int v = q_dequeue(q);
        if (v == -1) break;
        printf("|%d", v);
        fflush(stdout);
    }
    printf("|done!\n");
}

void child(struct queue_type *q, int n, int d) {
    int v = 0;
    for (;;) {
        if (v == -1) break;
        useconds_t t = rand() % 10000;
        usleep(t * 1000);
        v = next_value(n, d, v);
        q_enqueue(q, v);
    }
}

下面是我测试的队列实现的其余部分,但您可能想研究无锁队列,看看如何在不需要传统临界区保护的情况下实现单消费者/单生产者队列。

#define Q_MAX_ELEM 1

struct queue_type {
    volatile uint32_t head;
    volatile uint32_t tail;
    volatile int q[Q_MAX_ELEM];
};

void q_init(struct queue_type *q) {
    static const struct queue_type q_zero;
    *q = q_zero;
}

bool q_is_empty(struct queue_type *q) {
    uint32_t tail = q->tail;
    return q->head == tail;
}

bool q_is_full(struct queue_type *q) {
    uint32_t head = q->head;
    return (q->tail - head) == Q_MAX_ELEM;
}

Try it online!

【讨论】:

    【解决方案2】:

    执行此操作的典型方法是使用 POSIX pipe() 函数创建一对在子节点和父节点之间共享的管道。 Linux 手册页甚至包含一个示例:https://www.man7.org/linux/man-pages/man2/pipe.2.html

    【讨论】:

    • 谢谢,但很遗憾我不能在这个作业中使用pipe()
    • 如果您的解决方案有限制(例如不使用管道),那么您需要在问题中说出来,@capncook。在人们使用 X、Y 或 Z 中的一个或多个提供答案后,返回“哦,但我不能使用 X、Y 或 Z”是不公平的。