System Call 流程圖
當User application呼叫一個時, 會先執行glibc.so中Wrapper Routine. Wrapper Routine會產生一個trap(在Linux下會跳到int 0x80),此時系統將mode bit由user mode改成monitor mode(1->0)並查尋trap vector找尋相對應trap service routine(此時可做context switch 0->1)
執行完此routine發出interrupt告訴os已經完成:
流程:
User program 與 wrapper routine 是 user space 的 code
system call handler 與 service routine 則是屬於 kernel space
從 User space 切換到 kernel space 是透過中斷
以下的用link這個system call當例子,解釋程式碼
syscall_table.S
PATH : /usr/src/linux/arch/i386/kernel/syscall_table.S
在 Linux 中, 每個 system call 都有自己獨有的號碼。
當 user-space 執行一個 system call 時,process是去參考 syscall 的號碼而不是名字。
unistd.h
PATH : /usr/src/linux/include/asm/unistd.h
unistd.h 是一個重要的標頭檔,裡頭是 system call 編號的定義,當 system call 發生時,system call 的號碼將透過 register (EAX) 傳給 kernel。
#define _syscall2(type,name,type1,arg1,type2,arg2) \
type name(type1 arg1,type2 arg2) \
{ \
long __res; \
__asm__ volatile ("push %%ebx ; movl %2,%%ebx ; int $0x80 ; pop %%ebx" \
: "=a" (__res) \
: "0" (__NR_##name),"ri" ((long)(arg1)),"c" ((long)(arg2)) \
: "memory"); \
__syscall_return(type,__res); \
}
unistd.h 也定義了不同參數的 system call handler,上面的程式碼是處理 2 個參數的 handler
這是一個 macro,當遇到系統呼叫的時候,就會被展開。
參數傳遞與傳回值
system call 的編號透過 %eax 暫存器來指定;若要傳遞參數,則是透過其它暫存器來傳遞(最上面的流程圖上有標示)
Linux system call 最多可傳遞6個參數,參數的傳遞是透過以下的暫存器來完成:
%ebx:第1個參數。
%ecx:第2個參數。
%edx:第3個參數。
%esi:第4個參數。
%edi:第5個參數。
%ebp:第6個參數(做臨時用途)。
x86 的 Interrupt
x86 的interrupt(中斷)可分為系統定義與使用者自訂:
中斷向量0~8、10~14、16~18:predefined interrupts and exceptions。
中斷向量19-31:保留。
中斷向量32-255:user-defined interrupts(maskable interrupts)。
當從 shell 執行 link 時,0x80 號中斷向量會指到 system_call 進入點的位址,由於 link 有兩個參數,const char* oldfile 和 const char* newfile,因此 shell 會執行 syscall2(int link, const char* oldname, const char* newname),執行 sys_link(),sys_link()會呼叫linkat()。在 linux 中,目錄和檔案在系統中被視為同樣。
int link(const char* oldname, const char* newname)
{
long _res;
_asm_ volatile(“int $0x80”
:”=a”(_res)
:”0”(_NR_link),”b”((long)(oldname)),
“c”((long)(newname)));
do{
if((unsigned long)(_res)>=(unsigned long)(-(128+1))){
errno=-(_res);
_res=-1;
}
return (int)(_res);
}while(0);
}
下面這段組語是 system call 的進入點,也就是 system call table
PATH : /usr/src/linux/arch/i386/kernel/entry.S
ENTRY(system_call)
pushl %eax # save orig_eax
SAVE_ALL
GET_CURRENT(%ebx)
testb $0x02,tsk_ptrace(%ebx) # PT_TRACESYS
jne tracesys
cmpl $(NR_syscalls),%eax
jae badsys
call *SYMBOL_NAME(sys_call_table)(,%eax,4)
movl %eax,EAX(%esp) # save the return value
ENTRY(ret_from_sys_call)
cli # need_resched and signals atomic test
cmpl $0,need_resched(%ebx)
jne reschedule
cmpl $0,sigpending(%ebx)
jne signal_return
syscalls.h
PATH : /usr/src/linux/include/linux/syscalls.h
這個檔案包含了 system call 的宣告。
asmlinkage 是在i386 system call實做中,gcc 很重要的一個標籤。他是一個macro,會被展開成
#define asmlinkage __attribute__((regparm(0)))
這是/usr/include/asm/linkage.h 裡頭的定義,regparm(0)表示不使用register傳遞參數
如此一來所有的參數就會被強迫放在stack當中
這麼做的原因是因為system call handler是assembly code,但是system call routine是C code
為了要保證當system call handler呼叫相對應的system call routine時,符合C語言參數傳遞的規則
是以 stack 方式傳參數,在C function的 prototype前面就要加上 "asmlinkage"。
System call 結束
當 system call 執行完的時候,最後會執行 ret_from_sys_call() 離開。
最後回到 syscallX() 中去。在 syscallX() 中,檢測是否有錯誤碼,然後返回。
(秩名)