自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

為什么 Linux 系統(tǒng)調(diào)用會消耗較多資源

系統(tǒng) Linux
當(dāng)我們在編寫應(yīng)用程序時,系統(tǒng)調(diào)用并不是一個離我們很遠(yuǎn)的概念,一個簡單的 Hello World 會在執(zhí)行時觸發(fā)幾十次系統(tǒng)調(diào)用,而在線上出現(xiàn)性能問題時,可能也需要我們與系統(tǒng)調(diào)用打交道。

系統(tǒng)調(diào)用是計算機(jī)程序在執(zhí)行的過程中向操作系統(tǒng)內(nèi)核申請服務(wù)的方法,這可能包含硬件相關(guān)的服務(wù)、新進(jìn)程的創(chuàng)建和執(zhí)行以及進(jìn)程調(diào)度,對操作系統(tǒng)稍微有一些了解的人都知道 — 系統(tǒng)調(diào)用為用戶程序提供了操作系統(tǒng)的接口。

圖 1 - 操作系統(tǒng)接口

C 語言的著名的 glibc 封裝了操作系統(tǒng)提供的系統(tǒng)調(diào)用并提供了定義良好的接口[^2],工程師可以直接使用器中封裝好的函數(shù)開發(fā)上層的應(yīng)用程序,其他編程語言的標(biāo)準(zhǔn)庫也會封裝系統(tǒng)調(diào)用,它們對外提供語言原生的接口,內(nèi)部使用匯編語言觸發(fā)系統(tǒng)調(diào)用。

我們在使用標(biāo)準(zhǔn)庫時需要經(jīng)常與系統(tǒng)調(diào)用打交道,只是很多時候我們不知道標(biāo)準(zhǔn)庫背后的實(shí)現(xiàn),以常見的 Hello World 程序?yàn)槔?,這么簡單的幾行函數(shù)在真正運(yùn)行時會執(zhí)行幾十次系統(tǒng)調(diào)用:

  1. #include <stdio.h> 
  2. int main() { 
  3.    printf("Hello, World!"); 
  4.    return 0; 
  5.  
  6. $ gcc hello.c -o hello 
  7. $ strace ./hello 
  8. execve("./hello", ["./hello"], 0x7ffd64dd8090 /* 23 vars */) = 0 
  9. brk(NULL)                               = 0x557b449db000 
  10. access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory) 
  11. access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory) 
  12. openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3 
  13. fstat(3, {st_mode=S_IFREG|0644, st_size=26133, ...}) = 0 
  14. mmap(NULL, 26133, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f645455a000 
  15. close(3)                                = 0 
  16. ... 
  17. munmap(0x7f645455a000, 26133)           = 0 
  18. fstat(1, {st_mode=S_IFCHR|0600, st_rdev=makedev(136, 0), ...}) = 0 
  19. brk(NULL)                               = 0x557b449db000 
  20. brk(0x557b449fc000)                     = 0x557b449fc000 
  21. write(1, "Hello, World!", 13Hello, World!)           = 13 
  22. exit_group(0)                           = ? 
  23. +++ exited with 0 +++ 

strace 是 Linux 中用于監(jiān)控和篡改進(jìn)程與內(nèi)核之間操作的工具,上述命令會打印出 hello 執(zhí)行過程中觸發(fā)系統(tǒng)調(diào)用、參數(shù)以及返回值等信息。執(zhí)行 Hello World 程序時觸發(fā)的多數(shù)系統(tǒng)調(diào)用都是程序啟動觸發(fā)的,只有 munmap 后的系統(tǒng)調(diào)用才是 printf函數(shù)觸發(fā)的,作為應(yīng)用程序我們能做的事情非常有限,很多功能都需要依賴操作系統(tǒng)提供的服務(wù)。

多數(shù)編程語言的函數(shù)調(diào)用只需要分配新的??臻g、向寄存器寫入?yún)?shù)并執(zhí)行 CALL 匯編指令跳轉(zhuǎn)到目標(biāo)地址執(zhí)行函數(shù),在函數(shù)返回時通過?;蛘呒拇嫫鞣祷貐?shù)。與函數(shù)調(diào)用相比,系統(tǒng)調(diào)用會消耗更多的資源,如下圖所示,使用 SYSCALL 指定執(zhí)行系統(tǒng)調(diào)用消耗的時間是 C 函數(shù)調(diào)用的幾十倍:

