[mydocker]---構造容器03-實現增加管道

前言

上節[mydocker]---構造容器02-實現資源限制02中已經加入了對memory的限制. 本節將會加入管道功能, 通常進程間使用管道進行通信, 所以本文將對之前進程間傳輸的command用管道的方式來執行.

代碼: 代碼下載
tag: code-3.3

效果

root@nicktming:~/go/src/github.com/nicktming# pwd
/root/go/src/github.com/nicktming
root@nicktming:~/go/src/github.com/nicktming# git clone https://github.com/nicktming/mydocker.git
root@nicktming:~/go/src/github.com/nicktming# cd mydocker
root@nicktming:~/go/src/github.com/nicktming/mydocker# git checkout code-3.3
root@nicktming:~/go/src/github.com/nicktming/mydocker# go build .
root@nicktming:~/go/src/github.com/nicktming/mydocker# ./mydocker run -it /bin/sh
2019/04/03 23:26:14 read from commandline:
2019/04/03 23:26:14 read from pipe:/bin/sh
# ps -ef
UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0 23:26 pts/2    00:00:00 /bin/sh
root         4     1  0 23:26 pts/2    00:00:00 ps -ef
# 

可以看到容器執行的用戶程序命令/bin/sh是從管道中讀取的.

了解管道

多個進程在協作完成同一任務時,通常彼此要傳輸數據,共享資源.

匿名管道:shell 中的 pipe 就是匿名管道,只能在父子進程 / 有親緣關系的進程之間使用.
命名管道:允許無親緣關系的進程間傳輸數據.

由于代碼中使用的是命名管道,因此接下來通過兩個簡單的例子來了解一下管道是如何工作.

例子1

非常簡單的一個例子, 同步操作, 先用管道的寫端寫數據,然后管道的讀端讀數據.

func Test001(t *testing.T) {
    reader, writer, err := os.Pipe()
    if err != nil {
        log.Fatalf("os.pipe error:%v\n", err)
    }
    _, err = writer.Write([]byte("pipe content"))
    if err != nil {
        log.Fatalf("writer.Write error:%v\n", err)
    }

    buf := make([]byte, 20)
    n, err := reader.Read(buf)
    if err != nil {
        log.Fatalf("reader.Read(buf) error:%v\n", err)
    }
    log.Printf("Read Content:%q\n", string(buf[:n]))
}

執行操作

root@nicktming:~/go/src/github.com/nicktming/mydocker/test/pipe# go test -v pipe_test.go -test.run Test001
=== RUN   Test001
2019/04/03 23:34:14 Read Content:"pipe content"
--- PASS: Test001 (0.00s)
PASS
ok      command-line-arguments  0.002s

例子2

將上面的例子從同步改成異步, 啟動兩個goroutine一個寫端一直寫10次, 管道的讀端讀管道里面的所有內容.

func Test002(t *testing.T) {
    reader, writer, err := os.Pipe()
    if err != nil {
        log.Fatalf("os.pipe error:%v\n", err)
    }
    go func() {
        for i := 0; i < 10; i++ {
            content := fmt.Sprintf("%s-%d\n", "pipe content", i)
            _, err = writer.Write([]byte(content))
            if err != nil {
                log.Fatalf("writer.Write error:%v\n", err)
            }
        }
        writer.Close()
    }()

    go func() {
        n, err := ioutil.ReadAll(reader)
        if err != nil {
            log.Fatalf("reader.Read(buf) error:%v\n", err)
        }
        log.Printf("Read Content:%q\n", n)
    }()

    for i := 0; i <= 100; i++{
        time.Sleep(1 * time.Second)
    }
}

執行, 注意只有等到管道寫端Close()后讀端才可以讀到所有的內容.

root@nicktming:~/go/src/github.com/nicktming/mydocker/test/pipe# go test -v pipe_test.go -test.run Test002
=== RUN   Test002
2019/04/04 00:16:03 Read Content:"pipe content-0\npipe content-1\npipe content-2\npipe content-3\npipe content-4\npipe content-5\npipe content-6\npipe content-7\npipe content-8\npipe content-9\n"

實現

其實實現也是比較比較簡單, 就是把原先/proc/self/exe init /bin/sh改成/proc/self/exe并且把command(/bin/sh)通過管道傳輸. 也就是在run方法中生成管道并用寫端把comand寫進去后關閉寫端. 利用cmd把管道的讀端傳輸給子進程, 然后在子進程中用管道讀端讀取command從而執行該command.

1. command/run.go

在此文件中將Run方法改動如下

a. reader, writer, err := os.Pipe()
b. cmd := exec.Command("/proc/self/exe", "init")不再把用戶命令傳輸該init中.
c. 新增sendInitCommand方法使用管道寫端將command寫進去.
d. 因為init中需要用到該管道的讀端, 因此使用cmd.ExtraFiles = []*os.File{reader}將其傳輸.

func Run(command string, tty bool, cg *cgroups.CroupManger)  {
    //cmd := exec.Command(command)

    reader, writer, err := os.Pipe()
    if err != nil {
        log.Printf("Error: os.pipe() error:%v\n", err)
        return
    }

    //cmd := exec.Command("/proc/self/exe", "init", command)

    cmd := exec.Command("/proc/self/exe", "init")

    cmd.SysProcAttr = &syscall.SysProcAttr{
        Cloneflags: syscall.CLONE_NEWUTS | syscall.CLONE_NEWPID | syscall.CLONE_NEWNS | syscall.CLONE_NEWNET | syscall.CLONE_NEWIPC,
    }

    cmd.ExtraFiles = []*os.File{reader}
    sendInitCommand(command, writer)

    if tty {
        cmd.Stderr = os.Stderr
        cmd.Stdout = os.Stdout
        cmd.Stdin = os.Stdin
    }
    /**
     *   Start() will not block, so it needs to use Wait()
     *   Run() will block
     */
    if err := cmd.Start(); err != nil {
        log.Printf("Run Start err: %v.\n", err)
        log.Fatal(err)
    }
    //log.Printf("222 before process pid:%d, memory:%s\n", cmd.Process.Pid, memory)

    //subsystems.Set(memory)
    //subsystems.Apply(strconv.Itoa(cmd.Process.Pid))
    //defer subsystems.Remove()

//  sendInitCommand(command, writer)

    cg.Set()
    defer cg.Destroy()
    cg.Apply(strconv.Itoa(cmd.Process.Pid))

    cmd.Wait()
}

func sendInitCommand(command string, writer *os.File)  {
    _, err := writer.Write([]byte(command))
    if err != nil {
        log.Printf("writer.Write error:%v\n", err)
        return
    }
    writer.Close()
}

2. command/init.go

與上面對應的此處改動如下:

a. command從命令行中獲取的值為“”
b. 新增一個函數負責利用管道的讀端來獲得run方法中傳輸過來的command. 其中reader := os.NewFile(uintptr(3), "pipe")uintptr(3)就是指index 為3 的文件描述符,也就是傳遞進來的管道的一端

func Init(command string)  {

    log.Printf("read from commandline:%s\n", command)

    command = readFromPipe()

    log.Printf("read from pipe:%s\n", command)

    defaultMountFlags := syscall.MS_NOEXEC | syscall.MS_NOSUID | syscall.MS_NODEV
    syscall.Mount("proc", "/proc", "proc", uintptr(defaultMountFlags), "")

    /*
    cmd := exec.Command(command)

    cmd.Stdin = os.Stdin
    cmd.Stderr = os.Stderr
    cmd.Stdout = os.Stdout

    if err := cmd.Run(); err != nil {
        log.Printf("Init Run() function err : %v\n", err)
        log.Fatal(err)
    }
    */

    if err := syscall.Exec(command, []string{command}, os.Environ()); err != nil {
        log.Printf("syscall.Exec err: %v\n", err)
        log.Fatal(err)
    }
}

func readFromPipe() string {
    reader := os.NewFile(uintptr(3), "pipe")
    command, err := ioutil.ReadAll(reader)
    if err != nil {
        log.Printf("reader.Read(buf) error:%v\n", err)
        return ""
    }
    return string(command)
}

時序圖

code-3.3.png

參考

1. https://wuyin.io/2019/02/19/ipc-pipe/
2. 自己動手寫docker.(基本參考此書,加入一些自己的理解,加深對docker的理解)

全部內容

mydocker.png

1. [mydocker]---環境說明
2. [mydocker]---urfave cli 理解
3. [mydocker]---Linux Namespace
4. [mydocker]---Linux Cgroup
5. [mydocker]---構造容器01-實現run命令
6. [mydocker]---構造容器02-實現資源限制01
7. [mydocker]---構造容器02-實現資源限制02
8. [mydocker]---構造容器03-實現增加管道
9. [mydocker]---通過例子理解存儲驅動AUFS
10. [mydocker]---通過例子理解chroot 和 pivot_root
11. [mydocker]---一步步實現使用busybox創建容器
12. [mydocker]---一步步實現使用AUFS包裝busybox
13. [mydocker]---一步步實現volume操作
14. [mydocker]---實現保存鏡像
15. [mydocker]---實現容器的后臺運行
16. [mydocker]---實現查看運行中容器
17. [mydocker]---實現查看容器日志
18. [mydocker]---實現進入容器Namespace
19. [mydocker]---實現停止容器
20. [mydocker]---實現刪除容器
21. [mydocker]---實現容器層隔離
22. [mydocker]---實現通過容器制作鏡像
23. [mydocker]---實現cp操作
24. [mydocker]---實現容器指定環境變量
25. [mydocker]---網際協議IP
26. [mydocker]---網絡虛擬設備veth bridge iptables
27. [mydocker]---docker的四種網絡模型與原理實現(1)
28. [mydocker]---docker的四種網絡模型與原理實現(2)
29. [mydocker]---容器地址分配
30. [mydocker]---網絡net/netlink api 使用解析
31. [mydocker]---網絡實現
32. [mydocker]---網絡實現測試

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,237評論 6 537
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,957評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,248評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,356評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,081評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,485評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,534評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,720評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,263評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,025評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,204評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,787評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,461評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,874評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,105評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,945評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,205評論 2 375

推薦閱讀更多精彩內容

  • 一般,進程之間交換信息的方法只能是經由fork或exec傳送打開文件,或者通過文件系統。而進程間相互通信還有其他技...
    丶Em1tu0F閱讀 1,436評論 1 1
  • 1:InputChannel提供函數創建底層的Pipe對象 2: 1)客戶端需要新建窗口 2)new ViewRo...
    自由人是工程師閱讀 5,356評論 0 18
  • 一、Linux系統概述 不加引號可理解為宏,直接替換,單引號中特殊字符會被解釋為普通字符,雙引號中$,,'還是特殊...
    赤果_b4a7閱讀 1,530評論 0 2
  • 前言 管道是UNIX環境中歷史最悠久的進程間通信方式,也是最簡單的進程間通信方式,一般用來作為IPC的入門,最合適...
    GeekerLou閱讀 1,168評論 0 6
  • 唯有美食可以慰藉我的心
    TNANNAN閱讀 212評論 0 0