成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

為什么 Linux 系統調用會消耗較多資源

系統 Linux
當我們在編寫應用程序時,系統調用并不是一個離我們很遠的概念,一個簡單的 Hello World 會在執行時觸發幾十次系統調用,而在線上出現性能問題時,可能也需要我們與系統調用打交道。

系統調用是計算機程序在執行的過程中向操作系統內核申請服務的方法,這可能包含硬件相關的服務、新進程的創建和執行以及進程調度,對操作系統稍微有一些了解的人都知道 — 系統調用為用戶程序提供了操作系統的接口。

圖 1 - 操作系統接口

C 語言的著名的 glibc 封裝了操作系統提供的系統調用并提供了定義良好的接口[^2],工程師可以直接使用器中封裝好的函數開發上層的應用程序,其他編程語言的標準庫也會封裝系統調用,它們對外提供語言原生的接口,內部使用匯編語言觸發系統調用。

我們在使用標準庫時需要經常與系統調用打交道,只是很多時候我們不知道標準庫背后的實現,以常見的 Hello World 程序為例,這么簡單的幾行函數在真正運行時會執行幾十次系統調用:

  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 中用于監控和篡改進程與內核之間操作的工具,上述命令會打印出 hello 執行過程中觸發系統調用、參數以及返回值等信息。執行 Hello World 程序時觸發的多數系統調用都是程序啟動觸發的,只有 munmap 后的系統調用才是 printf函數觸發的,作為應用程序我們能做的事情非常有限,很多功能都需要依賴操作系統提供的服務。

多數編程語言的函數調用只需要分配新的棧空間、向寄存器寫入參數并執行 CALL 匯編指令跳轉到目標地址執行函數,在函數返回時通過棧或者寄存器返回參數。與函數調用相比,系統調用會消耗更多的資源,如下圖所示,使用 SYSCALL 指定執行系統調用消耗的時間是 C 函數調用的幾十倍:

圖 2 - 系統調用與函數調用耗時比較

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

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

圖 3 - 系統調用的三種方法

從上面的系統調用與函數調用的基準測試中,我們可以發現不使用 vSDO 加速的系統調用需要的時間是普通函數調用的幾十倍,為什么系統調用會帶來這么大的額外開銷,它在內部到底執行了哪些工作呢,本文將介紹 Linux 執行系統調用的三種方法:

  • 使用軟件中斷(Software interrupt)觸發系統調用;
  • 使用 SYSCALL / SYSENTER 等匯編指令觸發系統調用;
  • 使用虛擬動態共享對象(virtual dynamic shared object、vDSO)執行系統調用;

軟件中斷

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

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

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

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

(1) 應用程序通過調用 C 語言庫中的函數發起系統調用;

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

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

(4) 函數執行 INT 0x80 指令,處理器會從用戶態切換到內核態并執行預先定義好的處理器;

(5) 執行中斷處理器 entry_INT80_32 處理系統調用;

  • 執行 SAVE_ALL 將寄存器的值存儲到內核棧上并調用 do_int80_syscall_32;
  • 調用 do_syscall_32_irqs_on 檢查系統調用的序號是否合法;
  • 在系統調用表 ia32_sys_call_table 中查找對應的系統調用實現并傳入寄存器的值;
  • 系統調用在執行期間會檢查參數的合法性、在用戶態內存和內核態內存之間傳輸數據,系統調用的結果會被存儲到 eax 寄存器中;
  • 從內核棧中恢復寄存器的值并將返回值放到棧上;
  • 系統調用會返回 C 函數,包裝函數會將結果返回給應用程序;

(6) 如果系統調用服務在執行過程中出現了錯誤,C 語言函數會將錯誤存儲在全局變量 errno 中并根據系統調用的結果返回一個用整數 int 表示的狀態;

圖 5 - 系統調用的執行步驟

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

實際上,使用 INT 0x80 來觸發系統調用早就是過去時了,大多數的程序都會盡量避免這種觸發方式。然而這一規則也不是通用的,因為 Go 語言團隊在做基準測試時發現 INT 0x80 觸發系統調用在部分操作系統上與其他方式有著幾乎相同的性能,所以在 Android/386 和 Linux/386 等架構上仍然會使用中斷來執行系統調用。

匯編指令

因為使用軟件中斷實現的系統調用在 Pentium 4 的處理器上表現非常差。Linux 為了解決這個問題,在較新的版本使用了新的匯編指令 SYSENTER / SYSCALL,它們是 Intel 和 AMD 上用于實現快速系統調用的指令,我們會在 32 位的操作系統上使用 SYSENTER / SYSEXIT,在 64 位的操作系統上使用 SYSCALL / SYSRET:

圖 6 - 快速系統調用指令

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

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

在 64 位的操作系統上,我們會使用 SYSCALL / SYSRET 進入和退出系統調用,該指令會在操作系統最高權限等級中執行。內核在初始化時會調用 syscall_init 函數將 entry_SYSCALL_64 存入 MSR 寄存器(Model Specific Register、MSR)中,MSR 寄存器是 x86 指令集中用于調試、追蹤以及性能監控的控制寄存器:

  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.     ... 

當內核收到了用戶程序觸發的系統調用時,它會在 MSR 寄存器中讀取需要執行的函數并按照 x86-64 的調用慣例在寄存器中讀取系統調用的編號以及參數,你能在 entry_SYSCALL_64 函數的注釋中找到相關的調用慣例。

