理解 libuv 运行机制
2022年02月07日
libuv 作为 Node.js 很重要的一个依赖,为其引入了事件循环(Event Loop)的机制,统一了 Linux、Mac 和 Windows 等各个平台非阻塞 IO 操作接口,且提供了以一些功能:包括操作文件、DNS、网络、子进程、管道、信号处理等等。
本篇文章就深入探究一下其内部的运行机制。
libuv 代码中的两个抽象
为了与事件循环机制相结合,libuv 提供了两个抽象:handles 和 requests。
当处于激活状态时,handles 代表长生命周期的一些操作,例如:
- 事件循环的每一次循环中,当 prepare handle 激活时,其关联的回调函数的执行
- TCP handle,每有当新连接到来时,其所关联的回调函数都会执行
而 request 一般仅代表短生命周期的操作。可以通过 handle 来执行操作:write requests 需要 handle 来执行写操作;也可独立执行:getaddrinfo requests 直接在循环中执行。
Event Loop
I/O 循环(Event Loop)是 libuv 的核心部分。所有的 I/O 操作都在事件循环当中,事件循环应该被绑定在单个线程上。所以如果想要在多线程上跑的话,应该每个线程都引入事件循环。
基本上各个平台都会支持异步非阻塞的套接字,libuv 会根据平台的不同,去引入对应的最好的机制:Linux 是 epoll、OSX 是 kqueue、Windows 是 IOCP。作为循环的组成部分,网络套接字在空闲时会阻塞住,直到套接字变得可读或者可写,然后事件循环便继续进行。
为了更好地理解,下图展示了循环的所有阶段:
可阅读代码 src/unix/core.c uv_run 方法,更好地理解上面的流程图。
事件循环核心流程描述如下:
- 更新循环阶段的当前时间。在每次循环的开始阶段,都会缓存当前的时间,接下来循环中都会使用此时间,目的是为了减少系统调用。
- 判断当前循环是否处于活跃状态,如果非活跃状态则直接退出循环。那么,什么情况下循环是活跃状态呢?当循环关联有活跃的 handles 时、或活跃的 requests 时、或存在 closing hanldes 时,循环可被认为是活跃的。
- 执行 timer 阶段。所有早于当前时间的 timers 可被执行,它们关联的回调函数将被调用。
- 执行 pending callbacks 阶段。
- 执行 idle handle 阶段。如果有存在活跃状态的 idle 阶段,它们将在每个循环中被执行。
- 执行 prepare handle 阶段。在循环阻塞 I/O 前,会执行此阶段。
- 计算 poll timeout。在 I/O 阻塞前,计算应该被阻塞多久。有下面的几条规则用于计算 timeout:
- 如果 loop 是 UV_RUN_NOWAIT 方式,则 timeout 为 0
- 如果 loop 将要被停止(调用过 uv_stop()),则 timeout 为 0
- 如果没有活跃的 handles 或 requests,则 timeou 为 0
- 如果存在任一活跃 idle handle,则 timeout 为 0
- 如果存在正在关闭的 handles,则 timeout 为 0
- 上面的情况不满足,取最近的到期 timer 作为 timeout,如果无 timer,则 timeout 为无穷
- 循环阻塞在 I/O。在此阶段,循环一直阻塞,直到上一步计算的 timeout 到达。当有文件描述符变得可读或者可写,将会在此阶段调用它们关联的回调函数。
- 执行 check handle 阶段。
- 调用 close 回调。如果 handle 被调用 uv_close(),则会在此调用回调。
- 如果循环是 UV_RUN_ONCE 方式启动,将会执行 timer 回调。
- 此次循环结束。如果是以 UV_RUN_NOWAIT 或 UV_RUN_ONCE 方式启动的循环,则退出循环。如果是以 UV_RUN_DEFAULT 开始的循环(Node.js 主线程为此状态),如果处于活跃状态则会再次从头进入循环(跳到第一步),否则退出循环。
本地安装 libuv
1git clone git@github.com:libuv/libuv.git 2cd libuv 3./autogen.sh 4./configure 5make 6make install
安装之后就会有 /usr/local/include/uv.h 和 /usr/local/lib/libuv.a 文件
编译代码时执行 gcc main.c -luv 即可
核心代码注释
以下基于 libuv-1.43.0 版本,src/unix/core.c uv_run 代码注释
1int uv_run(uv_loop_t* loop, uv_run_mode mode) { 2 int timeout; 3 int r; 4 int ran_pending; 5 6 r = uv__loop_alive(loop); // 判断循环是否活跃 7 if (!r) 8 uv__update_time(loop); 9 10 while (r != 0 && loop->stop_flag == 0) { // 如果循环活跃且没被手动停止,进入循环 11 uv__update_time(loop); // 更新当前时间,减少系统调用 12 uv__run_timers(loop); // 执行 timers 阶段 13 ran_pending = uv__run_pending(loop); // 执行 pending 阶段 14 uv__run_idle(loop); // 执行 idle 阶段 15 uv__run_prepare(loop); // 执行 prepare 阶段 16 17 timeout = 0; 18 if ((mode == UV_RUN_ONCE && !ran_pending) || mode == UV_RUN_DEFAULT) 19 timeout = uv_backend_timeout(loop); // 计算 timeout 20 21 uv__io_poll(loop, timeout); // 进入 io 阻塞阶段,传入上一步计算出的 timeout 22 23 uv__metrics_update_idle_time(loop); 24 25 uv__run_check(loop); // 执行 check 阶段 26 uv__run_closing_handles(loop); // 处理 close 相关回调 27 28 if (mode == UV_RUN_ONCE) { 29 uv__update_time(loop); 30 uv__run_timers(loop); 31 } 32 33 r = uv__loop_alive(loop); // 判断循环是否活跃 34 if (mode == UV_RUN_ONCE || mode == UV_RUN_NOWAIT) 35 break; // 如果是 UV_RUN_ONCE 和 UV_RUN_NOWAIT,跳出循环 36 } 37 38 if (loop->stop_flag != 0) 39 loop->stop_flag = 0; 40 41 return r; 42}