1. 系統調用的作用
- 爲用戶空間提供了一種硬件的抽象接口。如Linux的“萬物皆文件”的思想,APP讀取文件不用關心磁盤或者文件系統的類型
- 保證系統的穩定和安全。因爲內核是作爲Hardware跟APP之間的中間人,可以避免APP不正確地使用Hardware,竊取其他進程的資源,或其他危害系統的事情。
- 實現多任務和虛擬內存的需要
2. 在Linux中,系統調用是除了異常和陷入之外,用戶空間訪問kernel的唯一手段。
3. 系統調用在出現錯誤的時候C庫會把錯誤碼寫入errno全局變量。通過調用perror()庫函數,可以把變量翻譯成用戶可以理解的錯誤字符串。
4. 在include/linux/syscalls.h中列出了一些列定義系統調用時用到的宏,如:
#define SYSCALL_DEFINE1(name, ...) SYSCALL_DEFINEx(1, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE2(name, ...) SYSCALL_DEFINEx(2, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE3(name, ...) SYSCALL_DEFINEx(3, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE4(name, ...) SYSCALL_DEFINEx(4, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE5(name, ...) SYSCALL_DEFINEx(5, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE6(name, ...) SYSCALL_DEFINEx(6, _##name, __VA_ARGS__)
#define SYSCALL_DEFINEx(x, sname, ...) \
SYSCALL_METADATA(sname, x, __VA_ARGS__) \
__SYSCALL_DEFINEx(x, sname, __VA_ARGS__)
如open系統調用的定義 (fs/open.c中):
SYSCALL_DEFINE3(open,constchar __user *, filename,int, flags,umode_t, mode)
{
if(force_o_largefile())
flags |= O_LARGEFILE;
return do_sys_open(AT_FDCWD, filename, flags, mode);
}
5. 在定義系統調用是會使用asmlinkage限定詞,目的是告訴編譯器僅從棧中提取該函數的參數。
6. 每一個系統調用都被賦予一個系統調用號,獨一無二。用戶空間的進程執行系統調用時會將系統調用號傳遞給kernel,kernel據此判斷需要執行那個系統調用。
在arm架構的kernel代碼,系統調用表如下,其中在表中的號就是系統調用號。
arch/arm/kernel/entry-common.S中:
.type sys_call_table,#object
ENTRY(sys_call_table)
#include "calls.S"
在arch/arm/kernel/call.S中:
/*0*/ CALL(sys_restart_syscall)
CALL(sys_exit)
CALL(sys_fork)
CALL(sys_read)
CALL(sys_write)
/*5*/ CALL(sys_open)
CALL(sys_close)
CALL(sys_ni_syscall)/* was sys_waitpid */
CALL(sys_creat)
......
在arm64架構的kernel代碼中如下:
在arch/arm64/kernel/sys.c中:
#undef __SYSCALL
#define __SYSCALL(nr, sym) [nr] = sym,
/*
*The sys_call_table array must be 4K aligned to be accessible from
* kernel/entry.S.
*/
void *sys_call_table[__NR_syscalls] __aligned(4096)={
[0... __NR_syscalls -1]= sys_ni_syscall,
#include <asm/unistd.h>
};
在arch/arm64/include/asm/unistd32.h中:
#define __NR_restart_syscall 0
__SYSCALL(__NR_restart_syscall, sys_restart_syscall)
#define __NR_exit 1
__SYSCALL(__NR_exit, sys_exit)
#define __NR_fork 2
__SYSCALL(__NR_fork, sys_fork)
#define __NR_read 3
__SYSCALL(__NR_read, sys_read)
#define __NR_write 4
__SYSCALL(__NR_write, sys_write)
#define __NR_open 5
__SYSCALL(__NR_open, compat_sys_open)
#define __NR_close 6
__SYSCALL(__NR_close, sys_close)
/*7 was sys_waitpid */
__SYSCALL(7, sys_ni_syscall)
#define __NR_creat 8
__SYSCALL(__NR_creat, sys_creat)
......
7. Linux內核通過軟中斷引發一個異常來促使系統切換到內核態去執行異常處理程序,在該異常處理程序中會或獲得用戶傳給的系統調用號和其他參數,根據系統調用號決定執行那個系統調用處理程序。對於arm架構是通過svc軟中斷指令實現的。
8. copy_to_user()和copy_from_user()
在內核空間接受一個用戶空間的指針的時候,需要做如下檢查:
- 指針指向的內存區域屬於用戶空間
- 指針指向額內存區域在進程的地址空間裏
- 如果是讀,該內存應被標記爲可讀; 如果是寫,該內存應被標記爲可寫; 如果是可執行,該內存應被標記爲可執行;
上述的兩個方法在完成了必須的檢查和內核空間和用戶空間之間數據拷貝 。需要注意的是返回值的含義:
- 如果執行失敗,返回沒能完成拷貝的數據的字節數
- 成功的話,返回0
- 如果在檢查用戶空間指針合法性時出錯,返回 -EFAULT
此外,當包含用戶數據的頁被換出到硬盤上而不是在物理內存上時,這個兩函數都會引起阻塞,此時進程會休眠,知道缺頁處理程序將頁從硬盤重新換回到物理內存。
9. 內核執行系統調用的時候處於進程上下文,current指針指向當前任務,即引發系統調用的那個進程
10. 在進程上下文,內核可以睡眠並且可以被搶佔。
11. 必須保證系統調用是可衝入的。
12. 下面是執行系統調用的連鎖反應:陷入內核,傳遞系統調用號和參數,執行正確的系統調用函數,並把返回值帶回到用戶空間