匯編函數 entry_SYSCALL_64 會在執行的過程中調用 do_syscall_64,它的實現與上一節中的 do_int80_syscall_32 有些相似,它們都會在系統調用表中查找函數并傳入寄存器中的參數。

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

vDSO

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

vDSO 使用了標準的鏈接和加載技術,作為一個動態鏈接庫,它由 Linux 內核提供并映射到每一個正在執行的進程中,我們可以使用如下所示的命令查看該動態鏈接庫在進程中的位置:

  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   

因為 vDSO 是由操作系統直接提供的,所以它并不存在對應的文件,在程序執行的過程中我們也能在虛擬內存中看到它加載的位置。vDSO 可以為用戶程序提供虛擬的系統調用,它會使用內核提供的數據在用戶態模擬系統調用:

圖 7 - 內核和用戶控件的初始化

系統調用 gettimeofday 是一個非常好的例子,如上圖所示,使用 vDSO 的系統調用 gettimeofday 會按照如下所示的步驟進行初始化:

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

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

總結

當我們在編寫應用程序時,系統調用并不是一個離我們很遠的概念,一個簡單的 Hello World 會在執行時觸發幾十次系統調用,而在線上出現性能問題時,可能也需要我們與系統調用打交道。雖然程序中的系統調用非常頻繁,但是與普通的函數調用相比,它會帶來明顯地額外開銷:

  • 使用軟件中斷觸發的系統調用需要保存堆棧和返回地址等信息,還要在中斷描述表中查找系統調用的響應函數,雖然多數的操作系統不會使用 INT 0x80 觸發系統調用,但是在一些特殊場景下,我們仍然需要利用這一古老的技術;
  • 使用匯編指令 SYSCALL / SYSENTER 執行系統調用是今天最常見的方法,作為專門為系統調用打造的指令,它們可以省去一些不必要的步驟,降低系統調用的開銷;
  • 使用 vSDO 執行系統調用是操作系統為我們提供的最快路徑,該方式可以將系統調用的開銷與函數調用拉平,不過因為將內核態的系統調用映射到『用戶態』確實存在安全風險,所以操作系統也僅會放開有限的系統調用;

應用程序能夠完成的工作相當有限,我們需要使用操作系統提供的服務才能編寫功能豐富的用戶程序。系統調用作為操作系統提供的接口,它與底層的硬件關系十分緊密,因為硬件的種類繁雜,所以不同架構要使用不同的指令,隨著內核的快速演進,想要找到準確的資料也非常困難,不過了解不同系統調用的實現原理對我們認識操作系統也有很大的幫助。到最后,我們還是來看一些比較開放的相關問題,有興趣的讀者可以仔細思考一下下面的問題:

  • vDSO 提供的系統調用 rt_sigreturn 有哪些作用?
  • vDSO 提供的四種系統調用中三種都與獲取時間有關,為什么它可以在用戶態提供 rt_sigreturn,不存在安全風險么?

 

責任編輯:趙寧寧 來源: 真沒什么邏輯
相關推薦

2022-04-06 07:51:21

數據庫Web連接池

2012-05-02 10:08:51

桌面Linux微軟

2017-01-05 18:43:58

閏秒Linux服務器

2016-11-08 11:06:20

2022-03-07 11:25:29

移動網絡5G綠色收益

2012-03-26 10:26:43

openstackeucalyptus

2020-03-30 15:05:46

Kafka消息數據

2012-08-17 10:01:07

云計算

2021-07-09 09:24:06

NanoID UUID軟件開發

2021-01-25 07:14:53

Cloud DevOps云計算

2022-04-13 20:53:15

Spring事務管理

2023-03-22 09:10:18

IT文檔語言

2014-03-05 14:58:00

蘋果CarPlayiOS

2015-12-07 10:49:43

卸載App用戶體驗

2022-05-11 08:22:54

IO負載NFSOS

2020-05-21 11:23:08

微軟LinuxWindows

2018-05-14 11:07:48

服務器Linux系統

2010-01-06 15:41:07

Linux操作系統

2024-02-21 21:28:29

Linux系統

2009-12-14 18:27:21

Linux操作系統
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 性一交一乱一透一a级 | 99久久免费精品国产男女高不卡 | 欧美日韩精品久久久免费观看 | 国产欧美精品一区二区三区 | 亚洲一区av在线 | 先锋资源吧 | 日韩欧美在线播放 | 日操操夜操操 | 91精品www| 欧美一区二区三区 | 国产网站在线免费观看 | 午夜理伦三级理论三级在线观看 | 偷派自拍 | 日韩电影免费在线观看中文字幕 | 亚洲一区二区三区在线观看免费 | 免费午夜视频 | 亚洲成人av | 国产二区精品视频 | 精品永久| 亚洲成人网在线播放 | 日韩在线免费 | 亚洲精品福利在线 | 精品国产一区二区三区性色 | 国产日韩电影 | 中文字幕二区 | 久久一区二区三区免费 | 亚洲精品视频导航 | 99精品久久久久久 | 国产精品久久久亚洲 | 日韩一级电影免费观看 | 欧美一区二区三区大片 | 91中文字幕 | 日本不卡免费新一二三区 | 精品国产第一区二区三区 | 国产精品久久9 | 狠狠入ady亚洲精品经典电影 | 国产91久久久久蜜臀青青天草二 | 久久高清亚洲 | 日本超碰 | 中文字幕免费 | 91精品国产综合久久婷婷香蕉 |