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)这一行在做什么呢?

这部分就是难点了,如果理解了这部分就会了解程序为什么包装了这一层,它的目的何在。

下面则是一大段英文。我门来试着理解一下。

// 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(p, (*Process).Release)应该是为这个新启动的进程设定了一个垃圾回收机制,也就是说我们如何回收已经完结的进程或者运行过程中的进程所产生的一系列垃圾数据。

时间: 2024-10-27 05:45:19

go语言创建新进程过程详解 (os.StartProcess源码分析)的相关文章

Android事件传递机制详解及最新源码分析——ViewGroup篇

在上一篇<Android事件传递机制详解及最新源码分析--View篇>中,详细讲解了View事件的传递机制,没掌握或者掌握不扎实的小伙伴,强烈建议先阅读上一篇. 好了,废话还是少说,直奔主题,开始本篇的ViewGroup事件传递机制探索之旅. 依然从简单的Demo例子现象开始分析 新建安卓工程,首先自定义一个Button以及一个RelativeLayout,很简单,只是重写了主要与事件传递机制相关的方法,代码如下: 自定义WLButton类: 1 public class WLButton e

Java中包含继承关系时对象的创建与销毁顺序详解(附源码)

前言 通过组合和继承方法来创建新类时,永远不必担心对象的清理问题,子对象通常都会留给垃圾回收器进行处理.如果确实遇到清理的问题,那么必须用心为新类创建dispose()方法(在这里我选用此名称,读者可以提出更好的).并且由于继承的缘故,如果我们有其他作为垃圾回收一部分的特殊清理动作,就必须在导出类中覆盖dispose()方法.当覆盖被继承类的dispose()方法时,务必记住调用基类版本dispose()方法:否则,基类的清理动作就不会发生.下例就证明了这一点: 示例源码 package com

android WebView详解,常见漏洞详解和安全源码(下)

上篇博客主要分析了 WebView 的详细使用,这篇来分析 WebView 的常见漏洞和使用的坑. 上篇:android WebView详解,常见漏洞详解和安全源码(上) 转载请注明出处:http://blog.csdn.net/self_study/article/details/55046348 对技术感兴趣的同鞋加群 544645972 一起交流. WebView 常见漏洞 WebView 的漏洞也是不少,列举一些常见的漏洞,实时更新,如果有其他的常见漏洞,知会一下我-- WebView

android WebView详解,常见漏洞详解和安全源码

这篇博客主要来介绍 WebView 的相关使用方法,常见的几个漏洞,开发中可能遇到的坑和最后解决相应漏洞的源码,以及针对该源码的解析. 由于博客内容长度,这次将分为上下两篇,上篇详解 WebView 的使用,下篇讲述 WebView 的漏洞和坑,以及修复源码的解析. 下篇:android WebView详解,常见漏洞详解和安全源码(下) 转载请注明出处:http://blog.csdn.net/self_study/article/details/54928371. 对技术感兴趣的同鞋加群 54

详解聚富彩票源码搭建 HTML表单与PHP

表单的的与PHP相关联的属性 action属性指向处理表单的PHP脚本. method属性接受两个参数,post/get;详解聚富彩票源码搭建bbs.yasewl.com请添加链接描述 post与get的差异 1.get会公开地将用户输入表单的信息发送给php脚本,不安全: 2.get传送的信息有限. 3.使用get方式的表单创建的页面能够被添加为书签,post不可以. 4.重载post访问的页面,会显示提示信息,get不会. 所以get主要用于从服务器强求数据的行为例如,搜索请求等,post用

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

Docker网络详解及pipework源码解读与实践

Docker作为目前最火的轻量级容器技术,有很多令人称道的功能,如Docker的镜像管理.然而,Docker同样有着很多不完善的地方,网络方面就是Docker比较薄弱的部分.因此,我们有必要深入了解Docker的网络知识,以满足更高的网络需求.本文首先介绍了Docker自身的4种网络工作方式,然后通过3个样例 -- 将Docker容器配置到本地网络环境中.单主机Docker容器的VLAN划分.多主机Docker容器的VLAN划分,演示了如何使用pipework帮助我们进行复杂的网络设置,以及pi

Java中共享对象的创建与销毁详解(附源码)

前言 在上一篇文章的示例中还应该注意到,Frog对象拥有其自己的成员对象.Frog对象创建了它自己的成员对象,并且知道它们存活多久(只要Frog存活着),因此Frog对象知道何时调用dispose()去释放其成员对象.然而,如果这些成员对象中存在于其他一个或者多个对象共享的情况,问题就变得更加复杂了,你就不能简单地假设你可以调用dispose()了.在这种情况下,也就必需使用引用计数来跟踪仍旧访问着共享对象的对象数量了.下面是相关的代码: 示例源码 package com.mufeng.thee

Pipeline的入站流程详解(netty源码死磕7)

精进篇:netty源码死磕7  巧夺天工--Pipeline入站流程详解 1. Pipeline的入站流程 在讲解入站处理流程前,先脑补和铺垫一下两个知识点: (1)如何向Pipeline添加一个Handler节点 (2)Handler的出站和入站的区分方式 1.1. HandlerContext节点的添加 在Pipeline实例创建的同时,Netty为Pipeline创建了一个Head和一个Tail,并且建立好了链接关系. 代码如下: protected DefaultChannelPipel