最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

源碼剖析Golang如何fork一個(gè)進(jìn)程

 更新時(shí)間:2023年06月05日 14:40:51   作者:藍(lán)胖子的編程夢  
創(chuàng)建一個(gè)新進(jìn)程分為兩個(gè)步驟,一個(gè)是fork系統(tǒng)調(diào)用,一個(gè)是execve?系統(tǒng)調(diào)用,本文將從源碼的角度帶大家剖析一下Golang是如何fork一個(gè)進(jìn)程的

創(chuàng)建一個(gè)新進(jìn)程分為兩個(gè)步驟,一個(gè)是fork系統(tǒng)調(diào)用,一個(gè)是execve 系統(tǒng)調(diào)用,fork調(diào)用會復(fù)用父進(jìn)程的堆棧,而execve直接覆蓋當(dāng)前進(jìn)程的堆棧,并且將下一條執(zhí)行指令指向新的可執(zhí)行文件。

在分析源碼之前,我們先來看看golang fork一個(gè)子進(jìn)程該如何寫。(嚴(yán)格的講是先fork再execve創(chuàng)建一個(gè)子進(jìn)程)

cmd := exec.Command("/bin/sh")
		cmd.Env = os.Environ()
		cmd.Stdin = os.Stdin
		cmd.Stdout = os.Stdout
		cmd.Stderr = os.Stderr
		err = cmd.Run()

上述代碼將fork一個(gè)子進(jìn)程,然后子進(jìn)程將會調(diào)用execve系統(tǒng)調(diào)用,使用新的可執(zhí)行文件/bin/sh代替當(dāng)前子進(jìn)程的程序。并且當(dāng)前的標(biāo)準(zhǔn)輸入輸出也傳遞給了子進(jìn)程。

我們將著重看下golang是如何創(chuàng)建和將父進(jìn)程的文件描述符傳遞給子進(jìn)程的。

cmd.Run() 會調(diào)用到cmd.Start 方法,里面有一段邏輯和標(biāo)準(zhǔn)輸入輸出流的傳遞相關(guān),我們來看看。

// /usr/local/go/src/os/exec/exec.go:625 
func (c *Cmd) Start() error {
......
childFiles := make([]*os.File, 0, 3+len(c.ExtraFiles))
   // 創(chuàng)建子進(jìn)程的stdin 標(biāo)準(zhǔn)輸入
	stdin, err := c.childStdin()
	if err != nil {
		return err
	}
	childFiles = append(childFiles, stdin)
	// 創(chuàng)建子進(jìn)程的stdout 標(biāo)準(zhǔn)輸出
	stdout, err := c.childStdout()
	if err != nil {
		return err
	}
	childFiles = append(childFiles, stdout)
	// 創(chuàng)建子進(jìn)程的stderr 標(biāo)準(zhǔn)錯(cuò)誤輸出
	stderr, err := c.childStderr(stdout)
	if err != nil {
		return err
	}
	// 此時(shí)childFiles 已經(jīng)包含了上述3個(gè)標(biāo)準(zhǔn)輸入輸出流
	childFiles = append(childFiles, stderr)
	childFiles = append(childFiles, c.ExtraFiles...)

	env, err := c.environ()
	if err != nil {
		return err
	}
   // os.StartProcess 將會啟動一個(gè)子進(jìn)程并從childFiles繼承父進(jìn)程的放入其中的文件描述符
	c.Process, err = os.StartProcess(c.Path, c.argv(), &os.ProcAttr{
		Dir:   c.Dir,
		Files: childFiles,
		Env:   env,
		Sys:   c.SysProcAttr,
	})
	.....
}

如上所述,cmd.Start 會分別調(diào)用childStdin,childStdout,childStderr創(chuàng)建用于子進(jìn)程的標(biāo)準(zhǔn)輸入輸出。來看看其中一個(gè)childStdin實(shí)現(xiàn)原理,其余childStdout,childStderr 實(shí)現(xiàn)原理也是和它類似的。

// /usr/local/go/src/os/exec/exec.go:489
func (c *Cmd) childStdin() (*os.File, error) {
	
	.....
	
	pr, pw, err := os.Pipe()
	if err != nil {
		return nil, err
	}

	c.childIOFiles = append(c.childIOFiles, pr)
	c.parentIOPipes = append(c.parentIOPipes, pw)
	// pw 寫入的數(shù)據(jù) 來源于 c.Stdin  父進(jìn)程會啟動一個(gè)協(xié)程復(fù)制c.Stdin 到 pw
	c.goroutine = append(c.goroutine, func() error {
		_, err := io.Copy(pw, c.Stdin)
		if skipStdinCopyError(err) {
			err = nil
		}
		if err1 := pw.Close(); err == nil {
			err = err1
		}
		return err
	})
	....
	return pr, nil
}

