当前位置: 首页 > 工具软件 > runC > 使用案例 >

runc 源码分析笔记: NewSockPair函数

韩麒
2023-12-01

一己之见,阅读源码一定要找到底层的真正实现,方法的层层调用要追根溯源,一直定位到有确切文档的API或者软件工程意义上当前抽象层的下一级。这样才能从根本上去理解,本抽象层如何通过封装下一层,具体实现的功能。
对于runc来讲,个人觉得是要定位到Unix系统调用。

这篇文章解析一个函数NewSockPair,使用golang创建一个本地的unix domain socket. UDS用于本机进程间通信,不需要经过协议栈等一系列中间商,因此速度比较快。

Unix domain sockets can be abbreviated as UDS, where data between different programs can be exchanged at the operating system level, with the help of the file system.
For the program itself, it only needs to read and write to the shared socket file, which means that different programs interact with each other through the socket file.

实现解析

// NewSockPair returns a new unix socket pair
func NewSockPair(name string) (parent *os.File, child *os.File, err error) {
	fds, err := unix.Socketpair(unix.AF_LOCAL, unix.SOCK_STREAM|unix.SOCK_CLOEXEC, 0)
	if err != nil {
		return nil, nil, err
	}
	return os.NewFile(uintptr(fds[1]), name+"-p"), os.NewFile(uintptr(fds[0]), name+"-c"), nil
}

使用了golang.org/x/sys/unixSocketpair函数,这个函数当然是个透传函数。

  • syscall_unix.go
func Socketpair(domain, typ, proto int) (fd [2]int, err error) {
	var fdx [2]int32
	err = socketpair(domain, typ, proto, &fdx)
	if err == nil {
		fd[0] = int(fdx[0])
		fd[1] = int(fdx[1])
	}
	return
}
  • zsyscall_linux_amd64.go

这里调用具体架构linux/amd64的socketpair函数。

func socketpair(domain int, typ int, proto int, fd *[2]int32) (err error) {
	_, _, e1 := RawSyscall6(SYS_SOCKETPAIR, uintptr(domain), uintptr(typ), uintptr(proto), uintptr(unsafe.Pointer(fd)), 0, 0)
	if e1 != 0 {
		err = errnoErr(e1)
	}
	return
}

这个函数实际使用了Unix socketpair系统调用。这个系统调用会产生一对可全双工通信的Socket文件

SYNOPSIS       
       #include <sys/socket.h>

       int socketpair(int domain, int type, int protocol, int sv[2]);
DESCRIPTION     
       The socketpair() call creates an unnamed pair of connected
       sockets in the specified domain, of the specified type, and using
       the optionally specified protocol.  For further details of these
       arguments, see socket(2).

       The file descriptors used in referencing the new sockets are
       returned in sv[0] and sv[1].  The two sockets are
       indistinguishable.
RETURN VALUE      
       On success, zero is returned.  On error, -1 is returned, errno is
       set to indicate the error, and sv is left unchanged

       On Linux (and other systems), socketpair() does not modify sv on
       failure.  A requirement standardizing this behavior was added in
       POSIX.1-2008 TC2.

函数测试

下面测试一下NewSockPair函数的效果。即创建一对套接字文件A和B,往A写的数据可以从B读出来,反之亦然。

  • main.go
package main

import (
	"fmt"
	"io"
	"os"
	"time"
)
import "golang.org/x/sys/unix"

func main() {

	parent,child,err:=NewSockPair("test")
	if err != nil {
		panic(err.Error())
	}
	_, _ = parent.Write([]byte("I'm parent"))

	_, _ = child.Write([]byte("I'm child"))


	go ReadFromFile(child)

	go ReadFromFile(parent)

	time.Sleep(10 * time.Second)
}

// 从文件中读数据
func ReadFromFile(file *os.File){

	b := make([]byte, 1024)

	for {
		// read content to buffer
		readTotal, err := file.Read(b)
		if err != nil {
			if err != io.EOF {
				fmt.Println(err)
			}
			break
		}
		fileContent := string(b[:readTotal])
		// print content from buffer
		fmt.Println("read message from ",file.Name())
		fmt.Println(fileContent)
	}
}

// NewSockPair returns a new unix socket pair
func NewSockPair(name string) (parent *os.File, child *os.File, err error) {
	fds, err := unix.Socketpair(unix.AF_LOCAL, unix.SOCK_STREAM|unix.SOCK_CLOEXEC, 0)
	if err != nil {
		return nil, nil, err
	}
	return os.NewFile(uintptr(fds[1]), name+"-parent"), os.NewFile(uintptr(fds[0]), name+"-child"), nil
}

运行结果:

[root@localhost runcapitest]# go run main.go 
read message from  test-parent
I'm child
read message from  test-child
I'm parent
// 大约10s退出
 类似资料: