Node的底层调用你知道吗?
完成整个异步I/O环节的有事件循环、观察者和请求对象等。
事件循环
首先,我们着重强调一下Node自身的执行模型——事件循环,正是它使得回调函数十分普遍。在进程启动时,Node便会创建一个类似于while(true)
的循环,每执行一次循环体的过程我们称为Tick。每个Tick的过程就是查看是否有事件待处理,如果有,就取出事件及其相关的回调函数。如果存在关联的回调函数,就执行它们。然后进入下个循环,如果不再有事件处理,就退出进程。流程图如图所示。
观察者
在每个Tick的过程中,如何判断是否有事件需要处理呢?这里必须要引入的概念是观察者。每个事件循环中有一个或者多个观察者,而判断是否有事件要处理的过程就是向这些观察者询问是否有要处理的事件。
浏览器采用了类似的机制。事件可能来自用户的点击或者加载某些文件时产生,而这些产生的事件都有对应的观察者。在Node中,事件主要来源于网络请求、文件I/O等,这些事件对应的观察者有文件I/O观察者、网络I/O观察者等。观察者将事件进行了分类。
事件循环是一个典型的生产者/消费者模型。异步I/O、网络请求等则是事件的生产者,源源不断为Node提供不同类型的事件,这些事件被传递到对应的观察者那里,事件循环则从观察者那里取出事件并处理。
在Windows下,这个循环基于IOCP创建,而在*nix下则基于多线程创建。
请求对象
我们将通过解释Windows下异步I/O(利用IOCP实现)的简单例子来探寻从JavaScript代码到系统内核之间都发生了什么。对于一般的(非异步)回调函数,函数由我们自行调用,如下所示:
var forEach = function (list, callback) {for (var i = 0; i < list.length; i++) { callback(list[i], i, list); }};
对于Node中的异步I/O调用而言,回调函数却不由开发者来调用。那么从我们发出调用后,到回调函数被执行,中间发生了什么呢?事实上,从JavaScript发起调用到内核执行完I/O操作的过渡过程中,存在一种中间产物,它叫做请求对象。
下面我们以最简单的 fs.open()
方法来作为例子,探索Node与底层之间是如何执行异步I/O调用以及回调函数究竟是如何被调用执行的:
fs.open = function(path, flags, mode, callback) {})
fs.open()
的作用是根据指定路径和参数去打开一个文件,从而得到一个文件描述符,这是后续所有I/O操作的初始操作。JavaScript层面的代码通过调用C++核心模块进行下层的操作。图为调用示意图。
从JavaScript调用Node的核心模块,核心模块调用C++内建模块,内建模块通过libuv进行系统调用,这是Node里经典的调用方式。这里libuv作为封装层,有两个平台的实现,实质上是调用了uv_fs_open()
方法。在 uv_fs_open()
的调用过程中,我们创建了一个 FSReqWrap 请求对象。从JavaScript层传入的参数和当前方法都被封装在这个请求对象中,其中我们最为关注的回调函数则被设置在这个对象的 oncomplete_sym
属性上:
req_wrap->object_->Set(oncomplete_sym, callback);
对象包装完毕后,在Windows下,则调用 QueueUserWorkItem()
方法将这个 FSReqWrap 对象推入线
程池中等待执行,该方法的代码如下所示:
QueueUserWorkItem(&uv_fs_thread_proc, req, WT_EXECUTEDEFAULT)
QueueUserWorkItem()
方法接受3个参数:第一个参数是将要执行的方法的引用,这里引用的是 uv_fs_thread_proc
,第二个参数是 uv_fs_thread_proc
方法运行时所需要的参数;第三个参数是执行的标志。当线程池中有可用线程时,我们会调用 uv_fs_thread_proc()
方法。 uv_fs_thread_proc()
方法会根
据传入参数的类型调用相应的底层函数。以uv_fs_open()
为例,实际上调用 fs__open()
方法。
至此,JavaScript调用立即返回,由JavaScript层面发起的异步调用的第一阶段就此结束。
JavaScript线程可以继续执行当前任务的后续操作。当前的I/O操作在线程池中等待执行,不管它是否阻塞I/O,都不会影响到JavaScript线程的后续执行,如此就达到了异步的目的。
请求对象是异步I/O过程中的重要中间产物,所有的状态都保存在这个对象中,包括送入线程池等待执行以及I/O操作完毕后的回调处理。
执行回调
组装好请求对象、送入I/O线程池等待执行,实际上完成了异步I/O的第一部分,回调通知是第二部分。
线程池中的I/O操作调用完毕之后,会将获取的结果储存在 req->result
属性上,然后调用 PostQueuedCompletionStatus()
通知IOCP,告知当前对象操作已经完成:
PostQueuedCompletionStatus((loop)->iocp, 0, 0, &((req)->overlapped))
PostQueuedCompletionStatus()
方法的作用是向IOCP提交执行状态,并将线程归还线程池。通过PostQueuedCompletionStatus()
方法提交的状态,可以通过 GetQueuedCompletionStatus()
提取。
在这个过程中,我们其实还动用了事件循环的I/O观察者。在每次Tick的执行中,它会调用IOCP相关的 GetQueuedCompletionStatus()
方法检查线程池中是否有执行完的请求,如果存在,会将请求对象加入到I/O观察者的队列中,然后将其当做事件处理。
I/O观察者回调函数的行为就是取出请求对象的result
属性作为参数,取出 oncomplete_sym
属性作为方法,然后调用执行,以此达到调用JavaScript中传入的回调函数的目的。
至此,整个异步I/O的流程完全结束,如图所示。
事件循环、观察者、请求对象、I/O线程池这四者共同构成了Node异步I/O模型的基本要素。
不同的是线程池在Windows下由内核(IOCP)直接提供,*nix系列下由libuv自行实现。
Node的底层调用你知道吗?相关推荐
- JNI与底层调用-1
JNI开发系列阅读 JNI与底层调用1:http://blog.csdn.net/axi295309066/article/details/60758515 JNI与底层调用2:http://blog ...
- IO操作底层调用过程 | 用户态切换内核态原理 | 中断概念
IO操作底层调用过程|内核|中断| 做后端的程序员都知道我们编写的程序主要分方法程序和IO操作程序. 有什么不一样呢? 方法程序就不多说了.IO程序有什么不同呢?IO操作指的是对硬件设备操作,比如键盘 ...
- DatabaseMetaData 底层调用的sql.
DatabaseMetaData dbmd = conn.getMetaData(); rs = dbmd.getColumns(null, "test", "%&quo ...
- C语言中直接函数调用和间接函数调用的底层调用方式 X86 AArch64
总结一下X86和AArch64架构下C语言中直接函数调用和间接函数调用的底层调用方式.主要是想记录一下在机器码这个层面直接函数调用和间接函数调用如何获取目标函数的地址. 1.直接函数调用 话不多说,先 ...
- node.js + Electron 调用 Windows API 踩坑日记
前排提示:深坑,建议使用 C#.C++.VB 等方式 + 本地网络传输或进程管道通信替代. TOOLS 工具 Node.js(12.18.1) Electron(此处使用 ^2.0.0,因为 cef ...
- Node.JS中调用JShaman,加密JS代码
在Node.JS环境中,调用JShaman的WebAPI接口,对JS代码进行混淆加密. 效果如下: 代码: //js代码 var js_code = `function NewObject(prefi ...
- IO流-节点流和处理流(涵盖底层调用关系)
一.节点流和处理流概览 二.节点流和处理流的区别关系 三.推导节点流和处理流的调用关系 (一)总的父类Reader (二)2个节点流 1.文件节点流 2.字符串节点流 (三)处理流 (四)测试类 四. ...
- 【Node.js】 调用阿里云短信验证码服务
目录 1.获取必备的参数 ① 获取 AccessKey ② 获取签名名称和短信模板 code 2.写代码 ① 调用sdk ② 生成六位数验证码 ③ 拿到前端传过来的手机号,调用 API 发送短信 ④ ...
- JNI与底层调用-2
欢迎使用Markdown编辑器写博客 本Markdown编辑器使用StackEdit修改而来,用它写博客,将会带来全新的体验哦: Markdown和扩展Markdown简洁的语法 代码块高亮 图片链接 ...
最新文章
- 索引原理,查询机制(转)
- xftp连接海康摄像头报错:sftp子系统申请已拒绝 请确保ssh连接的sftp子系统设置有效
- 二十七、Node.js搭建第一个Express应用框架
- JavaFX鼠标滚动放大缩小图片
- laravel windos 无法生成 appkey 的问题解决方法
- idea项目在maven projects中变灰色带有删除线的解决办法
- 英语学习笔记2019-11-08
- Python学习日志(二)
- SpringBoot动态获取项目部署的端口号
- 服务器能不能清理系统垃圾,在服务器上如何清理垃圾
- 纯策略纳什均衡与混合策略纳什均衡的比较
- 如何在python 设置输入字符的 颜色 背景色,前景色
- ### LinuxCBT VBOX Edition ###
- C语言实现对一维数组所有元素排序,然后将m1到m2之间的元素逆序
- linux webdav 乱码,webDav遇到的乱码问题
- 爱因斯坦的题目:在你面前有一条长长的阶梯,如果每步跨2阶,那么最后剩1阶;如果每步跨3阶,那么最后剩2阶.....................
- 安全修复之Web——【中危】启用了不安全的TLS1.0、TLS1.1协议
- ucharts Cannot read property ‘replace‘ of undefined
- 2021年高教社杯全国大学生数学建模竞赛
- 【组队学习】【31期】李宏毅机器学习(含深度学习)
热门文章
- 做数据中心,腾讯是认真的!
- 中国开源燃烧!Zilliz 获全球开源基础软件最大单笔融资 4300 万美元
- 2020已过大半,量子计算机发展如何了?
- 一行代码如何隐藏 Linux 进程?
- IntelliJ IDEA 2020.1 正式发布,15 项重大特性、官方支持中文了! | 原力计划
- 京东回应“两年将回购20亿美元股份”;微软即刻关闭全球所有旗下商店;. Net 5首个预览版发布|极客头条...
- 阿里工程师手把手教你设计 B 端垂类营销中心!
- TIOBE 2 月编程语言排行榜:Objective-C 的出路在何方?
- C 语言这么厉害,它自身是用什么语言写的?
- 如何理解 Python 中的面向对象编程?