1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
|
// netpoll checks for ready network connections.
// Returns list of goroutines that become runnable.
//
// delay < 0: blocks indefinitely;
// delay == 0: does not block, just polls;
// delay > 0: block for up to that many nanoseconds;
func netpoll(delay int64) gList {
// 1) epoll没有初始化
if epfd == -1 {
return gList{}
}
// 2) 下面把纳秒级的 delay 转换成毫秒级的 waitms。
var waitms int32
if delay < 0 {
waitms = -1 // 永久阻塞,直到事件就绪返回
} else if delay == 0 {
waitms = 0 // 不阻塞,立即返回
} else if delay < 1e6 { // 小于1毫秒,修改为阻塞1毫秒
waitms = 1 // 阻塞1毫秒
} else if delay < 1e15 { // 小于 11.5 day
// 1e6 表示 1毫秒
waitms = int32(delay / 1e6) // 阻塞指定毫秒
} else {
// An arbitrary cap on how long to wait for a timer.
// 1e9 ms == ~11.5 days.
waitms = 1e9 // 最长 11.5天
}
// 3) 通过epollwait函数等待IO事件,缓冲区大小为128个epollevent。
// 超时时间是 waitms 毫秒。如果 epollwait函数被中断打断,就通过goto来重试。
// waitms 大于0时不会重试,因为需要返回调用者中去重新计算超时时间。
// 下面传入epollwait的数量是128,表示一次最大就绪128个事件
// 这样可能存在此次大于128数量时,需要等到下一个IO轮询时间窗口
// type epollevent struct {
// events uint32 // 事件类型
// data [8]byte // 存储用户数据,刚好是一个指针存储的大小,该数据用户是可以修改的
// }
var events [128]epollevent // 用于存储已经准备好的描述符的事件数据
retry:
// 调用epollwait等待文件描述符转换成可写或可读,如果没有epollwait会阻塞
n := epollwait(epfd, &events[0], int32(len(events)), waitms) // 返回活跃的数量n
if n < 0 {
// EBADF:epfd 不是有效的文件描述符。
// EFAULT:事件指向的内存区域无法用写权限访问。
// EINVAL:epfd 不是epoll文件描述符,或者maxevents(第三个参数)小于等于0。
if n != -_EINTR { // EINTR 被CPU中断
println("runtime: epollwait on fd", epfd, "failed with", -n)
throw("runtime: netpoll failed")
}
// If a timed sleep was interrupted, just return to
// recalculate how long we should sleep now.
//
// 如果定时睡眠被中断,只需返回重新计算我们现在应该睡多久。
// _EINTR: 在任何请求的事件发生或超时到期之前,该调用被信号处理程序中断。
if waitms > 0 {
return gList{}
}
// 被中断 AND waitms <= 0 情况重试
goto retry
}
// type gList struct { head guintptr }
var toRun gList
// 意味着被监控的文件描述符出现了待处理的事件
for i := int32(0); i < n; i++ {
// 就绪的IO事件集
ev := &events[i]
// 当前就绪描述符没有事件类型,直接跳过
if ev.events == 0 {
continue
}
// 事件来源是否是netpollBreakRd,该描述符来之pipe2函数创建
// 来自pipe2函数创建的通信,netpollBreakRd是LT水平触发如果不读取会一直触发
// 该事件来自 netpollBreak() 方法,该方法只会在创建timer时和findRunnable()函数中被调用。
if *(**uintptr)(unsafe.Pointer(&ev.data)) == &netpollBreakRd {
// 对于文件描述符 netpollBreakRd 而言,只有 _EPOLLIN 事件是正常的,其他都会被视为异常。
if ev.events != _EPOLLIN {
println("runtime: netpoll: break fd ready for", ev.events)
throw("runtime: netpoll: break fd ready for something unexpected")
}
// 这种情况下只处理不是立即返回情况下,如果是立即返回情况时,数据并未被读取,下次还会触发该信号
// 这种情况下只在runtime.findrunnable()函数中存在,线程在寻找g无果时,最后只能在IO轮询处等待
// 只有在 delay 不为0,也就是阻塞式netpoll时,才读取netpollBreakRd中的数据。
if delay != 0 { // netpollBreakRd 的本意也是只唤醒delay != 0的netpoll,因为这些在阻塞需要返回
// netpollBreak could be picked up by a
// nonblocking poll. Only read the byte
// if blocking.
// netpollBreak 可以通过非阻塞轮询来获取。 仅在阻塞时读取字节。
var tmp [16]byte
// 把写入的数据读取让缓存区为空
read(int32(netpollBreakRd), noescape(unsafe.Pointer(&tmp[0])), int32(len(tmp)))
// 将netpollWakeSig由1变成0,表示当前事件已被读取
atomic.Store(&netpollWakeSig, 0)
}
continue
}
// 根据epoll返回的IO事件标志位为mode赋值
// r 表示可读,w 表示可写,r+w 表示既可读又可写。
// 检测IO事件中的错误标志位,并相应的为pd.everr赋值。
// 判断发生的事件类型,读类型或者写类型
var mode int32 // 存储当前类型
// EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭)
// EPOLLRDHUP:对端描述符产生一个挂断事件
// EPOLLHUP:本端描述符产生一个挂断事件,默认监测事件
// EPOLLERR:表示对应的文件描述符发生错误
if ev.events&(_EPOLLIN|_EPOLLRDHUP|_EPOLLHUP|_EPOLLERR) != 0 {
mode += 'r'
}
// EPOLLOUT:表示对应的文件描述符可以写
// EPOLLHUP:本端描述符产生一个挂断事件,默认监测事件
// EPOLLERR:表示对应的文件描述符发生错误
if ev.events&(_EPOLLOUT|_EPOLLHUP|_EPOLLERR) != 0 {
mode += 'w'
}
// mode不为0,表示有IO事件,需要从ev.data字段得到与IO事件关联的pollDesc。
if mode != 0 {
// 取出保存在 epollevent 里的pollDesc,因为要根据这个内容去恢复g如果g已被挂起时
pd := *(**pollDesc)(unsafe.Pointer(&ev.data)) // *pollDesc
// 对应的_EPOLLERR文件描述符出现错误时,标记错误
pd.setEventErr(ev.events == _EPOLLERR) // 设置pollDesc的EpollErr错误位,如果是这种状态
netpollready(&toRun, pd, mode) // 处理就绪的描述符
}
}
return toRun
}
|