圖 2 - 系統(tǒng)調(diào)用與函數(shù)調(diào)用耗時比較

上圖中的 vDSO 全稱是虛擬動態(tài)鏈接對象(Virtual Dynamically Shared Object、vDSO),它可以減少系統(tǒng)調(diào)用的消耗的時間,我們會在后面詳細(xì)分析它的實(shí)現(xiàn)原理。

getpid(2) 是一個相對比較快的系統(tǒng)調(diào)用,該系統(tǒng)調(diào)用不包含任何參數(shù),只會切換到內(nèi)核態(tài)、讀取變量并返回 PID,我們可以將它的執(zhí)行時間當(dāng)做系統(tǒng)調(diào)用的基準(zhǔn)測試;除了 getpid(2) 之外,使用 close(999) 系統(tǒng)調(diào)用關(guān)閉不存在的文件描述符會消耗更少的資源[^5],與 getpid(2) 相比大概會少 20 個 CPU 周期[^6],當(dāng)然想要實(shí)現(xiàn)用于測試額外開銷的系統(tǒng)調(diào)用,使用自定義的空函數(shù)應(yīng)該是最完美的選擇,感興趣的讀者可以自行嘗試一下。

圖 3 - 系統(tǒng)調(diào)用的三種方法

從上面的系統(tǒng)調(diào)用與函數(shù)調(diào)用的基準(zhǔn)測試中,我們可以發(fā)現(xiàn)不使用 vSDO 加速的系統(tǒng)調(diào)用需要的時間是普通函數(shù)調(diào)用的幾十倍,為什么系統(tǒng)調(diào)用會帶來這么大的額外開銷,它在內(nèi)部到底執(zhí)行了哪些工作呢,本文將介紹 Linux 執(zhí)行系統(tǒng)調(diào)用的三種方法:

  • 使用軟件中斷(Software interrupt)觸發(fā)系統(tǒng)調(diào)用;
  • 使用 SYSCALL / SYSENTER 等匯編指令觸發(fā)系統(tǒng)調(diào)用;
  • 使用虛擬動態(tài)共享對象(virtual dynamic shared object、vDSO)執(zhí)行系統(tǒng)調(diào)用;

軟件中斷

中斷是向處理器發(fā)送的輸入信號,它能夠表示某個時間需要操作系統(tǒng)立刻處理,如果操作系統(tǒng)接收了中斷,那么處理器會暫停當(dāng)前的任務(wù)、存儲上下文狀態(tài)、并執(zhí)行中斷處理器處理發(fā)生的事件,在中斷處理器結(jié)束后,當(dāng)前處理器會恢復(fù)上下文繼續(xù)完成之前的工作。

圖 4 - 硬件中斷和軟件中斷

根據(jù)事件發(fā)出者的不同,我們可以將中斷分成硬件和軟件中斷兩種,硬件中斷是由處理器外部的設(shè)備觸發(fā)的電子信號;而軟件中斷是由處理器在執(zhí)行特定指令時觸發(fā)的,某些特殊的指令也可以故意觸發(fā)軟件中斷]。

在 32 位的 x86 的系統(tǒng)上,我們可以使用 INT 指令來觸發(fā)軟件中斷,早期的 Linux 會使用 INT 0x80 觸發(fā)軟件中斷、注冊特定的中斷處理器 entry_INT80_32 來處理系統(tǒng)調(diào)用,我們來了解一下使用軟件中斷執(zhí)行系統(tǒng)調(diào)用的具體過程:

