成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專(zhuān)欄INFORMATION COLUMN

探索runC (下)

gekylin / 1227人閱讀

摘要:而不幸的是是多線程的。至此,子進(jìn)程就從父進(jìn)程處得到了的配置,繼續(xù)往下,又創(chuàng)建了兩個(gè)從注釋中了解到,這是為了和它自己的子進(jìn)程和孫進(jìn)程進(jìn)行通信。

回顧

本文接 探索runC(上)

前文講到,newParentProcess() 根據(jù)源自 config.json 的配置,最終生成變量 initProcess ,這個(gè) initProcess 包含的信息主要有

cmd 記錄了要執(zhí)行的可執(zhí)行文件名,即 "/proc/self/exe init",注意不要和容器要執(zhí)行的 sleep 5 混淆了

cmd.Env 記錄了名為 _LIBCONTAINER_FIFOFD=%d 記錄的命名管道exec.fifo 的描述符,名為_LIBCONTAINER_INITPIPE=%d記錄了創(chuàng)建的 SocketPairchildPipe 一端的描述符,名為_LIBCONTAINER_INITTYPE="standard"記錄要?jiǎng)?chuàng)建的容器中的進(jìn)程是初始進(jìn)程

initProcessbootstrapData 記錄了新的容器要?jiǎng)?chuàng)建哪些類(lèi)型的 Namespace。

/* libcontainer/container_linux.go */
func (c *linuxContainer) start(process *Process) error {
    parent, err := c.newParentProcess(process) /*  1. 創(chuàng)建parentProcess (已完成) */

    err := parent.start();                     /*  2. 啟動(dòng)這個(gè)parentProcess */
    ......

準(zhǔn)備工作完成之后,就要調(diào)用 start() 方法啟動(dòng)。

注意: 此時(shí) sleep 5 線索存儲(chǔ)在變量 parent
runC create的實(shí)現(xiàn)原理 (下)

start() 函數(shù)實(shí)在太長(zhǎng)了,因此逐段來(lái)看

/* libcontainer/process_linux.go */
func (p *initProcess) start() error {
     
    p.cmd.Start()                 
    p.process.ops = p    
    io.Copy(p.parentPipe, p.bootstrapData)

    .....
}

p.cmd.Start() 啟動(dòng) cmd 中設(shè)置的要執(zhí)行的可執(zhí)行文件 /proc/self/exe,參數(shù)是 init,這個(gè)函數(shù)會(huì)啟動(dòng)一個(gè)新的進(jìn)程去執(zhí)行該命令,并且不會(huì)阻塞。

io.Copyp.bootstrapData 中的數(shù)據(jù)通過(guò) p.parentPipe 發(fā)送給子進(jìn)程

/proc/self/exe 正是runc程序自己,所以這里相當(dāng)于是執(zhí)行runc init,也就是說(shuō),我們輸入的是runc create命令,隱含著又去創(chuàng)建了一個(gè)新的子進(jìn)程去執(zhí)行runc init。為什么要額外重新創(chuàng)建一個(gè)進(jìn)程呢?原因是我們創(chuàng)建的容器很可能需要運(yùn)行在一些獨(dú)立的 namespace 中,比如 user namespace,這是通過(guò) setns() 系統(tǒng)調(diào)用完成的,而在setns man page中寫(xiě)了下面一段話

A multi‐threaded process may not change user namespace with setns().  It is not permitted  to  use  setns() to reenter the caller"s current user names‐pace

即多線程的進(jìn)程是不能通過(guò) setns()改變user namespace的。而不幸的是 Go runtime 是多線程的。那怎么辦呢 ?所以setns()必須要在Go runtime 啟動(dòng)之前就設(shè)置好,這就要用到cgo了,在Go runtime 啟動(dòng)前首先執(zhí)行嵌入在前面的 C 代碼。

具體的做法在nsenter README描述 在runc init命令的響應(yīng)在文件 init.go 開(kāi)頭,導(dǎo)入 nsenter

/* init.go */
import (
    "os"
    "runtime"

    "github.com/opencontainers/runc/libcontainer"
    _ "github.com/opencontainers/runc/libcontainer/nsenter"
    "github.com/urfave/cli"
)

nsenter包中開(kāi)頭通過(guò) cgo 嵌入了一段 C 代碼, 調(diào)用 nsexec()

package nsenter
/*
/* nsenter.go */
#cgo CFLAGS: -Wall
extern void nsexec();
void __attribute__((constructor)) init(void) {
    nsexec();
}
*/
import "C"

接下來(lái),輪到 nsexec() 完成為容器創(chuàng)建新的 namespace 的工作了, nsexec() 同樣很長(zhǎng),逐段來(lái)看

/* libcontainer/nsenter/nsexec.c */
void nsexec(void)
{
    int pipenum;
    jmp_buf env;
    int sync_child_pipe[2], sync_grandchild_pipe[2];
    struct nlconfig_t config = { 0 };

    /*
     * If we don"t have an init pipe, just return to the go routine.
     * We"ll only get an init pipe for start or exec.
     */
    pipenum = initpipe();
    if (pipenum == -1)
        return;

    /* Parse all of the netlink configuration. */
    nl_parse(pipenum, &config);
   
    ......    

上面這段 C 代碼中,initpipe() 從環(huán)境中讀取父進(jìn)程之前設(shè)置的pipe(_LIBCONTAINER_INITPIPE記錄的的文件描述符),然后調(diào)用 nl_parse 從這個(gè)管道中讀取配置到變量 config ,那么誰(shuí)會(huì)往這個(gè)管道寫(xiě)配置呢 ? 當(dāng)然就是runc create父進(jìn)程了。父進(jìn)程通過(guò)這個(gè)pipe,將新建容器的配置發(fā)給子進(jìn)程,這個(gè)過(guò)程如下圖所示:

發(fā)送的具體數(shù)據(jù)在 linuxContainerbootstrapData() 函數(shù)中封裝成netlink msg格式的消息。忽略大部分配置,本文重點(diǎn)關(guān)注namespace的配置,即要?jiǎng)?chuàng)建哪些類(lèi)型的namespace,這些都是源自最初的config.json文件。

至此,子進(jìn)程就從父進(jìn)程處得到了namespace的配置,繼續(xù)往下, nsexec() 又創(chuàng)建了兩個(gè)socketpair,從注釋中了解到,這是為了和它自己的子進(jìn)程和孫進(jìn)程進(jìn)行通信。

void nsexec(void)
{
   .....
    /* Pipe so we can tell the child when we"ve finished setting up. */
    if (socketpair(AF_LOCAL, SOCK_STREAM, 0, sync_child_pipe) < 0)  //  sync_child_pipe is an out parameter
        bail("failed to setup sync pipe between parent and child");

    /*
     * We need a new socketpair to sync with grandchild so we don"t have
     * race condition with child.
     */
    if (socketpair(AF_LOCAL, SOCK_STREAM, 0, sync_grandchild_pipe) < 0)
        bail("failed to setup sync pipe between parent and grandchild");
   
}

然后就該創(chuàng)建namespace了,看注釋可知這里其實(shí)有考慮過(guò)三個(gè)方案

first clone then clone

first unshare then clone

first clone then unshare

最終采用的是方案 3,其中緣由由于考慮因素太多,所以準(zhǔn)備之后另寫(xiě)一篇文章分析

接下來(lái)就是一個(gè)大的 switch case 編寫(xiě)的狀態(tài)機(jī),大體結(jié)構(gòu)如下,當(dāng)前進(jìn)程通過(guò)clone()系統(tǒng)調(diào)用創(chuàng)建子進(jìn)程,子進(jìn)程又通過(guò)clone()系統(tǒng)調(diào)用創(chuàng)建孫進(jìn)程,而實(shí)際的創(chuàng)建/加入namespace是在子進(jìn)程完成的

