Copyright (慣C) 2018 宅色夫
儘管 1991 年 Linux 核心的原始程式碼已公開釋出,但到了 1992 年才開始以 GNU GPLv2 釋出原始程式碼,自此透過大量開發者的投入,創造真正的「九二共識」。
Linus Torvalds 在 2001 年紀錄片《Revolution OS》說過:
「作業系統就是你永遠不會看到的東西,因為沒有人直接使用作業系統,人們使用的是程式。在他們的電腦上,作業系統唯一的使命就是,幫助其它程式執行,所以作業系統從未獨立運行,而僅是默默等待程式,來向它要求現有資源、某個存在硬碟上的檔案或要求其它程式將這個程式連接到外面去,然後作業系統再一步步地,試著讓人們寫程式容易一些」
這席話背後的機制,恰好就是系統呼叫。
Linus Torvalds 在 2001 年 10 月重申「九二共識」:
From a technical standpoint, I believe the kernel will be "more of the same" and that all the _really_interesting staff will be going out in user space.
推薦閱讀:論文 Analyzing a Decade of Linux System Calls
《C 語言編程透視》是中國友人 falcon 著眼於透視 C 的前世今生,所撰寫的電子書,在 打造史上最小可執行 ELF 檔案 談及系統呼叫使用的機制。
給定 hello.c
:
以 gcc/clang 編譯 hello.c
,隨後用 ltrace 追蹤:
幾個觀察:
__libc_start_main
是程式真正的進入點,詳見 你所不知道的 C 語言: 執行階段程式庫 (CRT)puts
函式的參數puts
函式返回值為 14
,表示字串長度status 0
為 status code,也就是 main 函式的return
值對 gcc 輸出的組合語言進行縮減,得到以下 hello.s
:
組譯和連結:
在 你所不知道的 C 語言: 編譯器和最佳化原理篇 提及 gcc 會將 printf("hello, world!\n")
最佳化為 puts("hello, world!\n")
,以降低解析 format string 和對應處理的成本。qrintf - sprintf accelerator 展示了字串處理的執行時期開銷。對應到組合語言就是 call puts
,以及之前參數傳遞的指令。
可避免動態連結函式庫中的 printf
或 puts
,也不用直接呼叫 _exit,而在組合語言裡頭使用系統呼叫,即可可以去掉和動態連結關聯的內容。重寫後得到如下 x86 程式碼:
對應的 x86_64 版本:
組譯和連結:
原來,無論是 x86 的 int $0x80
抑或 x86_64 的 syscall
都是系統呼叫的 call gate,後期 Intel 引入快速系統呼叫 (fast system call)。
此處所指的 call gate 為涉及到特權模式移轉的操作,可以讓使用者在特權等級較低的情況下,跳到較高的特權等級(通常意味著作業系統),而非指 x86 特有的機制 CALL FAR instruction
搭配閱讀: System Calls Make the World Go Round
使用 syscall(2) 改寫為以下:
透過 strace(1) 追蹤系統呼叫:
查閱 syscall(2):
arch/ABI | instruction | syscall # | retval |
---|---|---|---|
arm/OABI | swi NR |
- | a1 |
arm/EABI | swi 0x0 |
r7 | r0 |
arm64 | svc #0 |
x8 | x0 |
i386 | int $0x80 |
eax | eax |
x86_64 | syscall |
rax | rax |
x32 | syscall |
rax | rax |
x32 ABI allows programs to take advantage of the benefits of x86-64 instruction set (larger number of CPU registers, better floating-point performance, faster position-independent code, shared libraries, function parameters passed via registers, faster syscall instruction) while using 32-bit pointers and thus avoiding the overhead of 64-bit pointers.
_syscall(2) 已棄置
搭配閱讀: Computer Science from the Bottom Up: System Calls
Kernel Probes (Kprobes) 僅能讀取系統呼叫的參數和返回值,無法變更暫存器內含值。
複習 透過 eBPF 觀察作業系統行為。
準備 call.py
先在一個終端機執行:
等待五秒,再開啟另一個終端機並執行以下命令:
依據 nc(1):
arbitrary TCP and UDP connections and listens
-l' Used to specify that nc should listen for an incoming connection rather than initiate a connection to a remote host.
0
是 hostname (也就是本地端),4242
是 port
預期將看到以下訊息:
修改上述程式的第 8 行為:
重作實驗,預期得到以下訊息:
修改上述 kprobe__inet_listen 函式,取代為以下:
重作實驗,預期得到以下訊息:
延伸閱讀:
檔案 linux/arch/x86/entry/syscall_64.c:
搭配閱讀 How does the Linux kernel handle a system call 和 Anatomy of a system call, part 1
問問 git (有飯桶」和「笨蛋」的意思) 需要什麼?
等等,檔案 linux-vdso.so.1
在哪?
參見 什麼是 Linux vDSO 與 vsyscall?——發展過程
benefits to gettimeofday() is implemented with a userspace-only vsyscall/vdso, which avoids the syscall
overhead.
gettimeofday、clockgettime 以及不同時鐘源的影響
在 32-bit 和 64-bit 環境還有更多考量,參見 vDSO on arm64
搭配閱讀: