Process and Thread

进程的创建

fork() 是类 Unix 系统中创建进程的基本操作,其具有 “调用一次,返回两次”、“复制但分离的地址空间”、“文件共享”、以及 “并发执行” 这些特点。运行并理解如下程序的输出,同时可使用 strace -f 来追踪程序执行过程中(包括子进程)产生的系统调用。

#include <stdio.h>
#include <unistd.h>

int count = 0;

int main(void) {
  fork();
  fork();

  for (int i = 0 ; i < 1000 ; i++)
    count++;
  
  printf("process (%d): %d\n", getpid(), count);
  return 0;
}
#include <stdio.h>
#include <unistd.h>

int main(void) {
  for (int i = 0 ; i < 2 ; i++) {
    fork();
    printf("hello\n");
  }
  return 0;
}

进程的改变

很多时候父子进程并不是同样的执行逻辑,例如 Shell 创建进程并不是为了创建一个 Shell 子进程,而是 “加载” 某个可执行文件来运行,execve() 就是这样的系统调用。

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

int main(int argc, char *argv[]) {
  int rc = fork();
  if (rc < 0) {
    perror("fork failed\n");
    exit(1);
  } else if (rc == 0) {
    printf("[%d] child process\n", (int) getpid());
    // run word count program /bin/wc with arguments and environment variables
    char *args[] = {"/bin/wc", "fork.c", NULL}; 
    char *envp[] = {"KEY=Value", NULL};
    execve(args[0], args, envp);
    // the followings should not be executed if execve is succesfully executed
    printf("Hello?\n");
  } else {
    wait(NULL);
    printf("[%d] parent process\n", (int) getpid());
  }
  return 0;
}

两个系统调用

类 Unix 系统使用 fork() + execve() 的方式来运行一个新的应用程序,这种方式可以在执行 fork() 之后、但在执行 execve() 之前对子进程的执行环境进行修改,从而实现一些特定的能力。

重定向

例如,可以在 execve() 之前通过精心操作文件描述符 (file descriptor) 来实现标准输出 stdout 的重定向 (例如实现 Shell 中的 wc fork.c > output.txt)。

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

int main(int argc, char *argv[]) {
  int rc = fork();
  if (rc < 0) {
    perror("fork failed\n");
    exit(1);
  } else if (rc == 0) {
    // redirect standard output to a file
    close(STDOUT_FILENO); 
    open("./output.txt", O_CREAT|O_WRONLY|O_TRUNC, S_IRWXU);
    // now run "wc"
    char *args[] = {"wc", "fork.c", NULL}; 
    execvp(args[0], args);    
  } else {
    wait(NULL);
  }
  return 0;
}

管道

通过 pipe() 系统调用,我们可以创建一个匿名管道(pipe)对象来将父子进程的输出和输入连接起来,通过父进程向管道 write()、子进程从管道 read() 的方式实现进程间的通信 (例如实现 Shell 中的 cat a.txt b.txt | sort)。下面代码给出了创建管道的一个简单示例:

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

#define MSGSIZE 10
char *msg[] = {"Message #1", "Message #2", "Message #3"};

int main(void) {
  char inbuf[MSGSIZE];
  int fd[2];

  if (pipe(&fd[0]) < 0) {
    perror("pipe failed!\n");
    exit(1);
  } else { 
    printf("pipe file descriptor: fd[0] = %d, fd[1] = %d\n", fd[0], fd[1]);

    int pid = fork();
    if (pid < 0) {
      perror("fork failed!\n");
      exit(1);
    } else if (pid > 0) {
      // parent process (writer)
      close(fd[0]);
      for (int i = 0; i < 2; i++) {
        write(fd[1], msg[i], MSGSIZE);
        printf("Send: %s\n", msg[i]);
        sleep(2);
      }
    } else {
      // child process (reader)
      close(fd[1]);
      for (int j = 0 ; j < 3 ; j++) {
        int ret = read(fd[0], inbuf, MSGSIZE);
        printf("Receive: %s (ret = %d)\n", inbuf, ret);
      }
    }
  }
  return 0;
}

POSIX API

posix_spwan() 是基于 POSIX 标准的创建进程并执行某个特定文件的方法,其提供一个统一的接口来实现 fork() + execve() 的能力。

#include <stdio.h>
#include <stdlib.h>
#include <spawn.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>

extern char **environ;  // environment variables

int main(void) {
  pid_t pid;
  char *argv[] = {"wc", "fork.c", NULL};

  // create a posix_spawn_file_actions_t object
  posix_spawn_file_actions_t file_actions;
  if (posix_spawn_file_actions_init(&file_actions) != 0) {
    perror("posix_spawn_file_actions_init");
    return 1;
  }

  // open a file
  int fd = open("output.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
  if (fd < 0) {
    perror("open");
    return 1;
  }

  // redirect stdout to the above file 
  if (posix_spawn_file_actions_adddup2(&file_actions, fd, STDOUT_FILENO) != 0) {
    perror("posix_spawn_file_actions_adddup2");
    return 1;
  }

  // create a child process and execute it
  int ret = posix_spawnp(&pid, argv[0], &file_actions, NULL, argv, environ);
  if (ret != 0) {
    errno = ret;
    perror("posix_spawnp");
    return 1;
  }

  close(fd);
  posix_spawn_file_actions_destroy(&file_actions);

  // wait for child
  wait(NULL);
  return 0;
}