Linux系統調用system_call


2016-03-25

張超的《Linux內核分析》MOOC課程http://mooc.study.163.com/course/USTC-1000029000

我的虛擬環境和代碼在https://www.shiyanlou.com/courses/reports/1028332

我們這次主要分為兩部分:

1.系統調用system_call的處理過程

2.給MenuOS增加time和time-asm命令

 

1.系統調用system_call的處理過程

490ENTRY(system_call)
491    RING0_INT_FRAME            # can't unwind into user space anyway
492    ASM_CLAC
493    pushl_cfi %eax            # save orig_eax
494    SAVE_ALL
495    GET_THREAD_INFO(%ebp)
496                    # system call tracing in operation / emulation
497    testl $_TIF_WORK_SYSCALL_ENTRY,TI_flags(%ebp)
498    jnz syscall_trace_entry
499    cmpl $(NR_syscalls), %eax
500    jae syscall_badsys
501syscall_call:
502    call *sys_call_table(,%eax,4)
503syscall_after_call:
504    movl %eax,PT_EAX(%esp)        # store the return value
505syscall_exit:
506    LOCKDEP_SYS_EXIT
507    DISABLE_INTERRUPTS(CLBR_ANY)    # make sure we don't miss an interrupt
508                    # setting need_resched or sigpending
509                    # between sampling and the iret
510    TRACE_IRQS_OFF
511    movl TI_flags(%ebp), %ecx
512    testl $_TIF_ALLWORK_MASK, %ecx    # current->work
513    jne syscall_exit_work
514
515restore_all:
516    TRACE_IRQS_IRET
517restore_all_notrace:
518#ifdef CONFIG_X86_ESPFIX32
519    movl PT_EFLAGS(%esp), %eax    # mix EFLAGS, SS and CS
520    # Warning: PT_OLDSS(%esp) contains the wrong/random values if we
521    # are returning to the kernel.
522    # See comments in process.c:copy_thread() for details.
523    movb PT_OLDSS(%esp), %ah
524    movb PT_CS(%esp), %al
525    andl $(X86_EFLAGS_VM | (SEGMENT_TI_MASK << 8) | SEGMENT_RPL_MASK), %eax
526    cmpl $((SEGMENT_LDT << 8) | USER_RPL), %eax
527    CFI_REMEMBER_STATE
528    je ldt_ss            # returning to user-space with LDT SS
529#endif
530restore_nocheck:
531    RESTORE_REGS 4            # skip orig_eax/error_code
532irq_return:
533    INTERRUPT_RETURN
534.section .fixup,"ax"
535ENTRY(iret_exc)
536    pushl $0            # no error code
537    pushl $do_iret_error
538    jmp error_code
539.previous
540    _ASM_EXTABLE(irq_return,iret_exc)
system_call

 我們的system_call代碼如上所述。

有實際開發經驗的人都知道,在操作系統上運行的某個應用程序,如果想完成一些實際有用的功能,必然會用到操作系統提供的接口,這些接口被稱為系統調用(System Call)。

由操作系統提供的功能,通常應用程序本身是無法實現的。例如對文件進行操作,應用程序必需通過系統調用才能做到,因為只有操作系統才具有直接管理外圍設備的權限。又如進

程或線程間的同步互斥操作,也必需經由操作系統對內核變量進行維護才能完成。

應用程序的進程通常在user模式下運行,當它調用一個系統調用時,進程進入kernel模式,執行的是kernel內部的代碼,從而具有執行特權指令的權限,完成特定的功能。換句話說,

系統調用是應用程序主動進入操作系統內核的入口。

由程序員的代碼主動發起的中斷。有兩種用法:(1)用來實現系統調用;(2)通知調試器某個特殊事件。

至此,我們發現了中斷與系統調用的關系:系統調用是一種特殊的中斷類型。

系統調用的處理例程在IDT表中占有一項。這一項是在trap_init函數中被初始化的,如下:

set_system_gate(SYSCALL_VECTOR,&system_call);

當系統調用發生時,通過中斷機制,系統調用例程system_call被調用。system_call由匯編語言和C的代碼構成,它的執行過程大概分為4個步驟(注意參數的傳入和返回值的傳出過

程):

從寄存器中取出系統調用號(system call number)和輸入參數,然后將這些寄存器的值壓入kernel棧中。這一部分的代碼用匯編寫成。

根據系統調用號(system call number)查找系統調用分派表(system call dispatch table),找到系統調用服務例程(system call service routine )。匯編語言。

調用查到的系統調用服務例程。這一部分用C語言寫成,因為已經將輸入參數保存在kernel棧中,所以在C函數的參數表中能夠拿到輸入參數,使得系統調用服務例程在表面上

看與一個普通的C函數沒有區別。

將系統調用服務例程的返回值出棧,重新保存在寄存器中。匯編語言。

上面描述的系統調用例程system_call在kernel空間中執行。在執行前,系統調用號和輸入參數已經存入了寄存器,這個存入過程由user空間的代碼完成。實際上,每個真正的系統調

用基本上都有一個封裝它的庫函數,一般是在這個庫函數中完成系統調用號和輸入參數的保存動作。當系統調用例程system_call執行完畢后,返回值通過寄存器再傳回user空間的庫

函數。

下面詳細地介紹上面所講的4個步驟。

在第1步之前,user空間的封裝函數已經將對應的系統調用號保存在eax寄存器中,將輸入參數保存在ebx, ecx, edx, esi,以及edi寄存器中(因此最多傳6個參數,包括系統調用號)。

第1步中將輸入參數寄存器的值壓入kernel棧的操作由匯編代碼__SAVE_ALL宏完成。如下:

#define __SAVE_ALL \

cld; \

pushl %es; \

pushl %ds; \

pushl %eax; \

pushl %ebp; \

pushl %edi; \

pushl %esi; \

pushl %edx; \

pushl %ecx; \

pushl %ebx; \

movl $(__USER_DS), %edx; \

movl %edx, %ds; \

movl %edx, %es;

第2步中的系統分派表在kernel代碼中以變量sys_call_table表示。查找系統調用服務例程的動作就是從sys_call_table里找系統調用號(存在eax寄存器中)指向的那一項,如下:

syscall_call:

call *sys_call_table(,%eax,4)

sys_call_table中的項在sys_call_table.c文件中定義:

syscall_handler_t *sys_call_table[] = {

......

[ __NR_exit ] (syscall_handler_t *) sys_exit,

[ __NR_fork ] (syscall_handler_t *) sys_fork,

[ __NR_read ] = (syscall_handler_t *) sys_read,

[ __NR_write ] = (syscall_handler_t *) sys_write,

......

[ __NR_socketcall ] (syscall_handler_t *) sys_socketcall,

......

};

在這里我們注意到一些常用的系統調用號,如,exit系統調用號為__NR_exit = 1,fork系統調用號為__NR_fork =2,read系統調用號為__NR_read = 3,write系統調用號為

__NR_write =4,所有socket相關的API的系統調用號都是__NR_socketcall= 102。

第3步,執行C函數實現的系統調用例程。該例程最多接受6個參數(包括系統調用號),返回值是一個整型。返回值為非負,表示執行成功;返回值為負,表示執行出錯,該錯誤碼的

絕對值會最后存在user空間的errno全局變量中。

第4步,調用syscall_exit_work退出系統調用,並從kernel模式回到user模式。第3步的C函數執行return err的時候,編譯后的代碼已經將返回值存在了eax寄存器中。

最后,回到user模式的封裝函數中,對返回值eax進行檢查。如果eax小於0,則將eax的相反數(即絕對值)存到errno全局變量中,同時將eax值置為-1,這時封裝函數返回-1;如果

eax大於等於0,則封裝函數返回eax的值。

具體分析:

大致過程:SYSterm_call---運行到SAVE—ALL(保護現場)繼續運行----table表找對應程序----iret結束返回

分析call對應函數功能(簡化)

注意)通過SAVE_ALL宏完成把所有相關寄存器的內容都保存在堆棧中

以下是各項簡化函數的表示意思,依據視頻依次是:

GET_THREAD_INFO(%ebp):將當前信息保存在ebp

testl $_TIF_WORK_SYSCALL_ENTRY,TI_flags(%esp)  jnz syscall_trace_entry:判斷是否 trace調用

cmpl $(NR_syscalls), %eax jae syscall_badsys:判斷系統調用號是否超出最大值

call *sys_call_table(,%eax,4):系統調用的數字實際上是一個序列號,表示其在系統的一個數組sys_call_table[]中的位置。

movl %eax,PT_EAX(%esp):保存系統調用的返回值

DISABLE_INTERRUPTS(CLBR_ANY):屏蔽其他系統調用

movl TI_flags(%esp), %eax:寄存器ecx是通用寄存器,在保護模式中,可以作為內存偏移指針(此時,DS作為 寄存器或段選擇器),此時為返回到系統調用之前做准備

testl $_TIF_ALLWORK_MASK, %eax     jne syscall_exit_work :退出系統調用之前,檢查是否需要處理信號

RESTORE_REGS 4​ :x86架構恢復寄存器代碼

INTERRUPT_RETURN即iret: 系統調用是通過軟中斷指令

INT 0x80 實現的,而這條INT 0x80指令就被封裝在C庫的函數中。(軟中斷和我們常說的硬中斷不同之處在於,軟中斷是由指令觸發的,而不是由硬件外設引起的。)

INT 0x80 這條指令的執行會讓系統跳轉到一個預設的內核空間地址,它指向系統調用處理程序,即system_call函數。

實驗過程如下:

1.先切換到我們的虛擬機的LinuxKernel目錄下

2.qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img -s -S

3.右鍵->新建窗口(水平分割)-> gdb

4.file linux-3.18.6/vmlinux

5.target remote:1234

6.b start_kernel

7.c

 

 

 

 

2.給MenuOS增加time和time-asm命令

0)更新menu代碼到最新版

1)在main函數中增加MenuConfig

2)增加對應的Time函數和TimeASM函數

3)make rootfs

具體如下:

 


免責聲明!

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



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