Lab 2: Process & IPC

  1. fork()
    #include <stdio.h>
    #include <sys/types.h>
    #include <errno.h>
    
    int main(void) {
        pid_t pid;
    
        switch (pid = fork()) {
        case -1:
    	/* Error */
    	fprintf(stderr, "Failed to call fork(): %s\n", strerror(errno));
    	return (1);
        case 0:
    	/* Child process */
    	printf("I am a child process, ppid = %d, pid = %d\n",
    	       getppid(), getpid());
    	break;
        default:
    	/* Parent process */
    	printf("I am a parent process, ppid = %d, pid = %d\n",
    	       getppid(), getpid());
    
    	/* 
    	 * Parent process should call either wait(NULL) or wait(pid, NULL, 0)
    	 * to wait for the child process to exit 
    	 */
    	if (wait(NULL) < 0) {
    	    fprintf(stderr, "Failed to call wait(): %s\n", strerror(errno));
    	    return (1);
    	}
    	break;
        }
    
        return (0);
    }
    
  2. Argument list
    #include <stdio.h>
    
    int main(int argc, char *argv[]) {
        int i;
    
        printf("argc = %d\n", argc);
        for (i = 0; i < argc; i++) {
    	printf("argv[%d] = %s\n", i, argv[i]);
        }
    }
    
  3. Exec
    #include <stdio.h>
    #include <sys/types.h>
    #include <errno.h>
    
    /*
     * This may not work for all programs
     */
    void do_execve(void) {
        execve("./display", NULL, NULL);
    }
    
    /*
     * The guarantee way to work for all programs
     */
    void do_execve2(void) {
        char *argv[] = {
    	"display",
    	NULL
        };
    
        execve("./display", argv, NULL);
    }
    
    /*
     * Do /bin/ls
     */
    void do_execve3(void) {
        char *argv[] = {
    	"ls",
    	NULL
        };
    
        if (execve("/bin/ls", argv, NULL) < 0) {
    	fprintf(stderr, "Failed to call execve(): %s\n", strerror(errno));
        }
    }
    
    /*
     * Do /bin/ls -l -a
     */
    void do_execve4(void) {
        char *argv[] = {
    	"ls",
    	"-l",
    	"-a",
    	NULL
        };
    
        if (execve("/bin/ls", argv, NULL) < 0) {
    	fprintf(stderr, "Failed to call execve(): %s\n", strerror(errno));
        }
    }
    
    /*
     * One of my favor command :)
     */
    void do_execve5(void) {
        char *argv[] = {
    	"cat",
    	"/etc/passwd",
    	NULL
        };
    
        if (execve("/bin/cat", argv, NULL) < 0) {
    	fprintf(stderr, "Failed to call execve(): %s\n", strerror(errno));
        }
    }
    
    /*
     * Similar to do_execve3() but use ls instead of /bin/ls, which it won't work.
     * Expect error.
     */
    void do_execve6(void) {
        char *argv[] = {
    	"ls",
    	NULL
        };
    
        if (execve("ls", argv) < 0) {
    	fprintf(stderr, "Failed to call execve(): %s\n", strerror(errno));
        }
    }
    
    /*
     * Fix do_execve6() by calling execvp(), which will follow PATH and look for
     * the program
     */
    void do_execvp(void) {
        char *argv[] = {
    	"ls",
    	NULL
        };
    
        if (execvp("ls", argv) < 0) {
    	fprintf(stderr, "Failed to call execvp(): %s\n", strerror(errno));
        }
    }
    
    int main(void) {
        pid_t pid;
    
        switch (pid = fork()) {
        case -1:
    	/* Error */
    	fprintf(stderr, "Failed to call fork(): %s\n", strerror(errno));
    	return (1);
        case 0:
    	/* Child process */
    	do_execve();
    	/* Should not reach here */
    	break;
        default:
    	/* Parent process */
    
    	/* 
    	 * Parent process should call either wait(NULL) or wait(pid, NULL, 0)
    	 * to wait for the child process to exit 
    	 */
    	if (wait(NULL) < 0) {
    	    fprintf(stderr, "Failed to call wait(): %s\n", strerror(errno));
    	    return (1);
    	}
    	break;
        }
    
        return (0);
    }
    
  4. pipe() & fork()
    #include <stdio.h>
    #include <errno.h>
    #include <string.h>
    #include <unistd.h>
    #include <sys/wait.h>
    
    int main(void) {
        int fds[2];    /* fds[0] is for reading, fds[1] is for writing */
    
        /* Get a pipe */
        if (pipe(fds) < 0) {
    	fprintf(stderr, "Failed to call pipe(): %s\n", strerror(errno));
    	return (1);
        }
    
        switch (fork()) {
        case -1:
    	/* Error */
    	fprintf(stderr, "Failed to call fork(): %s\n", strerror(errno));
    	return (1);
        case 0: {
    	/* Child process */
    	char buf[128];
    	int n;
    
    	/* Close fds[1] */
    	close(fds[1]);
    
    	/* Read from fds[0] */
    	n = read(fds[0], buf, sizeof(buf));
    	printf("Parent said: \"%.*s\"\n", n, buf);
    
    	break;
        }
        default: {
    	/* Parent process */
    	char msg[] = "Go back to your room and think about what you've done!";
    
    	/* Close fds[0] */
    	close(fds[0]);
    
    	/* Write to from fds[1] */
    	write(fds[1], msg, sizeof(msg));      
    
    	wait(NULL);
        }
        }
    
        return (0);
    }
    
  5. dup() & dup2()
    #include <stdio.h>
    #include <unistd.h>
    
    int main(void) {
        /* Close stdout */
        close(1);
    
        /* 
         * Dup stderr to stdout.
         * ie. everything goes to stdout will now go to stderr
         */
        dup2(2, 1);
    
        /* Output something to stdout */
        printf("This goes to stderr, not stdout!\n");
    
        return (0);
    }
    
  6. toupper() & tolower()
    #include <stdio.h>
    #include <string.h>
    
    int main(void) {
        int i;
        char *hello = "Hello World!";
    
        for (i = 0; i < strlen(hello); i++) {
    	printf("%c", toupper(hello[i]));
        }
        printf("\n");
    
        return (0);
    }
    
  7. NOTE: The pipe arrangement in your lab2 is opposite to example in "Pipe & Fork". So, don't just cut and paste.
  8. It is always a good practice to check the return values of system/function calls and document your code.