You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
function _forkChild(fd, serializationMode) {
// set process.send()
const p = new Pipe(PipeConstants.IPC);
p.open(fd);
p.unref();
const control = setupChannel(process, p, serializationMode);
process.on('newListener', function onNewListener(name) {
if (name === 'message' || name === 'disconnect') control.refCounted();
});
process.on('removeListener', function onRemoveListener(name) {
if (name === 'message' || name === 'disconnect') control.unrefCounted();
});
}
Table of Contents
1. 前言
开始写一些博客主要是在看完代码后再温故总结一遍, 也是为了后面回头也能查阅。本系列会从官网的例子出发, 尽可能以链路追踪的方式记录其中源码核心模块的实现, 本篇例子来源
涉及的知识点
2. 例子 spawn/main.c
创建一个子进程的例子。
3. 例子 thread-loader
这里还想记录一下以前看 thread-loader 的代码, 当时困惑的一个点本篇文章看完也能得到解释
thread-loader 经常会从主进程拷贝大量的文件数据到子进程去交给 loader 处理, 结果又会传给主进程。
那么开阔额外的管道, 通过 pipe 的形式通信, 不但可以减小内存消耗又能提高效率。
3.1. uv_spawn
main > uv_spawn
uv_spawn 创建一个子进程, 主要包括以下几步
3.2. uv__process_init_stdio
main > uv_spawn > uv__process_init_stdio
通过 options.stdio 参数的设置, 决定进程间通信使用的 fd
3.3. uv__make_socketpair
main > uv_spawn > uv__process_init_stdio > uv__make_socketpair
主要是调用 socketpair 函数, 创建进程间通信的管道
关于进程通信, 先看看对于操作系统,进程是 task_struct 类型的结构体
进程间通信的方式一般有哪些?
3.4. socketpair - 通信的关键
先说一下 nodejs 子进程 options.stdio 其中的两个选项
'pipe':在子进程和父进程之间创建管道。 管道的父端作为 child_process 对象上的 subprocess.stdio[fd] 属性暴露给父进程。 为文件描述符 0、1 和 2 创建的管道也可分别作为 subprocess.stdin、subprocess.stdout 和 subprocess.stderr 使用。
'ipc':创建 IPC 通道,用于在父进程和子进程之间传递消息或文件描述符。 一个 ChildProcess 最多可以有一个 IPC stdio 文件描述符。 设置此选项会启用 subprocess.send() 方法。 如果子进程是 Node.js 进程,则 IPC 通道的存在将会启用 process.send() 和 process.disconnect() 方法、以及子进程内的 'disconnect' 和 'message' 事件。
基于这个场景, socketpair 能够给到完美的支持
3.4.1. socketpair
关于 socketpair 创建的 Unix domain socket 补充知识
例子分析: 一开始由socketpair创建一个套接字对,父进程关闭fd[1],子进程关闭fd[0],父进程sleep(1)让子进程先执行,子进程read(fd[1], &val, sizeof(val))阻塞,
然后父进程write(fd[0]..)发送数据,子进程接收数据处理后再发送给父进程数据write(fd[1]..),父进程读取数据,打印输出。
既然 socketpair 返回的一对文件描述符中的任何一个都可读和可写,那么 nodejs options.stdio 设置为
3.5. ipc - 主进程
下面是我写的一个简单的例子, 当我们在主进程中启动一个子进程时, stdio 其中一项设置为 ipc
关于写入子进程的环境变量中的 NODE_CHANNEL_FD
这里回顾一下 socketpair 的例子, 这个 fd 可以理解为例子中的 fd[1], 其实现主要是后面要讲的 uv__process_child_init 函数通过调用 dup2 函数把子进程中的 files 文件指针数组的第 3 个 fd 重定向到了 fd[1]!
3.6. ipc - 子进程
下面的流程主要讲了
提示: 以下涉及的 i/o 相关的实现已经忽略, 可以参考【libuv 源码学习笔记】2. 线程池与i/o
主进程通过 spawn 方法运行一个新的 node 子进程, 类似于新运行一个 node c.js 命令, 会走一遍初始化数据等流程, 其中一步会运行 lib/internal/bootstrap/pre_execution.js 中的 setupChildProcessIpcChannel 函数
提示: emit, on 为 event 事件触发器 的实现, 在 nodejs 中大部分类都继承于 event, 在前端浏览器端也能经常看到。
3.6.1. 子进程向父进程发消息的实现
这里由于篇幅有限, 子进程发送信息给主进程, 其实上向环境变量拿到的 fd 写入数据, 正如我们上面看到的 socketpair 的例子, 子进程可以从 fd[1] 中读数据, 也可从 fd[1] 中写入数据, 当子进程向 fd[1] 写入数据后, 主进程的 fd[0] 就会有了数据被 epoll 捕获调用主进程 i/o 观察者相应的回调, 其机制和子进程收到父进程 的消息类似。
3.6.2. pipe 与 ipc 读写数据的区别
两次模式下, 读取另一个进程来的消息也是有区别的
recvmsg 其实是能接受更多的参数替代 read 的存在, 这个具体的原因也是查了很多的资料。其中一个重要原因是 recvmsg 中 msg_control 字段能够接受来自其他进程的 fd。那么与像 node 一样写入子进程环境变量 NODE_CHANNEL_FD 传递有何异同了?
原来是可以实现 fd 引用的传递, 在 node cluster 集群 模块的实现起到了重要的作用。后面需要单独写一篇 cluster 集群的实现来细说一下 node 进程的通信。
3.7. uv__process_child_init
main > uv_spawn > uv__process_child_init
这里很关键的是使用 dup2 函数可以把将第二个参数fd指向第一个参数fd所指的文件,相当于重定向功能
类似于我们运行一个命令使其的标准输出重定向到一个文件中, 可以这样做
上面的 for 循环, 实质上是对进程的文件指针数组的操作, 并使用 dup2 函数进行重定向。
3.8. dup2
将 newfd 指向 oldfd 所指的文件,相当于重定向功能。如果 newfd 已经指向一个已经打开的文件,那么他会首先关闭这个文件,然后在使newfd指向oldfd文件;
3.9. uv__make_pipe
main > uv_spawn > uv__make_pipe
Tips: libuv 为了保证 fork 出的子进程成功运行 execvp 函数才设置流的 i/o 观察者。
首先通过 uv__make_pipe 函数调用 pipe2 函数, 设置 flags = O_CLOEXEC, 即当 fork 出的子进程运行 execvp 函数后, 会发送一个退出信号, 主进程 read 函数返回 0, 主进程才能继续往下运行。
主进程 uv_spawn 中的 read
3.10. uv__process_open_stream
main > uv_spawn > uv__process_open_stream
设置主进程的 i/o 观察者的 fd, 相当于 socketpair 例子中的 fd[0], 当 fd 有变化时, 即收到来自子进程的写入的数据
流相关实现参考 【libuv 源码学习笔记】网络与流
3.11. uv_signal_start
main > uv_spawn > uv_signal_start
通过 libuv 信号机制, 在子进程退出时, 调用例子中设置的 on_exit 回调函数, 程序运行结束
信号相关实现参考 【libuv 源码学习笔记】信号
4. 小结
The text was updated successfully, but these errors were encountered: