C程式函式呼叫&系統呼叫

by_mzy發表於2024-06-11

理解程式的執行

我們要知道CPU可以自由地訪問暫存器、記憶體。另外,程式是由作業系統執行的,所以作業系統能夠控制程式的所有執行情況,限制程式的行為。

程式地執行過程:

  • 程式是一個二進位制檔案,包含程式的程式碼指令、程式碼中的文字資訊等(參考C語言的程式的各種段)
  • 執行一個程式後,會將這個二進位制載入到記憶體中,那麼這個程式的程式碼(想象成各種彙編指令)也就記載道了記憶體中
  • CPU執行程式時從固定的位置main處開始執行(eip暫存器指向這裡),逐條語句讀取執行(這是CPU自帶的功能)
    • 語句可能發生跳轉(eip切換到其他彙編指令出)
    • 語句可能會操作棧(其實就是往一塊特殊地記憶體空間寫入資料、讀出資料,CPU有相關的指令pop push解決這個問題)
  • 程式可能會執行系統呼叫(作業系統賦予的能力,例如讀寫檔案,網路通訊等)。現代作業系統將這些能力都放到了核心態來執行了,即只有核心程式碼才能做實際的讀寫檔案操作,普通使用者程式只能透過系統呼叫來執行這些能力。
    • 所以執行系統呼叫後,cpu就會相應地跳轉到系統呼叫地入口處(這個系統呼叫的入口也時固定的,對應的是核心中的一段C程式碼
    • 核心的系統呼叫入口函式,根據系統呼叫號(對每個系統呼叫的標識),找到相應的處理函式執行(其實也是執行call函式)
    • 系統呼叫處理完後,繼續返回到使用者自己的程式程式碼處執行(所以,在執行系統呼叫前需要把使用者程式碼執行的位置記錄下來,並且在系統呼叫結束後自動設定eip指向這個地方)

函式呼叫

C語言函式呼叫關鍵

c語言函式呼叫的幾個關鍵點在於:

  • 保護呼叫者的上下文(暫存器、棧指標(ebp,esp)資訊)
  • 將傳入引數透過esi、edi等放到被暫存器中、或者push到棧中(當引數比較多時)
  • 執行call呼叫函式,call的副作用是將eip壓入到棧中
  • 將計算的返回值放到eax中
  • pop出ebp、esp
  • 執行ret,將eip從棧中pop出來,然後指令繼續執行重新回到呼叫者上下文(將esp指向呼叫者呼叫函式後的語句)

系統呼叫

  • syscall sysenter sysret
  • int 0x80

在 x86-64 架構上,當應用程式需要執行系統呼叫時,CPU 會從使用者態切換到核心態,經歷以下過程:

  1. 使用者態程式執行 syscall 指令:
    • 使用者態程式透過執行 syscall 指令來觸發系統呼叫請求。
  2. CPU 切換到核心態:
    • syscall 指令會引發一個特殊的異常,導致 CPU 從當前的使用者態特權級切換到核心態的更高特權級。
    • 這個過程會自動儲存使用者態的部分暫存器狀態,如 riprflags 等,並將控制權轉交給核心。
  3. 核心處理系統呼叫:
    • 核心接管控制權後,會根據系統呼叫號找到對應的系統呼叫處理函式,並執行相應的操作。
    • 核心執行完成後,會將結果返回給使用者態程式。
  4. 從核心態切換回使用者態:
    • 核心執行完系統呼叫處理後,會透過 sysret 指令從核心態切換回使用者態。
    • sysret 指令會自動恢復之前儲存的使用者態暫存器狀態,並將控制權轉回給使用者態程式。

整個切換過程由硬體和作業系統核心共同完成,應用程式感知不到這個切換過程。這種基於硬體支援的使用者態 - 核心態切換機制,能夠大幅降低系統呼叫的開銷,提高作業系統的整體效能。
需要注意的是,除了 syscall/sysret 指令,Intel 的 x86-64 架構也支援使用 int 0x80 軟中斷來執行系統呼叫,不過 syscall/sysret 方式通常更加高效。

linux系統呼叫 - chenjx_ucs - 部落格園 (cnblogs.com)

cpu的特權級別(privilege level )

chatgpt告訴我們:Linux 系統透過以下幾種方式來實現當前特權級別的切換:

  1. 系統呼叫:
    • 使用者態應用程式透過系統呼叫機制從 Ring 3 切換到 Ring 0 核心態。
    • 應用程式執行 int 0x80syscall 指令觸發軟中斷,CPU 切換到 Ring 0 執行核心程式碼。
    • 核心處理完成後,透過 iretsysret 指令返回到使用者態。
  2. 中斷/異常處理:
    • 當 CPU 遇到硬體中斷或軟體異常時, 會自動從當前特權級切換到 Ring 0 核心態。
    • 核心處理完中斷/異常後,透過 iret 指令返回到之前的特權級別。
  3. 特權級切換指令:
    • x86 架構提供了一些用於特權級切換的指令,如 call gatetask gate 等。
    • 這些指令可以在不同特權級之間跳轉,並自動完成上下文切換。
  4. 程序切換:
    • 當核心需要切換程序時,會切換程序的特權級別。
    • 核心將新程序的特權級別設定為 Ring 3,並透過 iret 指令返回到使用者態。

在 Linux 中,大多數情況下都是透過系統呼叫和中斷/異常處理來實現特權級切換。核心程式碼執行在 Ring 0 級別,使用者態應用程式執行在 Ring 3 級別。當應用程式需要訪問受保護的系統資源時,會透過系統呼叫陷入核心態,由核心程式碼執行相應的操作。中斷和異常處理也會觸發核心態的切換,核心負責處理各種硬體事件。總之,Linux 系統利用 CPU 硬體提供的特權級機制,透過系統呼叫、中斷/異常處理、特權級切換指令等方式,實現了核心態和使用者態之間的特權級切換,保證了系統的安全和穩定性。

Ring 0和Ring 3也有其他區別,例如Ring 0 程式可以執行所有的 CPU 指令集,包括特權指令。Ring 3 程式只能執行非特權指令集,無法直接執行特權級別的指令。

參考資料:
使用者空間與核心空間,程序上下文與中斷上下文[總結] - Rabbit_Dale - 部落格園 (cnblogs.com)

相關文章