系統調用徹底理解
用戶程序需要系統提供服務的時候,會通過系統調用產生一個int 0x80的軟中斷,就會進入到系統調用的入口函數,入口函數存放在以下文件當中:
以下是系統調用的入口:
ENTRY(system_call)
RING0_INT_FRAME # cant unwind into user space anyway
pushl %eax # save orig_eax ,將系統調用號壓入棧中
CFI_ADJUST_CFA_OFFSET 4
SAVE_ALL #將寄存器的值壓入堆棧當中,壓入堆棧的順序對應著結構體struct pt_regs ,當出棧的時候,就將這些值傳遞到結構體struct pt_regs裏面的成員,從而實現從匯編代碼向C程序傳遞參數。
Struct pt_regs 對應定義在
struct pt_regs {
long ebx;
long ecx;
long edx;
long esi;
long edi;
long ebp;
long eax;
int xds;
int xes;
int xfs;
int xgs;
long orig_eax;
long eip;
int xcs;
long eflags;
long esp;
int xss;
};
#GET_THREAD_INFO宏獲得當前進程的thread_info結構的地址,獲取當前進程的信息。
GET_THREAD_INFO(%ebp)
# system call tracing in operation / emulation
#thread_inof結構中flag字段的_TIF_SYSCALL_TRACE或_TIF_SYSCALL_AUDIT
#被置1。如果發生被跟蹤的情況則轉向相應的處理命令處。
testl $_TIF_WORK_SYSCALL_ENTRY,TI_flags(%ebp)
jnz syscall_trace_entry #比較結果不為零的時候跳轉。
#對用戶態進程傳遞過來的系統調用號的合法性進行檢查。
#如果不合法則跳轉到syscall_badsys標記的命令處。
cmpl $(nr_syscalls), %eax
jae syscall_badsys #比較結果大於或者等於最大的系統調用號的時候跳轉,不合法
#合法則跳轉到相應系統調用號所對應的服務例程當中,
#也就是在sys_call_table表中找到了相應的函數入口點。
#由於sys_call_table表的表項占4字節,因此獲得服務例程指針的具體方法
#是將由eax保存的系統調用號乘以4再與sys_call_table表的基址相加。
syscall_call:
call *sys_call_table(,%eax,4)
movl %eax,PT_EAX(%esp) # store the return value 將保存的結果返回。
接下來,會進入到系統調用表查找到系統調用服務程序的入口函數的地址,再進行跳轉,整個過程如下圖所示:
ENTRY(sys_call_table)
.long sys_restart_syscall /* 0 - old "setup()" system call, used for restarting */
.long sys_exit
.long ptregs_fork
.long sys_read
.long sys_write
.long sys_open /* 5 */
.long sys_close
.long sys_waitpid
.long sys_creat
.long sys_link
.long sys_unlink /* 10 */
.long ptregs_execve
例如我們跟蹤一下系統調用open的打開流程:
1、open的系統調用號,存放於
#define __NR_restart_syscall 0
#define __NR_exit 1
#define __NR_fork 2
#define __NR_read 3
#define __NR_write 4
#define __NR_open 5
#define __NR_close 6
#define __NR_waitpid 7
#define __NR_creat 8
#define __NR_link 9
#define __NR_unlink 10
2、系統調用函數的原型在:
asmlinkage long sys_open(const char __user *filename,
int flags, int mode);
其中這裏使用了一個宏asmlinkage ,我們再看一下它在系統裏的定義:
#ifdef CONFIG_X86_32
#define asmlinkage CPP_ASMLINKAGE __attribute__((regparm(0)))
後面的 __attribute__((regparm(0)))表示的是不通過寄存器來傳遞參數,通過棧來傳遞
所以系統調用的入口函數裏面
ENTRY(system_call)
SAVE_ALL #將寄存器的值壓入堆棧當中,壓入堆棧的順序對應著結構體struct pt_regs ,當出棧的時候,就將這些值傳遞到結構體struct pt_regs裏面的成員,從而實現從匯編代碼向C程序傳遞參數。
定義了這個SAVE_ALL是將參數壓倒堆棧裏面,然後通過堆棧來進行參數的傳遞。
3、經過了系統調用入口函數之後,會調用到
syscall_call:
call *sys_call_table(,%eax,4)
sys_call_table每一項占用4個字節。system_call函數可以讀取eax寄存器獲得當前系統調用的系統調用號,將其乘以4生成偏移地址,然後以sys_call_table為基址,基址加上偏移地址所指向的內容即是應該執行的系統調用服務例程的地址。
ENTRY(sys_call_table)
.long sys_restart_syscall /* 0 - old "setup()" system call, used for restarting */
.long sys_exit
.long ptregs_fork
.long sys_read
.long sys_write
.long sys_open /* 5 */
對應於系統調用sys_open是系統調用服務程序的入口地址
5、在新的內核中,函數的實現並不是對應這sys_XXX的一個函數的實現。而是會經過一個宏的封裝,在中
SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, int, mode)
{
long ret;
if (force_o_largefile())
flags |= O_LARGEFILE;
ret = do_sys_open(AT_FDCWD, filename, flags, mode);
/* avoid REGPARM breakage on x86: */
asmlinkage_protect(3, ret, filename, flags, mode);
return ret;
}
這個宏SYSCALL_DEFINE3的定義在中,我們可以看到有一些這樣的宏的定義:
#define SYSCALL_DEFINE0(sname) \
static const struct syscall_metadata __used \
__attribute__((__aligned__(4))) \
__attribute__((section("__syscalls_metadata"))) \
__syscall_meta_##sname = { \
.name = "sys_"#sname, \
.nb_args = 0, \
}; \
asmlinkage long sys_##sname(void)
#else
#define SYSCALL_DEFINE0(name) asmlinkage long sys_##name(void)
#endif
#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__)
結合系統函數sys_open的定義
asmlinkage long sys_open(const char __user *filename,
int flags, int mode);
可以知道,SYSCALL_DEFINE3中的數字3表示的是我們這個函數需要傳遞3個參數。
#define SYSCALL_DEFINE3(name, ...) SYSCALL_DEFINEx(3, _##name, __VA_ARGS__)
##的意思就是宏中的字符直接替換,如上面_##name展開之後就會變成_open
SYSCALL_DEFINEx(3, _open, __VA_ARGS__)
#define SYSCALL_DEFINEx(x, sname, ...) \
__SYSCALL_DEFINEx(x, sname, __VA_ARGS__)
#define __SYSCALL_DEFINEx(x, name, ...) \
asmlinkage long sys##name(__SC_DECL##x(__VA_ARGS__)); \
static inline long SYSC##name(__SC_DECL##x(__VA_ARGS__)); \
asmlinkage long SyS##name(__SC_LONG##x(__VA_ARGS__)) \
{ \
__SC_TEST##x(__VA_ARGS__); \
return (long) SYSC##name(__SC_CAST##x(__VA_ARGS__)); \
} \
SYSCALL_ALIAS(sys##name, SyS##name); \
static inline long SYSC##name(__SC_DECL##x(__VA_ARGS__))
所以上面的宏完整展開之後會變成這樣子,所以最後會調用到 系統調用服務程序sys_open這個函數。
#define SYSCALL_DEFINE3(3, _open, ...) \
__SYSCALL_DEFINE3(3, _open, __VA_ARGS__)
#define __SYSCALL_DEFINE3(3, _open, ...) \
asmlinkage long sys_open(__SC_DECL3(__VA_ARGS__)); \
static inline long SYSC_open(__SC_DECL3(__VA_ARGS__)); \
asmlinkage long SyS_open(__SC_LONG3(__VA_ARGS__)) \
{ \
__SC_TEST3(__VA_ARGS__); \
return (long) SYSC_open(__SC_CAST3(__VA_ARGS__)); \
} \
SYSCALL_ALIAS(sys_open, SyS_open); \
static inline long SYSC_open(__SC_DECL3(__VA_ARGS__))
所以當我們自己定義一個不需要傳遞參數的系統調用的時候,可以這樣定義我們的函數:
SYSCALL_DEFINE0(mycall)
{
printk("This is my_sys_call\n");
return 0;
}
系統調用徹底理解