Homework for Chapter 5 of 《Operating Systems: Three Easy Pieces》: Process API

  •  42 views
  • 1.Let’s see what if we change the variant v separately.

    root@C202511211157657:~# gcc main.c -o a.out
    root@C202511211157657:~# cat main.c
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <string.h>
    #include <fcntl.h>
    #include <sys/wait.h>
    
    int main(int argc, char *argv[])
    {
        int v = 0;
        int rc = fork();
        if (rc < 0) {    // fork failed; exit
            fprintf(stderr, "fork failed\n");
            exit(1);
        } else if (rc == 0) { // child: redirect standard output to a file
            v = 100;
            printf("child %d\n", v);
        } else {                           // parent goes down this path (main)
            int wc = wait(NULL);
            printf("parent %d\n", v);
        }
        return 0;
    }
    
    root@C202511211157657:~# ./a.out
    child 100
    parent 0

    I have heard of the “copy-on-write” strategy and think that’s what had happened. Variant v is stored in the stack, and when we fork a process, the data stored in the stack will be copied if we modified it. So, when we change the value in the child process, the parent’s value will be kept intact, vice versa:

    root@C202511211157657:~# gcc main.c -o a.out
    root@C202511211157657:~# cat main.c
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <string.h>
    #include <fcntl.h>
    #include <sys/wait.h>
    
    int main(int argc, char *argv[])
    {
        int v = 0;
        int rc = fork();
        if (rc < 0) {    // fork failed; exit
            fprintf(stderr, "fork failed\n");
            exit(1);
        } else if (rc == 0) { // child: redirect standard output to a file
            v = 100;
            printf("child %d\n", v);
        } else {                           // parent goes down this path (main)
            int wc = wait(NULL);
            printf("parent %d\n", v);
        }
        return 0;
    }
    
    root@C202511211157657:~# ./a.out
    child 100
    parent 0
    root@C202511211157657:~# vi main.c
    root@C202511211157657:~# gcc main.c -o a.out
    root@C202511211157657:~# ./a.out
    parent makes some change to v:1
    child 0
    root@C202511211157657:~# ./a.out
    parent makes some change to v:1
    child 0
    root@C202511211157657:~# ./a.out
    parent makes some change to v:1
    child 0
    root@C202511211157657:~# ./a.out
    parent makes some change to v:1
    root@C202511211157657:~# child 0
    (it hanged up)

    When the parent branch is reached earlier, it changes its value, but the latter child’s value is kept intact, too.
    Also I noticed that when the child branch is reached earlier, the process froze as the child process finished but we didn’t recycle it, so it becomes a zombie process.

    1. 
      root@C202511211157657:~# cat main.c
      #include <stdio.h>
      #include <stdlib.h>
      #include <unistd.h>
      #include <string.h>
      #include <fcntl.h>
      #include <sys/wait.h>

    int main(int argc, char *argv[])
    {
    close(STDOUT_FILENO);
    open(“./p4.output”, O_CREAT|O_WRONLY|O_TRUNC, S_IRWXU);
    int v = 0;
    int rc = fork();
    if (rc < 0) { // fork failed; exit
    fprintf(stderr, “fork failed\n”);
    exit(1);
    } else if (rc == 0) { // child: redirect standard output to a file
    printf(“child %d\n”, v);
    } else { // parent goes down this path (main)
    printf(“parent makes some change to v:%d\n”, ++v);
    }
    return 0;
    }

    root@C202511211157657:# ./a.out root@C202511211157657:# cat p4.output
    parent makes some change to v:1
    child 0

    Both process can write to the file, nothing special.
    (Revised: the offset will be shared by the process!)
    
    3.
    I would like to use shared variant at first but it failed:
    ```c
    root@C202511211157657:~# cat main.c
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <string.h>
    #include <fcntl.h>
    #include <sys/wait.h>
    
    int main(int argc, char *argv[])
    {
        static int child_run = 0;
        int v = 0;
        int rc = fork();
        if (rc < 0) {    // fork failed; exit
            fprintf(stderr, "fork failed\n");
            exit(1);
        } else if (rc == 0) {
            printf("hello\n");
            child_run = 1;
        } else {                           // parent goes down this path (main)
            while(child_run == 0);
            printf("goodbye\n");
        }
        return 0;
    }
    
    root@C202511211157657:~# gcc main.c -o a.out
    root@C202511211157657:~# ./a.out
    hello
    ^C

    I thought it was the child process that did not ends properly but soon I found myself wrong. It’s because all the memory address space will be different from the parent’s one, so it includes the static variant!
    ChatGPT tells me I can use pipe to implement real shared variant and I did so and succeeded:

    ~# cat share.c
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <string.h>
    #include <fcntl.h>
    #include <sys/wait.h>
    #include <sys/mman.h>
    
    int main(int argc, char *argv[])
    {
        int *child_run = mmap(NULL, sizeof(int),
                              PROT_READ | PROT_WRITE,
                              MAP_SHARED | MAP_ANONYMOUS,
                              -1, 0);
        *child_run = 0;
    
        int rc = fork();
        if (rc < 0) {
            fprintf(stderr, "fork failed\n");
            exit(1);
        } else if (rc == 0) {
            printf("hello\n");
            *child_run = 1;
        } else {
            while (*child_run == 0);
            printf("goodbye\n");
        }
    
        return 0;
    }
    
    root@C202511211157657:~# gcc share.c -o share.out
    root@C202511211157657:~# ./share.out
    hello
    goodbye
    root@C202511211157657:~# ./share.out
    hello
    goodbye
    root@C202511211157657:~# ./share.out
    hello
    goodbye
    ...(Always like that)

    However, it’s not a good practice since it make cpu busy.

    1. Omitted. Because C doen’t support default parameters.

    2. 
      root@C202511211157657:~# ./a.out
      ...
      goodbye, ret=700336, status=0root@C202511211157657:~# cat main.c
      #include <stdio.h>
      #include <stdlib.h>
      #include <unistd.h>
      #include <string.h>
      #include <fcntl.h>
      #include <sys/wait.h>

    int main(int argc, char argv[])
    {
    static int child_run = 0;
    int v = 0;
    int rc = fork();
    if (rc < 0) { // fork failed; exit
    fprintf(stderr, “fork failed\n”);
    exit(1);
    } else if (rc == 0) {
    char
    args[] = {“/bin/ls”, NULL};
    execlp(“/bin/ls”, “ls”, NULL);
    printf(“hello\n”);
    child_run = 1;
    exit(0);
    } else { // parent goes down this path (main)
    int status;
    pid_t pid = wait(&status);
    printf(“goodbye, ret=%d, status=%d”, pid, status);
    }
    return 0;
    }

    It returns PID and exit status (from param) of the subprocess.
    When you execute wait() in the subprocess, it returns -1.
    
    6. waitpid() likes a more specific version of wait. It has one more arguments called pid.
    When pid == 0, it waits one of subprocesses in the same group of the caller to finish;
    When pid < 0, it waits the process group |pid| to finish;
    When pid > 0, it waits the specific process of this pid to finish,
    
    7.Let's have a try:
    root@C202511211157657:~# cat main.c
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <string.h>
    #include <fcntl.h>
    #include <sys/wait.h>
    
    int main(int argc, char *argv[])
    {
        static int child_run = 0;
        int v = 0;
        int rc = fork();
        if (rc < 0) {    // fork failed; exit
            fprintf(stderr, "fork failed\n");
            exit(1);
        } else if (rc == 0) {
            close(STDOUT_FILENO);
            printf("hello\n");
            exit(0);
        } else {                           // parent goes down this path (main)
            int status;
            pid_t pid = wait(&status);
        }
        return 0;
    }
    
    root@C202511211157657:~# ./a.out
    root@C202511211157657:~#
    
    Well, it outputs nothing.
    
    8. 
    ```shell
    root@C202511211157657:~# cat pipe.c
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <string.h>
    #include <fcntl.h>
    #include <sys/wait.h>
    
    int main(int argc, char *argv[])
    {
        int fd[2];
        int p = pipe(fd);
        int v = 0;
    
        int buf;
        if (p == -1) {    // pipe create failed; exit
            fprintf(stderr, "pipe create failed\n");
            exit(1);
        }
        int rc = fork();
        if (rc < 0) {    // fork failed; exit
            fprintf(stderr, "fork failed\n");
            exit(1);
        } else if (rc == 0) {
            close(fd[0]);
            dup2(fd[1], STDOUT_FILENO);
            close(fd[1]);
            printf("hello, greeting from process 1\n");
            printf("hope you can see it\n");
            exit(0);
        }
    
        int rc2 = fork();
        if (rc2 < 0) {    // fork failed; exit
            fprintf(stderr, "fork2 failed\n");
            exit(1);
        } else if (rc2 == 0) {
            close(fd[1]);
            dup2(fd[0], STDIN_FILENO);
            close(fd[0]);
            char buf[100];
            while (gets(buf)) printf("process2: %s", buf);
            exit(0);
        }
        close(fd[0]);
        close(fd[1]);
        (void)wait(NULL);
        (void)wait(NULL);
        return 0;
    }
    root@C202511211157657:~# ./p.out
    process2: hello, greeting from process 1process2: hope you can see itroot@C202511211157657:~#

    I don’t know a thing about pipe at the first time. But now I think I can explain something about this code for you:
    To finish this problem, we need to know how file descriptor is. It’s some data that a process owns. You can treat it as a int, in core, operating system would use such a int to locate a specific file object. For example: for 0, it corresponds to the standard input (Yes, I think you can treat standard input as a file), and for 1, it corresponds to the standard output.
    A process, by default, has 3 file descriptors, stdin, stdout and stderr. They store in the container for file descriptors of the process.
    And if we create a pipe for a process, it adds two more file descriptors in that container for file descriptors, which is like “pipe_in -> 3, pipe_out -> 4” (Typically they are 3 and 4, but that is not guaranteed). When we use fork(), we copy the container for each copy of the process.
    So, if we want to let the stdout of one process be the stdin of the other one, for one of those two subprocesses, we can modify what stdout, the file descriptor, actually points to (a file object), to the writing end of the pipe, which is also a file object. And for the other process, we can modify what stdin actually points to, to the reading end to the pipe.
    After the modification above, it’ll be like:

    subprocess1: I would like to use printf() to output something to the console.
    os: Ok, let's see what stdout actually points to, and write the data to it.
    (Something is written to the buffer of the pipe file object, and the writing end of the pipe closed)
    subprocess2: I was blocked by the gets() at first, but now I received a end of file. Let's see what can we get from the stdin.
    os: Ok, I would lookup the file object that the stdin points to and read data from it.
    (System fetch the data from the reading end from the pipe, and subprocess2 gets it)

    But how can we do the “modification”? That’s what dup2() do.
    dup2(newfd, oldfd);
    It redirects oldfd to the file object of oldfd. (Actually it will decrease the reference count of the file object that oldfd points to)
    And that’s my implementation for this problem.
    P.S: You need to be careful about the reference count, as it’s vital or it will block your process since it won’t receive a proper EOF(End of file).

    Leave a Reply

    Your email address will not be published. Required fields are marked *