前幾天看內核中系統調用代碼,在系統調用向量表初始化中,有下面這段代碼寫的讓我有點摸不着頭腦:
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 <asm/syscalls_32.h>
};
咱先不管上面代碼的意思,先來回顧一下 C 語言中數組初始化的相關知識,然后再回頭來理解上面這段代碼。
數組初始化
C 語言中數組的初始化,可以在定義時就給出其初始值,以逗號隔開,用花括號括起來,例如:
int my_array[5] = {0, 1, 2, 3, 4};
當然你可以不用顯示地去初始化所有的元素,例如,下面的代碼就是顯示初始化了數組的前三項,后面兩項默認為0:
int my_array[5] = {0, 1, 2};
在 C89 標准中,要求按照數組中元素固定的順序對數組的元素進行初始化;然而在 ISO C99 中,你可以以任意的順序對數組元素初始化,只是需要給出數組元素所在的索引號;當然 GNU 編譯器 GCC 對 C89 進行了擴展,也允許這么做。為了指明初始特殊的數組元素,需要在元素值前加上 [index] =,如:
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 這樣的語法:
int my_array[100] = { [0 ... 9] = 1, [10 ... 98] = 2, 3 };
這是將my_array數組的第0~9個元素初始化為1, 第10~98個元素初始化為2, 第99個元素初始化為3(你也可以顯示地寫成[99] = 3)。** 注意 **:在語法中... 兩邊必須要留有空格符。
回到上面
對數組特定元素進行初始化我之前還真沒遇到過,但也是 C 標准所支持的。內核中系統調用表是指根據系統調用號來找到系統調用的函數入口地址,結合上面數組初始化這個語法點,再回頭看看上面系統調用表的定義:
const sys_call_ptr_t sys_call_table[__NR_syscall_max+1] = {
[0 ... __NR_syscall_max] = &sys_ni_syscall,
#include <asm/syscalls_32.h>
};
先對表中所有 __NR_syscall_max+1 項初始化為指向 sys_ni_syscall 的函數,該函數只返回 -ENOSYS,表示該系統調用未實現。接下來包含一個頭文件#include <asm/syscalls_32.h>,該文件是在編譯時生成的,內容為:
__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 是一個宏定義:
#define __SYSCALL_I386(nr, sym, compat) [nr] = sym,
這樣上面的系統調用表定義就展開為:
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 指令陷入到內核態,首先保存寄存器,然后檢查系統調用號是否合法,最后跳轉到相應的內核系統調用函數中執行:
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 的用處。
再舉一例
內核中還有其他地方應用到此種初始化數組的方法:
/* 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的初始化。
參考資料:
