查看: 869|回复: 0

[DIV/CSS] Node.js异步IO实现浅析

发表于 2017-7-22 08:00:02

其实就是对 Node.js 的异步 IO 很感兴趣,加之最近可能要定制 Node.js ,所以决定研究研究看看。本身是 C/C++ 出身,看这点代码还是轻车熟路的,分析中并没有涉及 V8 的内部实现。

入口点

因为是要研究研究异步 IO ,我觉得从 fs 模块下手是最简单的了。源码通过 Git 克隆下来以后,直觉告诉我 fs 模块的源码入口点在 lib 里面。这里我从 fs.readFile 开始下手。

层层深入 - JS层

基于我克隆的版本的这个函数定义是在 fs.js 的 253 行,代码如下:

  1. fs.readFile = function(path, options, callback){
  2. callback = maybeCallback(arguments[arguments.length - 1]);
  3. options = getOptions(options, { flag: 'r' });
  4. if (!nullCheck(path, callback))
  5. return;
  6. var context = new ReadFileContext(callback, options.encoding);
  7. context.isUserFd = isFd(path); // file descriptor ownership
  8. var req = new FSReqWrap();
  9. req.context = context;
  10. req.oncomplete = readFileAfterOpen;
  11. if (context.isUserFd) {
  12. process.nextTick(function(){
  13. req.oncomplete(null, path);
  14. });
  15. return;
  16. }
  17. binding.open(pathModule._makeLong(path),
  18. stringToFlags(options.flag || 'r'),
  19. 0o666,
  20. req);
  21. };
复制代码

这段代码的逻辑不解释了基本都看得明白,最后的调用 binding.open 的是原生调用,实现基于 C++ ,具体因为不是关注重点我直接忽略了。

关于 fd 的判断直接忽略,我们关注到创建的 FSReqWrap 的 context 是一个 ReadFileContext 实例;oncomplete 指向一个读文件的回调,进入它可以看到:

  1. function readFileAfterOpen(err, fd) {
  2. var context = this.context;
  3. if (err) {
  4. context.callback(err);
  5. return;
  6. }
  7. context.fd = fd;
  8. var req = new FSReqWrap();
  9. req.oncomplete = readFileAfterStat;
  10. req.context = context;
  11. binding.fstat(fd, req);
  12. }
  13. function readFileAfterStat(err, st) {
  14. var context = this.context;
  15. if (err)
  16. return context.close(err);
  17. var size = context.size = st.isFile() ? st.size : 0;
  18. if (size === 0) {
  19. context.buffers = [];
  20. context.read();
  21. return;
  22. }
  23. if (size > kMaxLength) {
  24. err = new RangeError('File size is greater than possible Buffer: ' +
  25. `0x${kMaxLength.toString(16)} bytes`);
  26. return context.close(err);
  27. }
  28. context.buffer = Buffer.allocUnsafeSlow(size);
  29. context.read();
  30. }
复制代码

这里直接我跳过两个方法的分析, 但是要注意 this 的指向和 context 的传递 0.0 ,最后我们看到了 context.read ,context 是一步一步传递下来的 ReadFileContext 实例,我们进入它的定义看看:

  1. ReadFileContext.prototype.read = function() {
  2. var buffer;
  3. var offset;
  4. var length;
  5. if (this.size === 0) {
  6. buffer = this.buffer = Buffer.allocUnsafeSlow(kReadFileBufferLength);
  7. offset = 0;
  8. length = kReadFileBufferLength;
  9. } else {
  10. buffer = this.buffer;
  11. offset = this.pos;
  12. length = this.size - this.pos;
  13. }
  14. var req = new FSReqWrap();
  15. req.oncomplete = readFileAfterRead;
  16. req.context = this;
  17. binding.read(this.fd, buffer, offset, length, -1, req);
  18. };
复制代码

最终我们还是遇到了 binding.read 。这个调用之前的逻辑我相信大家看得懂,我们开始进入 C++ 的世界了 ==

层层深入 - C++层

这段代码定义在哪呢?我不知道各位有木有研究过 node 的 native 模块定义,其实这段代码很好找,过程不说了文件其实是:node_file.cc

基于我克隆的版本,绑定在 1457 行, 定义在 1192 行,最后调用了一个宏:ASYNC_CALL ,我们看到注释:

  1. Wrapper for read(2).
  2. bytesRead = fs.read(fd, buffer, offset, length, position)
  3. 0 fd integer. file descriptor
  4. 1 buffer instance of Buffer
  5. 2 offset integer. offset to start reading into inside buffer
  6. 3 length integer. length to read
  7. 4 position file position - null for current position
复制代码

可能会引起误解,这里的意思是接口兼容 read(2) 实现,但是其实不是基于read(2) ,而是使用宏 ASYNC_CALL 方式调用,我们深入 ASYNC_CALL 研究到它是 ASYNC_DEST_CALL 的宏,而 ASYNC_DEST_CALL 定义的内容如下:

  1. #define ASYNC_DEST_CALL(func, request, dest, encoding, ...) \
  2. Environment* env = Environment::GetCurrent(args); \
  3. CHECK(request->IsObject()); \
  4. FSReqWrap* req_wrap = FSReqWrap::New(env, request.As<Object>(), \
  5. #func, dest, encoding); \
  6. int err = uv_fs_ ## func(env->event_loop(), \
  7. req_wrap->req(), \
  8. __VA_ARGS__, \
  9. After); \
  10. req_wrap->Dispatched(); \
  11. if (err < 0) { \
  12. uv_fs_t* uv_req = req_wrap->req(); \
  13. uv_req->result = err; \
  14. uv_req->path = nullptr; \
  15. After(uv_req); \
  16. req_wrap = nullptr; \
  17. } else { \
  18. args.GetReturnValue().Set(req_wrap->persistent()); \
  19. }
复制代码

别告诉我 ## 和 # 宏定义你不认识,因为我发现我周围基本没几个人认识(可能我们一群菜鸡Orz…),其实按照当前的层次深入,就是调用了 uv_fs_read ,可知这是一个 libuv 提供的接口。

不过我们发现,其提供的 event_loop 来自参数作用域,我们想深入探究一下其作用域,根据调用栈回溯一下得到参数来自 fs.js

来自:https://www.dosk.win/2017/02/05/node-async-io/



回复

使用道具 举报