Node.js異步IO實現淺析
其實就是對 Node.js 的異步 IO 很感興趣,加之最近可能要定制 Node.js ,所以決定研究研究看看。本身是 C/C++ 出身,看這點代碼還是輕車熟路的,分析中并沒有涉及 V8 的內部實現。
入口點
因為是要研究研究異步 IO ,我覺得從 fs 模塊下手是最簡單的了。源碼通過 Git 克隆下來以后,直覺告訴我 fs 模塊的源碼入口點在 lib 里面。這里我從 fs.readFile 開始下手。
層層深入 - JS層
基于我克隆的版本的這個函數定義是在 fs.js 的 253 行,代碼如下:
fs.readFile = function(path, options, callback){
callback = maybeCallback(arguments[arguments.length - 1]);
options = getOptions(options, { flag: 'r' });
if (!nullCheck(path, callback))
return;
var context = new ReadFileContext(callback, options.encoding);
context.isUserFd = isFd(path); // file descriptor ownership
var req = new FSReqWrap();
req.context = context;
req.oncomplete = readFileAfterOpen;
if (context.isUserFd) {
process.nextTick(function(){
req.oncomplete(null, path);
});
return;
}
binding.open(pathModule._makeLong(path),
stringToFlags(options.flag || 'r'),
0o666,
req);
};
這段代碼的邏輯不解釋了基本都看得明白,最后的調用 binding.open 的是原生調用,實現基于 C++ ,具體因為不是關注重點我直接忽略了。
關于 fd 的判斷直接忽略,我們關注到創建的 FSReqWrap 的 context 是一個 ReadFileContext 實例;oncomplete 指向一個讀文件的回調,進入它可以看到:
function readFileAfterOpen(err, fd) {
var context = this.context;
if (err) {
context.callback(err);
return;
}
context.fd = fd;
var req = new FSReqWrap();
req.oncomplete = readFileAfterStat;
req.context = context;
binding.fstat(fd, req);
}
function readFileAfterStat(err, st) {
var context = this.context;
if (err)
return context.close(err);
var size = context.size = st.isFile() ? st.size : 0;
if (size === 0) {
context.buffers = [];
context.read();
return;
}
if (size > kMaxLength) {
err = new RangeError('File size is greater than possible Buffer: ' +
`0x${kMaxLength.toString(16)} bytes`);
return context.close(err);
}
context.buffer = Buffer.allocUnsafeSlow(size);
context.read();
}
這里直接我跳過兩個方法的分析, 但是要注意 this 的指向和 context 的傳遞 0.0 ,最后我們看到了 context.read ,context 是一步一步傳遞下來的 ReadFileContext 實例,我們進入它的定義看看:
ReadFileContext.prototype.read = function() {
var buffer;
var offset;
var length;
if (this.size === 0) {
buffer = this.buffer = Buffer.allocUnsafeSlow(kReadFileBufferLength);
offset = 0;
length = kReadFileBufferLength;
} else {
buffer = this.buffer;
offset = this.pos;
length = this.size - this.pos;
}
var req = new FSReqWrap();
req.oncomplete = readFileAfterRead;
req.context = this;
binding.read(this.fd, buffer, offset, length, -1, req);
};
最終我們還是遇到了 binding.read 。這個調用之前的邏輯我相信大家看得懂,我們開始進入 C++ 的世界了 ==
層層深入 - C++層
這段代碼定義在哪呢?我不知道各位有木有研究過 node 的 native 模塊定義,其實這段代碼很好找,過程不說了文件其實是:node_file.cc
基于我克隆的版本,綁定在 1457 行, 定義在 1192 行,最后調用了一個宏:ASYNC_CALL ,我們看到注釋:
Wrapper for read(2).
bytesRead = fs.read(fd, buffer, offset, length, position)
0 fd integer. file descriptor
1 buffer instance of Buffer
2 offset integer. offset to start reading into inside buffer
3 length integer. length to read
4 position file position - null for current position
可能會引起誤解,這里的意思是接口兼容 read(2) 實現,但是其實不是基于read(2) ,而是使用宏 ASYNC_CALL 方式調用,我們深入 ASYNC_CALL 研究到它是 ASYNC_DEST_CALL 的宏,而 ASYNC_DEST_CALL 定義的內容如下:
#define ASYNC_DEST_CALL(func, request, dest, encoding, ...) \
Environment* env = Environment::GetCurrent(args); \
CHECK(request->IsObject()); \
FSReqWrap* req_wrap = FSReqWrap::New(env, request.As<Object>(), \
#func, dest, encoding); \
int err = uv_fs_ ## func(env->event_loop(), \
req_wrap->req(), \
__VA_ARGS__, \
After); \
req_wrap->Dispatched(); \
if (err < 0) { \
uv_fs_t* uv_req = req_wrap->req(); \
uv_req->result = err; \
uv_req->path = nullptr; \
After(uv_req); \
req_wrap = nullptr; \
} else { \
args.GetReturnValue().Set(req_wrap->persistent()); \
}
別告訴我 ## 和 # 宏定義你不認識,因為我發現我周圍基本沒幾個人認識(可能我們一群菜雞Orz…),其實按照當前的層次深入,就是調用了 uv_fs_read ,可知這是一個 libuv 提供的接口。
不過我們發現,其提供的 event_loop 來自參數作用域,我們想深入探究一下其作用域,根據調用棧回溯一下得到參數來自 fs.js
來自:https://www.dosk.win/2017/02/05/node-async-io/