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)。
由操作系統提供的功能,通常應用程序本身是無法實現的。例如對文件進行操作,應用程序必需通過系統調用才能做到,因為只有操作系統才具有直接管理外圍設備的權限。又如進
程或線程間的同步互斥操作,也必需經由操作系統對內核變量進行維護才能完成。
應用程序的進程通常在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
具體如下: