系統調用是用戶空間訪問內核的唯一手段,除異常和陷入外,它們是內核唯一的合法入口。其實,應用程序通過在用戶空間實現的應用編程接口(API)而不是直接通過系統調用來編程。一般應用程序中的API調用C庫,C庫再調用內核中的系統調用。在Unix中,最流行的應用編程接口是基於POSIX標准的,C庫提供了POSIX的絕大部分API。
如何定義一個系統調用呢?
asmlinkage long sys_test(void)
注意函數聲明中的asmlinkage限定詞,這是一個編譯指令,通知編譯器僅從棧中提取該函數的參數,所有的系統調用都需要這個限定詞。另外,系統調用在內核空間和用戶空間有著不同的返回值,在用戶空間為int,在內核空間為long。最後函數名是以sys_打頭,這個系統調用函數名的命名規定。
每一個系統調用都有一個系統調用號,內核中記錄了系統調用表中的所有已經注冊過的系統調用列表存儲在sys_call_table中,這個表為每一個有效的系統調用指定了唯一的系統調用號。
由於內核駐留在受保護的地址空間中,所以用戶空間程序無法直接訪問內核空間。通知內核的機制是靠軟中斷實現的,這個軟中斷都是通過執行int $0x80指令觸發。通過引發一個異常來促使系統切換到內核態去執行異常處理程序,這個異常處理程序就是系統調用處理程序system_call
因為所有的系統調用陷入內核的方式都是執行int $0x80指令,然後進入系統調用處理程序system_call。僅僅這麼做是不能區分所有的系統調用的,我們需要在陷入內核之前,用戶空間就把相應的系統調用放入EAX中,這樣系統調用處理程序一旦運行就可以從EAX中得到數據。
除了系統調用號外,大部分系統調用還需要一些外部額的參數輸入,在X86-32系統中,EBX,ECX,EDX,ESI,EDI按照順序存放前五個參數,如果不夠則再用一個單獨的寄存器。
給用戶空間返回值也是通過寄存器傳遞的,在X86系統上,它存放在EAX寄存器中。
在接收一個用戶空間指針之前,內核必須保證:不能哄騙內核去讀取內核空間數據;不能哄騙內核去讀取其他進程的數據;不能繞過內核訪問權限。內核提供了兩種方法來完成必須的檢查和內核空間和用戶空間之間的數據拷貝,copy_to_user和copy_from_user,這兩個函數都會引起阻塞。
內核在執行系統調用的時候處於進程上下文,在進程上下文中,內核可以休眠(比如系統調用阻塞或顯式調用schedule函數的時候),並且可以被搶占。
系統調用返回的時候,控制權仍在system_call中,它最終會負責切換到用戶空間,並讓用戶繼續執行下去。
內核設計系統調用:
主要完成系統調用函數編寫,系統調用號,系統調用表的填寫。
step1: 在/include/linux/syscalls.h中增加要添加的系統調用的聲明
asmlinkage long sys_test(void);
step2: 在/arch/arm/include/asm/unistd.h中增加系統調用號的定義
#define __NR_test (__NR_SYSCALL_BASE+361)
step3: 在/kernel/sys.c中實現系統調用函數
asmlinkage long sys_test(void)
{
printk("pid: %d,this is test call.\n",current->pid);
return 0;
}
step4: 在/arch/arm/kernel/calls.S中加入系統調用表的初始化部分
/* 361 */ CALL(sys_test)
step5: 生成內核鏡像
應用層測試程序
#include <linux/unistd.h>
#include <stdio.h>
#include <errno.h>
int main()
{
int i = 10;
i = syscall(361);
printf("after syscall: i=%d\n",i);
return 0;
}
測試結果
pid: 859, this is test call.
after syscall:i=0