當我們快樂的使用js寫服務端程序的時候,是否想過如此酸爽的編程體驗是如何實現(xiàn)的。為了滿足自己的好奇心,我特意下載了node的源碼座掘,進行了一次深度跟蹤。
我們看一下readFile
這個程序到底是如何實現(xiàn)的吧柔滔。由于v8很復雜溢陪,所以這篇文章中v8如何幫助我們在js和cpp來回調(diào)用就忽略了,等我把v8也研究了再來補上睛廊。js形真,node,和libuv的代碼超全,會比較詳細的跟蹤咆霜。
跟蹤方法
- 新建測試文件
新建一個文件:fsdemo.js
, 用這個文件來調(diào)試吧。
var fs = require("fs");
// 異步讀取
fs.readFile('vcbuild.bat', function(err, data) {
if (err) {
return console.error("Yin : in the fsdemo.js :: 異步讀取發(fā)生錯誤 : " + err);
}
console.log("Yin : in the fsdemo.js :: 異步讀取完畢 嘶朱!\n");
// console.log("異步讀取: " + data.toString());
});
// 同步讀取
// var data = fs.readFileSync('input.txt');
// console.log("同步讀取: " + data.toString());
console.log("Yin : in the fsdemo.js :: 程序執(zhí)行完畢蛾坯。\n");
下載node
https://github.com/nodejs/node
切換到4.4.7版本。最新的跟不上疏遏,太老的bug多脉课,我們跟蹤一個LTSb按本吧救军。
我是在windows上用visual studio看代碼,所以直接運行vcbuild倘零。
確保按照readme的說明編譯成功哦唱遭。打上log
先過一遍代碼后,可以在感興趣的地方打上log呈驶。如果希望用斷點調(diào)試拷泽,也可以直接在VS中F11
,找到入口袖瞻。后面會貼上我增加的log跌穗,感興趣的同學可以自己對照著改改。反復調(diào)試
一次找到所有的路徑是很困難的虏辫,因為異步的代碼會跳來跳去,所以需要仔細思考锈拨,才能找到整條路徑砌庄。
Log講解
在調(diào)試了以4,5個小時后,感覺已經(jīng)摸清了異步調(diào)用的基本流程奕枢。感興趣的朋友可以仔細看看娄昆。下面的log需要你對代碼頁非常熟悉。
** 1. 加載需要測試的文件 **
Yin : in the node_file.cc! :: Open
Yin : in the node_file.cc! :: Open :: SYNC_CALL! path = \?\D:\projects\node\fsdemo.js
Yin : in the fs.c :: uv_fs_open!
Yin : in the fs.c :: uv_fs_open :: fs__open(req);!
Yin : in the fs.c :: fs__open
Yin : in the node_file.cc! :: Read
Yin : in the node_file.cc! :: Read :: SYNC_CALL, <fd = 3>
Yin : in the fs.c :: uv_fs_read!
Yin : in the fs.c :: uv_fs_read :: fs__read(req); <fd = 3>!
Yin : in the fs.c :: fs__read
Yin : in the fs.c :: fs__read :: ReadFile <index = 0>
上面的log向我們展示了node本身加載fsdemo.js的過程缝彬。通過log可以看到這個過程是同步的萌焰,最后通過libuv的fs__read
完成讀取工作。
** 2. js讀取文件**
Yin : in the fs.js :: fs.readFile!
Yin : in the node_file.cc! :: Open
Yin : in the node_file.cc! :: Open :: ASYNC_CALL! path = \?\D:\projects\node\vcbuild.bat
Yin : in the fs.c :: uv_fs_open!
Yin : in the fs.c ::** uv_fs_open :: QUEUE_FS_TP_JOB**!
Yin : in the threadpool.c :: worker
Yin : in the fs.c :: uv__fs_work :: <fstype = open>
Yin : in the fs.c :: fs__open
Yin : in the threadpool.c :: worker :: uv_async_send <&w->loop->wq_async = 23934432>
Yin : in the fsdemo.js :: 程序執(zhí)行完畢谷浅。
通過上面的log扒俯,我們可以知道讀取文件用的是異步方式,任務被放到隊列中一疯,由線程池去執(zhí)行具體的任務撼玄,現(xiàn)在可以看到第一個任務是open,調(diào)用fs__open
墩邀。open完成后掌猛,還會通知主線程(uv_async_send
)。還可以注意到 fsdemo.js這時候已經(jīng)執(zhí)行完畢了眉睹。
3. open文件的過程
Yin : in core.c :: uv_run
Yin : in the req-inl.h :: uv_process_reqs
Yin : in the node.cc :: StartNodeInstance :: <loop number = 1, more event = 1>
Yin : in core.c :: uv_run
Yin : in the req-inl.h :: uv_process_reqs
Yin : in the req-inl.h :: uv_process_reqs : UV_WAKEUP
Yin : in the threadpool.c :: uv__work_done
Yin : in the threadpool.c :: uv__work_done :: w->done(w, err)
Yin : in the fs.c :: uv__fs_done
Yin : in the node_file.cc! :: After
Yin : in the node_file.cc! :: After :: MakeCallback :: <argc = 2, argv =h???燙燙?>
Yin : in the fs.js :: readFileAfterOpen !
我們可以看到主線程會有一個循環(huán)處理事件±蟛纾現(xiàn)在有一個UV_WAKEUP
事件要處理。事件處理完成后會再回調(diào)fs.js
里面的readFileAfterOpen
函數(shù)竹海。
需要說明的是這里面忽略了另一個線程如何通知主循環(huán)的細節(jié)慕蔚,window平臺是通過IOCP,可以搜索函數(shù)GetQueuedCompletionStatus
了解細節(jié)站削,liunx上并不是這樣坊萝。
4. check文件狀態(tài)
Yin : in the threadpool.c :: worker
Yin : in the node.cc :: StartNodeInstance :: <loop number = 2, more event = 1>
Yin : in core.c :: uv_run
Yin : in the req-inl.h :: uv_process_reqs
Yin : in the fs.c :: uv__fs_work :: <fstype = fstat>
Yin : in the threadpool.c :: worker :: uv_async_send <&w->loop->wq_async = 23934432>
Yin : in the node.cc :: StartNodeInstance :: <loop number = 3, more event = 1>
Yin : in core.c :: uv_run
Yin : in the req-inl.h :: uv_process_reqs
Yin : in the req-inl.h :: uv_process_reqs : UV_WAKEUP
Yin : in the threadpool.c :: uv__work_done
Yin : in the threadpool.c :: uv__work_done :: w->done(w, err)
Yin : in the fs.c :: uv__fs_done
Yin : in the node_file.cc! :: After
Yin : in the node_file.cc! :: After :: MakeCallback :: <argc = 2, argv =h???燙燙?>
Yin : in the fs.js :: readFileAfterStat !
這里通過log發(fā)現(xiàn)讀取之前還檢查了一下文件狀態(tài)孵稽,前面都open到fd了,還check啥狀態(tài)十偶,我覺得這里可以優(yōu)化菩鲜,這個就不仔細深究了。
5. 讀文件
Yin : in the fs.js :: ReadFileContext.prototype.read !
Yin : in the node_file.cc! :: Read
Yin : in the node_file.cc! :: Read :: ASYNC_CALL, <fd = 3>
Yin : in the fs.c :: uv_fs_read!
Yin : in the fs.c :: uv_fs_read :: QUEUE_FS_TP_JOB <fd = 3>!
Yin : in the threadpool.c :: worker
Yin : in the node.cc :: StartNodeInstance :: <loop number = 4, more event = 1>
Yin : in the fs.c :: uv__fs_work :: <fstype = read>
Yin : in core.c :: uv_run
Yin : in the fs.c :: fs__read
Yin : in the req-inl.h :: uv_process_reqs
Yin : in the fs.c :: fs__read :: ReadFile <index = 0>
Yin : in the threadpool.c :: worker :: uv_async_send <&w->loop->wq_async = 23934432>
Yin : in the node.cc :: StartNodeInstance :: <loop number = 5, more event = 1>
Yin : in core.c :: uv_run
Yin : in the req-inl.h :: uv_process_reqs
Yin : in the req-inl.h :: uv_process_reqs : UV_WAKEUP
Yin : in the threadpool.c :: uv__work_done
Yin : in the threadpool.c :: uv__work_done :: w->done(w, err)
Yin : in the fs.c :: uv__fs_done
讀文件也是異步的惦积,和打開文件很像
6.關閉文件并調(diào)用js回調(diào)
Yin : in the node_file.cc! :: After
Yin : in the node_file.cc! :: After :: MakeCallback :: <argc = 2, argv =h???燙燙?>
Yin : in the fs.js :: ReadFileContext.prototype.close !
Yin : in the threadpool.c :: worker
Yin : in the node.cc :: StartNodeInstance :: <loop number = 6, more event = 1>
Yin : in core.c :: uv_run
Yin : in the req-inl.h :: uv_process_reqs
Yin : in the fs.c :: uv__fs_work :: <fstype = close>
Yin : in the threadpool.c :: worker :: uv_async_send <&w->loop->wq_async = 23934432>
Yin : in the node.cc :: StartNodeInstance :: <loop number = 7, more event = 1>
Yin : in core.c :: uv_run
Yin : in the req-inl.h :: uv_process_reqs
Yin : in the req-inl.h :: uv_process_reqs : UV_WAKEUP
Yin : in the threadpool.c :: uv__work_done
Yin : in the threadpool.c :: uv__work_done :: w->done(w, err)
Yin : in the fs.c :: uv__fs_done
Yin : in the node_file.cc! :: After
Yin : in the node_file.cc! :: After :: MakeCallback :: <argc = 1, argv =h?>
Yin : in the fs.js :: readFileAfterClose !
Yin : in the fsdemo.js :: 異步讀取完畢 接校!
Yin : in the node.cc :: StartNodeInstance :: <loop number = 8, more event = 0>
可以看到最后回到了js文件。
結(jié)論
可以看到狮崩,用js幾行代碼完成的事情蛛勉,實際上流程非常復雜,上面的代碼還不包括v8如何解析js的情況睦柴,如果能把這些都搞清楚了诽凌,才是大牛吧。
對代碼的修改可以看這個diff文件坦敌。
https://github.com/benhaben/blog/issues/15