《Linux作業系統分析》之分析系統呼叫system_call的處理過程
本篇文章通過將上篇文章中使用庫函式API和C程式碼中嵌入彙編程式碼兩種方式設計的系統呼叫新增到系統menu中,來說明在Linux系統中,系統呼叫的實現的時機以及具體執行,以及一般的中斷處理過程。
相關知識
首先關於這篇文章會介紹一些用到的知識。
一、將系統呼叫號與相應的服務例程關聯起來,核心利用了一個系統呼叫分派表(dispatch table)。這個表存放在sys_call_table陣列中,有NR_syscalls個表項:第n個表項包含系統呼叫號為n的服務例程的地址。
分析過程
首先我們將自己寫的系統呼叫fetpid和getpid-asm放進Menu系統的中。需要進行的操作有:
0)更新menuOS到最新版本
1)在test.c中的main函式中新增MenuConfig
2)新增對應的getpid函式和getpid-asm函式
3)make rootfs
第零步
rm menu -rf 強制刪除原menu檔案
git clone http://github.com/mengning/menu.git 從github中克隆
第一二步結果如下圖:
對其進行編譯執行,即第三步
執行結果如上圖。
設定斷點進行跟蹤:
我們發現斷點進入到entry_32.s中。但是在這裡沒有停止,而是直接執行結束了。
在進入entry_32.s之前,執行的過程如下:
1)main.c中start_kernel函式:trap_init()
2)set_system_trap_gate(SYSCALL_VECTOR,&system_call)
SYSCALL_VECTOR:系統呼叫的中斷向量
&system_call:彙編程式碼入口
3)一執行int 0x80,系統直接跳轉到system_call。
然後執行到了entry_32.s,
在上面程式碼中我們知道無論是中斷返回(ret_from_intr) ,還是系統呼叫返回,都使用了 work_pending 和resume_userspace。# system call handler stub ENTRY(system_call) #系統呼叫處理入口(核心態) RING0_INT_FRAME # can't unwind into user space anyway ASM_CLAC pushl_cfi %eax # save orig_eax #儲存eax,也就是呼叫號 SAVE_ALL # 儲存所有會被覆蓋的暫存器資訊 GET_THREAD_INFO(%ebp) # 當前程序的thread_info結構的地址,獲取當前程序的資訊。</span> # system call tracing in operation / emulation testl $_TIF_WORK_SYSCALL_ENTRY,TI_flags(%ebp) # 檢測是否由系統跟蹤 jnz syscall_trace_entry # 有系統跟蹤則先去執行 cmpl $(NR_syscalls), %eax # 比較輸入的系統呼叫號 是否大於等於 最大的系統呼叫號 jae syscall_badsys # 大於或等於則無效,跳轉到syscall_badsys,小於則跳轉到相應系統呼叫號所對應的服務例程當中。 syscall_call: call *sys_call_table(,%eax,4) # 在系統呼叫表中的呼叫相應的服務例程,eax為呼叫號。sys_call_table表的表項佔4位元組。 syscall_after_call: movl %eax,PT_EAX(%esp) # store the return value # 儲存返回值 syscall_exit: 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 # 檢測是否還有其他任務 testl $_TIF_ALLWORK_MASK, %ecx # current->work jne syscall_exit_work<span style="white-space:pre"> </span>#如果有其他的任務,則跳轉到syscall_exit_work。沒有就執行restore_all等恢復現場的動作。 syscall_exit_work: testl $_TIF_WORK_SYSCALL_EXIT, %ecx jz work_pending # 測試是否退出前還有工作要處理,如果有的話跳轉到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) 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<span style="white-space:pre"> </span>#系統中斷跟蹤關閉 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) movl %esp, %eax jne work_notifysig_v86 # returning to kernel-space or # vm86-space restore_all: TRACE_IRQS_IRET # 恢復中斷跟蹤 restore_all_notrace: #ifdef CONFIG_X86_ESPFIX32 movl PT_EFLAGS(%esp), %eax # mix EFLAGS, SS and CS # Warning: PT_OLDSS(%esp) contains the wrong/random values if we # are returning to the kernel. # See comments in process.c:copy_thread() for details. movb PT_OLDSS(%esp), %ah movb PT_CS(%esp), %al andl $(X86_EFLAGS_VM | (SEGMENT_TI_MASK << 8) | SEGMENT_RPL_MASK), %eax cmpl $((SEGMENT_LDT << 8) | USER_RPL), %eax CFI_REMEMBER_STATE je ldt_ss # returning to user-space with LDT SS #endif restore_nocheck: RESTORE_REGS 4 # skip orig_eax/error_code irq_return: INTERRUPT_RETURN
對於巨集SAVE_ALL來說,會把將暫存器的值壓入堆疊當中,壓入順序對應struct pt_regs ,出棧時亦然。struct pt_regs可以在ptrace.h中檢視。
系統呼叫的流程圖如下:
總結:
1)系統呼叫的初始化的順序是:start_kernel()->trap_init()->set_system_trap_gates(SYSCALL_VECTOR,&system_call);
2)使用者態到核心態通過0x80進行中斷,在核心初始化期間呼叫trap_init(),用函式set_system_trap_gates(),建立了對應於向量128的中斷描述符表表項,從而進入相應的中斷服務。
3)system_call()函式首先將系統呼叫號或中斷處理程式需要用到的所有的CPU暫存器儲存到相應的棧中。然後進行服務的處理。當系統呼叫服務例程結束時,system_call()函式從eax獲得它的返回值。然後進行一系列的檢查,最後恢復使用者態程序的執行。
如果想看詳細的講解,請大家參考:深入理解linux核心的第十章系統呼叫。
備註:
楊峻鵬 + 原創作品轉載請註明出處 + 《Linux核心分析》MOOC課程http://mooc.study.163.com/course/USTC-1000029000