Golang 创建进程

golang 封装了一系列方法用来创建一个进程,这里自习看看分别是如何实现的。

一个简单的创建进程的代码如下

package main

import (
    "fmt"
    "os"
)

func main() {
    name := "ls"
    args := []string{"/"}
    attr := &os.ProcAttr{}
    proc, err := os.StartProcess(name, args, attr)

    if err != nil {
        fmt.Println(err)
    }

    _, err = proc.Wait()
    if err != nil {
        fmt.Println(err)
    }
}

os.StartProcess

StartProcess 方法如下

// StartProcess starts a new process with the program, arguments and attributes
// specified by name, argv and attr.
// StartProcess 使用提供的程序名、命令行参数、属性这三个参数来开启一个新进程。
//
// StartProcess is a low-level interface. The os/exec package provides
// higher-level interfaces.
// 它是一个低级接口,os/exec 包提供了高级的接口。
//
// If there is an error, it will be of type *PathError.
// 如果出错,错误的类型会是*PathError。
func StartProcess(name string, argv []string, attr *ProcAttr) (*Process, error) {
    return startProcess(name, argv, attr)
}

这个方法调用了 startProcess,我们看看这个方法

func startProcess(name string, argv []string, attr *ProcAttr) (p *Process, err error) {
    // If there is no SysProcAttr (ie. no Chroot or changed
    // UID/GID), double-check existence of the directory we want
    // to chdir into. We can make the error clearer this way.
    if attr != nil && attr.Sys == nil && attr.Dir != "" {
        if _, err := Stat(attr.Dir); err != nil {
            pe := err.(*PathError)
            pe.Op = "chdir"
            return nil, pe
        }
    }

    sysattr := &syscall.ProcAttr{
        Dir: attr.Dir,
        Env: attr.Env,
        Sys: attr.Sys,
    }
    if sysattr.Env == nil {
        sysattr.Env = Environ()
    }
    for _, f := range attr.Files {
        sysattr.Files = append(sysattr.Files, f.Fd())
    }

    pid, h, e := syscall.StartProcess(name, argv, sysattr)
    if e != nil {
        return nil, &PathError{"fork/exec", name, e}
    }
    return newProcess(pid, h), nil
}

这段代码主要在处理 ProcAttr 这个结构,我们看看这个结构的样子

// ProcAttr holds the attributes that will be applied to a new process
// started by StartProcess.
type ProcAttr struct {
    // If Dir is non-empty, the child changes into the directory before
    // creating the process.
    // 如果 Dir 非空,子进程会在创建 Process 实例之前会先进入该目录。即将这个忙碌碌作为子进程的工作目录
    Dir string
    // If Env is non-nil, it gives the environment variables for the
    // new process in the form returned by Environ.
    // If it is nil, the result of Environ will be used.
    // 如果 Env 非空,它为作为新进程的环境变量。比如采用 Environ 返回值的格式
    // 如果 Env 为 nil,将使用 Environ 函数的返回值
    Env []string
    // Files specifies the open files inherited by the new process. The
    // first three entries correspond to standard input, standard output, and
    // standard error. An implementation may support additional entries,
    // depending on the underlying operating system. A nil entry corresponds
    // to that file being closed when the process starts.
    // Files 指定被新进程继承的打开文件的对象
    // 前三个绑定为标准输入,标准输出,标准错误输出。
    // 依赖底层操作系统的实现可能还会有其他额外支持的文件对象
    // 如果传入的条目是nil,该进程启动时,file就是关闭的
    Files []*File

    // Operating system-specific process creation attributes.
    // Note that setting this field means that your program
    // may not execute properly or even compile on some
    // operating systems.
    // 操作系统特定的创建属性
    // 注意设置本字段意味着你的程序可能会执行异常甚至在某些操作系统中无法通过编译。这时候可以通过为特定系统设置。
    Sys *syscall.SysProcAttr
}

type SysProcAttr struct {
    Chroot     string      // 用于改变进程的根目录,使用的 chroot 系统调用
    Credential *Credential // 进程凭证,Unix 中进程都有一套数字表示用户 ID 和 组 ID,这些就是凭证
    Ptrace     bool        // 是否允许 tracing
    Setsid     bool        // 创建 session.
    Setpgid    bool        // 是否设组 ID 给新进程
    Setctty    bool        // 是否可以使用终端访问
    Noctty     bool        // 将 fd 0 从终端分离
    Ctty       int         // Controlling TTY fd
    Foreground bool        // Place child's process group in foreground. (Implies Setpgid. Uses Ctty as fd of controlling TTY)
    Pgid       int         // Child's process group ID if Setpgid.
}

再回头看 startProcess 的代码。

如果 Dir 不为空,首先检查 Dir 是否合法,然后把 os.ProcAttr 的值放入到 syscall.ProcAttr 结构体内。最后调用了 syscall.StartProcess。

其实 os.StartProcess 和 syscall.StartProcess 是差不多的,只是多了一层封装,这层封装主要用来处理垃圾回收了。

接下来 syscall.StartProcess 调用了 forkexec 方法,这个处理了 wait 操作和底层的 fork 操作。现在我也还不清楚,以后再研究看看。

参考:

http://allragedbody.blog.51cto.com/1352220/1747146

https://qianlonggit.gitbooks.io/the-golang-standard-library-by-example/content/chapter10/10.2.html