一、概述
Libuv 官方文档( https://github.com/libuv/libuv/tree/v1.x/docs )对各种 C 结构及操作对应结构的 API 也有清晰的描述。本文首先简单分析 uv_loop_s
结构的定义。接着深入源码分析了 uv_loop_XXX
和 uv__loop_XXX
系列函数。
二、uv_loop_s
结构定义
uv_loop_s
是一个 C 结构,其作为 Libuv 的核心结构,几乎无处不在无处不用。
其定义如下:
1 | struct uv_loop_s { |
单从定义是无法完全分析出结构各字段的作用的,需结合使用结构的代码进行分析。
分析结构的字段,首先明确字段的作用是什么,在哪些代码对其进行了赋值或修改;如果是容器(数组、队列或堆等),其中的元素是什么,必要的话看其中的元素是如何增删的。
1、 data
: 用户数据
较早的 Libuv 是完全不用 data 字段的,后来开发人员认为这种情况下可能对调试有帮助:在调试模式下,使用 uv_loop_close
函数关闭不清除 data ,在再次使用 uv_loop_init
函数进行重新初始化的时候沿用之前的 data 。
2、 handle_queue
: Handle 队列
Handle 队列元素数总是小于或等于 active_handles
。
排除 uv_loop_s
、uv_handle_s
和 uv_stream_t
,Libuv 当期有 14 种 Handle ,其中 3 种是 Stream。
① 入列
绝大部分 Handle 的初始化函数格式是 uv_XXX_init
,如 uv_timer_init
。这些初始化函数内部使用的宏 uv__handle_init
会将 Handle 加入到队列的尾部:
1 |
uv_tcp_s
,uv_pipe_s
和uv_tty_s
三个 Stream 是在uv__stream_init
函数里进行初始化的。特别的,对于uv_process_s
, 没有uv_process_init
函数,是在uv_spawn
函数里进行初始化的。
uv_walk
函数会针对每个 Handle 分别添加一个回调函数,回调函数本身也是加入到队列的。
1 | void uv_walk(uv_loop_t* loop, uv_walk_cb walk_cb, void* arg) { |
② 出列
uv__finish_close
会将 Handle 移出队列。调用链:
uv_run
-> uv__run_closing_handles
-> uv__finish_close
3、 active_handles
: 事件循环中活动的 Handle 计数
Handle 在循环中不总是”活动”的,因为 Handle 有三种状态:未初始化、已初始化未激活、已激活。通常需要 start
或类似的操作进行激活。 active_handles
的值总是小于或等于循环中的 Handle 数。(扩展:为什么 Handle 使用一个中间的“未激活”的状态,而不像 Request 那样?)
① 增加 Handle 计数的调用链
② 减少 Handle 计数的调用链
4、 active_reqs
: 活动的 Request 队列
不像 Handle ,Request 在循环中总是”活动”的。对 Request 进行初始化时,会将其加入循环中。在使用过程中发生错误,或者使用完成后再从循环中删除。
不是所有的 Request 都关联了一个 Handle。(扩展:具体哪些 Request 与 Handle 无关。)
5、 stop_flag
: 用于指示循环停止的内部标记
6、 reserved
: 保留字段
7、 UV_LOOP_PRIVATE_FIELDS
: 宏,定义平台相关的私有字段集
主要是为了跨 UNIX 平台和 Windows 平台。针对 Linux 和 macOS 等 UNIX (或类 UNIX ) 平台,使用 UV_LOOP_PRIVATE_FIELDS
内部的 UV_PLATFORM_LOOP_FIELDS
宏来定义平台相关的字段集。
三、UV_LOOP_PRIVATE_FIELDS
宏
UV_LOOP_PRIVATE_FIELDS
宏定义如下:
1 |
如果在分析具体的结构时遇到宏,可以先预处理展开宏,如:
1 | gcc -E src/uv-common.c -o src/uv-common.i -Iinclude -Isrc -Isrc/unix |
然后在预处理结果文件中定位到 struct uv_loop_s
的定义处,找到UV_LOOP_PRIVATE_FIELDS
宏展开的代码( UV_LOOP_PRIVATE_FIELDS
宏嵌套不深,预处理后的代码和定义几乎对应的。另,这里包含了 macOS 上 UV_PLATFORM_LOOP_FIELDS
宏展开的代码):
1 | // UV_LOOP_PRIVATE_FIELDS |
1、 flags
: 循环标记
目前仅支持唯一一个标记: UV_LOOP_BLOCK_SIGPROF
。如果启用该标记,则每次轮询时会阻止 SIGPROF
信号传递。
目的是使用取样探查器( sampling profile )时减少 kevent
/epoll_pwait
等 I/O 复用函数的中断次数和随后使用 clock_gettime
系统调用的次数。
当启用该标记时,在 Linux 2.6.19 及以后, 将从 epoll_wait
切换到 epoll_pwait
;
在 Linux 2.6.19 以前和其他平台,使用 pthread_sigmask
阻止 SIGPROF
信号传递。
一般使用 uv_loop_configure
函数设置该标记。
2、 backend_fd
: 后台文件描述符
实际上就是 kqueue
或 epoll
等对象的文件描述符。
3、 pending_queue
: 挂起的回调队列
大部分时候, I/O 回调在 I/O 轮询( poll )之后马上被调用,但有时回调会延迟到循环的下一次迭代,即保存至 pending_queue
中,具体见 uv__io_feed
。uv_run
函数调用 uv__run_pending
函数,后者调用上一次迭代产生的回调。
4、 watcher_queue
: 监视器队列
该队列用于收集要监听的文件描述符和事件,最终会在调用 kevent
/epoll_pwait
等 I/O 复用函数时使用。
5、 watchers
: 监视器队列
6、 nwatchers
: watchers
元素数加 2
当文件描述符被关闭后, 并且新的文件描述符 (同样数字) 在 kqueue
/epoll
/port
循环的回调中被打开,旧的事件可能在错误的监视器上调用回调函数。
检查在调用所有事件后是否更改了观察程序并使用了无效的相同的文件描述符。
备注:未完待续