简体   繁体   中英

Passing information between child and parent process - linux (without `pipe()`)

As part of my assignment, I have to implement child-parent processes.

"The parent process shall NOT wait for the child to exit but instead shall prints an element as soon as it arrives into the shared buffer ".

I know the case when the parent has to wait for child to end, but how to implement communication of message from child to parent in async way?

PS The exact Q is

Write a program whose main routine obtains two parameters n and d from the user, ie passed to your program when it was invoked from the shell. Your program shall then create a shared memory and a child process. The child process should obtain the values of n and d (you have multiple choices on how to do that) and create an arithmetic sequence of length n, and whose first element is 0 and each subsequent element has the value of kd, where k is the element number (k= 0 to n-1). The child process shall create the elements, one at a time and wait for a random interval of time (0 to 9.999 seconds) between generating elements of the sequence. As soon as an element is generated, the child places the element in the shared buffer by organizing it as described in slides 33-37 of lecture 4. (eg: if n=5 and d=2, the sequence shall be 0,2,4,6,8) The parent process shall NOT wait for the child to exit but instead shall prints an element as soon as it arrives into the shared buffer (again, in a manner similar to slides 33-37 of lecture 4) Hint; use fflush() to ensure printf's are printed immediately into the screen.

My code so far - 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;
}

The typical way to do this would be to create a pair of pipes shared between the child and parent, with the POSIX pipe() function. The Linux manual page even contains an example: https://www.man7.org/linux/man-pages/man2/pipe.2.html

Since you are acquiring the values from the command line at the time the program is invoked by the shell, when the parent calls fork() , the child will have access to the same variables. There is no need to "pass" the values from parent to child.

When the child creates the elements, I assume the parent is reading the elements from the shared memory. The "as soon as it arrives" is a somewhat dubious requirement unless you are on a parallel and real-time system. However, simulating that requirement can be achieved with a busy wait loop of the parent checking to see if the shared memory has acquired new data.

In order to not starve the operating system of CPU cycles while the parent is in the busy wait loop, the parent can call sched_yield() between check iterations, which behaves like a sleep that instantly wakes up.

The shared memory between parent and child can be treated as some kind of queue, and the busy wait of the parent is checking if the queue is non-empty, and if so, processes queue elements until it is empty, at which point it busy waits again.

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

Instead of sched_yield() , you could use usleep() or nanosleep() with a value of 1.

The child is of course adding elements to the queue, following the stipulations of the assignment.

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

You may want to add an indication (such as enqueueing -1 ) that the child is done. The child can then exit, and the parent can know it is safe to reap the child.

The full check and empty check can logically be viewed as part of the enqueue and dequeue operations themselves. So, a possible implementation may be:

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;
}

And then the parent and child functions might look like:

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);
    }
}

Below is the rest of the queue implementation I tested with, but you may want to research lock-free queues to see how you might implement single consumer / single producer queues without the need for traditional critical section protection.

#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;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM