背景

forkmaster-slaveforkgoroutinefork
fork + exec
fork

为了实现上述需求,我们需要利用Go语言的一些特性进行模拟。

C语言的实现方式

在了解Go如何实现fork之前,先看一下C里的传统使用方式:

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

void child() {
    printf("child process\n");
}

int main() {
    printf("main process\n");
    pid_t pid = fork();
    int wstatus;

    if (pid == 0) {
        child();
    } else {
        printf("main exit\n");
        waitpid(pid, &wstatus, 0);
    }
}

运行一下:

$ gcc main.c && ./a.out 
main process
main exit
child process

Go语言的实现方案

然后我们看看Docker提供的实现fork的方式:

package main

import (
	"log"
	"os"

	"github.com/docker/docker/pkg/reexec"
)

func init() {
	log.Printf("init start, os.Args = %+v\n", os.Args)
	reexec.Register("childProcess", childProcess)
	if reexec.Init() {
		os.Exit(0)
	}
}

func childProcess() {
	log.Println("childProcess")
}

func main() {
	log.Printf("main start, os.Args = %+v\n", os.Args)
	cmd := reexec.Command("childProcess")
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	if err := cmd.Start(); err != nil {
		log.Panicf("failed to run command: %s", err)
	}
	if err := cmd.Wait(); err != nil {
		log.Panicf("failed to wait command: %s", err)
	}
	log.Println("main exit")
}

运行一下:

$ go run main.go
2018/03/08 19:52:39 init start, os.Args = [/tmp/go-build209640177/b001/exe/main]
2018/03/08 19:52:39 main start, os.Args = [/tmp/go-build209640177/b001/exe/main]
2018/03/08 19:52:39 init start, os.Args = [childProcess]
2018/03/08 19:52:39 childProcess
2018/03/08 19:52:39 main exit
fork
initmainos.Args[0]reexec.Commandos.Args[0]reexec.Initreexec.Registeros.Exit(0)fork

总结

runtimeinitmainforkfork