前言
上節[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]---網絡實現測試