Linux 新增系統呼叫的啟示

2021-05-21 20:57:28 字數 3017 閱讀 7132

新的建立檔案描述符的 syscall 一般都支援額外的 flags 引數,可以直接指定 o_nonblock 和 fd_cloexec,例如:

以上 6 個 syscalls,除了最後乙個是新功能,其餘的都是增強原有的呼叫,把數字尾號去掉就是原來的 syscall。

o_nonblock的功能是開啟「非阻塞io」,而檔案描述符預設是阻塞的。

這些建立檔案描述符的系統呼叫能直接設定 o_nonblock 選項,或許能反映當前 linux (服務端)開發的風向,那就是我在前一篇部落格《多執行緒伺服器的常用程式設計模型》裡推薦的 one loop per thread + (non-blocking io with io multiplexing)。從這些核心改動來看,non-blocking io 已經主流到讓核心增加 syscall 以節省一次 fcntl(2) 呼叫的程度了。

另外,以下新系統呼叫可以在建立檔案描述符時開啟 fd_cloexec 選項:

fd_cloexec的功能是讓程式 fork() 時,子程序會自動關閉這個檔案描述符

(見下面的更正)。而檔案描述預設是被子程序繼承的(這是傳統 unix 的一種典型 ipc,比如用 pipe(2) 在父子程序間單向通訊)。

以上 8 個新 syscalls 都允許直接指定 fd_cloexec,或許說明 fork() 的主要目的已經不再是建立 worker process 並通過共享的檔案描述符和父程序保持通訊,而是像 windows 的 createprocess 那樣建立「乾淨」的程序,其與父程序沒有多少瓜葛。

以上兩個 flags 在我看來,說明 linux 伺服器開發的主流模型正在由 fork() + worker processes 模型轉變為我前文推薦的多執行緒模型。fork() 的使用頻度會大大降低,將來或許只有專門負責啟動別的程序的「看門狗程式」才會呼叫 fork(),而一般的伺服器程式(此處「伺服器程式」的定義見我前一篇文章)不會再 fork() 出子程序了。原因之一是,fork() 一般不能在多執行緒程式中呼叫,因為 linux 的 fork() 只轉殖當前執行緒的 thread of control,不轉殖其他執行緒。也就是說不能一下子 fork() 出乙個和父程序一樣的多執行緒子程序,linux 沒有 forkall() 這樣的系統呼叫。forkall() 其實也是很難辦的(從語意上),因為其他執行緒可能等在 condition variable 上,可能阻塞在系統呼叫上,可能等這 mutex 以跨入臨界區,還可能在密集的計算中,這些都不好全盤搬到子程序裡。由此可見,「看門狗程式」應該是單程序的,而且能捕獲 sigchld,如果 signal 能像「檔案」一樣讀就能大大簡化開發,下面第 2 點正好印證了。

既然如此,那麼在 fork() 時關閉不相干的檔案描述符就成了常見的需求,乾脆做到系統呼叫裡得了。

signal 處理是 unix 程式設計的難點,因為 signal 是非同步的,而且發生在「當前執行緒」裡,會遇到「可重入」的難題。其實「執行緒」是 1993 才加入到 unix 中,之前的 20 多年根本就沒有「主線程」一說,我這裡的意思是 signal handler 是像 coroutine 一樣被呼叫的,而不是通常的 subroutine。raymond chen 有一篇文章談到了這個問題。

在 unix/linux 支援執行緒以後,signal 就更難處理了,規則變得晦澀(想想 signal delivery 的物件)。而且它不符合「every thing is a file」 的 unix 哲學,不能把 signal 事件當成檔案來讀。不過 2.6.22 加入的 signalfd 讓事情有了轉機,程式能像處理檔案一樣處理 signal,可以 read,也可以 select/poll/epoll,能融入標準的 io multiplexing 框架中,而不需要在程式裡另外用一對 pipe 來把 signal 轉為 io event。(libev 似乎是這麼做的,另外還有 ghc http://hackage.haskell.org/trac/ghc/ticket/1520 )

這下多執行緒程式與 signals 打交道容易多了,乙個 event loop 就能搞定 io 和 timer 和 signals,完美。

如果需要在 event loop 裡做無阻塞的高精度定時,現在可以用 timerfd 了。而且它既然是個 fd,就能很方便地和 non-blocking io 與 io multiplexing 融合到一起,渾然天成。當然,檔案描述符是稀缺資源,如果每個 event loop 都採用 timerfd 來做 timer/timeout 似乎是一種浪費(每個 timer 乙個 timerfd 更是巨大浪費,因為不是每個 timer 都需要高精度定時),我寧願採用傳統的優先佇列辦法來管理等待到期的 timers(毫秒級的定時精度已經能滿足我的需要),只在特殊場合動用 timerfd。

《多執行緒伺服器的常用程式設計模型》 提到程序間通訊只用 tcp,而 pipe 的惟一作用是非同步喚醒 event loop,現在有了 eventfd,pipe 連這個作用都沒有了。eventfd 是乙個比 pipe 更高效的執行緒間事件通知機制,一方面它比 pipe 少用乙個 file descriper,節省了資源;另一方面,eventfd 的緩衝區管理也簡單得多,全部「buffer」一共只有 8 bytes,不像 pipe 那樣可能有不定長的真正 buffer。

pipe 將來的作用或許主要是被「看門狗程式」用來截獲子程序的 stdout/stderr。

綜上,我前面一篇部落格中提倡的 one loop per thread + (non-blocking io with io multiplexing) 伺服器模型依賴乙個優質的基於 reactor 模式的網路庫。如果要編寫乙個話,最好能用 2.6.22 以後的新核心,預計程式設計會簡化不少(至少 eventfd 和 signalfd 能發揮很大作用),我準備寫乙個簡單的試試。

最後,我研究 linux kernel,目的是為了更好地編寫 linux 的伺服器應用程式。我不是 kernel 專家,也不打算成為專家。

2010-feb-27 更正:前面說「fd_cloexec 的功能是讓程式 fork() 時,子程序會自動關閉這個檔案描述符」,這是錯誤的,fd_cloexec 顧名思義是在執行 exec() 呼叫時關閉檔案描述符,防止檔案描述符洩漏給子程序。我對fork()的第一反應是立即執行exec(),故造成了誤解。

Linux 新增系統呼叫的啟示

新的建立檔案描述符的 syscall 一般都支援額外的 flags 引數,可以直接指定 o nonblock 和 fd cloexec,例如 以上 6 個 syscalls,除了最後乙個是新功能,其餘的都是增強原有的呼叫,把數字尾號去掉就是原來的 syscall。o nonblock的功能是開啟 非...

linux 新增系統呼叫

此文於2010 02 26被推薦到csdn首頁 如何被推薦?新的建立檔案描述符的 syscall 一般都支援額外的 flags 引數,可以直接指定 o nonblock 和 fd cloexec,例如 以上 6 個 syscalls,除了最後乙個是新功能,其餘的都是增強原有的呼叫,把數字尾號去掉就是...

Linux新增系統呼叫

修改核心 新增函式,新增函式宣告以及新增系統呼叫id,來實現給自己編譯的核心新增系統呼叫。當然這個過程是在編譯核心之前完成的,核心編譯過程請參照linux核心編譯 進入解壓的檔案目錄 cd usr src linux 5.1 kernel在sys.c中新增函式 執行vim sys.c使用vim開啟s...