Learning_C

fork

fork makes a copy of the clone of the current process, and its return value indicates which fork you're in at run time: 0 for the child fork and child_pid if you are in the parent.

Interestingly, virtual addresses of variables stay the same, but since the process is cloned, the data between forks stays isolated:

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <unistd.h>

void wait_or_die() {
    int rc = wait(NULL);
    assert(rc > 0);
}

int fork_or_die() {
    int rc = fork();
    assert(rc >= 0);
    return rc;
}

int main(int argc, char *argv[]) {
    // process a
    int *x = malloc(sizeof(int));

    printf("BEFORE *x = 100:\n");
    printf("pid: %d; &x: %p, *x: %d\n", getpid(), x, *x);
    *x = 100;
    printf("AFTER *x = 100:\n");
    printf("pid: %d; &x: %p, *x: %d\n\n", getpid(), x, *x);

    sleep(2);

    if (fork_or_die() == 0) {

        printf("BEFORE *x = 200:\n");
        printf("pid: %d; &x: %p, *x: %d\n", getpid(), x, *x);
        *x = 200;
        printf("AFTER *x = 200:\n");
        printf("pid: %d; &x: %p, *x: %d\n\n", getpid(), x, *x);

        sleep(2);
        // process b
        exit(0);
    }

    printf("BEFORE *x = 300:\n");
    printf("pid: %d; &x: %p, *x: %d\n", getpid(), x, *x);
    *x = 300;
    printf("AFTER *x = 300:\n");
    printf("pid: %d; &x: %p, *x: %d\n\n", getpid(), x, *x);

    wait_or_die();

    printf("BEFORE *x = 400:\n");
    printf("pid: %d; &x: %p, *x: %d\n", getpid(), x, *x);
    *x = 400;
    printf("AFTER *x = 400:\n");
    printf("pid: %d; &x: %p, *x: %d\n\n", getpid(), x, *x);
    return 0;
}

Results in:

BEFORE *x = 100:
pid: 15868; &x: 0x564b24d7c2a0, *x: 0
AFTER *x = 100:
pid: 15868; &x: 0x564b24d7c2a0, *x: 100

BEFORE *x = 300:
pid: 15868; &x: 0x564b24d7c2a0, *x: 100
AFTER *x = 300:
pid: 15868; &x: 0x564b24d7c2a0, *x: 300

BEFORE *x = 200:
pid: 15869; &x: 0x564b24d7c2a0, *x: 100
AFTER *x = 200:
pid: 15869; &x: 0x564b24d7c2a0, *x: 200

BEFORE *x = 400:
pid: 15868; &x: 0x564b24d7c2a0, *x: 300
AFTER *x = 400:
pid: 15868; &x: 0x564b24d7c2a0, *x: 400

(Note the *x value before setting to 200 in the child is still 100 from before the fork.)

If a child process closes stdout, it can't print to it, but the parent still can.

exec

Calling exec completely replaces the current process with a new, potentially different one. Combined with fork this allows creation of arbitrary new processes.

wait

Calling wait without a child process returns -1.

pipe

The pipe sys call sets up a read descriptor and a write descriptor for two processes to communicate. This along with dup can be used to simulate a shell pipe, where stdout from one child process goes to stdin of another:

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <unistd.h>

void wait_or_die() {
    int rc = wait(NULL);
    assert(rc > 0);
}

int fork_or_die() {
    int rc = fork();
    assert(rc >= 0);
    return rc;
}

int main(int argc, char *argv[]) {
    int pipefd[2];

    if(pipe(pipefd) == -1) {
      perror("pipe");
      exit(EXIT_FAILURE);
    }

    // first child writes to the pipe
    if (fork_or_die() == 0) {
        // close pipe read desc
        close(pipefd[0]);
        // close stdout
        close(STDOUT_FILENO);
        // replace stdout with the pipe write desc
        dup(pipefd[1]);

        printf("PRINT SOME STUFF\n");
        exit(0);
    }

    // second child reads from the pipe
    if (fork_or_die() == 0) {
        // close pipe write desc
        close(pipefd[1]);
        // close stdin
        close(STDIN_FILENO);
        // replace stdin with the pipe read desc
        dup(pipefd[0]);

        char str[1000];
        fgets(str, sizeof(str), stdin);
        printf("Received: %s\n from stdin", str);
        exit(0);
    }

    wait_or_die();

    wait_or_die();

    return 0;
}