switch (setjmp(env)) {
  case JUMP_PARENT:{
           .....
           clone_parent(&env, JUMP_CHILD);
           .....
       }
  case JUMP_CHILD:{
           ......
           if (config.namespaces)
                join_namespaces(config.namespaces);
           clone_parent(&env, JUMP_INIT);
           ......
       }
  case JUMP_INIT:{
       }

本文不準(zhǔn)備展開(kāi)分析這個(gè)狀態(tài)機(jī)了,而將這個(gè)狀態(tài)機(jī)的流程畫(huà)在了下面的時(shí)序圖中,需要注意的是以下幾點(diǎn)

namespacesrunc init 2完成創(chuàng)建

runc init 1runc init 2最終都會(huì)執(zhí)行exit(0),但runc init 3不會(huì),它會(huì)繼續(xù)執(zhí)行runc init命令的后半部分。因此最終只會(huì)剩下runc create進(jìn)程和runc init 3進(jìn)程

再回到runc create進(jìn)程

func (p *initProcess) start() error {

    p.cmd.Start()
    p.process.ops = p
    io.Copy(p.parentPipe, p.bootstrapData);

    p.execSetns()
    ......

再向 runc init發(fā)送了 bootstrapData 數(shù)據(jù)后,便調(diào)用 execSetns() 等待runc init 1進(jìn)程終止,從管道中得到runc init 3的進(jìn)程 pid,將該進(jìn)程保存在 p.process.ops

/* libcontainer/process_linux.go */
func (p *initProcess) execSetns() error {
    status, err := p.cmd.Process.Wait()

    var pid *pid
    json.NewDecoder(p.parentPipe).Decode(&pid)

    process, err := os.FindProcess(pid.Pid)

    p.cmd.Process = process
    p.process.ops = p
    return nil
}

繼續(xù) start()

func (p *initProcess) start() error {

    ...... 
    p.execSetns()
    
    fds, err := getPipeFds(p.pid())
    p.setExternalDescriptors(fds)
    p.createNetworkInterfaces()
    
    p.sendConfig()
    
    parseSync(p.parentPipe, func(sync *syncT) error {
        switch sync.Type {
        case procReady:
            .....
            writeSync(p.parentPipe, procRun);
            sentRun = true
        case procHooks:
            .....
            // Sync with child.
            err := writeSync(p.parentPipe, procResume); 
            sentResume = true
        }

        return nil
    })
    ......

可以看到,runc create又開(kāi)始通過(guò)pipe進(jìn)行雙向通信了,通信的對(duì)端自然就是runc init 3進(jìn)程了,runc init 3進(jìn)程在執(zhí)行完嵌入的 C 代碼后(實(shí)際是runc init 1執(zhí)行的,但runc init 3也是由runc init 1間接clone()出來(lái)的),因此將開(kāi)始運(yùn)行 Go runtime,開(kāi)始響應(yīng)init命令

sleep 5 通過(guò) p.sendConfig() 發(fā)送給了runc init進(jìn)程

init命令首先通過(guò) libcontainer.New("") 創(chuàng)建了一個(gè) LinuxFactory,這個(gè)方法在上篇文章中分析過(guò),這里不再解釋。然后調(diào)用 LinuxFactoryStartInitialization() 方法。

/* libcontainer/factory_linux.go */
// StartInitialization loads a container by opening the pipe fd from the parent to read the configuration and state
// This is a low level implementation detail of the reexec and should not be consumed externally
func (l *LinuxFactory) StartInitialization() (err error) {
    var (
        pipefd, fifofd int
        envInitPipe    = os.Getenv("_LIBCONTAINER_INITPIPE")  
        envFifoFd      = os.Getenv("_LIBCONTAINER_FIFOFD")
    )

    // Get the INITPIPE.
    pipefd, err = strconv.Atoi(envInitPipe)

    var (
        pipe = os.NewFile(uintptr(pipefd), "pipe")
        it   = initType(os.Getenv("_LIBCONTAINER_INITTYPE")) // // "standard" or "setns"
    )
    
    // Only init processes have FIFOFD.
    fifofd = -1
    if it == initStandard {
        if fifofd, err = strconv.Atoi(envFifoFd); err != nil {
            return fmt.Errorf("unable to convert _LIBCONTAINER_FIFOFD=%s to int: %s", envFifoFd, err)
        }
    }

    i, err := newContainerInit(it, pipe, consoleSocket, fifofd)

    // If Init succeeds, syscall.Exec will not return, hence none of the defers will be called.
    return i.Init() //
}

StartInitialization() 方法嘗試從環(huán)境中讀取一系列_LIBCONTAINER_XXX變量的值,還有印象嗎?這些值全是在runc create命令中打開(kāi)和設(shè)置的,也就是說(shuō),runc create通過(guò)環(huán)境變量,將這些參數(shù)傳給了子進(jìn)程runc init 3

拿到這些環(huán)境變量后,runc init 3調(diào)用 newContainerInit 函數(shù)

/* libcontainer/init_linux.go */
func newContainerInit(t initType, pipe *os.File, consoleSocket *os.File, fifoFd int) (initer, error) {
    var config *initConfig

    /* read config from pipe (from runc process) */
    son.NewDecoder(pipe).Decode(&config); 
    populateProcessEnvironment(config.Env);
    switch t {
    ......
    case initStandard:
        return &linuxStandardInit{
            pipe:          pipe,
            consoleSocket: consoleSocket,
            parentPid:     unix.Getppid(),
            config:        config, // <=== config
            fifoFd:        fifoFd,
        }, nil
    }
    return nil, fmt.Errorf("unknown init type %q", t)
}

newContainerInit() 函數(shù)首先嘗試從 pipe 讀取配置存放到變量 config 中,再存儲(chǔ)到變量 linuxStandardInit 中返回

   runc create                    runc init 3
       |                               |
  p.sendConfig() --- config -->  NewContainerInit()
sleep 5 線索在 initStandard.config 中

回到 StartInitialization(),在得到 linuxStandardInit 后,便調(diào)用其 Init()方法了

/* init.go */
func (l *LinuxFactory) StartInitialization() (err error) {
    ......
    i, err := newContainerInit(it, pipe, consoleSocket, fifofd)

    return i.Init()  
}

本文忽略掉 Init() 方法前面的一大堆其他配置,只看其最后

func (l *linuxStandardInit) Init() error {
   ......
   name, err := exec.LookPath(l.config.Args[0])

   syscall.Exec(name, l.config.Args[0:], os.Environ())
}

可以看到,這里終于開(kāi)始執(zhí)行 用戶最初設(shè)置的 sleep 5

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/33102.html

相關(guān)文章

  • 探索 runC (上)

    摘要:當(dāng)前業(yè)內(nèi)比較有名的有,等。至少在筆者的主機(jī)上是這樣。而第部加載,在上,就是返回一個(gè)結(jié)構(gòu)。方法的實(shí)現(xiàn)如下第部分第部分上面的可分為兩部分調(diào)用方法用創(chuàng)建注意第二個(gè)參數(shù)是,表示新創(chuàng)建的會(huì)作為新創(chuàng)建容器的第一個(gè)。 前言 容器運(yùn)行時(shí)(Container Runtime)是指管理容器和容器鏡像的軟件。當(dāng)前業(yè)內(nèi)比較有名的有docker,rkt等。如果不同的運(yùn)行時(shí)只能支持各自的容器,那么顯然不利于整個(gè)容...

    yanest 評(píng)論0 收藏0
  • 探索 runC (上)

    摘要:當(dāng)前業(yè)內(nèi)比較有名的有,等。至少在筆者的主機(jī)上是這樣。而第部加載,在上,就是返回一個(gè)結(jié)構(gòu)。方法的實(shí)現(xiàn)如下第部分第部分上面的可分為兩部分調(diào)用方法用創(chuàng)建注意第二個(gè)參數(shù)是,表示新創(chuàng)建的會(huì)作為新創(chuàng)建容器的第一個(gè)。 前言 容器運(yùn)行時(shí)(Container Runtime)是指管理容器和容器鏡像的軟件。當(dāng)前業(yè)內(nèi)比較有名的有docker,rkt等。如果不同的運(yùn)行時(shí)只能支持各自的容器,那么顯然不利于整個(gè)容...

    Aomine 評(píng)論0 收藏0
  • 探索runC ()

    摘要:而不幸的是是多線程的。至此,子進(jìn)程就從父進(jìn)程處得到了的配置,繼續(xù)往下,又創(chuàng)建了兩個(gè)從注釋中了解到,這是為了和它自己的子進(jìn)程和孫進(jìn)程進(jìn)行通信。 回顧 本文接 探索runC(上) 前文講到,newParentProcess() 根據(jù)源自 config.json 的配置,最終生成變量 initProcess ,這個(gè) initProcess 包含的信息主要有 cmd 記錄了要執(zhí)行的可執(zhí)行...

    jzman 評(píng)論0 收藏0
  • runc 1.0-rc7 發(fā)布之際

    摘要:在年月底時(shí),我寫(xiě)了一篇文章發(fā)布之際。為何有存在前面已經(jīng)基本介紹了相關(guān)背景,并且也基本明確了就是在正式發(fā)布之前的最后一個(gè)版本,那為什么會(huì)出現(xiàn)呢我們首先要介紹今年的一個(gè)提權(quán)漏洞。 在 18 年 11 月底時(shí),我寫(xiě)了一篇文章 《runc 1.0-rc6 發(fā)布之際》 。如果你還不了解 runc 是什么,以及如何使用它,請(qǐng)參考我那篇文章。本文中,不再對(duì)其概念和用法等進(jìn)行說(shuō)明。 在 runc 1....

    zhunjiee 評(píng)論0 收藏0
  • runc 1.0-rc7 發(fā)布之際

    摘要:在年月底時(shí),我寫(xiě)了一篇文章發(fā)布之際。為何有存在前面已經(jīng)基本介紹了相關(guān)背景,并且也基本明確了就是在正式發(fā)布之前的最后一個(gè)版本,那為什么會(huì)出現(xiàn)呢我們首先要介紹今年的一個(gè)提權(quán)漏洞。 在 18 年 11 月底時(shí),我寫(xiě)了一篇文章 《runc 1.0-rc6 發(fā)布之際》 。如果你還不了解 runc 是什么,以及如何使用它,請(qǐng)參考我那篇文章。本文中,不再對(duì)其概念和用法等進(jìn)行說(shuō)明。 在 runc 1....

    YanceyOfficial 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<