Go:创建新进程(os.StartProcess源码解读)
关于如何使用go语言实现新进程的创建和进程间通信,我在网上找了不少的资料,但是始终未能发现让自己满意的答案,因此我打算自己来分析这部分源代码,然后善加利用,并且分享给大家,期望大家能从中获得启发。
首先我们来看一段代码
proc, _ := os.StartProcess(name, args, attr)
if err != nil {
fmt.Println(err)
}
_, err = proc.Wait()
if err != nil {
fmt.Println(err)
}
我们来看看这个os.StartProcess里面到底做了什么东西? 而 proc.Wait()又做了什么?跟我一起深入进去吧。
// StartProcess starts a new process with the program, arguments and attributes
// specified by name, argv and attr.
//
// StartProcess is a low-level interface. The os/exec package provides
// higher-level interfaces.
//
// If there is an error, it will be of type *PathError.
func StartProcess(name string, argv []string, attr *ProcAttr) (*Process, error) {
return startProcess(name, argv, attr)
}
注释是说,这个函数依照提供三个参数来实现开启新进程的操作,它是一个低级接口,而os/exec包装提供高级接口。如果这里出现报错,应该会是一个指针型路径错误。
下一步我们探究startProcess是什么?
func startProcess(name string, argv []string, attr *ProcAttr) (p *Process, err error) {
sysattr := &syscall.ProcAttr{
Dir: attr.Dir,
Env: attr.Env,
Sys: attr.Sys,
}
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
}
首先我们看到sysattr被赋予一个 &syscall.ProcAttr指针,这个syscall里面的ProcAttr是什么结构呢,要先理解它,这样有助于理解我们利用它来启动后面的syscall
// 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 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 []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 []*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
}
第一句简单明了,说明了ProcAttr结构中包含了我们启动进程过程中使用的多项属性值,
1)Dir是目录的意思,相当于新进程的工作目录,如果配置了就会跳转目录。
2)Env是指新的进程的环境变量列表。
3)Files前三项对应标准输入,标准输出和标准错误输出。每个实现可以支持其他条目,如果传入的条目是nil,该进程启动时,file就是关闭的。
4)最后一个*syscall.SysProcAttr就是系统属性,不过作者也提醒道有些参数在跨平台过程中有可能不起作用。
下面我们看下*syscall.SysProcAttr结构。
type SysProcAttr struct {
Chroot string // Chroot.
Credential *Credential // Credential.
Ptrace bool // Enable tracing.
Setsid bool // Create session.
Setpgid bool // Set process group ID to new pid (SYSV setpgrp)
Setctty bool // Set controlling terminal to fd 0
Noctty bool // Detach fd 0 from controlling terminal
}
// Credential holds user and group identities to be assumed
// by a child process started by StartProcess.
type Credential struct {
Uid uint32 // User ID.
Gid uint32 // Group ID.
Groups []uint32 // Supplementary group IDs.
}
可以看到这里面所涉及到的属性。(部分属性跨平台不起作用)
1)Chroot
2) Credential包括uid\gid\groups设定
3)一些bool属性,参与设定新进程的使用过程。
Ptrace 是否允许tracing
Setsid 是否开启sid
Setpgid 是否设定组id给新进程
Setctty 是否可以使用终端访问
Noctty 将终端和fd0 进行分离。
OK,现在我们了解了这么多之后,还是谁去看看前面的代码吧。如下:
func startProcess(name string, argv []string, attr *ProcAttr) (p *Process, err error) {
sysattr := &syscall.ProcAttr{
Dir: attr.Dir,
Env: attr.Env,
Sys: attr.Sys,
}
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
}
继续看startProcess
sysattr := &syscall.ProcAttr{
Dir: attr.Dir,
Env: attr.Env,
Sys: attr.Sys,
}
Dir工作目录,Env环境变量、Sys 内容被赋予了sysattr 。
for _, f := range attr.Files {
sysattr.Files = append(sysattr.Files, f.Fd())
}
文件Files属性被安排加入到sysattr中,这样我们就把attr *ProcAttr参数的整体内容都赋予了sysattr ,下面看如何利用这个sysattr
pid, h, e := syscall.StartProcess(name, argv, sysattr) sysattr作为第三项参数传入了新的
syscall.StartProcess(name, argv, sysattr)
注意:这里我们注意到一个问题,看看我们期初的代码
proc, _ := os.StartProcess(name, args, attr)
if err != nil {
fmt.Println(err)
}
这一行代码和我们的期初的代码是多么相像啊,于是我们明白调用os的StartProcess就是调用syscall.StartProcess,因此我们明白,syscall.StartProcess属于底层调用。os.StartProcess是上层调用。os.StartProces只是在syscall.StartProcess外面包装了一层而已,因此,我们明白,当我们想新创建一个进程的时候,只要参数都已经输入完毕,我们既可以使用os.StartProcess来实现,也可以使用syscall.StartProcess来实现。只不过需要注意的是,两者返回的对象不相同。
怎么个不相同呢?
我们看到了os.StartProcess 返回的是return newProcess(pid, h), nil, 而
syscall.StartProcess返回的是pid, h, e
也就是说os.StartProcess 返回的是syscall.StartProcess返回值对pid和h的包装的结果。
// Process stores the information about a process created by StartProcess.
type Process struct {
Pid int
handle uintptr
isdone uint32 // process has been successfully waited on, non zero if true
}
func newProcess(pid int, handle uintptr) *Process {
p := &Process{Pid: pid, handle: handle}
runtime.SetFinalizer(p, (*Process).Release)
return p
}
通过观察这个包装的过程我们明白,之所以返回这个结果的目的是为了处理一些程序在进行时过程中的问题。下面我们就得了解下程序运行时的概念。
runtime.SetFinalizer(p, (*Process).Release)这一行在做什么呢?
这部分就是难点了,如果理解了这部分就会了解程序为什么包装了这一层,它的目的何在。
下面则是一大段英文。我门来试着理解一下。该段英文引用自 malloc.go.
// SetFinalizer sets the finalizer associated with x to f.
// When the garbage collector finds an unreachable block
// with an associated finalizer, it clears the association and runs
// f(x) in a separate goroutine. This makes x reachable again, but
// now without an associated finalizer. Assuming that SetFinalizer
// is not called again, the next time the garbage collector sees
// that x is unreachable, it will free x.
//
// SetFinalizer(x, nil) clears any finalizer associated with x.
//
// The argument x must be a pointer to an object allocated by
// calling new or by taking the address of a composite literal.
// The argument f must be a function that takes a single argument
// to which x's type can be assigned, and can have arbitrary ignored return
// values. If either of these is not true, SetFinalizer aborts the
// program.
//
// Finalizers are run in dependency order: if A points at B, both have
// finalizers, and they are otherwise unreachable, only the finalizer
// for A runs; once A is freed, the finalizer for B can run.
// If a cyclic structure includes a block with a finalizer, that
// cycle is not guaranteed to be garbage collected and the finalizer
// is not guaranteed to run, because there is no ordering that
// respects the dependencies.
//
// The finalizer for x is scheduled to run at some arbitrary time after
// x becomes unreachable.
// There is no guarantee that finalizers will run before a program exits,
// so typically they are useful only for releasing non-memory resources
// associated with an object during a long-running program.
// For example, an os.File object could use a finalizer to close the
// associated operating system file descriptor when a program discards
// an os.File without calling Close, but it would be a mistake
// to depend on a finalizer to flush an in-memory I/O buffer such as a
// bufio.Writer, because the buffer would not be flushed at program exit.
//
// It is not guaranteed that a finalizer will run if the size of *x is
// zero bytes.
//
// It is not guaranteed that a finalizer will run for objects allocated
// in initializers for package-level variables. Such objects may be
// linker-allocated, not heap-allocated.
//
// A single goroutine runs all finalizers for a program, sequentially.
// If a finalizer must run for a long time, it should do so by starting
// a new goroutine.
我这里不是想照抄英文,只是为了文章的完整性,我们来看看它说了什么吧。下面是以上大段英文的简单理解。在垃圾回收机制中,有两种回收方式,其中一种是自动方式,另外一种是手动方式,自动方式是将长期未使用的数据自动回收掉,还有一种是必须手工强制回收的方式,而runtime.SetFinalizer属于后者。
这个方法是有两个参数,前面那个是一个变量,后面紧跟着一个这个变量的释放函数。
因此我们明白runtime.SetFinalizer(p, (*Process).Release)就是当进程运行完毕之后,将这个进程释放掉的意思。其中
(*Process).Release指的是这个函数
func (p *Process) Release() error {
return p.release()
}
func (p *Process) release() error {
// NOOP for Plan 9.
p.Pid = -1
// no need for a finalizer anymore
runtime.SetFinalizer(p, nil)
return nil
}
把该进程的进程号设定为-1 然后见p释放掉。
这就不难理解为什么从os下面要包装住syscall的原因了。我可以这么理解,os属于系统级别的,因此包含了对垃圾处理的过程,而syscall属于简单系统调用未能实现这部分。
好了我们继续理解,syscall的startProcess过程吧。
pid, h, e := syscall.StartProcess(name, argv, sysattr)
这个过程调用是调用了startProcess内部函数。
// StartProcess wraps ForkExec for package os.
func StartProcess(argv0 string, argv []string, attr *ProcAttr) (pid int, handle uintptr, err error) {
pid, err = startProcess(argv0, argv, attr)
return pid, 0, err
}
这就是startProcess内部函数。
func startProcess(argv0 string, argv []string, attr *ProcAttr) (pid int, err error) {
type forkRet struct {
pid int
err error
}
forkc := make(chan forkRet, 1)
go func() {
runtime.LockOSThread()
var ret forkRet
ret.pid, ret.err = forkExec(argv0, argv, attr)
// If fork fails there is nothing to wait for.
if ret.err != nil || ret.pid == 0 {
forkc <- ret
return
}
waitc := make(chan *waitErr, 1)
// Mark that the process is running.
procs.Lock()
if procs.waits == nil {
procs.waits = make(map[int]chan *waitErr)
}
procs.waits[ret.pid] = waitc
procs.Unlock()
forkc <- ret
var w waitErr
for w.err == nil && w.Pid != ret.pid {
w.err = Await(&w.Waitmsg)
}
waitc <- &w
close(waitc)
}()
ret := <-forkc
return ret.pid, ret.err
}
这个函数我们看一下。先定义了一个供返回的结构forkRet。然后创建了一个channel用于存放返回结构体。然后启动一个携程走fork的过程。这个过程如下:
1)先锁住程序runtime.LockOSThread
2)执行 fork过程 ret.pid, ret.err = forkExec(argv0, argv, attr)
3)通过
var procs struct {
sync.Mutex
waits map[int]chan *waitErr
}
这样一个结构加锁,然后将获得的pid作为key存放到一个waits 的map里面,然后解锁。
4)将结果传到go协程之外。 forkc <- ret
5)单独处理waitErr流程,await是用来等待进程执行完毕之后关闭进程用的,相关的代码在开始时就存在,以下是一开始时的代码,也是通过系统调用syscall来实现的。虽然这部分也有很多底层代码,但是我觉得还是需要读者按照以上分析思路自己去探究一下,这里就不做分析了。
_, err = proc.Wait()
if err != nil {
fmt.Println(err)
}
至此,如何启动一个新的进程的部分已经分析完毕,根据我的总结,我们发现在创建进程和销毁进程使用进程的过程中,os包起到了包装底层调用的作用。因此我们日常中无需刻意分析这么多源代码,只需要明白os 进程部分提供给我们的api就可以了。鉴于此,下一篇文章则开始介绍如何启动和使用进程,进行详细描述。以便让初学者完成这一部分的操作。
Go:创建新进程(os.StartProcess源码解读)的更多相关文章
- Linux内核分析— —创建新进程的过程
分析Linux内核创建一个新进程的过程 实验过程 要求:使用gdb跟踪分析一个fork系统调用内核处理函数sys_clone ,验证对Linux系统创建一个新进程的理解,推荐在实验楼Linux虚拟机环 ...
- linux进程学习-创建新进程
init进程将系统启动后,init将成为此后所有进程的祖先,此后的进程都是直接或间接从init进程“复制”而来.完成该“复制”功能的函数有fork()和clone()等. 一个进程(父进程)调用for ...
- Prism 源码解读1-Bootstrapper和Region的创建
介绍 之前也研究过Prism框架但是一直没有深入理解,现在项目上想把一个Winform的桌面应用程序改造成WPF程序,同时我希望程序是可测试可维护架构良好的,Prism的这些设计理念正好符合我的需求, ...
- create-react-app源码解读之为什么不搞个山寨版的create-react-app呢?
最近把 vue-cli@2.x 和 create-react-app 的源码都看了一遍.由于现在官方推荐使用 vue-cli@3.0 ,改动比较大,所以就不写关于 vue-cli 的了(据说是因为 v ...
- AFNetworking 3.0 源码解读 总结(干货)(下)
承接上一篇AFNetworking 3.0 源码解读 总结(干货)(上) 21.网络服务类型NSURLRequestNetworkServiceType 示例代码: typedef NS_ENUM(N ...
- AFNetworking 3.0 源码解读(三)之 AFURLRequestSerialization
这篇就讲到了跟请求相关的类了 关于AFNetworking 3.0 源码解读 的文章篇幅都会很长,因为不仅仅要把代码进行详细的的解释,还会大概讲解和代码相关的知识点. 上半篇: URI编码的知识 关于 ...
- AFNetworking 3.0 源码解读(四)之 AFURLResponseSerialization
本篇是AFNetworking 3.0 源码解读的第四篇了. AFNetworking 3.0 源码解读(一)之 AFNetworkReachabilityManager AFNetworking 3 ...
- HttpClient 4.3连接池参数配置及源码解读
目前所在公司使用HttpClient 4.3.3版本发送Rest请求,调用接口.最近出现了调用查询接口服务慢的生产问题,在排查整个调用链可能存在的问题时(从客户端发起Http请求->ESB-&g ...
- php-msf 源码解读【转】
php-msf: https://github.com/pinguo/php-msf 百度脑图 - php-msf 源码解读: http://naotu.baidu.com/file/cc7b5a49 ...
随机推荐
- cURL使用教程及实例演示
curl里面的函数不多,主要有: curl_init — 初始化一个CURL会话curl_setopt — 为CURL调用设置一个选项curl_exec — 执行一个CURL会话curl_close ...
- EmguCV3.0几个控件的使用
值方图(Histogram)是一种统计图数据,在影像处理中最常被用来统计一张图像或是感兴趣(ROI)区域的色彩分布,在这边本人使用的EmguCV 2.4.0版的内建值方图工具只有被包含在WinForm ...
- hadoop启动脚本分析及常见命令
进程------------------ [hdfs]start-dfs.sh NameNode NN DataNode DN SecondaryNamenode 2NN [yarn]start-ya ...
- 剑指offer 38_统计数组中k出现的个数
思路: 二分法,分别找出第一个和最后一个k出现的位置.相减 加一 #include <stdio.h> //获取第一个K的位置 int getFirstK (int k,int *numb ...
- Android常用开源库集合【持续更新】
1.FastJson 阿里巴巴工程师做的一个方便的JSON转换库 2.ButterKnife 只要作用能代替代码中大量的findviewbyid语句的使用,使用@injectview注入方式 3.v ...
- C#正则表达式匹配双引号
html: <img class="bubble large" src="/images/hero-logos/cog.svg" width=" ...
- ReactNative http网络通讯
安装 fetch npm install whatwg-fetch --save 1.fetch的Get方式通讯 async sendGet(){ let response = await fetch ...
- ubuntu16安装pylearn2 出现错误提示importerror:no module named six.moves
由于市面上的一些教程时间比较早,入门学习时跟随教程安装容易出现各种错误,这些错误基本都是版本不同导致的 所以,我们安装过程中一定要指出包的版本,如果你已经遇到no module named six.m ...
- Mind Map-在线软件(转)
From http://blog.sina.com.cn/s/blog_74b687ac0102dtp1.html 第一款:http://tu.mindpin.com/ 用非常简单, 先用Email ...
- Halcon将裁剪后的图像还原为原始大小
************************************************************* * Halcon将裁剪后的图像还原为原始大小 * Author: LiGua ...