(1) 應(yīng)用程序通過調(diào)用 C 語言庫中的函數(shù)發(fā)起系統(tǒng)調(diào)用;

(2) C 語言函數(shù)通過棧收到調(diào)用方傳入的參數(shù)并將系統(tǒng)調(diào)用需要的參數(shù)拷貝到寄存器;

(3) Linux 中的每一個系統(tǒng)調(diào)用都有特定的序號,函數(shù)會將系統(tǒng)調(diào)用的編號拷貝到 eax寄存器;

(4) 函數(shù)執(zhí)行 INT 0x80 指令,處理器會從用戶態(tài)切換到內(nèi)核態(tài)并執(zhí)行預(yù)先定義好的處理器;

(5) 執(zhí)行中斷處理器 entry_INT80_32 處理系統(tǒng)調(diào)用;

  • 執(zhí)行 SAVE_ALL 將寄存器的值存儲到內(nèi)核棧上并調(diào)用 do_int80_syscall_32;
  • 調(diào)用 do_syscall_32_irqs_on 檢查系統(tǒng)調(diào)用的序號是否合法;
  • 在系統(tǒng)調(diào)用表 ia32_sys_call_table 中查找對應(yīng)的系統(tǒng)調(diào)用實(shí)現(xiàn)并傳入寄存器的值;
  • 系統(tǒng)調(diào)用在執(zhí)行期間會檢查參數(shù)的合法性、在用戶態(tài)內(nèi)存和內(nèi)核態(tài)內(nèi)存之間傳輸數(shù)據(jù),系統(tǒng)調(diào)用的結(jié)果會被存儲到 eax 寄存器中;
  • 從內(nèi)核棧中恢復(fù)寄存器的值并將返回值放到棧上;
  • 系統(tǒng)調(diào)用會返回 C 函數(shù),包裝函數(shù)會將結(jié)果返回給應(yīng)用程序;

(6) 如果系統(tǒng)調(diào)用服務(wù)在執(zhí)行過程中出現(xiàn)了錯誤,C 語言函數(shù)會將錯誤存儲在全局變量 errno 中并根據(jù)系統(tǒng)調(diào)用的結(jié)果返回一個用整數(shù) int 表示的狀態(tài);

圖 5 - 系統(tǒng)調(diào)用的執(zhí)行步驟

從上述系統(tǒng)調(diào)用的執(zhí)行過程中,我們可以看到基于軟件中斷的系統(tǒng)調(diào)用是一個比較復(fù)雜的流程,應(yīng)用程序通過軟件中斷陷入內(nèi)核態(tài)并在內(nèi)核態(tài)查詢并執(zhí)行系統(tǒng)調(diào)用表注冊的函數(shù),整個過程不僅需要存儲寄存器中的數(shù)據(jù)、從用戶態(tài)切換至內(nèi)核態(tài),還需要完成驗(yàn)證參數(shù)的合法性,與函數(shù)調(diào)用的過程相比確實(shí)會帶來很多的額外開銷。

實(shí)際上,使用 INT 0x80 來觸發(fā)系統(tǒng)調(diào)用早就是過去時了,大多數(shù)的程序都會盡量避免這種觸發(fā)方式。然而這一規(guī)則也不是通用的,因?yàn)?Go 語言團(tuán)隊(duì)在做基準(zhǔn)測試時發(fā)現(xiàn) INT 0x80 觸發(fā)系統(tǒng)調(diào)用在部分操作系統(tǒng)上與其他方式有著幾乎相同的性能,所以在 Android/386 和 Linux/386 等架構(gòu)上仍然會使用中斷來執(zhí)行系統(tǒng)調(diào)用。

匯編指令

因?yàn)槭褂密浖袛鄬?shí)現(xiàn)的系統(tǒng)調(diào)用在 Pentium 4 的處理器上表現(xiàn)非常差。Linux 為了解決這個問題,在較新的版本使用了新的匯編指令 SYSENTER / SYSCALL,它們是 Intel 和 AMD 上用于實(shí)現(xiàn)快速系統(tǒng)調(diào)用的指令,我們會在 32 位的操作系統(tǒng)上使用 SYSENTER / SYSEXIT,在 64 位的操作系統(tǒng)上使用 SYSCALL / SYSRET:

圖 6 - 快速系統(tǒng)調(diào)用指令

上述的幾個匯編指令是低延遲的系統(tǒng)調(diào)用和返回指令,它們會認(rèn)為操作系統(tǒng)實(shí)現(xiàn)了線性內(nèi)存模型(Linear-memory Model),極大地簡化了操作系統(tǒng)系統(tǒng)調(diào)用和返回的過程,其中包括不必要的檢查、預(yù)加載參數(shù)等,與軟件中斷驅(qū)動的系統(tǒng)調(diào)用相比,使用快速系統(tǒng)調(diào)用指令可以減少 25% 的時鐘周期。

線性內(nèi)存模型是一種內(nèi)存尋址的常見范式,在這種模式中,線性內(nèi)存與應(yīng)用程序存儲在單一連續(xù)的空間地址中,CPU 可以不借助內(nèi)存碎片或者分頁技術(shù)使用地址直接訪問可用的內(nèi)存地址。

在 64 位的操作系統(tǒng)上,我們會使用 SYSCALL / SYSRET 進(jìn)入和退出系統(tǒng)調(diào)用,該指令會在操作系統(tǒng)最高權(quán)限等級中執(zhí)行。內(nèi)核在初始化時會調(diào)用 syscall_init 函數(shù)將 entry_SYSCALL_64 存入 MSR 寄存器(Model Specific Register、MSR)中,MSR 寄存器是 x86 指令集中用于調(diào)試、追蹤以及性能監(jiān)控的控制寄存器:

  1. void syscall_init(void) { 
  2.     wrmsr(MSR_STAR, 0, (__USER32_CS << 16) | __KERNEL_CS); 
  3.     wrmsrl(MSR_LSTAR, (unsigned long)entry_SYSCALL_64); 
  4.     ... 

當(dāng)內(nèi)核收到了用戶程序觸發(fā)的系統(tǒng)調(diào)用時,它會在 MSR 寄存器中讀取需要執(zhí)行的函數(shù)并按照 x86-64 的調(diào)用慣例在寄存器中讀取系統(tǒng)調(diào)用的編號以及參數(shù),你能在 entry_SYSCALL_64 函數(shù)的注釋中找到相關(guān)的調(diào)用慣例。

匯編函數(shù) entry_SYSCALL_64 會在執(zhí)行的過程中調(diào)用 do_syscall_64,它的實(shí)現(xiàn)與上一節(jié)中的 do_int80_syscall_32 有些相似,它們都會在系統(tǒng)調(diào)用表中查找函數(shù)并傳入寄存器中的參數(shù)。

與 INT 0x80 通過觸發(fā)軟件中斷實(shí)現(xiàn)系統(tǒng)調(diào)用不同,SYSENTER 和 SYSCALL 是專門為系統(tǒng)調(diào)用設(shè)計的匯編指令,它們不需要在中斷描述表(Interrupt Descriptor Table、IDT)中查找系統(tǒng)調(diào)用對應(yīng)的執(zhí)行過程,也不需要保存堆棧和返回地址等信息,所以能夠減少所需要的額外開銷。

vDSO

虛擬動態(tài)共享對象(virtual dynamic shared object、vDSO)是 Linux 內(nèi)核對用戶空間暴露內(nèi)核空間部分函數(shù)的一種機(jī)制,簡單來說,我們將 Linux 內(nèi)核中不涉及安全的系統(tǒng)調(diào)用直接映射到用戶空間,這樣用戶空間中的應(yīng)用程序在調(diào)用這些函數(shù)時就不需要切換到內(nèi)核態(tài)以減少性能上的損失。

vDSO 使用了標(biāo)準(zhǔn)的鏈接和加載技術(shù),作為一個動態(tài)鏈接庫,它由 Linux 內(nèi)核提供并映射到每一個正在執(zhí)行的進(jìn)程中,我們可以使用如下所示的命令查看該動態(tài)鏈接庫在進(jìn)程中的位置:

  1. $ ldd /bin/cat 
  2.     linux-vdso.so.1 (0x00007fff2709c000) 
  3.     ... 
  4.  
  5. $ cat /proc/self/maps 
  6. ... 
  7. 7f28953ce000-7f28953cf000 r--p 00027000 fc:01 2079                       /lib/x86_64-linux-gnu/ld-2.27.so 
  8. 7f28953cf000-7f28953d0000 rw-p 00028000 fc:01 2079                       /lib/x86_64-linux-gnu/ld-2.27.so 
  9. 7f28953d0000-7f28953d1000 rw-p 00000000 00:00 0 
  10. 7ffe8ca4d000-7ffe8ca6e000 rw-p 00000000 00:00 0                          [stack] 
  11. 7ffe8ca8d000-7ffe8ca90000 r--p 00000000 00:00 0                          [vvar] 
  12. 7ffe8ca90000-7ffe8ca92000 r-xp 00000000 00:00 0                          [vdso] 
  13. ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0   

因?yàn)?vDSO 是由操作系統(tǒng)直接提供的,所以它并不存在對應(yīng)的文件,在程序執(zhí)行的過程中我們也能在虛擬內(nèi)存中看到它加載的位置。vDSO 可以為用戶程序提供虛擬的系統(tǒng)調(diào)用,它會使用內(nèi)核提供的數(shù)據(jù)在用戶態(tài)模擬系統(tǒng)調(diào)用:

圖 7 - 內(nèi)核和用戶控件的初始化

系統(tǒng)調(diào)用 gettimeofday 是一個非常好的例子,如上圖所示,使用 vDSO 的系統(tǒng)調(diào)用 gettimeofday 會按照如下所示的步驟進(jìn)行初始化:

  • 內(nèi)核中的 ELF 加載器會負(fù)責(zé)映射 vDSO 的內(nèi)存頁并設(shè)置輔助向量(Auxiliary Vector)中 AT_SYSINFO_EHDR,該標(biāo)簽存儲了 vDSO 的基地址;
  • 動態(tài)鏈接器會查詢輔助向量中 AT_SYSINFO_EHDR,如果設(shè)置了該標(biāo)簽會鏈接 vDSO;
  • libc 在初始化時會在 vDSO 中查找 __vdso_gettimeofday 符號并將符號鏈接到全局的函數(shù)指針上;

除了 gettimeofday 之外,多數(shù)架構(gòu)上的 vDSO 還包含 clock_gettime、clock_getres 和 rt_sigreturn 等三個系統(tǒng)調(diào)用,這些系統(tǒng)調(diào)用完成功能相對來說比較簡單,也不會帶來安全上的問題,所以將它們映射到用戶空間可以明顯地提高系統(tǒng)調(diào)用的性能,就像我們在圖二中看到的,使用 vDSO 可以將上述幾個系統(tǒng)調(diào)用的時間提高幾十倍。

總結(jié)

當(dāng)我們在編寫應(yīng)用程序時,系統(tǒng)調(diào)用并不是一個離我們很遠(yuǎn)的概念,一個簡單的 Hello World 會在執(zhí)行時觸發(fā)幾十次系統(tǒng)調(diào)用,而在線上出現(xiàn)性能問題時,可能也需要我們與系統(tǒng)調(diào)用打交道。雖然程序中的系統(tǒng)調(diào)用非常頻繁,但是與普通的函數(shù)調(diào)用相比,它會帶來明顯地額外開銷:

  • 使用軟件中斷觸發(fā)的系統(tǒng)調(diào)用需要保存堆棧和返回地址等信息,還要在中斷描述表中查找系統(tǒng)調(diào)用的響應(yīng)函數(shù),雖然多數(shù)的操作系統(tǒng)不會使用 INT 0x80 觸發(fā)系統(tǒng)調(diào)用,但是在一些特殊場景下,我們?nèi)匀恍枰眠@一古老的技術(shù);
  • 使用匯編指令 SYSCALL / SYSENTER 執(zhí)行系統(tǒng)調(diào)用是今天最常見的方法,作為專門為系統(tǒng)調(diào)用打造的指令,它們可以省去一些不必要的步驟,降低系統(tǒng)調(diào)用的開銷;
  • 使用 vSDO 執(zhí)行系統(tǒng)調(diào)用是操作系統(tǒng)為我們提供的最快路徑,該方式可以將系統(tǒng)調(diào)用的開銷與函數(shù)調(diào)用拉平,不過因?yàn)閷?nèi)核態(tài)的系統(tǒng)調(diào)用映射到『用戶態(tài)』確實(shí)存在安全風(fēng)險,所以操作系統(tǒng)也僅會放開有限的系統(tǒng)調(diào)用;

應(yīng)用程序能夠完成的工作相當(dāng)有限,我們需要使用操作系統(tǒng)提供的服務(wù)才能編寫功能豐富的用戶程序。系統(tǒng)調(diào)用作為操作系統(tǒng)提供的接口,它與底層的硬件關(guān)系十分緊密,因?yàn)橛布姆N類繁雜,所以不同架構(gòu)要使用不同的指令,隨著內(nèi)核的快速演進(jìn),想要找到準(zhǔn)確的資料也非常困難,不過了解不同系統(tǒng)調(diào)用的實(shí)現(xiàn)原理對我們認(rèn)識操作系統(tǒng)也有很大的幫助。到最后,我們還是來看一些比較開放的相關(guān)問題,有興趣的讀者可以仔細(xì)思考一下下面的問題:

  • vDSO 提供的系統(tǒng)調(diào)用 rt_sigreturn 有哪些作用?
  • vDSO 提供的四種系統(tǒng)調(diào)用中三種都與獲取時間有關(guān),為什么它可以在用戶態(tài)提供 rt_sigreturn,不存在安全風(fēng)險么?

 

責(zé)任編輯:趙寧寧 來源: 真沒什么邏輯
相關(guān)推薦

2022-04-06 07:51:21

數(shù)據(jù)庫Web連接池

2012-05-02 10:08:51

桌面Linux微軟

2017-01-05 18:43:58

閏秒Linux服務(wù)器

2016-11-08 11:06:20

2022-03-07 11:25:29

移動網(wǎng)絡(luò)5G綠色收益

2012-08-17 10:01:07

云計算

2020-03-30 15:05:46

Kafka消息數(shù)據(jù)

2012-03-26 10:26:43

openstackeucalyptus

2021-07-09 09:24:06

NanoID UUID軟件開發(fā)

2023-03-22 09:10:18

IT文檔語言

2022-04-13 20:53:15

Spring事務(wù)管理

2014-03-05 14:58:00

蘋果CarPlayiOS

2015-12-07 10:49:43

卸載App用戶體驗(yàn)

2021-01-25 07:14:53

Cloud DevOps云計算

2022-05-11 08:22:54

IO負(fù)載NFSOS

2020-05-21 11:23:08

微軟LinuxWindows

2009-12-14 18:27:21

Linux操作系統(tǒng)

2010-01-05 17:16:51

2010-01-06 15:41:07

Linux操作系統(tǒng)

2018-05-14 11:07:48

服務(wù)器Linux系統(tǒng)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號