前幾天看核心中系統呼叫程式碼,在系統呼叫向量表初始化中,有下面這段程式碼寫的讓我有點摸不著頭腦:
1 2 3 4 5 6 7 8 |
const sys_call_ptr_t sys_call_table[__NR_syscall_max+1] = { /* * Smells like a compiler bug -- it doesn't work * when the & below is removed. */ [0 ... __NR_syscall_max] = &sys_ni_syscall, #include }; |
我們先不管上面程式碼的意思,先來回顧一下 C 語言中陣列初始化的相關知識,然後再回頭來理解上面這段程式碼。
陣列初始化
C 語言中陣列的初始化,可以在定義時就給出其初始值,以逗號隔開,用花括號括起來,例如:
1 |
int my_array[5] = {0, 1, 2, 3, 4}; |
當然你可以不用顯示地去初始化所有的元素,例如,下面的程式碼就是顯示初始化了陣列的前三項,後面兩項預設為0:
1 |
int my_array[5] = {0, 1, 2}; |
在 C89 標準中,要求按照陣列中元素固定的順序對陣列的元素進行初始化;然而在 ISO C99 中,你可以以任意的順序對陣列元素初始化,只是需要給出陣列元素所在的索引號;當然 GNU 編譯器 GCC 對 C89 進行了擴充套件,也允許這麼做。為了指明初始特殊的陣列元素,需要在元素值前加上 [index] =
,如:
1 2 3 4 5 |
int my_array[6] = { [4] = 29, [2] = 15 }; 或者寫成: int my_array[6] = { [4] 29, [2] 15 }; //省略到索引與值之間的=,GCC 2.5 之後該用法已經過時了,但 GCC 仍然支援 兩者均等價於: int my_array[6] = {0, 0, 15, 0, 29, 0}; |
GNU 還有一個擴充套件:在需要將一個範圍內的元素初始化為同一值時,可以使用 [first ... last] = value
這樣的語法:
1 |
int my_array[100] = { [0 ... 9] = 1, [10 ... 98] = 2, 3 }; |
這是將my_array陣列的第0~9個元素初始化為1, 第10~98個元素初始化為2, 第99個元素初始化為3(你也可以顯示地寫成[99] = 3)。** 注意 **:在語法中...
兩邊必須要留有空格符。
回到上面
對陣列特定元素進行初始化我之前還真沒遇到過,但也是 C 標準所支援的。核心中系統呼叫表是指根據系統呼叫號來找到系統呼叫的函式入口地址,結合上面陣列初始化這個語法點,再回頭看看上面系統呼叫表的定義:
1 2 3 4 |
const sys_call_ptr_t sys_call_table[__NR_syscall_max+1] = { [0 ... __NR_syscall_max] = &sys_ni_syscall, #include }; |
先對錶中所有 __NR_syscall_max+1 項初始化為指向 sys_ni_syscall 的函式,該函式只返回 -ENOSYS,表示該系統呼叫未實現。接下來包含一個標頭檔案#include
,該檔案是在編譯時生成的,內容為:
1 2 3 4 5 6 7 |
__SYSCALL_I386(0, sys_restart_syscall, sys_restart_syscall) __SYSCALL_I386(1, sys_exit, sys_exit) __SYSCALL_I386(2, sys_fork, stub32_fork) __SYSCALL_I386(3, sys_read, sys_read) __SYSCALL_I386(4, sys_write, sys_write) __SYSCALL_I386(5, sys_open, compat_sys_open) ... |
__SYSCALL_I386 是一個巨集定義:
1 |
#define __SYSCALL_I386(nr, sym, compat) [nr] = sym, |
這樣上面的系統呼叫表定義就展開為:
1 2 3 4 5 6 7 8 |
const sys_call_ptr_t sys_call_table[__NR_syscall_max+1] = { [0 ... __NR_syscall_max] = &sys_ni_syscall, [0] = sys_restart_syscall, [1] = sys_exit, [2] = sys_fork, [3] = sys_read, //... }; |
當使用者程式發生系統呼叫,通過軟中斷 int 0x80 或者 sysenter 指令陷入到核心態,首先儲存暫存器,然後檢查系統呼叫號是否合法,最後跳轉到相應的核心系統呼叫函式中執行:
1 2 3 4 5 6 7 8 9 10 |
ENTRY(system_call) pushl_cfi %eax # 儲存原始 eax SAVE_ALL # 儲存暫存器幀 GET_THREAD_INFO(%ebp) testl $_TIF_WORK_SYSCALL_ENTRY,TI_flags(%ebp) # 檢查是否跟蹤系統呼叫標誌 jnz syscall_trace_entry cmpl $(NR_syscalls), %eax # 檢查系統呼叫號是否合法 jae syscall_badsys syscall_call: call *sys_call_table(,%eax,4) # 呼叫相應函式,等價於 call sys_call_table[%eax*4] |
上面就是系統呼叫的進入過程,比較簡單,這裡只是說明了我們之前定義的系統呼叫表 sys_call_table 的用處。
再舉一例
核心中還有其他地方應用到此種初始化陣列的方法:
1 2 3 4 5 6 7 8 9 10 11 12 |
/* There are machines which are known to not boot with the GDT being 8-byte unaligned. Intel recommends 16 byte alignment. */ static const u64 boot_gdt[] __attribute__((aligned(16))) = { /* CS: code, read/execute, 4 GB, base 0 */ [GDT_ENTRY_BOOT_CS] = GDT_ENTRY(0xc09b, 0, 0xfffff), /* DS: data, read/write, 4 GB, base 0 */ [GDT_ENTRY_BOOT_DS] = GDT_ENTRY(0xc093, 0, 0xfffff), /* TSS: 32-bit tss, 104 bytes, base 4096 */ /* We only have a TSS here to keep Intel VT happy; we don't actually use it for anything. */ [GDT_ENTRY_BOOT_TSS] = GDT_ENTRY(0x0089, 4096, 103), }; |
這是對系統啟動時對全域性符號表GDT的初始化。
參考資料: