linux 新增系統呼叫

2021-05-21 21:25:00 字數 3062 閱讀 5634

此文於2010-02-26被推薦到csdn首頁

如何被推薦?

新的建立檔案描述符的 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新增系統呼叫

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

Linux新增系統呼叫

通常新增系統呼叫有兩種方案 重新編譯核心 新增核心模組 此處我們採用重新編譯核心的方式增加系統呼叫。實驗環境 x86 64 gnu linux 4.15.0 ubuntu18.04 將其拷貝到 usr src目錄下 cp linux 4.19.1.tar.xz usr src 解壓縮並鏈結 xz c...

為linux新增系統呼叫

這篇文章簡單的介紹了如何為linux新增乙個系統呼叫。也就是為linux增加乙個api函式。通過這篇文章,應該學會如何 簡單的修改linux源 編譯核心。首先 1 cd usr src linux kernel a linkage int sys mycall int num 3.cd usr sr...