在 Firefox OS 中,有一個類似 Android Zygote 的 process,利用
fork() 複製出應用程序並在這些應用程序間透過 copy-on-write 共享記憶體分頁。這麼做有助於降低記憶體用量和更快速的啟動應用程式。她的名字叫 Nuwa – 女媧 [[1]](https://tech.mozilla.com.tw/#r1)[[2]](https://tech.mozilla.com.tw/#r2)。
不過,
fork() 有一個限制是只有呼叫
fork() 的 thread 會被保留下來,可是 FxOS 是 multi-threaded,那究竟女媧是怎麼”捏”出 process 來的呢?讓我們一起來看看。
首先,FxOS 透過 ld 的 wrap 功能 [[3]](https://tech.mozilla.com.tw/#r3) 包裝了某些函式。例如,所有對
pthread_create() 的呼叫,實際上會呼叫定義在 Nuwa.cpp [[4]](https://tech.mozilla.com.tw/#r4) 裡的
__wrap_pthread_create() 。這樣一來,我們可以對這些函式增加額外的行為,且不影響原本使用這些函式的程式。
在更深入以前,我想先解釋一下大致上的概念。FxOS 會讓需要被重建的 threads 在 Nuwa process 裡暫停下來,並記錄下它們暫停前的狀態。稍後在 fork 出來的 child process 裡重建新的 threads 後,讓它們從剛剛暫停前的狀態繼續往下執行。沒錯,我們將會用上
setjmp() /
longjmp() 來記錄並繼續,讓我們接著往下看。
準備動作
當
pthread_create() 在女媧 process 裡被呼叫時,
__wrap_pthread_create() 會 new 出一個 thread info structure 來儲存相關資訊,並把它加入一個 sAllThreads 的 linked list 中。除此之外,還會
malloc 一個 stack 給即將被創建的 thread 使用。這個 stack 是必須的,假設我們沒有自己
malloc 一個 stack 的話,在
fork() 後的 child process 裡,這個 thread 的 stack 會消失,我們就沒有辦法讓它在 child process 裡繼續接著往下執行了。實際的 thread 函式稍後會在
_thread_create_startup() 中呼叫。
暫停
當 thread 開始執行並呼叫
epoll_wait() ,
poll() ,
pthread_mutex_lock() ,
pthread_cond_wait() , 和
pthread_cond_timedwait() 其中任一函式時,它會執行
setjmp() 並對一個已經被 locked 的 mutex 呼叫
pthread_mutex_lock() 讓整個 thread 暫停下來。這些動作在對應的 wrapper 函式中,由 macro
THREAD_FREEZE_POINT* 完成。
fork() 並繼續
稍後當一個 child process 從女媧
fork() 出來,
RecreateThreads() 會在 child process 中被呼叫,並對 sAllThreads 中每一個有
TINFO_FLAG_NUWA_SUPPORT flag 的 thread 進行重建。注意 thread 的 stack 會被指定是我們剛解釋過的 malloc’d stack。在這裡,thread 函式是
thread_recreate_startup() ,它將重設 thread 的名字,紀錄重建後的 pthread ID 和 native thread ID,呼叫
setjmp() ,接著
longjmp() 回到稍早該 thread 在女媧 process 裡
setjmp() 的地方,繼續往下執行。當原始的 thread 函式結束後,接著會回到
thread_create_startup() 再
longjmp() 回到剛剛在
thread_recreate_startup() 設下的
setjmp() 。透過下圖我們可以更清楚的了解整個準備-暫停-繼續的流程:
也許眼尖的網友會注意到還有一些其他的 pthread 函式也被包裝,例如:
pthread_join() 。這是因為在女媧 process 裡創建的 thread,其 ID 可能被儲存在某個變數中而被 child process 繼承。如果在 child process 中呼叫
pthread_join() 且傳入該 ID,它需要被替換成重建後的 pthread ID,否則將會產生錯誤,因此
pthread_join() 才會也被包裝。
希望這篇文章能讓大家對女媧的運作有些許的了解。:)
[1] http://en.wikipedia.org/wiki/N%C3%BCwa
[2] https://bugzilla.mozilla.org/show_bug.cgi?id=771765
[3] http://ftp.gnu.org/pub/old-gnu/Manuals/ld-2.9.1/html_node/ld_3.html
[4] http://hg.mozilla.org/mozilla-central/file/7297cfffd91c/mozglue/build/Nuwa.cpp
