1. 程式人生 > >《Linux作業系統分析》之分析系統呼叫system_call的處理過程

《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,

我們將entry_32.s檔案開啟,看一下里面的內容。

# 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
在上面程式碼中我們知道無論是中斷返回(ret_from_intr) ,還是系統呼叫返回,都使用了 work_pending 和resume_userspace。

對於巨集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