Jaychao2099
  • NEW!
    NEW!  Connect Ideas Across Notes
    Save time and share insights. With Paragraph Citation, you can quote others’ work with source info built in. If someone cites your note, you’ll see a card showing where it’s used—bringing notes closer together.
    Got it
      • Create new note
      • Create a note from template
        • Sharing URL Link copied
        • /edit
        • View mode
          • Edit mode
          • View mode
          • Book mode
          • Slide mode
          Edit mode View mode Book mode Slide mode
        • Customize slides
        • Note Permission
        • Read
          • Only me
          • Signed-in users
          • Everyone
          Only me Signed-in users Everyone
        • Write
          • Only me
          • Signed-in users
          • Everyone
          Only me Signed-in users Everyone
        • Engagement control Commenting, Suggest edit, Emoji Reply
      • Invite by email
        Invitee

        This note has no invitees

      • Publish Note

        Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note No publishing access yet

        Your note will be visible on your profile and discoverable by anyone.
        Your note is now live.
        This note is visible on your profile and discoverable online.
        Everyone on the web can find and read all notes of this public team.

        Your account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

        Your team account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

        Explore these features while you wait
        Complete general settings
        Bookmark and like published notes
        Write a few more notes
        Complete general settings
        Write a few more notes
        See published notes
        Unpublish note
        Please check the box to agree to the Community Guidelines.
        View profile
      • Commenting
        Permission
        Disabled Forbidden Owners Signed-in users Everyone
      • Enable
      • Permission
        • Forbidden
        • Owners
        • Signed-in users
        • Everyone
      • Suggest edit
        Permission
        Disabled Forbidden Owners Signed-in users Everyone
      • Enable
      • Permission
        • Forbidden
        • Owners
        • Signed-in users
      • Emoji Reply
      • Enable
      • Versions and GitHub Sync
      • Note settings
      • Note Insights New
      • Engagement control
      • Make a copy
      • Transfer ownership
      • Delete this note
      • Save as template
      • Insert from template
      • Import from
        • Dropbox
        • Google Drive
        • Gist
        • Clipboard
      • Export to
        • Dropbox
        • Google Drive
        • Gist
      • Download
        • Markdown
        • HTML
        • Raw HTML
    Menu Note settings Note Insights Versions and GitHub Sync Sharing URL Create Help
    Create Create new note Create a note from template
    Menu
    Options
    Engagement control Make a copy Transfer ownership Delete this note
    Import from
    Dropbox Google Drive Gist Clipboard
    Export to
    Dropbox Google Drive Gist
    Download
    Markdown HTML Raw HTML
    Back
    Sharing URL Link copied
    /edit
    View mode
    • Edit mode
    • View mode
    • Book mode
    • Slide mode
    Edit mode View mode Book mode Slide mode
    Customize slides
    Note Permission
    Read
    Only me
    • Only me
    • Signed-in users
    • Everyone
    Only me Signed-in users Everyone
    Write
    Only me
    • Only me
    • Signed-in users
    • Everyone
    Only me Signed-in users Everyone
    Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note No publishing access yet

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.

    Your account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

    Your team account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

    Explore these features while you wait
    Complete general settings
    Bookmark and like published notes
    Write a few more notes
    Complete general settings
    Write a few more notes
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       Owned this note    Owned this note      
    Published Linked with GitHub
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    # 第五講:賦予應用程式生命的系統呼叫 > 本筆記僅為個人紀錄,相關教材之 Copyright 為[jserv](http://wiki.csie.ncku.edu.tw/User/jserv)及其他相關作者所有 * 直播:==[Linux 核心設計:賦予應用程式生命的系統呼叫 (2018-12-07)](https://www.youtube.com/watch?v=rPWt6KgL8uQ)== * 詳細共筆:[Linux 核心設計: 賦予應用程式生命的系統呼叫](https://hackmd.io/@sysprog/linux-syscall) * 主要參考資料: * [Analyzing a Decade of Linux System Calls](http://research.cs.queensu.ca/~cordy/Papers/BKBHDC_ESE_Linux.pdf) * [System Calls Make the World Go Round](https://manybutfinite.com/post/system-calls/) * [Computer Science from the Bottom Up: System Calls](https://www.bottomupcs.com/system_calls.xhtml) * [Anatomy of a system call, part 1 (LWN.net)](https://lwn.net/Articles/604287/) * [Anatomy of a system call, part 2 (LWN.net)](https://lwn.net/Articles/604515/) * [什麼是 Linux vDSO 與 vsyscall?——發展過程](https://alittleresearcher.blogspot.com/2017/04/linux-vdso-and-vsyscall-history.html) --- 本課闡述了 Linux 核心中系統呼叫 (System Call) 的概念、機制、演進及其重要性。系統呼叫是應用程式與作業系統核心溝通的橋樑,賦予應用程式存取硬體資源和核心服務的能力。 --- ## Linux 系統呼叫的發展 Linux 的「九二共識」,並非政治術語,而是指 1992 年 Linux 核心開始以 GNU GPLv2 (GNU General Public License version 2,GNU 通用公共授權條款第二版) 釋出原始程式碼這一里程碑事件。儘管 Linux 核心的原始程式碼在 1991 年已公開,但 GPLv2 的採用奠定了其自由開放的發展模式,吸引了全球大量開發者的投入,共同塑造了 Linux 的輝煌。 ### 作業系統的使命 Linus Torvalds 在 2001 年的紀錄片《[Revolution OS](https://hackmd.io/s/SyuRJIPI-)》中指出: > 「作業系統就是你永遠不會看到的東西,因為沒有人直接使用作業系統,人們使用的是程式。在他們的電腦上,**作業系統唯一的使命就是,幫助其它程式執行**,所以作業系統從未獨立運行,而僅是默默等待程式,來向它要求現有資源、某個存在硬碟上的檔案或要求其它程式將這個程式連接到外面去,然後作業系統再一步步地,試著讓人們寫程式容易一些。」 這段話揭示了作業系統的本質:服務於應用程式。而實現這一服務的關鍵機制正是系統呼叫。應用程式透過系統呼叫請求作業系統提供檔案操作、網路通訊、記憶體管理等服務。 Linus Torvalds 在同年 10 月進一步闡述了他對 Linux 發展方向的看法,可視為對此「九二共識」的技術性重申: > "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**." 這句話強調,Linux 核心應保持其核心功能的穩定與精簡,而真正創新和有趣的發展將更多地出現在 User Space。意味著不應將過多的應用層級功能塞入核心,避免核心過於臃腫和複雜。 一個反面教材是早期曾有人嘗試將 Web 伺服器 (如 Tux 專案) 整合進 Linux 核心以期提升效能,但這種做法違背了核心設計原則,增加了核心的複雜性和潛在風險,最終未能普及。Windows NT 也曾為了圖形效能將顯示引擎移入核心,但也因此引入了安全漏洞。 ### Linux 核心的系統呼叫 Linux 核心透過提供一組定義良好的系統呼叫介面 (約數百個,隨版本變動),來支持 User Space 應用程式的運行。核心會儘可能將功能實現在 User Space,例如資料庫、繪圖引擎、視窗系統等。 然而,有時為了特定的效能優化,核心也會引入特殊的系統呼叫,例如 `sendfile` 系統呼叫,它允許在兩個檔案描述符之間直接傳輸資料 (例如從磁碟檔案到網路 socket),避免了資料在 Kernel Space 和 User Space 之間的多次複製,從而顯著提升了網路伺服器傳輸靜態檔案的效率。 ![](https://i.imgur.com/eoX2FDC.png) 上圖展示了應用程式 (Ring 3,使用者模式) 如何透過系統呼叫請求核心 (Ring 0,核心模式) 提供的服務,如檔案操作、網路通訊、硬體存取等。 > 延伸閱讀:[Analyzing a Decade of Linux System Calls](http://research.cs.queensu.ca/~cordy/Papers/BKBHDC_ESE_Linux.pdf),該論文分析了從 2005 年 4 月到 2014 年 12 月的系統呼叫變化,Linux 系統呼叫的數量和功能在過去十年間持續演進、增減,以適應不斷變化的硬體和應用需求,但其核心介面保持了相對穩定。 > ![image](https://hackmd.io/_uploads/HJkERcNzxl.png) --- ## 從縮減 Hello World 程式談起 理解系統呼叫的一個實用方法是觀察一個簡單程式 (如 "Hello World") 在編譯和執行過程中的行為,並逐步移除對標準函式庫的依賴,直接使用系統呼叫。 ### 1. 用 `ltrace` 觀察 給定一個標準的 `hello.c` 程式: ```c= #include <stdio.h> int main(void) { printf("hello, world!\n"); return 0; } ``` 使用 `ltrace` 工具 (Library trace) 追蹤其執行時的函式庫呼叫,可以看到: ```shell $ ltrace ./hello __libc_start_main(0x55931d22e149, 1, 0x7ffd4f6d8fb8, ...(略) puts("hello, world!"hello, world! ) = 14 +++ exited (status 0) +++ ``` 觀察發現: 1. `__libc_start_main` 是程式真正的進入點,它由 C 執行階段程式庫 (CRT, C Runtime Library) 提供,負責進行一些初始化工作,並呼叫 `main` 函式。 > 延伸閱讀:[C 語言的執行階段程式庫 (CRT)](https://hackmd.io/@Jaychao2099/crt) 4. 儘管原始碼中使用的是 `printf`,但在編譯最佳化後,實際執行的是 `puts` 函式。這是因為對於不包含格式化字串的簡單字串輸出,`puts` 比 `printf` 更有效率,可以降低解析格式字串的成本。 5. `puts` 函式的返回值 `14` 表示成功輸出的字串長度。 6. `status 0` 是 `main` 函式的返回值,表示程式正常退出。 ### 2. 用組合語言觀察 為了進一步理解底層機制,可以將 C 程式碼編譯為組合語言。GCC 編譯器可能會將 `printf()` 最佳化為 `puts()`。我們可以手動編寫更精簡的組合語言,直接使用系統呼叫來輸出字串並退出程式,從而避免連結 C 標準函式庫中的 `printf()` 或 `puts()`。 以下是一個直接使用 Linux 系統呼叫的 x86 (32位元) 組合語言範例 `hello.s`: ```asm= .LC0: .string "Hello world!\xa\x0" # \xa 是換行符 \n,\x0 是 null .text .global _start _start: # sys_write(fd, addr, len) movb $4, %al # eax = 4 (sys_write 的系統呼叫號碼) xorl %ebx, %ebx incl %ebx # ebx = 1 (檔案描述符,標準輸出 stdout) movl $.LC0, %ecx # ecx = 字串 "Hello world!\n" 的位址 xorl %edx, %edx movb $13, %dl # edx = 13 (字串長度,不含 null terminator) int $0x80 # 執行系統呼叫 # sys_exit(status) xorl %eax, %eax incl %eax # eax = 1 (sys_exit 的系統呼叫號碼) xorl %ebx, %ebx # ebx = 0 (退出狀態碼) int $0x80 # 執行系統呼叫 ``` 組譯和連結指令: ```shell $ as --32 -o hello.o hello.s $ ld -melf_i386 -o hello hello.o ``` 對於 x86_64 (64位元) 架構,系統呼叫的方式有所不同,通常使用 `syscall` 指令,且參數傳遞透過不同的暫存器: ```asm= .data msg: .ascii "Hello, world!\n" len = . - msg # 計算字串長度 .text .global _start _start: # sys_write(fd, buf, count) movq $1, %rax # rax = 1 (sys_write 的系統呼叫號碼) movq $1, %rdi # rdi = 1 (檔案描述符 stdout) movq $msg, %rsi # rsi = 字串位址 movq $len, %rdx # rdx = 字串長度 syscall # 執行系統呼叫 # sys_exit(error_code) movq $60, %rax # rax = 60 (sys_exit 的系統呼叫號碼) xorq %rdi, %rdi # rdi = 0 (退出狀態碼) syscall # 執行系統呼叫 ``` 組譯和連結指令: ```shell $ gcc -c hello.s # 或者用 as $ ld -o hello hello.o ``` 無論是 x86 的 `int $0x80` 還是 x86_64 的 `syscall` 指令,它們都是觸發系統呼叫的入口 (軟體中斷),常被稱為 **Call Gate**。這並非指 x86 特有的 `CALL FAR` 指令的 Call Gate,而是廣義上指代一種機制,**允許使用者模式的程式碼轉換到核心模式以執行特權指令**。Intel 後期也引入了 `SYSENTER` 和 `SYSEXIT` 等指令作為快速系統呼叫機制,以降低傳統中斷方式的開銷。 > 延伸閱讀:[System Calls Make the World Go Round](https://manybutfinite.com/post/system-calls/) > * 系統呼叫作為應用程式和核心之間的橋樑: > ![](https://i.imgur.com/eI0WONT.png) > * 系統呼叫時的 instruction pointer 變化: > ![未命名](https://hackmd.io/_uploads/SJANw2Nzlx.jpg) #### ABI 比對 下表總結了不同硬體架構和 ABI (Application Binary Interface,應用程式二進位介面) 下,系統呼叫的指令、系統呼叫號碼傳遞方式及返回值所在暫存器 (參考 [`syscall(2)` man page](https://man7.org/linux/man-pages/man2/syscall.2.html)): | Arch/ABI | Instruction | Syscall # Reg | Return Val Reg | | -------- | -----------:| --------------:| --------------:| | arm/OABI | `swi NR` | (NR in opcode) | r0 | | 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 | | RISC-V | `ecall` | a7| sret | > * [x32 ABI](https://en.wikipedia.org/wiki/X32_ABI) 是一種特殊的 ABI,它允許程式在 x86-64 環境下運行,利用 64 位元指令集的優勢 (如更多暫存器),同時使用 32 位元指標,以減少 64 位元指標帶來的記憶體開銷。 > * [`_syscall(2)`](http://man7.org/linux/man-pages/man2/_syscall.2.html) 巨集已被棄用。 ### 3. 用 syscall 觀察 我們也可以使用 C 語言的 `syscall(2)` 函式直接發起系統呼叫: ```c= #define _GNU_SOURCE #include <unistd.h> #include <sys/syscall.h> // 包含 __NR_write 等巨集 // __NR_write 是 sys_write 的系統呼叫號碼 int main() { syscall(__NR_write, 1, "Hello, world!\n", 14); // 1 = stdout syscall(__NR_exit, 0); return 0; // 這行在呼叫 __NR_exit 後實際不會執行到 } ``` 透過 `strace` 工具追蹤此程式的系統呼叫: ```shell $ strace ./hello_syscall ...(略) write(1, "Hello, world!\n", 14hello, world! ) = 14 exit(0) = ? +++ exited with 0 +++ ``` 可以看到,程式直接呼叫了 `write` 系統呼叫。 > 建議閱讀:[Computer Science from the Bottom Up: System Calls](https://www.bottomupcs.com/system_calls.xhtml) --- ## 透過 kprobes + eBPF 來追蹤系統呼叫 傳統的系統呼叫追蹤工具如 **`strace` 會帶來較大的效能開銷**,且其 **基於 `ptrace` 的機制可能被某些程式繞過**。現代 Linux 核心提供了更強大且低開銷的追蹤機制,如 Kprobes (Kernel Probes) 和 eBPF (extended Berkeley Packet Filter)。 [Kprobes](https://www.kernel.org/doc/Documentation/kprobes.txt) 允許在核心函式的入口或特定指令處插入探測點,執行自訂的處理函式。Kprobes 僅能讀取系統呼叫的參數和返回值,無法變更暫存器內容。 eBPF 是一個 **核心內的虛擬機器**,可以執行 **經過驗證的 BPF 位元組碼**。透過將 eBPF 程式掛載到 Kprobes 等探測點,可以實現高度靈活和高效能的核心行為觀測與分析。 > 延伸閱讀:[第四講:透過 eBPF 觀察作業系統行為](https://hackmd.io/@Jaychao2099/Linux-kernel-4)。 ### 用 eBPF 觀察 以下是一個使用 `bcc` (BPF Compiler Collection) 工具集中的 Python 函式庫來結合 Kprobes 和 eBPF 追蹤核心函式 `inet_listen` (與網路監聽相關) 的範例。 準備 `call.py`: ```python= from bcc import BPF # eBPF C 程式碼 bpf_text = """ #include <net/inet_sock.h> // 用於 inet_sk 巨集和 struct inet_sock #include <bcc/proto.h> // 用於 bpf_trace_printk 等 int kprobe__inet_listen(struct pt_regs *ctx, struct socket *sock, int backlog) { bpf_trace_printk("Hello World! from inet_listen\\n"); return 0; }; """ # 載入 eBPF 程式 b = BPF(text=bpf_text) # 持續讀取並印出追蹤訊息 print("Tracing inet_listen... Ctrl-C to exit.") while True: try: print(b.trace_readline().decode('utf-8', 'replace')) except KeyboardInterrupt: exit() ``` 執行步驟: 1. 在一個終端機中以 root 權限執行程式: ```shell $ sudo python call.py ``` 2. 等待約五秒,在另一個終端機中執行Netcat 工具: ```shell $ nc -l -p 4242 # -l 表示監聽,-p 4242 指定監聽 4242 埠 ``` 預期在第一個終端機中會看到類似以下的輸出,表明 `inet_listen` 被呼叫: ```shell nc-752 [006] ....1 367.091793: bpf_trace_printk: Hello World! from inet_listen ``` > [!Warning] 輸出格式可能因 bcc 版本和核心配置而略有不同。 ### 印出更多資訊 可以修改 eBPF 程式以獲取更多有用的資訊,例如 `backlog` 參數: 將 `bpf_trace_printk("Hello World! from inet_listen\\n");` 修改為: ```c=9 bpf_trace_printk("Listening with %d pending connections!\\n", backlog); ``` 重新執行實驗,預期可以看到 `backlog` 的值 (通常是 Netcat 預設的佇列大小)。 進一步,可以讀取 `sock` 參數指向的 `struct socket` 結構,並透過 `inet_sk` 巨集轉換為 `struct inet_sock` 來獲取監聽的 IP 位址和埠號: ```c=9 struct inet_sock *inet = inet_sk(sock->sk); u32 laddr = 0; u16 lport = 0; // 安全地從核心記憶體讀取資料 bpf_probe_read_kernel(&laddr, sizeof(laddr), &(inet->inet_rcv_saddr)); bpf_probe_read_kernel(&lport, sizeof(lport), &(inet->inet_sport)); // 將網路位元組序轉換為主機位元組序 (Big or Little Endian) bpf_trace_printk("Listening on %x %d\\n", ntohl(laddr), ntohs(lport)); ``` > [!Warning]注意: > 舊版 bcc 可能使用 `bpf_probe_read`,新版推薦使用 `bpf_probe_read_kernel` 或 `bpf_probe_read_user` 以明確區分讀取來源。 重新執行實驗,當執行 `nc -l -p 4242` 時,預期看到類似 "Listening on 0 4242" 的輸出 (0 通常表示監聽所有本地 IP 位址)。 > 延伸閱讀: > * [How to turn any syscall into an event: Introducing eBPF Kernel probes](https://blog.yadutaf.fr/2016/03/30/turn-any-syscall-into-event-introducing-ebpf-kernel-probes/) --- ## Linux 核心對系統呼叫的實作機制 Linux 核心內部透過一個稱為「系統呼叫表」 (System Call Table) 的結構來管理和分派系統呼叫。當使用者模式的程式觸發一個系統呼叫 (例如透過 `int $0x80` 或 `syscall` 指令) 後,CPU 會切換到核心模式,並執行一個預先定義好的入口函式。該入口函式會根據傳入的系統呼叫號碼 (System Call Number,通常存放在特定暫存器如 `eax` 或 `rax`) 在系統呼叫表中查詢對應的核心函式指標,然後跳轉執行該核心函式。 以 x86_64 架構為例,相關的實作可以在 4.16 版核心原始程式碼的 [arch/x86/entry/syscall_64.c](https://github.com/torvalds/linux/blob/f8781c4a226319fe60e652118b90cf094ccfe747/arch/x86/entry/syscall_64.c) 中找到。系統呼叫表 `sys_call_table` 是一個函式指標陣列: ```c=10 /* sys_ni_syscall 是一個預設的處理函式,用於處理未實現或無效的系統呼叫號碼 */ extern asmlinkage long sys_ni_syscall(const struct pt_regs *); #define __SYSCALL_64(nr, sym, qual) extern asmlinkage long sym(const struct pt_regs *); #include <asm/syscalls_64.h> // 通常由工具生成,包含了所有系統呼叫的宣告 #undef __SYSCALL_64 #define __SYSCALL_64(nr, sym, qual) [nr] = sym, // 巨集定義,用於初始化陣列 // asmlinkage 是一個編譯器指令,告訴編譯器參數從 stack 傳遞 // sys_call_ptr_t 是 typedef asmlinkage long (*sys_call_ptr_t)(const struct pt_regs *); asmlinkage const sys_call_ptr_t sys_call_table[__NR_syscall_max+1] = { /** * 使用 C99 的 designated initializer 特性, * 將所有表項預設指向 sys_ni_syscall。 * 如果某個系統呼叫號碼沒有對應的實作,被呼叫時就會執行 sys_ni_syscall, * 通常返回 -EINVAL。 */ [0 ... __NR_syscall_max] = &sys_ni_syscall, #include <asm/syscalls_64.h> // 再次包含,這次使用上面定義的巨集來填充表項 }; ``` `__NR_syscall_max` 定義了系統呼叫號碼的最大值。`pt_regs` 結構包含了系統呼叫發生時 CPU 暫存器的狀態。 當一個系統呼叫被觸發,核心會: 1. 保存 User Space 的上下文 (暫存器等)。 2. 從特定暫存器 (如 `rax`) 獲取系統呼叫號碼。 3. 使用該號碼作為索引,在 `sys_call_table` 中找到對應的服務常式 (例如 `sys_read`, `sys_write`)。 4. 從其他暫存器 (如 `rdi`, `rsi`, `rdx` 等) 獲取系統呼叫的參數。 5. 執行該服務常式。 6. 將服務常式的返回值存入特定暫存器 (如 `rax`)。 7. 恢復 User Space 的上下文,並返回到使用者模式繼續執行。 > [!Tip] **Linux System Call 的三種路徑** > ![image](https://hackmd.io/_uploads/rkCIAUBMxg.png) > 在 x86 架構的 Linux 系統中,Userspace 請求核心服務有三種不同途徑。重點在於 **歷史演進** 與 **效能優化**。 > #### **1. 傳統 32-bit 路徑:軟體中斷 (The Legacy Way)** > * **指令:** `INT 0x80` > * **位置:** 圖中最左側(黃色區塊)。 > * **機制:** 最古老的實作方式。程式將系統呼叫代號(如 `read` 為 3)放入 `EAX`,然後觸發 128 號中斷。 > * **關鍵特徵:** > * **經過 IDT:** CPU 必須查詢 **中斷描述符表 (IDT)** 中的 Trap Gate 來找到核心入口點。 > * **效能:** 由於涉及完整的中斷處理流程,權限切換與查表的開銷較大,速度最慢。 > #### **2. 快速 32-bit 路徑:快速呼叫 (The Fast 32-bit Way)** > * **指令:** `SYSENTER` (Intel) / `SYSCALL` (AMD) > * **位置:** 圖中中間路徑(從黃色區塊跨到橘色核心)。 > * **機制:** 為了解決 `INT 0x80` 效能低落而引入的機制。主要用於 64 位元核心上執行 32 位元程式(Compatibility Mode)。 > * **關鍵特徵:** > * **繞過 IDT:** **完全不查詢 IDT**。 > * **使用 MSR:** CPU 直接讀取預先設定好的 **MSR (Model Specific Register)** 暫存器內容,直接跳轉到核心函數 (`ia32_sysenter_target`),大幅提升速度。 > #### **3. 原生 64-bit 路徑:現代標準 (The Native 64-bit Way)** > * **指令:** `SYSCALL` > * **位置:** 圖中最右側(橘色區塊)。 > * **機制:** 現代 64 位元 Linux 的標準呼叫方式。注意系統呼叫代號不同(在 64 位元下,`read` 的代號是 0,而非 3),且使用 `RAX` 暫存器。 > * **關鍵特徵:** > * **繞過 IDT:** 同樣不走 IDT。 > * **使用 MSR_LSTAR:** 利用 `LSTAR` (Long System Target Address Register) 暫存器直接跳轉至核心入口 (`system_call` in `entry_64.S`),是目前效能最好的方式。 > ### **總結核心觀念** > 1. **殊途同歸:** 無論是透過慢速的中斷(Trap Gate)還是快速的暫存器跳轉(MSR),這三條路徑最終都會彙整到同一個 C 語言函數 **`sys_read()`** (`fs/read_write.c`),體現了核心如何將底層硬體差異抽象化。 > 2. **IDT 的角色:** 只有最左邊的古老路徑使用 IDT;現代的快速路徑(中間與右邊)都透過硬體暫存器(MSR)直接進入核心,以減少 CPU Cycle 的消耗。 > 搭配閱讀: > * [How does the Linux kernel handle a system call (Linux Insides)](https://0xax.gitbooks.io/linux-insides/SysCall/linux-syscall-2.html) > * [Anatomy of a system call, part 1 (LWN.net)](https://lwn.net/Articles/604287/) --- ## vsyscall 和 vDSO 在某些情況下,即使系統呼叫的邏輯非常簡單 (例如讀取當前時間),傳統的模式切換所帶來的開銷也可能相對較大。 為了優化這類系統呼叫的效能,Linux 引入了 vsyscall (Virtual System Call) 和 vDSO (Virtual Dynamic Shared Object) 機制。 當使用 `ldd` (List Dynamic Dependencies) 命令查看一個程式 (如 `/usr/bin/git`) 的動態連結依賴時,可能會看到一個名為 `linux-vdso.so.1` 的特殊共享物件: ```shell $ ldd /usr/bin/git linux-vdso.so.1 (0x00007ffc2c2db000) libpcre2-8.so.0 => /lib/x86_64-linux-gnu/libpcre2-8.so.0 (0x00007ff1d6bc5000) libz.so.1 => /lib/x86_64-linux-gnu/libz.so.1 (0x00007ff1d6ba9000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007ff1d6997000) /lib64/ld-linux-x86-64.so.2 (0x00007ff1d706f000) ``` 有趣的是,在檔案系統中通常找不到 `linux-vdso.so.1` 這個檔案。這是因為 vDSO 並不是一個普通的磁碟檔案,而是由核心在 **程式啟動時動態創建** 並 **映射到該程式的位址空間中** 的一段記憶體。 ### vsyscall (Virtual System Call, 虛擬系統呼叫): :::warning **已趨向廢棄,尤其在 x86-64 上由於安全原因和 ASLR 的衝突。** ::: vsyscall 最初是為 x86-64 設計的。 * **解決方案**:核心將一段包含時間資訊與特定系統呼叫實作 (如 `gettimeofday`, `time`, `getcpu`) 的記憶體區段,**映射 (Map)** 到特定的虛擬記憶體位址上。 * **權限設定**:這段記憶體對 Kernel 而言是 Write-only (唯寫,用於定時更新時間),對 User 而言是 Read-only (唯讀)。 * **優勢**:當程式呼叫這類函式時,直接讀取 User space 的記憶體即可,**完全不需要經過 User/Kernel 模式的轉換**,使 `gettimeofday()` 的執行速度可以提升高達 10 倍。 然而,vsyscall 頁面的固定位址與 ASLR (Address Space Layout Randomization,位址空間佈局隨機化,一種安全機制) 存在衝突,因為攻擊者可以利用這個固定位址。 ### vDSO (Virtual Dynamic Shared Object, 虛擬動態共享物件): :::info **vDSO 是對 vsyscall 的改進和替代。** ::: 它同樣由核心提供,將一些系統呼叫的實作 (通常是時間相關的,如 `gettimeofday`, `clock_gettime`) 作為一個迷你的共享函式庫 **映射到每個 user process 的位址空間**。與 vsyscall 不同的是: 1. **位址隨機化**:vDSO 的映射位址是隨機的,與 ASLR 相容。 2. **ELF 格式**:vDSO 是一個完整的 ELF 共享物件,這使得 debugger (如 GDB) 可以更好地處理它,並且標準的動態連結器可以解析其符號。 3. **靈活性**:vDSO 可以包含更複雜的邏輯,並且更容易擴展。 ![](https://i.imgur.com/1rtBs8n.png) ![image](https://hackmd.io/_uploads/ryxJTEUIWl.png) 當應用程式呼叫一個在 vDSO 中有實作的函式時 (例如 C 函式庫中的 `gettimeofday()`),C 函式庫會檢查是否存在 vDSO 並優先使用 vDSO 中的版本。這樣,呼叫就可以在 User Space 內完成,極大地提升了效能。例如,頻繁呼叫 `gettimeofday` 的應用程式可以從 vDSO 中獲益良多。 ![](https://i.imgur.com/9UbtUo0.png) > 搭配閱讀: > * [vDSO: 快速的 Linux 系統呼叫機制](https://hackmd.io/@sysprog/linux-vdso) > * [Anatomy of a system call, part 2 (LWN.net)](https://lwn.net/Articles/604515/) > 參考資料: > * [什麼是 Linux vDSO 與 vsyscall?——發展過程](https://alittleresearcher.blogspot.com/2017/04/linux-vdso-and-vsyscall-history.html) > * [gettimeofday、clock_gettime 以及不同時鐘源的影響](https://www.cnblogs.com/raymondshiquan/articles/gettimeofday_vs_clock_gettime.html) > * ARM 架構上的 vDSO 考量,可參考簡報 [The vDSO on arm64](https://blog.linuxplumbersconf.org/2016/ocw/system/presentations/3711/original/LPC_vDSO.pdf)。該簡報指出 vDSO 是一個由核心提供的完整共享函式庫,映射到所有使用者行程,主要用於在 User Space 提供「虛擬系統呼叫」以提升速度,特別是 **針對那些本身處理很快但核心進出 (kernel enter/exit) 開銷顯著的系統呼叫**。vDSO 相較於舊的 vsyscall page 機制更靈活,更易於除錯,且由於利用 ASLR 而更難被利用。在 arm64 上,vDSO 包含 `vdso_data` (核心與 User Space 共享的資料頁,vvar) 和包含函式實作的程式碼頁。 --- ## 總結 系統呼叫是作業系統提供服務的基石,而 Linux 核心在系統呼叫的設計、實作和優化方面,展現了其不斷演進和追求效能與安全的努力。從傳統的中斷機制到現代的 `syscall` 指令,再到 Kprobes、eBPF 等高級追蹤技術,以及 vsyscall 和 vDSO 這類效能優化手段,都體現了 Linux 核心設計的精妙與複雜性。 --- 回[主目錄](https://hackmd.io/@Jaychao2099/Linux-kernel)

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password
    or
    Sign in via Google Sign in via Facebook Sign in via X(Twitter) Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    By signing in, you agree to our terms of service.

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully