Linux系統調用過程


一. 概述

         系統調用是應用程序與內核交互的一種方式。系統調用作為一種接口,通過系統調用,應用程序能夠進入操作系統內核,從而使用內核提供的各種資源,比如操作硬件,開關中斷,改變特權模式等等。首先,系統調用是一個軟中斷,既然是中斷那么一般就具有中斷號和中斷處理程序兩個屬性,Linux使用0x80號中斷作為系統調用的入口,而中斷處理程序的地址放在中斷向量表里。

 

二. 過程

         基於linux-2.6.38,以read()系統調用函數為例進行說明。    

       在用戶空間,read()函數的聲明位於#include<unistd.h>,原型為:ssize_t read(int fd, void *buf, size_t count)。下面是read()函數在用戶空間的定義的偽代碼:

 1 ssize_t read(int fd, void *buf, size_t count)
 2 {
 3        long res;
 4        %eax = __NR_read
 5        %ebx = fd 
 6        %ecx = (long)buf
 7        %edx= count
 8        int $0x80
 9        res = %eax
10        return res;
11 }

         第4行,用eax寄存器保存read()的系統調用號,在/arch/x86/include/asm/unistd_32.h里定義(#define __NR_read  3);第5~7行,分別將三個參數放入三個寄存器(通過寄存器來傳遞參數);第8行,執行系統調用,進入內核;第9行,獲取eax寄存器所保存的函數返回值。

       執行第8行后已經進入了系統內核,由於這是一個中斷,因此程序進入到中斷向量表中記錄0x80號的中斷處理程序,中斷向量表的初始化在/arch/x86/kernel/traps.c中定義:

 1 void __init trap_init(void)
 2 {
 3 ...................
 4 
 5    #ifdef CONFIG_X86_32
 6        set_system_trap_gate(SYSCALL_VECTOR, &system_call);
 7        set_bit(SYSCALL_VECTOR, used_vectors);
 8    #endif
 9 ...................
10 }

如第6行所示。SYSCALL_VECTOR是系統調用的中斷號,在/arch/x86/include/asm/irq_vectors.h中定義:

1 #ifdef CONFIG_X86_32
2 # define SYSCALL_VECTOR            0x80
3 #endif

正好是0x80。而system_call是系統調用的中斷處理函數指針,用戶執行int $0x80后會執行到這個函數,它在/arch/x86/kernel/entry_32.S中定義:

 1 ENTRY(system_call)
 2     RING0_INT_FRAME            # can't unwind into user space anyway
 3     pushl_cfi %eax            # save orig_eax
 4     SAVE_ALL
 5     GET_THREAD_INFO(%ebp)
 6                     # system call tracing in operation / emulation
 7     testl $_TIF_WORK_SYSCALL_ENTRY,TI_flags(%ebp)
 8     jnz syscall_trace_entry
 9     cmpl $(nr_syscalls), %eax
10     jae syscall_badsys
11 syscall_call:
12     call *sys_call_table(,%eax,4)
13     movl %eax,PT_EAX(%esp)        # store the return value
...........

第4行,SAVE_ALL是一個宏,也在這個文件里定義:

 1 .macro SAVE_ALL
 2     cld
 3     PUSH_GS
 4     pushl_cfi %fs
 5     /*CFI_REL_OFFSET fs, 0;*/
 6     pushl_cfi %es
 7     /*CFI_REL_OFFSET es, 0;*/
 8     pushl_cfi %ds
 9     /*CFI_REL_OFFSET ds, 0;*/
10     pushl_cfi %eax
11     CFI_REL_OFFSET eax, 0
12     pushl_cfi %ebp
13     CFI_REL_OFFSET ebp, 0
14     pushl_cfi %edi
15     CFI_REL_OFFSET edi, 0
16     pushl_cfi %esi
17     CFI_REL_OFFSET esi, 0
18     pushl_cfi %edx
19     CFI_REL_OFFSET edx, 0
20     pushl_cfi %ecx
21     CFI_REL_OFFSET ecx, 0
22     pushl_cfi %ebx
23     CFI_REL_OFFSET ebx, 0
24     movl $(__USER_DS), %edx
25     movl %edx, %ds
26     movl %edx, %es
27     movl $(__KERNEL_PERCPU), %edx
28     movl %edx, %fs
29     SET_KERNEL_GS %edx
30 .endm

主要作用就是將各個寄存器壓入棧中。

第9行,比較eax的值是否大於等於nr_syscalls,nr_syscalls是比最大有效系統調用號大1的值,在/arch/x86/kernel/entry_32.S中定義:

1 #define nr_syscalls ((syscall_table_size)/4)

其中syscall_table_size就是系統調用表的大小(單位:字節),syscall_table_size其實是一個數組,數組里存放的是各個系統調用函數的地址,元素類型是long型,除以4剛好是系統調用函數的個數。

如果從eax寄存器傳進來的系統調用號有效,那么就執行第12行,在系統調用表里找到相應的系統調用服務程序,sys_call_table在/arch/x86/kernel/syscall_table_32.S中定義:

1 ENTRY(sys_call_table)
2     .long sys_restart_syscall    /* 0 - old "setup()" system call, used for restarting */
3     .long sys_exit
4     .long ptregs_fork
5     .long sys_read
6     .long sys_write
7     .long sys_open        /* 5 */
8     .long sys_close
9 .................
*sys_call_table(,%eax,4)指的是sys_call_table里偏移量為%eax*4上的那個值指向的函數,這里%eax=3,那么第5行的sys_read()函數就會被調用。sys_read()在/fs/read_write.c中定義:
 1 SYSCALL_DEFINE3(read, unsigned int, fd, char __user *, buf, size_t, count)
 2 {       
 3         struct file *file;      
 4         ssize_t ret = -EBADF;
 5         int fput_needed;
 6         
 7         file = fget_light(fd, &fput_needed);
 8         if (file) {
 9                 loff_t pos = file_pos_read(file);
10                 ret = vfs_read(file, buf, count, &pos);
11                 file_pos_write(file, pos);
12                 fput_light(file, fput_needed);
13         }
14         
15         return ret;
16 } 

可見,參數的形式和用戶空間的一樣。SYSCALL_DEFINE3是一個宏,在/include/linux/syscalls.h中定義:

1 #define SYSCALL_DEFINE3(name, ...) SYSCALL_DEFINEx(3, _##name, __VA_ARGS__)

SYSCALL_DEFINEx也是一個宏,也在此文件中定義:

1 #define SYSCALL_DEFINEx(x, sname, ...)                \
2     __SYSCALL_DEFINEx(x, sname, __VA_ARGS__)
3 .....
4 #define __SYSCALL_DEFINEx(x, name, ...)                    \
5     asmlinkage long sys##name(__SC_DECL##x(__VA_ARGS__))
6 ......

宏展開后,就是聲明了這么一個函數:

asmlinkage long sys_read(unsigned int fd, char __user *buf, size_t count);

asmlingage是一個宏,定義為:__attribute__((regparm(0))),作用是讓這個函數只從棧上獲取參數(因為之前的SAVE_ALL將參數壓到了棧里面)。

      當執行完中斷處理程序后,后面會調用RESTORE_REGS來恢復各個寄存器:

1 ..............
2     CFI_REMEMBER_STATE
3     je ldt_ss            # returning to user-space with LDT SS
4 restore_nocheck:
5     RESTORE_REGS 4            # skip orig_eax/error_code
6 ...............

第5行,RESTORE_REGS的定義:

 1 .macro RESTORE_REGS pop=0
 2     RESTORE_INT_REGS
 3 1:    popl_cfi %ds
 4     /*CFI_RESTORE ds;*/
 5 2:    popl_cfi %es
 6     /*CFI_RESTORE es;*/
 7 3:    popl_cfi %fs
 8     /*CFI_RESTORE fs;*/
 9     POP_GS \pop
10 .................

第2行,RESTORE_INT_REGS的定義:

 1 .macro RESTORE_INT_REGS
 2     popl_cfi %ebx
 3     CFI_RESTORE ebx
 4     popl_cfi %ecx
 5     CFI_RESTORE ecx
 6     popl_cfi %edx
 7     CFI_RESTORE edx
 8     popl_cfi %esi
 9     CFI_RESTORE esi
10     popl_cfi %edi
11     CFI_RESTORE edi
12     popl_cfi %ebp
13     CFI_RESTORE ebp
14     popl_cfi %eax
15     CFI_RESTORE eax
16 .endm

       到這里差不多了,再對read()跟蹤的話就會涉及文件系統方面的內容,以后會說的。

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM