国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

從源碼角度剖析 golang 如何fork一個(gè)進(jìn)程

這篇具有很好參考價(jià)值的文章主要介紹了從源碼角度剖析 golang 如何fork一個(gè)進(jìn)程。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問(wèn)。

從源碼角度剖析 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)用會(huì)復(fù)用父進(jìn)程的堆棧,而execve直接覆蓋當(dāng)前進(jìn)程的堆棧,并且將下一條執(zhí)行指令指向新的可執(zhí)行文件。

在分析源碼之前,我們先來(lái)看看golang fork一個(gè)子進(jìn)程該如何寫。(??嚴(yán)格的講是先f(wàn)ork再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)程將會(huì)調(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() 會(huì)調(diào)用到cmd.Start 方法,里面有一段邏輯和標(biāo)準(zhǔn)輸入輸出流的傳遞相關(guān),我們來(lái)看看。

// /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 將會(huì)啟動(dòng)一個(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 會(huì)分別調(diào)用childStdin,childStdout,childStderr創(chuàng)建用于子進(jìn)程的標(biāo)準(zhǔn)輸入輸出。來(lái)看看其中一個(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ù) 來(lái)源于 c.Stdin  父進(jìn)程會(huì)啟動(dòng)一個(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ù) 來(lái)源于 c.Stdin 父進(jìn)程會(huì)啟動(dòng)一個(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)程通過(guò)os.StartProcess的childFiles 傳遞給了子進(jìn)程,并作為子進(jìn)程的標(biāo)準(zhǔn)輸入,當(dāng)子進(jìn)程啟動(dòng)后將會(huì)從pr中獲取標(biāo)準(zhǔn)輸入終端的數(shù)據(jù)。

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

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

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

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

os.StartProcess 底層會(huì)調(diào)用到 forkAndExecInChild1 方法,由于代碼比較長(zhǎng),我這里只列出了關(guān)鍵步驟,并對(duì)其進(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)用前 會(huì)將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í),不會(huì)對(duì)子進(jìn)程要用到的文件描述符進(jìn)行覆蓋,會(huì)在接下來(lái)步驟1進(jìn)行詳細(xì)說(shuō)明
    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)程了,不過(guò)可以看到這里用的是clone系統(tǒng)調(diào)用,其實(shí)它和fork類似,不過(guò)區(qū)別在于clone系統(tǒng)調(diào)用可以通過(guò)flags指定新進(jìn)程 對(duì)于 父進(jìn)程的哪些屬性需要繼承,哪些屬性不需要繼承,比如子進(jìn)程需要新的網(wǎng)絡(luò)命名空間,則需要指定flags為syscall.CLONE_NEWNS
   r1, err1 = rawVforkSyscall(SYS_CLONE, flags, 0)
   ....
   
   // 步驟1: 總之經(jīng)過(guò)上面clone系統(tǒng)調(diào)用,已經(jīng)產(chǎn)生了子進(jìn)程了,下面兩個(gè)步驟都是子進(jìn)程才會(huì)進(jìn)行的步驟,父進(jìn)程在上述clone系統(tǒng)調(diào)用后,通過(guò)判斷err1 != 0 || r1 != 0  便返回了。
  //  這里將fd[i] < i 的文件描述符 通過(guò)dup 系統(tǒng)調(diào)用復(fù)制到了一個(gè)新的文件描述符,因?yàn)楹罄m(xù)步驟2里我們需要將復(fù)制 fd[i] 到第i個(gè)文件描述符 ,如果fd[i] < i ,那么將會(huì)導(dǎo)致復(fù)制的fd[i] 是子進(jìn)程已經(jīng)產(chǎn)生復(fù)制行為的文件描述符,而不是父進(jìn)程真正傳遞過(guò)來(lái)的文件描述符,所以要通過(guò)nextfd將這樣的文件描述符復(fù)制到fd數(shù)組外,并且設(shè)置O_CLOEXEC,這樣在后續(xù)的execve系統(tǒng)調(diào)用后,將會(huì)對(duì)它進(jìn)行自動(dòng)關(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è)文件描述符 ,注意這里就沒(méi)有設(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前, 通過(guò)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í),便會(huì)關(guān)閉掉。

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

golang的 os.open 函數(shù)底層會(huì)調(diào)用下面的代碼對(duì)文件進(jìn)行打開操作,可以看到打開時(shí)固定設(shè)置了syscall.O_CLOEXEC flag,所以,子進(jìn)程進(jìn)行execve時(shí)變會(huì)自動(dòng)對(duì)這些文件描述符進(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ù)文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-467319.html

// 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)程的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來(lái)自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場(chǎng)。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請(qǐng)注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請(qǐng)點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • Go For Web:Golang http 包詳解(源碼剖析)

    Go For Web:Golang http 包詳解(源碼剖析)

    本文作為解決如何通過(guò) Golang 來(lái)編寫 Web 應(yīng)用這個(gè)問(wèn)題的前瞻,對(duì) Golang 中的 Web 基礎(chǔ)部分進(jìn)行一個(gè)簡(jiǎn)單的介紹。目前 Go 擁有成熟的 Http 處理包,所以我們?nèi)ゾ帉懸粋€(gè)做任何事情的動(dòng)態(tài) Web 程序應(yīng)該是很輕松的,接下來(lái)我們就去學(xué)習(xí)了解一些關(guān)于 Web 的相關(guān)基礎(chǔ),了解一些概念,

    2023年04月14日
    瀏覽(23)
  • 《ARM Linux內(nèi)核源碼剖析》讀書筆記——0號(hào)進(jìn)程(init_task)的創(chuàng)建時(shí)機(jī)

    《ARM Linux內(nèi)核源碼剖析》讀書筆記——0號(hào)進(jìn)程(init_task)的創(chuàng)建時(shí)機(jī)

    最近在讀《ARM Linux內(nèi)核源碼剖析》,一直沒(méi)有看到0號(hào)進(jìn)程(init_task進(jìn)程)在哪里創(chuàng)建的。直到看到下面這篇文章才發(fā)現(xiàn)書中漏掉了set_task_stack_end_magic(init_task)這行代碼。 下面這篇文章提到:start_kernel()上來(lái)就會(huì)運(yùn)行 set_task_stack_end_magic(init_task)創(chuàng)建初始進(jìn)程。init_task是靜態(tài)定義的

    2024年01月17日
    瀏覽(22)
  • 【Linux進(jìn)程】查看進(jìn)程&&fork創(chuàng)建進(jìn)程

    【Linux進(jìn)程】查看進(jìn)程&&fork創(chuàng)建進(jìn)程

    目錄 前言 ?1. 查看進(jìn)程 ?2. 通過(guò)系統(tǒng)調(diào)用創(chuàng)建進(jìn)程-fork初識(shí) 總結(jié) ? ? ? ? ?你有沒(méi)有想過(guò)在使用Linux操作系統(tǒng)時(shí),后臺(tái)運(yùn)行的程序是如何管理的?在Linux中,進(jìn)程是一個(gè)非常重要的概念。本文將介紹如何查看當(dāng)前運(yùn)行的進(jìn)程,并且討論如何使用fork創(chuàng)建新的進(jìn)程。通過(guò)了解這些

    2024年01月22日
    瀏覽(30)
  • Golang的Fork/Join實(shí)現(xiàn)

    Golang的Fork/Join實(shí)現(xiàn)

    做過(guò)Java開發(fā)的同學(xué)肯定知道,JDK7加入的Fork/Join是一個(gè)非常優(yōu)秀的設(shè)計(jì),到了JDK8,又結(jié)合并行流中進(jìn)行了優(yōu)化和增強(qiáng),是一個(gè)非常好的工具。 Fork/Join本質(zhì)上是一種任務(wù)分解,即:將一個(gè)很大的任務(wù)分解成若干個(gè)小任務(wù),然后再對(duì)小任務(wù)進(jìn)一步分解,直到最小顆粒度,然后并發(fā)

    2024年02月08日
    瀏覽(20)
  • 【Linux初階】fork進(jìn)程創(chuàng)建 & 進(jìn)程終止 & 進(jìn)程等待

    【Linux初階】fork進(jìn)程創(chuàng)建 & 進(jìn)程終止 & 進(jìn)程等待

    ???hello,各位讀者大大們你們好呀?? ????系列專欄:【Linux初階】 ????本篇內(nèi)容:fork進(jìn)程創(chuàng)建,理解fork返回值和常規(guī)用法,進(jìn)程終止(退出碼、退出場(chǎng)景、退出方法、exit),進(jìn)程等待(wait、waitpid),阻塞等待和非阻塞等待 ????作者簡(jiǎn)介:本科在讀,計(jì)算機(jī)海洋

    2024年02月06日
    瀏覽(24)
  • 【Linux】進(jìn)程查看|fork函數(shù)|進(jìn)程狀態(tài)

    【Linux】進(jìn)程查看|fork函數(shù)|進(jìn)程狀態(tài)

    ?? 個(gè)人主頁(yè)—— ?? 開著拖拉機(jī)回家_Linux,大數(shù)據(jù)運(yùn)維-CSDN博客 ????? ???????????????? ?????????????? ???????????????????????? 感謝點(diǎn)贊和關(guān)注 ,每天進(jìn)步一點(diǎn)點(diǎn)!加油! 目錄 一、基本概念 1.1 概念提出 1.2 特征 二、描述進(jìn)程-PCB 2.1 什么是進(jìn)程

    2024年02月04日
    瀏覽(31)
  • Linux中的進(jìn)程、fork、進(jìn)程狀態(tài)、環(huán)境變量

    Linux中的進(jìn)程、fork、進(jìn)程狀態(tài)、環(huán)境變量

    ????????進(jìn)程信息被放在一個(gè)叫做進(jìn)程控制塊的數(shù)據(jù)結(jié)構(gòu)中,可以理解為進(jìn)程屬性的集合。課本上稱之為PCB(process control block),Linux操作系統(tǒng)下的PCB是: task_struct 在Linux中描述進(jìn)程的結(jié)構(gòu)體叫做task_struct。task_struct是Linux內(nèi)核的一種數(shù)據(jù)結(jié)構(gòu),它會(huì)被裝載到RAM(內(nèi)存)里并且包

    2024年02月10日
    瀏覽(22)
  • fork()函數(shù)創(chuàng)建子進(jìn)程

    fork()函數(shù)創(chuàng)建子進(jìn)程

    fork()用于創(chuàng)建子進(jìn)程, 一次調(diào)用 會(huì)有 兩個(gè)返回 (return),一次返回給父進(jìn)程子進(jìn)程的PID(Process ID),一次返回給子進(jìn)程,其返回值為0. 返回值=0,子進(jìn)程在運(yùn)行 返回值0,父進(jìn)程在運(yùn)行 返回值0,fork()調(diào)用出錯(cuò) 進(jìn)程獲取 自己的PID:getpid() 進(jìn)程獲取 父進(jìn)程PID:getppid() 由于一個(gè)進(jìn)

    2023年04月08日
    瀏覽(24)
  • Fork() 函數(shù):“父” 與 “子” 進(jìn)程的交互(進(jìn)程的創(chuàng)建)

    Fork() 函數(shù):“父” 與 “子” 進(jìn)程的交互(進(jìn)程的創(chuàng)建)

    前面我們講了C語(yǔ)言的基礎(chǔ)知識(shí),也了解了一些數(shù)據(jù)結(jié)構(gòu),并且講了有關(guān)C++的一些知識(shí),也學(xué)習(xí)了一些Linux的基本操作,也了解并學(xué)習(xí)了有關(guān)Linux開發(fā)工具vim 、gcc/g++ 使用、yum工具以及git 命令行提交代碼也相信大家都掌握的不錯(cuò),上一篇文章我們了解了關(guān)于進(jìn)程的基本概念,今

    2024年02月08日
    瀏覽(28)
  • Linux------進(jìn)程的fork()詳解

    Linux------進(jìn)程的fork()詳解

    目錄 前言 一、fork()的使用 二、fork()的返回值 我們?yōu)槭裁匆獎(jiǎng)?chuàng)建子進(jìn)程? 父進(jìn)程與子進(jìn)程的分流 三、fork的一些難理解的問(wèn)題 1.fork干了什么事情? 2.fork為什么會(huì)有兩個(gè)返回值? 3.fork的兩個(gè)返回值,為什么會(huì)給父進(jìn)程返回子進(jìn)程pid,給子進(jìn)程返回0? 4.fork之后,父子進(jìn)程誰(shuí)先

    2024年01月18日
    瀏覽(18)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請(qǐng)作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包