自己动手写Docker系列 -- 3.3使用命令管道优化参数传递

954 阅读4分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第3天,点击查看活动详情

简介

在上几篇中,基本是都是通过函数参数传递的方式进行的参数传递,本篇中使用Linux的管道优化参数传递

源码说明

同时放到了Gitee和Github上,都可进行获取

本章节对应的版本标签是:3.3,防止后面代码过多,不好查看,可切换到标签版本进行查看

在上几篇中,都是通过函数传递的方式获取的参数,但在书中说:用户输入的参数过长,或者其中带有一些特殊字符,那参数的获取就会有些问题

本篇中使用管道进行优化,这个管道有点类似于go中的channel和Java中的阻塞队列,可以写入和读取

当写满时,写进程就会阻塞,直到有读进程把管道的内容读出来

但读取时,如果管道为空,同样会被阻塞,一直等到有写进程忘里面写数据

编码实现

代码没有前面的多了,比较少了,主要为:

  • 管道的创建
  • 参数数据的写入
  • 参数数据的读取

1.管道的创建

首先是在fork进程启动的时候,创建出管道。写管道返回,用于写入数据;读管道作为文件句柄,传入新进程中

func NewParentProcess(tty bool) (*exec.Cmd, *os.File) {
	// 生成管道
    readPipe, writePipe, err := os.Pipe()
    if err != nil {
        log.Errorf("create pipe error: %v", err)
        return nil, nil
    }

    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,
    }
    if tty {
        cmd.Stdin = os.Stdin
        cmd.Stdout = os.Stdout
        cmd.Stderr = os.Stderr
    }

    // 将管道的一端传入fork的进程中
    cmd.ExtraFiles = []*os.File{readPipe}
    return cmd, writePipe
}

2.参数数据的写入

在fork进程起来后,我们将参数数据写入管道

func Run(tty bool, cmdArray []string, config *subsystem.ResourceConfig) {
	parent, writePipe := container.NewParentProcess(tty)
	if err := parent.Start(); err != nil {
		log.Error(err)
		return
	}

	......
	
	sendInitCommand(cmdArray, writePipe)

	log.Infof("parent process run")
	_ = parent.Wait()
	os.Exit(-1)
}

// 将运行参数写入管道
func sendInitCommand(array []string, writePipe *os.File) {
	command := strings.Join(array, " ")
	log.Infof("all command is : %s", command)
	if _, err := writePipe.WriteString(command); err != nil {
		log.Errorf("write pipe write string err: %v", err)
		return
	}
	if err := writePipe.Close(); err != nil {
		log.Errorf("write pipe close err: %v", err)
	}
}

3.参数数据的读取

没有读到数据时,会一直阻塞住,直到读取到了相关的参数数据

func RunContainerInitProcess() error {
	......

	cmdArray := readUserCommand()
	path, err := exec.LookPath(cmdArray[0])
	if err != nil {
		log.Errorf("can't find exec path: %s %v", cmdArray[0], err)
		return err
	}
	log.Infof("find path: %s", path)
	if err := syscall.Exec(path, cmdArray, os.Environ()); err != nil {
		log.Errorf("syscall exec err: %v", err.Error())
	}
	return nil
}

// 读取程序传入参数
func readUserCommand() []string {
	// 进程默认三个管道,从fork那边传过来的就是第四个(从0开始计数)
	readPipe := os.NewFile(uintptr(3), "pipe")
	msg, err := ioutil.ReadAll(readPipe)
	if err != nil {
		log.Errorf("read init argv pipe err: %v", err)
		return nil
	}
	return strings.Split(string(msg), " ")
}

运行验证

运行的结果如下,符合预期:

➜  dockerDemo git:(main) ✗ ./main run -ti -mem 100m ls -l
{"level":"info","msg":"memory cgroup path: /sys/fs/cgroup/memory/mydocker-cgroup","time":"2022-03-16T06:11:56+08:00"}
{"level":"info","msg":"memory cgroup path: /sys/fs/cgroup/memory/mydocker-cgroup","time":"2022-03-16T06:11:56+08:00"}
{"level":"info","msg":"all command is : ls -l","time":"2022-03-16T06:11:56+08:00"}
{"level":"info","msg":"parent process run","time":"2022-03-16T06:11:56+08:00"}
{"level":"info","msg":"init come on","time":"2022-03-16T06:11:56+08:00"}
{"level":"info","msg":"find path: /usr/bin/ls","time":"2022-03-16T06:11:56+08:00"}
total 4672
drwxr-xr-x 2 root root    4096 Mar 16 06:11 docs
drwxr-xr-x 3 root root    4096 Mar 15 08:33 example
-rw-r--r-- 1 root root     382 Mar 15 08:33 go.mod
-rw-r--r-- 1 root root    1965 Mar 15 08:33 go.sum
-rw-r--r-- 1 root root   11558 Mar 15 08:33 LICENSE
-rwxr-xr-x 1 root root 4746416 Mar 15 09:00 main
drwxr-xr-x 6 root root    4096 Mar 15 08:33 mydocker
-rw-r--r-- 1 root root     473 Mar 15 08:33 README.md

总结

本篇中使用管道优化了参数的传递,实现了目标

但在上篇中的加入的资源限制功能好像有点小缺陷,目前必须要传入资源限制参数,变成了一个必选的,后面把它优化下