[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阅读 225,565评论 6 525
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 96,696评论 3 406
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 172,935评论 0 370
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 61,327评论 1 303
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 70,338评论 6 401
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 53,760评论 1 316
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 42,085评论 3 431
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 41,091评论 0 280
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 47,656评论 1 327
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 39,657评论 3 348
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 41,767评论 1 355
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 37,360评论 5 351
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 43,088评论 3 341
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 33,493评论 0 25
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 34,654评论 1 278
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 50,374评论 3 383
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 46,841评论 2 367

推荐阅读更多精彩内容

  • 一般,进程之间交换信息的方法只能是经由fork或exec传送打开文件,或者通过文件系统。而进程间相互通信还有其他技...
    丶Em1tu0F阅读 1,431评论 1 1
  • 1:InputChannel提供函数创建底层的Pipe对象 2: 1)客户端需要新建窗口 2)new ViewRo...
    自由人是工程师阅读 5,338评论 0 18
  • 一、Linux系统概述 不加引号可理解为宏,直接替换,单引号中特殊字符会被解释为普通字符,双引号中$,,'还是特殊...
    赤果_b4a7阅读 1,516评论 0 2
  • 前言 管道是UNIX环境中历史最悠久的进程间通信方式,也是最简单的进程间通信方式,一般用来作为IPC的入门,最合适...
    GeekerLou阅读 1,161评论 0 6
  • 唯有美食可以慰藉我的心
    TNANNAN阅读 210评论 0 0