一己之见,阅读源码一定要找到底层的真正实现,方法的层层调用要追根溯源,一直定位到有确切文档的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/unix
包Socketpair
函数,这个函数当然是个透传函数。
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
}
这里调用具体架构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读出来,反之亦然。
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退出