linux服務器開發相關視頻決議:
linux多執行緒之epoll原理剖析與reactor原理及應用
手把手帶你實作一個Linux內核檔案系統
什么技術水平,才能拿到騰訊T9(原T3.1)offer?
一、什么是系統呼叫
系統呼叫 跟用戶自定義函式一樣也是一個函式,不同的是 系統呼叫 運行在內核態,而用戶自定義函式運行在用戶態,由于某些指令(如設定時鐘、關閉/打開中斷和I/O操作等)只能運行在內核態,所以作業系統必須提供一種能夠進入內核態的方式,系統呼叫 就是這樣的一種機制,
系統呼叫 是 Linux 內核提供的一段代碼(函式),其實作了一些特定的功能,用戶可以通過 int 0x80 中斷(x86 CPU)或者 syscall 指令(x64 CPU)來呼叫 系統呼叫,
二、進入系統呼叫
本文主要介紹的是 x86 CPU 進入系統呼叫的方式
Linux 提供了 int 0x80 中斷來讓用戶程式進入 系統呼叫,我們來看看 Linux 對 int 0x80 中斷的處理初始化程序:
void __init trap_init(void)
{
...
set_system_gate(SYSCALL_VECTOR, &system_call);
...
}
系統初始化時,會在 trap_init() 函式中對 int 0x80 中斷處理進行初始化,設定其中斷處理程序入口為 system_call,system_call 是一段由匯編語言撰寫的代碼,我們看看關鍵部分,如下:
ENTRY(system_call)
...
call *SYMBOL_NAME(sys_call_table)(,%eax,4)
movl %eax,EAX(%esp) # save the return value
...
我們把上面的匯編改寫成 C 代碼如下:
void system_call()
{
...
// 變數 eax 代表 eax 暫存器的值
syscall = sys_call_table[eax];
eax = syscall();
...
}
sys_call_table 變數是一個陣列,陣列的每一個元素代表一個 系統呼叫 的入口,其定義如下(在檔案 arch/i386/kernel/entry.S 中):
.data
ENTRY(sys_call_table)
.long SYMBOL_NAME(sys_ni_syscall)
.long SYMBOL_NAME(sys_exit)
.long SYMBOL_NAME(sys_fork)
.long SYMBOL_NAME(sys_read)
.long SYMBOL_NAME(sys_write)
.long SYMBOL_NAME(sys_open)
.long SYMBOL_NAME(sys_close)
...
翻譯成 C 代碼如下:
long sys_call_table[] = {
sys_ni_syscall,
sys_exit,
sys_fork,
sys_read,
sys_write,
sys_open,
sys_close,
...
};
用戶呼叫 系統呼叫 時,通過向 eax 暫存器寫入要呼叫的 系統呼叫 編號,這個編號就是 sys_call_table 陣列的下標, system_call 程序獲取 eax 暫存器的值,然后通過 eax 暫存器的值找到要呼叫的 系統呼叫 入口,并且進行呼叫,呼叫完成后,系統呼叫 會把回傳值保存到 eax 暫存器中,
原理如下圖:

【文章福利】需要C/C++ Linux服務器架構師學習資料加群812855908(資料包括C/C++,Linux,golang技術,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒體,CDN,P2P,K8S,Docker,TCP/IP,協程,DPDK,ffmpeg等)

三、系統呼叫實作
當用戶要呼叫 系統呼叫 時,需要通過向 eax 暫存器寫入要呼叫的 系統呼叫 編號,因為 用戶態 和 內核態 使用的堆疊不同,而呼叫 系統呼叫 是在用戶態呼叫的,而進入 系統呼叫 后會變成內核態,所以引數就不能通過堆疊來傳遞,Linux 使用暫存器來傳遞引數,引數與暫存器的關系如下:
- 第1個引數放置在 ebx 暫存器,
- 第2個引數放置在 ecx 暫存器,
- 第3個引數放置在 edx 暫存器,
- 第4個引數放置在 esi 暫存器,
- 第5個引數放置在 edi 暫存器,
- 第6個引數放置在 ebp 暫存器,
而 Linux 進入中斷處理程式時,會把這些暫存器的值保存到內核堆疊中,這樣 系統呼叫 就能通過內核堆疊來獲取到引數,
下面我們通過 sys_open() 系統呼叫來說明一下 系統呼叫 的運作方式,sys_open() 實作如下:
asmlinkage long sys_open(const char *filename, int flags, int mode)
{
...
}
一般 系統呼叫 都需要使用 asmlinkage 編譯選項,asmlinkage 編譯選項是告訴編譯器從堆疊中讀取引數,其實際是封裝了 GCC 的編譯選項,如下:
#define asmlinkage CPP_ASMLINKAGE __attribute__((regparm(0)))
attribute((regparm(0))) 就是告訴 GCC 所有引數都從堆疊中讀取,而 Linux 進入中斷處理背景關系時,會把 ebx、ecx、edx、esi、edi、ebp 暫存器的值保存到內核堆疊中,那么 系統呼叫 就可以從內核堆疊獲取到引數的值,
但由于暫存器只能傳遞 32 位的整型值(x86 CPU),所以引數一般只能傳遞指標或者整型的數值,如果要獲取指標對應結構的資料,就必須通過從用戶空間復制到內核空間,如 sys_open() 系統呼叫獲取要打開的檔案路徑:
asmlinkage long sys_open(const char *filename, int flags, int mode)
{
char * tmp;
...
tmp = getname(filename);
...
}
getname() 函式就是用于從用戶空間復制資料到內核空間,
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/254787.html
標籤:其他
