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就可以了。鉴于此,下一篇文章则开始介绍如何启动和使用进程,进行详细描述。以便让初学者完成这一部分的操作。

原文地址:https://www.cnblogs.com/Miracle-boy/p/10799354.html

时间: 2024-10-19 21:31:08

Go:创建新进程(os.StartProcess源码解读)的相关文章

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) } 我们来看

OpenStack_Swift源码分析——创建Ring及添加设备源码详细分析

1 创建Ring 代码详细分析 在OpenStack_Swift--Ring组织架构中我们详细分析了Ring的具体工作过程,下面就Ring中增加设备,删除设备,已经重新平衡的实现过程作详细的介绍. 首先看RingBuilder类 def __init__(self, part_power, replicas, min_part_hours): #why 最大 2**32 if part_power > 32: raise ValueError("part_power must be at

Linux环境编程之进程(四):创建新进程、执行程序和进程终止

引言: 对于每个进程,都有一个非负整数表示的唯一进程ID.虽然进程的ID是唯一的,但却是可重用的.系统中有一些专用的进程.如ID为0的进程通常是调度进程,也成交换进程或系统进程(它是内核进程).进程ID为1通常是init进程,它是一个普通的用户进程.一些与进程ID有关的函数: #include <unistd.h> pid_t getpid(void);   //返回值:调用进程的进程ID pit_t getppid(void); //返回值:调用进程的父进程ID uid_t getuid(v

APUE学习笔记——8.3~8.4创建新进程fork()、vfork()

#include <unistd.h> pid_t fork(void); Returns: 0 in child, process ID of child in parent,?1 on error Unix可以使用系统函数fork()创建一个新进程. fork()执行一次返回两次. 返回值: 0:            表示子进程 子进程id: 表示父进程 子进程可以通过getppid()来获取父进程id,所以只需要返回0,表示创建成功即可.而对于父进程来说,他无从得知子进程的id,因此在

新大番薯棋牌牛牛源码安装搭建 微信h5牛牛大厅开发选择方式

一个完整的客户端棋牌游戏从结构上包括了游戏.游戏大厅.游戏网站.客户端等部分,如果把做棋牌游戏比作建房子,那打造一个自己的棋牌之家就得经过前期的规划设计,中期的建筑施工,后期的装修设计等环节,有的人建房子可能会亲力亲为,有的人为了省钱而去购买小产权房或二手房,更多的人则选择开发商购买商品房.本质上,做棋牌游戏基本也是如此,无论个人还是团队企业,在当前日渐成熟的网络棋牌游戏市场,新大番薯棋牌牛牛源码安装搭建(h5.maliwl.com) 微信h5牛牛大厅开发选择方式   ,要想涉足无非也是以下这几

Prism 源码解读1-Bootstrapper和Region的创建

原文:Prism 源码解读1-Bootstrapper和Region的创建 目录 介绍 开始 0.PrismApplicationBase 1.BootstrapperShell 2.Regions 3.CustomRegions 总结 回到顶部 介绍 之前也研究过Prism框架但是一直没有深入理解,现在项目上想把一个Winform的桌面应用程序改造成WPF程序,同时我希望程序是可测试可维护架构良好的,Prism的这些设计理念正好符合我的需求,其主要用于WPF和Xamarin,用于构建松耦合,可

自动化WiFI钓鱼工具——WiFiPhisher源码解读

工具介绍 开源无线安全工具Wifiphisher是基于MIT许可模式的开源软件,运行于Kali Linux之上. github.com/sophron/wifiphisher 它能够对WPA加密的AP无线热点实施自动化钓鱼攻击,获取密码账户.由于利用了社工原理实施中间人攻击,Wifiphisher在实施攻击时无需进行暴力破解. 此外安利一个我们正在开发的项目,基于wifiphisher的校园网钓鱼工具,希望有小伙伴来一起玩耍:-P github.com/noScripts/Campus-Fake

HttpClient 4.3连接池参数配置及源码解读

目前所在公司使用HttpClient 4.3.3版本发送Rest请求,调用接口.最近出现了调用查询接口服务慢的生产问题,在排查整个调用链可能存在的问题时(从客户端发起Http请求->ESB->服务端处理请求,查询数据并返回),发现原本的HttpClient连接池中的一些参数配置可能存在问题,如defaultMaxPerRoute.一些timeout时间的设置等,虽不能确定是由于此连接池导致接口查询慢,但确实存在可优化的地方,故花时间做一些研究.本文主要涉及HttpClient连接池.请求的参数

AFNetworking 3.0 源码解读(四)之 AFURLResponseSerialization

本篇是AFNetworking 3.0 源码解读的第四篇了. AFNetworking 3.0 源码解读(一)之 AFNetworkReachabilityManager AFNetworking 3.0 源码解读(二)之 AFSecurityPolicy AFNetworking 3.0 源码解读(三)之 AFURLRequestSerialization 这次主要讲AFURLResponseSerialization(HTTP响应)这一个类的知识. 这是一个协议,只要遵守这个协议,就要实现N