20135103王海甯
《Linux核心分析》MOOC課程http://mooc.study.163.com/course/USTC-1000029000
這周的實驗在上周實驗四的基礎上,進一步的操作:
1.将系統調用函數getpid指令加入menuos中
2.通過gdb跟蹤sys_getpid系統調用執行的完整過程
步驟:給MenuOS增加getpid和getpid-asm指令
0)更新menu代碼到最新版
1)在main函數中增加MenuConfig
2)增加對應的getpid函數和getpid-asm函數
3)make rootfs
進入實驗樓環境後,敲入如下指令:
[plain] view plain copy
- <span style="font-size:14px;">cd LinuxKernel
- cd menu
- vi test.c</span>
進入test.c源程式後添加如下代碼:
[cpp] view plain copy
- <span style="font-size:14px;">//添加兩個函數,别忘了加頭檔案#include <unistd.h>
- int main() {
- pid_t tt;
- asm volatile (
- "movl $0x20, %%eax\n\t"
- "int $0x80\n\t"
- "movl %%eax, %0\n\t"
- :"=m"(tt)
- );
- printf("%u\n", tt);
- return 0;
- }
- tt = getpid();
- }</span>
- <span style="font-size:14px;">//然後在main函數中添加
- MenuConfig("time","Show System Time",Time);
- MenuConfig("time-asm","Show System Time(asm)",TimeAsm);</span>
代碼添加完成後make rootfs重新編譯,此時系統會自動啟動。如下圖:
下面用gdb跟蹤sys_getpid執行的過程:
1.執行以下指令開啟核心的調試功能 qemu -kernel linux-3.18.6/arch/x86/boot/bz/Image -initrd rootfs.img -s -S,此時系統處于停止狀态
2.再打開一個指令行視窗輸入gdb,在gdb指令提示符下依次輸入file linux-3.18.6/vmlinux, target remote:1234指令連接配接核心并跟蹤調試
3.設定斷點break sys_getpid, 接着continue開始運作,此時menuOS從stopped狀态開始執行。在menu程式的提示符下輸入who,程式執行到斷點時暫停,此時gdb視窗顯示程式斷在sys_getpid處
4.接着使用gdb單步執行指令。next:不進入函數體的單步執行;step:進入函數體的單步執行;finish:進入函數體後退回調用函數
分析
中斷相關的初始化代碼是通過linux-3.18.6/init/main.c檔案中的start_kernel函數裡的trap_init()初始化的。執行int $0x80指令後核心開始執行system_call入口處開始的代碼,位于entry_32.S彙編檔案中。
下面分析system_call彙編代碼:
1.SAVE ALL // 儲存調用前寄存器相關的資訊
2.call *sys_call_table(,%eax,4) // 執行系統調用對應的處理函數,eax存放系統調用号
// 通過linux-3.18.6/arch/x86/syscalls/syscall_32.tbl找到系統調用号對應處理函數
3.movl %eax,PT_EAX(%esp) // 儲存系統調用處理函數傳回值到exa
4. testl $_TIF_ALLWORK_MASK, %ecx # current->work
jne syscall_exit_work
// 這兩句檢查調用退出前是否有其他工作要處理,如有則跳到syscall_exit_work處繼續處理,以下是syscall_exit_work相關代碼:
syscall_exit_work:
testl $_TIF_WORK_SYSCALL_EXIT, %ecx // 測試是否退出前還有工作要處理,如有則跳到work_pending
jz work_pending
TRACE_IRQS_ON
ENABLE_INTERRUPTS(CLBR_ANY) # could let syscall_trace_leave() call
# schedule() instead
movl %esp, %eax
call syscall_trace_leave
jmp resume_userspace
END(syscall_exit_work)
5.下面是work_pending的相關代碼,在注釋中解釋相關内容
work_pending:
testb $_TIF_NEED_RESCHED, %cl // 是否有要繼續排程的相關信号
jz work_notifysig #跳轉到處理信号相關的代碼處
work_resched:
call schedule // 時間排程,程序排程的時機在這裡處理
LOCKDEP_SYS_EXIT
DISABLE_INTERRUPTS(CLBR_ANY) # make sure we don't miss an interrupt
# setting need_resched or sigpending
# between sampling and the iret
TRACE_IRQS_OFF
movl TI_flags(%ebp), %ecx
andl $_TIF_WORK_MASK, %ecx # is there any work to be done other // 是否有其他工作要處理
# than syscall tracing?
jz restore_all // 如果沒有則恢複中斷上下文,即恢複進入之前儲存的寄存器内容
testb $_TIF_NEED_RESCHED, %cl
jnz work_resched
work_notifysig: # deal with pending signals and // 處理相關信号代碼
# notify-resume requests
#ifdef CONFIG_VM86
testl $X86_EFLAGS_VM, PT_EFLAGS(%esp)
jne work_notifysig_v86 # returning to kernel-space or
# vm86-space
1:
#else
#endif
ENABLE_INTERRUPTS(CLBR_NONE)
movb PT_CS(%esp), %bl
andb $SEGMENT_RPL_MASK, %bl
cmpb $USER_RPL, %bl
jb resume_kernel
xorl %edx, %edx
call do_notify_resume
ALIGN
work_notifysig_v86:
pushl_cfi %ecx # save ti_flags for do_notify_resume
call save_v86_state # %eax contains pt_regs pointer
popl_cfi %ecx
movl %eax, %esp
jmp 1b
END(work_pending)
6. restore_all:
RESTORE_INT_REGS // 中斷傳回之前恢複相關寄存器的内容
7. irq_return:
INTERRUPT_RETURN // 這兩行代碼主要是傳回到使用者态
總結
1.執行int 0x80指令後系統從使用者态進入核心态,跳到system_call()函數處執行相應服務程序。在此過程中核心先儲存中斷環境,然後執行系統調用函數。
2.system_call()函數通過系統調用号查找系統調用表sys_cal_table來查找具體系統調用服務程序。
3.執行完系統調用後,iret之前,核心會檢查是否有新的中斷産生、是否需要程序切換、是否學要處理其它程序發送過來的信号等。
4.核心是處理各種系統調用的中斷集合,通過中斷機制實作程序上下文的切換,通過系統調用管理整個計算機軟硬體資源。
5.如沒有新的中斷,restore儲存的中斷環境并傳回使用者态完成一個系統調用過程。