netpoller
背景介绍
I/O多路复用模型(I/O Multiplexing):
select
阻塞,直到有FD准备好,FD数量有FD_SETSIZE
限制
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds,
struct timeval *timeout);
// readfds,writefds,exceptfds 需要检查的FDs
// nfds 比最大的FD大1(减少内核比较次数)
// timeout 最大等待时间
// fd_set 位掩码表示FD集合
poll
和select类似,在传递FD方式上不同
int poll(struct pollfd fds[], nfds_t nfds, int timeout);
// fds 需要检查的FDs和事件(events,revents),结果写入fds
// nfds fds中的FD数量
// timeout 最大等待时间
struct pollfd {
int fd; /* File descriptor */
short events; /* Requested events bit mask */
short revents; /* Returned events bit mask */
};
epoll
区分水平触发
和边缘触发
// 创建epoll示例:红黑树,就绪队列,返回epoll实例的FD
int epoll_create(int size);
int epoll_create1(int flag); // 与epoll_create1类似,对epfd有一定的控制
// FD注册
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *ev);
// epfd epoll_create返回的FD
// op 操作类型 EPOLL_CTL_ADD EPOLL_CTL_MOD EPOLL_CTL_DEL
// fd 操作对象FD
// ev 关注的事件类型(位掩码)
struct epoll_event {
uint32_t events; /* epoll events (bit mask) */
epoll_data_t data; /* User data */
};
typedef union epoll_data {
void *ptr; /* Pointer to user-defined data */
int fd; /* File descriptor */
uint32_t u32; /* 32-bit integer */
uint64_t u64; /* 64-bit integer */
} epoll_data_t;
// 获取就绪的FD
int epoll_wait(int epfd, struct epoll_event *evlist, int maxevents, int timeout);
// evlist,maxevents指定返回的event的存储空间和数量
// 自动销毁
epoll相对于其他I/O多路复用模型的优势:
1.不需要每次调用传递FD(用户态/内核态数据拷贝),epoll_ctl将FD添加到内核数据空间中;
2.epoll_wait的效率更高,不需要对比所有的FD,只需要从就绪队列中获取数据即可;
常见I/O模型:阻塞,非阻塞,I/O多路复用,信号驱动,异步I/O
分析实例:File.Read
在unix/linux平台上,netpoller是基于epoll模型来实现的,一下分析也是限定于此;
以简单的文件读取(unix|linux平台)为例,分析从代码层面开始是怎么一步步使用netpoller的。
## 用户代码
func main() {
f, err := os.Open("test.txt")
if err != nil {
log.Fatalln(err)
}
buf := make([]byte, 10)
f.Read(buf)
log.Println(string(buf))
}
使用os.Open
创建一个File实例,核心是获取到文件描述符(pfd)
type File struct {
*file // os specific
}
// unix平台实现
// ## os/file.go
type file struct {
pfd poll.FD
name string
dirinfo *dirInfo // nil unless directory being read
nonblock bool // whether we set nonblocking mode
stdoutOrErr bool // whether this is stdout or stderr
}
/*os/file.go*/
func (f *File) Read(b []byte) (n int, err error) {
if err := f.checkValid("read"); err != nil {
return 0, err
}
n, e := f.read(b)
return n, f.wrapErr("read", e)
}
/*os/file_unix.go(unix平台)*/
func (f *File) read(b []byte) (n int, err error) {
n, err = f.pfd.Read(b)
runtime.KeepAlive(f)
return n, err
}
调用Read
函数,实际上调用了底层的FD.Read
,其实现如下:
/*internal/poll/fd_unix.go*/
func (fd *FD) Read(p []byte) (int, error) {
if err := fd.readLock(); err != nil {
return 0, err
}
defer fd.readUnlock()
if len(p) == 0 {
// If the caller wanted a zero byte read, return immediately
// without trying (but after acquiring the readLock).
// Otherwise syscall.Read returns 0, nil which looks like
// io.EOF.
// TODO(bradfitz): make it wait for readability? (Issue 15735)
return 0, nil
}
if err := fd.pd.prepareRead(fd.isFile); err != nil {
return 0, err
}
if fd.IsStream && len(p) > maxRW {
p = p[:maxRW]
}
for {
// 系统调用 -- 读取数据
n, err := syscall.Read(fd.Sysfd, p)
// 出现错误 -- 可能是一般错误也可能是数据未准备好的特殊错误(EAGAIN)
if err != nil {
n = 0
// syscall.EAGAIN错误 & 可以使用netpoller
// 比如:pollable := kind == kindOpenFile || kind == kindPipe || kind == kind