golang源码剖析-网络库的基础实现-2

转自: http://skoo.me/go/2014/04/21/go-net-core
接上一篇: https://blog.csdn.net/robertkun/article/details/80087304

想要弄明白网络库的底层实现,貌似只要弄清楚echo服务器中的Listen、Accept、Read、Write四个函数的底层实现关系就可以了。本文将采用自底向上的方式来介绍,也就是从最底层到上层的方式,这也是我阅读源码的方式。底层实现涉及到的核心源码文件主要有:
net/fd_unix.go
net/fd_poll_runtime.go
runtime/netpoll.goc
runtime/netpoll_epoll.go
runtime/proc.c (调度器)

netpoll_epoll.go文件是Linux平台使用epoll作为网络IO多路复用的实现代码,这份代码可以了解到epoll相关的操作(比如:添加fd到epoll、从epoll删除fd等),只有4个函数,分别是runtime·netpollinit、runtime·netpollopen、runtime·netpollclose和runtime·netpoll。init函数就是创建epoll对象,open函数就是添加一个fd到epoll中,close函数就是从epoll删除一个fd,netpoll函数就是从epoll wait得到所有发生事件的fd,并将每个fd对应的goroutine(用户态线程)通过链表返回。用epoll写过程序的人应该都能理解这份代码,没什么特别之处。

ps: 引文中使用的是.c文件, 估计是版本比较老, 现在参考的是go1.9.1版本

func netpollinit() {
    epfd = epollcreate1(_EPOLL_CLOEXEC)
    if epfd >= 0 {
        return
    }
    epfd = epollcreate(1024)
    if epfd >= 0 {
        closeonexec(epfd)
        return
    }
    println("runtime: epollcreate failed with", -epfd)
    throw("runtime: netpollinit failed")
}

runtime·netpollinit函数首先使用runtime·epollcreate1创建epoll实例,如果没有创建成功,就换用runtime·epollcreate再创建一次。这两个create函数分别等价于glibc的epoll_create1和epoll_create函数。只是因为Go语言并没有直接使用glibc,而是自己封装的系统调用,但功能是等价于glibc的。可以通过man手册查看这两个create的详细信息。

func netpollopen(fd uintptr, pd *pollDesc) int32 {
    var ev epollevent
    ev.events = _EPOLLIN | _EPOLLOUT | _EPOLLRDHUP | _EPOLLET
    *(**pollDesc)(unsafe.Pointer(&ev.data)) = pd
    return -epollctl(epfd, _EPOLL_CTL_ADD, int32(fd), &ev)
}

添加fd到epoll中的runtime·netpollopen函数可以看到每个fd一开始都关注了读写事件,并且采用的是边缘触发,除此之外还关注了一个不常见的新事件EPOLLRDHUP,这个事件是在较新的内核版本添加的,目的是解决对端socket关闭,epoll本身并不能直接感知到这个关闭动作的问题。注意任何一个fd在添加到epoll中的时候就关注了EPOLLOUT事件的话,就立马产生一次写事件,这次事件可能是多余浪费的。

epoll操作的相关函数都会在事件驱动的抽象层中去调用,为什么需要这个抽象层呢?原因很简单,因为Go语言需要跑在不同的平台上,有Linux、Unix、Mac OS X和Windows等,所以需要靠事件驱动的抽象层来为网络库提供一致的接口,从而屏蔽事件驱动的具体平台依赖实现。runtime/netpoll.goc源文件就是整个事件驱动抽象层的实现,抽象层的核心数据结构是:
netpoll.go

// Network poller descriptor.
//
// No heap pointers.
//
//go:notinheap
type pollDesc struct {
    link *pollDesc // in pollcache, protected by pollcache.lock

    // The lock protects pollOpen, pollSetDeadline, pollUnblock and deadlineimpl operations.
    // This fully covers seq, rt and wt variables. fd is constant throughout the PollDesc lifetime.
    // pollReset, pollWait, pollWaitCanceled and runtime·netpollready (IO readiness notification)
    // proceed w/o taking the lock. So closing, rg, rd, wg and wd are manipulated
    // in a lock-free way by all operations.
    // NOTE(dvyukov): the following code uses uintptr to store *g (rg/wg),
    // that will blow up when GC starts moving objects.
    lock    mutex // protects the following fields
    fd      uintptr
    closing bool
    seq     uintptr // protects from stale timers and ready notifications
    rg      uintptr // pdReady, pdWait, G waiting for read or nil
    rt      timer   // read deadline timer (set if rt.f != nil)
    rd      int64   // read deadline
    wg      uintptr // pdReady, pdWait, G waiting for write or nil
    wt      timer   // write deadline timer
    wd      int64   // write deadline
    user    uint32  // user settable cookie
}

每个添加到epoll中的fd都对应了一个PollDesc结构实例,PollDesc维护了读写此fd的goroutine这一非常重要的信息。可以大胆的推测一下,网络IO读写操作的实现应该是:当在一个fd上读写遇到EAGAIN错误的时候,就将当前goroutine存储到这个fd对应的PollDesc中,同时将goroutine给park住,直到这个fd上再此发生了读写事件后,再将此goroutine给ready激活重新运行。事实上的实现大概也是这个样子的。

事件驱动抽象层主要干的事情就是将具体的事件驱动实现(比如: epoll)通过统一的接口封装成Go接口供net库使用,主要的接口也是:创建事件驱动实例、添加fd、删除fd、等待事件以及设置DeadLine。runtime_pollServerInit负责创建事件驱动实例,runtime_pollOpen将分配一个PollDesc实例和fd绑定起来,然后将fd添加到epoll中,runtime_pollClose就是将fd从epoll中删除,同时将删除的fd绑定的PollDesc实例删除,runtime_pollWait接口是至关重要的,这个接口一般是在非阻塞读写发生EAGAIN错误的时候调用,作用就是park当前读写的goroutine。


结尾彩蛋

EPOLL事件的两种模型:边缘触发和水平触发
Level Triggered (LT) 水平触发
socket接收缓冲区不为空 有数据可读 读事件一直触发
socket发送缓冲区不满 可以继续写入数据 写事件一直触发
符合思维习惯,epoll_wait返回的事件就是socket的状态

Edge Triggered (ET) 边沿触发
socket的接收缓冲区状态变化时触发读事件,即空的接收缓冲区刚接收到数据时触发读事件
socket的发送缓冲区状态变化时触发写事件,即满的缓冲区刚空出空间时触发写事件
仅在状态变化时触发事件(网上很多文章都写的是 触发读事件, 我觉得应该是笔误.)

从数字电路的角度理解:
这里写图片描述

https://www.cnblogs.com/kevin-cool/p/8877824.html
https://zhuanlan.zhihu.com/p/21374980

猜你喜欢

转载自blog.csdn.net/robertkun/article/details/80101591