childStdin 實(shí)際上是創(chuàng)建了一個(gè)管道,管道有返回值 pw,pr , 由pw寫入的數(shù)據(jù)可以由pr進(jìn)行讀取,w 寫入的數(shù)據(jù) 來源于 c.Stdin 父進(jìn)程會啟動一個(gè)協(xié)程復(fù)制c.Stdin 到 pw ,而c.Stdin 在我們最開的演示代碼那里賦值為了標(biāo)準(zhǔn)輸入。

cmd := exec.Command("/bin/sh")
		cmd.Env = os.Environ()
		cmd.Stdin = os.Stdin
		cmd.Stdout = os.Stdout
		cmd.Stderr = os.Stderr
		err = cmd.Run()

而pr 則返回由父進(jìn)程通過os.StartProcess的childFiles 傳遞給了子進(jìn)程,并作為子進(jìn)程的標(biāo)準(zhǔn)輸入,當(dāng)子進(jìn)程啟動后將會從pr中獲取標(biāo)準(zhǔn)輸入終端的數(shù)據(jù)。

看到這里,你應(yīng)該能明白了,子進(jìn)程是如何獲取獲取父進(jìn)程的終端信息的了,通過建立了一個(gè)管道,然后將管道的一端傳遞給了子進(jìn)程便能讓父子進(jìn)程進(jìn)行通信了

讓我們再回到創(chuàng)建進(jìn)程的主流程上,剛剛僅僅是分析出了,父進(jìn)程將會為子進(jìn)程創(chuàng)建它自己的標(biāo)準(zhǔn)輸入輸出流,雖然是通過管道包裝的,但還沒詳細(xì)分析出os.StartProcess 方法究竟通過了哪些手段來讓父進(jìn)程的文件描述符傳遞給子進(jìn)程。

注意下,golang中 fork 和execve 創(chuàng)建子進(jìn)程 的過程 被封裝成了一個(gè)統(tǒng)一的方法forkExec,它能夠控制子進(jìn)程,只繼承特定的文件描述符,而對其他文件描述符則進(jìn)行關(guān)閉。而內(nèi)核fork系統(tǒng)調(diào)用則是會對父進(jìn)程的所有文件描述符進(jìn)行復(fù)制,那么golang又是如何做到只繼承特定的文件描述符的呢?這個(gè)也是接下來分析的重點(diǎn)

接下來,讓我們深入os.StartProcess 方法,看看golang是如何辦到只繼承父進(jìn)程通過childFiles傳遞過來的文件描述符進(jìn)行fork和execve調(diào)用的

os.StartProcess 底層會調(diào)用到 forkAndExecInChild1 方法,由于代碼比較長,我這里只列出了關(guān)鍵步驟,并對其進(jìn)行了注釋。

func forkAndExecInChild1(argv0 *byte, argv, envv []*byte, chroot, dir *byte, attr *ProcAttr, sys *SysProcAttr, pipe int) (r1 uintptr, err1 Errno, p [2]int, locked bool) {
    ...
    //  fork 調(diào)用前 會將attr.Files 里的數(shù)據(jù)復(fù)制到fd數(shù)組,我們傳遞給子進(jìn)程的是childFiles,當(dāng)代碼執(zhí)行到這里的時(shí)候,childFiles已經(jīng)轉(zhuǎn)化成了文件描述符存到attr.Files了。nextfd是為了后續(xù)再進(jìn)行復(fù)制文件描述符時(shí),不會對子進(jìn)程要用到的文件描述符進(jìn)行覆蓋,會在接下來步驟1進(jìn)行詳細(xì)說明
    nextfd = len(attr.Files)
	for i, ufd := range attr.Files {
		if nextfd < int(ufd) {
			nextfd = int(ufd)
		}
		fd[i] = int(ufd)
	}
	nextfd++
   .....
   // 這里便進(jìn)行了fork調(diào)用創(chuàng)建新進(jìn)程了,不過可以看到這里用的是clone系統(tǒng)調(diào)用,其實(shí)它和fork類似,不過區(qū)別在于clone系統(tǒng)調(diào)用可以通過flags指定新進(jìn)程 對于 父進(jìn)程的哪些屬性需要繼承,哪些屬性不需要繼承,比如子進(jìn)程需要新的網(wǎng)絡(luò)命名空間,則需要指定flags為syscall.CLONE_NEWNS
   r1, err1 = rawVforkSyscall(SYS_CLONE, flags, 0)
   ....
   
   // 步驟1: 總之經(jīng)過上面clone系統(tǒng)調(diào)用,已經(jīng)產(chǎn)生了子進(jìn)程了,下面兩個(gè)步驟都是子進(jìn)程才會進(jìn)行的步驟,父進(jìn)程在上述clone系統(tǒng)調(diào)用后,通過判斷err1 != 0 || r1 != 0  便返回了。
  //  這里將fd[i] < i 的文件描述符 通過dup 系統(tǒng)調(diào)用復(fù)制到了一個(gè)新的文件描述符,因?yàn)楹罄m(xù)步驟2里我們需要將復(fù)制 fd[i] 到第i個(gè)文件描述符 ,如果fd[i] < i ,那么將會導(dǎo)致復(fù)制的fd[i] 是子進(jìn)程已經(jīng)產(chǎn)生復(fù)制行為的文件描述符,而不是父進(jìn)程真正傳遞過來的文件描述符,所以要通過nextfd將這樣的文件描述符復(fù)制到fd數(shù)組外,并且設(shè)置O_CLOEXEC,這樣在后續(xù)的execve系統(tǒng)調(diào)用后,將會對它進(jìn)行自動關(guān)閉。
     	for i = 0; i < len(fd); i++ {
		if fd[i] >= 0 && fd[i] < i {
			....
			_, _, err1 = RawSyscall(SYS_DUP3, uintptr(fd[i]), uintptr(nextfd), O_CLOEXEC)
			if err1 != 0 {
				goto childerror
			}
			fd[i] = nextfd
			nextfd++
		}
	}
   ....
   // 步驟2 : 遍歷fd 讓 子進(jìn)程fd[i] 個(gè)文件描述符復(fù)制給第i個(gè)文件描述符 ,注意這里就沒有設(shè)置O_CLOEXEC了,因?yàn)檫@里的文件描述符我們希望execve后還存在
	for i = 0; i < len(fd); i++ {
		....
		_, _, err1 = RawSyscall(SYS_DUP3, uintptr(fd[i]), uintptr(i), 0)
		if err1 != 0 {
			goto childerror
		}
	} 
	
	....
    // 進(jìn)行execve 系統(tǒng)調(diào)用
	_, _, err1 = RawSyscall(SYS_EXECVE,
		uintptr(unsafe.Pointer(argv0)),
		uintptr(unsafe.Pointer(&argv[0])),
		uintptr(unsafe.Pointer(&envv[0])))
}

可以看出,golang在execve前, 通過dup系統(tǒng)調(diào)用達(dá)到了繼承父進(jìn)程文件描述符的目的,最終達(dá)到的效果是繼承attr.Files 參數(shù)里的文件描述符,期間由于dup的使用 產(chǎn)生的多余的文件描述符也標(biāo)記為了O_CLOEXEC,在SYS_EXECVE 系統(tǒng)調(diào)用時(shí),便會關(guān)閉掉。

但是僅僅看到這里,并不能說明golang會對attr.Files外的文件描述符也進(jìn)行關(guān)閉,因?yàn)閒ork系統(tǒng)調(diào)用時(shí),子進(jìn)程會自動繼承父進(jìn)程的所有文件描述符,這些繼承的文件描述符會在execve后自動關(guān)閉嗎? 答案是默認(rèn)是會的。

golang的 os.open 函數(shù)底層會調(diào)用下面的代碼對文件進(jìn)行打開操作,可以看到打開時(shí)固定設(shè)置了syscall.O_CLOEXEC flag,所以,子進(jìn)程進(jìn)行execve時(shí)變會自動對這些文件描述符進(jìn)行關(guān)閉了。

func openFileNolog(name string, flag int, perm FileMode) (*File, error) {
	setSticky := false
	if !supportsCreateWithStickyBit && flag&O_CREATE != 0 && perm&ModeSticky != 0 {
		if _, err := Stat(name); IsNotExist(err) {
			setSticky = true
		}
	}
	var r int
	for {
		var e error
		r, e = syscall.Open(name, flag|syscall.O_CLOEXEC, syscallMode(perm))
		if e == nil {

監(jiān)聽的socket文件也是默認(rèn)開啟了syscall.SOCK_NONBLOCK參數(shù)

// descriptor as nonblocking and close-on-exec.
func sysSocket(family, sotype, proto int) (int, error) {
	s, err := socketFunc(family, sotype|syscall.SOCK_NONBLOCK|syscall.SOCK_CLOEXEC, proto)
	if err != nil {
		return -1, os.NewSyscallError("socket", err)
	}
	return s, nil
}

到此這篇關(guān)于源碼剖析Golang如何fork一個(gè)進(jìn)程的文章就介紹到這了,更多相關(guān)Golang fork進(jìn)程內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Golang實(shí)現(xiàn)基于角色的訪問控制(RBAC)的項(xiàng)目實(shí)踐

    Golang實(shí)現(xiàn)基于角色的訪問控制(RBAC)的項(xiàng)目實(shí)踐

    基于角色的訪問控制(RBAC)是一種安全機(jī)制,通過角色來管理用戶權(quán)限,本文介紹了一種可落地、易擴(kuò)展的Golang?RBAC實(shí)現(xiàn)方案,具有一定的參考價(jià)值,感興趣的可以了解一下
    2025-12-12
  • 深入了解Golang中reflect反射的使用

    深入了解Golang中reflect反射的使用

    這篇文章主要介紹了深入了解Golang中reflect反射的使用,Go語言中的反射是一種機(jī)制,可以在運(yùn)行時(shí)動態(tài)地獲取類型信息和操作對象,以及調(diào)用對象的方法和屬性等,需要詳細(xì)了解可以參考下文
    2023-05-05
  • 使用Go語言中的Context取消協(xié)程執(zhí)行的操作代碼

    使用Go語言中的Context取消協(xié)程執(zhí)行的操作代碼

    在 Go 語言中,協(xié)程(goroutine)是一種輕量級的線程,非常適合處理并發(fā)任務(wù),然而,如何優(yōu)雅地取消正在運(yùn)行的協(xié)程是一個(gè)常見的問題,本文將通過一個(gè)具體的例子來展示如何使用 context 包來取消協(xié)程的執(zhí)行,需要的朋友可以參考下
    2024-11-11
  • Golang收支記賬程序詳細(xì)編寫過程

    Golang收支記賬程序詳細(xì)編寫過程

    這篇文章主要介紹了Golang實(shí)現(xiàn)收支記賬程序流程,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)吧
    2022-12-12
  • 創(chuàng)建第一個(gè)Go語言程序Hello,Go!

    創(chuàng)建第一個(gè)Go語言程序Hello,Go!

    這篇文章主要介紹了創(chuàng)建第一個(gè)Go語言程序Hello,Go!本文詳細(xì)的給出項(xiàng)目創(chuàng)建、代碼編寫的過程,同時(shí)講解了GOPATH、Go install等內(nèi)容,需要的朋友可以參考下
    2014-10-10
  • go modules中replace使用方法

    go modules中replace使用方法

    這篇文章主要為大家介紹了go modules中replace使用方法,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-06-06
  • 輕松讀懂Golang中的數(shù)組和切片

    輕松讀懂Golang中的數(shù)組和切片

    這篇文章主要給大家介紹了關(guān)于Golang中數(shù)組和切片的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),對大家學(xué)習(xí)或者Golang具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2022-03-03
  • Go?tablewriter庫提升命令行輸出專業(yè)度實(shí)例詳解

    Go?tablewriter庫提升命令行輸出專業(yè)度實(shí)例詳解

    命令行工具大家都用過,如果是運(yùn)維人員可能會編寫命令行工具來完成各種任務(wù),命令行輸出的美觀和易讀性往往容易被忽視,很爛的輸出會讓人感覺不專業(yè),本文將介紹Go語言中牛逼的實(shí)戰(zhàn)工具tablewriter庫,使你在命令行輸出中展現(xiàn)出專業(yè)的一面
    2023-11-11
  • Golang中單引號、雙引號、反引號的實(shí)現(xiàn)

    Golang中單引號、雙引號、反引號的實(shí)現(xiàn)

    Go語言中單引號表示rune類型,雙引號用于字符串支持轉(zhuǎn)義,反引號保留原始內(nèi)容且支持多行不轉(zhuǎn)義,三者區(qū)分關(guān)鍵在于字符處理方式與轉(zhuǎn)義規(guī)則,下面就來介紹一下
    2025-06-06
  • Go設(shè)計(jì)模式之迭代器模式講解和代碼示例

    Go設(shè)計(jì)模式之迭代器模式講解和代碼示例

    迭代器是一種行為設(shè)計(jì)模式, 讓你能在不暴露復(fù)雜數(shù)據(jù)結(jié)構(gòu)內(nèi)部細(xì)節(jié)的情況下遍歷其中所有的元素,本文將為大家詳細(xì)介紹Go 迭代器模式,文中詳細(xì)的代碼示例,需要的朋友可以參考下
    2023-07-07

最新評論

津南区| 枣庄市| 安龙县| 连城县| 小金县| 彩票| 宁波市| 潮州市| 班玛县| 化德县| 鄂托克前旗| 延吉市| 诸城市| 江门市| 永兴县| 内黄县| 大石桥市| 南召县| 邵武市| 宿松县| 克什克腾旗| 灵石县| 丽江市| 开阳县| 聊城市| 延吉市| 韩城市| 广德县| 张掖市| 沈丘县| 马公市| 信阳市| 嘉义县| 汉源县| 成武县| 安庆市| 南丹县| 平定县| 阳山县| 佛学| 资中县|