經過分析system_call中斷處理過程來深刻理解系統調用

經過分析system_call中斷處理過程來深刻理解系統調用

前言說明

本篇爲網易雲課堂Linux內核分析課程的第五週做業,上一次做業中我以2個系統調用(getpid, open)做爲分析實例來分析系統調用的過程,本篇中我將深刻到system_call(彙編級別代碼)中來分析其執行過程.vim


關鍵詞:system_call, 系統調用


運行環境:異步

  • Ubuntu 14.04 LTS x64
  • gcc 4.9.2
  • gdb 7.8
  • vim 7.4 with vundle

分析過程

從上一次課後,咱們對於系統調用在執行過程當中的一些基本狀況有了一個比較抽象的認識,一個系統調用的基本過程是用戶態程序經過int 0x80中斷向量指令實現從用戶態進入內核態,系統調用過程當中,eax寄存器負責傳遞系統調用號,ebx,ecx等其餘寄存器負責傳遞其餘參數,可是對於執行完了int 0x80以後,在內核態時:測試

  • 操做系統到底幹了哪些具體的工做?
  • 系統調用在內核態這一階段的過程是什麼?

基本概念的總結

  1. 中斷
    中斷分爲2種":atom

    • 可屏蔽中斷: I/O設備發出的全部的中斷請求(IRQ)都產生可屏蔽中斷。可屏蔽中斷產生兩種狀態:屏蔽的(masked)或非屏蔽的(unmasked);當中斷被屏蔽,則CPU控制單元就忽略它。
    • 非可屏蔽中斷:老是由CPU辨認。只有幾個危急事件引發非屏蔽中斷。
  2. 進程上下文
    通常來講,CPU在任什麼時候刻都處於如下三種狀況之一:spa

    • 運行於用戶空間,執行用戶進程;
    • 運行於內核空間,處於進程上下文;
    • 運行於內核空間,處於中斷上下文。

    應用程序經過系統調用陷入內核,此時處於進程上下文。現代幾乎全部的CPU體系結構都支持中斷。當外部設備產生中斷,向CPU發送一個異步信號,CPU調用相應的中斷處理程序來處理該中斷,此時CPU處於中斷上下文。
    在進程上下文中,能夠經過current關聯相應的任務。進程以進程上下文的形式運行在內核空間,能夠發生睡眠,因此在進程上下文中,可使做信號量(semaphore)。實際上,內核常常在進程上下文中使用信號量來完成任務之間的同步,固然也可使用鎖。
    中斷上下文不屬於任何進程,它與current沒有任何關係(儘管此時current指向被中斷的進程)。因爲沒有進程背景,在中斷上下文中不能發生睡眠,不然又如何對它進行調度。因此在中斷上下文中只能使用鎖進行同步,正是由於這個緣由,中斷上下文也叫作原子上下文(atomic context)(關於同步之後再詳細討論)。在中斷處理程序中,一般會禁止同一中斷,甚至會禁止整個本地中斷,因此中斷處理程序應該儘量迅速,因此又把中斷處理分紅上部和下部。
    相對於進程而言,就是進程執行時的環境。具體來講就是各個變量和數據,包括全部的寄存器變量、進程打開的文件、內存信息等。一個進程的上下文能夠分爲三個部分:用戶級上下文、寄存器上下文以及系統級上下文操作系統

    • 用戶級上下文: 正文、數據、用戶堆棧以及共享存儲區;
    • 寄存器上下文: 通用寄存器、程序寄存器(IP)、處理器狀態寄存器(EFLAGS)、棧指針(ESP);
    • 系統級上下文: 進程控制塊task_struct、內存管理信息(mm_struct、vm_area_struct、pgd、pte)、內核棧。
  3. 中斷上下文與進程上下文
    硬件經過觸發信號,致使內核調用中斷處理程序,進入內核空間。這個過程當中,硬件的 一些變量和參數也要傳遞給內核,內核經過這些參數進行中斷處理。所謂的「 中斷上下文」,其實也能夠看做就是硬件傳遞過來的這些參數和內核須要保存的一些其餘環境(主要是當前被打斷執行的進程環境)。中斷時,內核不表明任何進程運行,它通常只訪問系統空間,而不會訪問進程空間,內核在中斷上下文中執行時通常不會阻塞.設計


System_Call

# 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
    SAVE_ALL                // 保存系統寄存器信息
    GET_THREAD_INFO(%ebp)   // 獲取thread_info結構的信息
                    # system call tracing in operation / emulation
    testl $_TIF_WORK_SYSCALL_ENTRY,TI_flags(%ebp) // 測試是否有系統跟蹤
    jnz syscall_trace_entry   // 若是有系統跟蹤,先執行,而後再回來
    cmpl $(NR_syscalls), %eax // 比較eax中的系統調用號和最大syscall,超過則無效
    jae syscall_badsys  // 無效的系統調用 直接返回
syscall_call:
    call *sys_call_table(,%eax,4) // 調用實際的系統調用程序
syscall_after_call:
    movl %eax,PT_EAX(%esp)      // 將系統調用的返回值eax存儲在棧中
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  //檢測是否全部工做已完成
    jne syscall_exit_work           //工做已經完成,則去進行系統調用推出工做

restore_all:
    TRACE_IRQS_IRET         // iret 從系統調用返回

System_Call的基本處理流程爲:指針

  • 首先保存中斷上下文(SAVE_ALL,也就是CPU狀態,包括各個寄存器),判斷請求的系統調用是否有效
  • 而後call *sys_call_table(,%eax,4)經過系統查詢系統調用查到相應的系統調用程序地址,執行相應的系統調用
  • 系統調用完後,返回系統調用的返回值
  • 關閉中斷響應,檢測系統調用的全部工做是否已經完成,若是完成則進行syscall_exit_work(完成系統調用退出工做)
  • 最後restore_all(恢復中斷請求響應),返回用戶態

接下來對於中間比較關鍵的片斷代碼進行重點分析rest

System_Call中的關鍵部分

syscall_exit_work

syscall_exit_work:
    testl $_TIF_WORK_SYSCALL_EXIT, %ecx //測試syscall的工做完成
    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 //返回用戶空間,只須要檢查need_resched
END(syscall_exit_work)

該過程爲系統調用完成後如何退出調用的過程,其中比較重要的是work_pending,詳見以下:code

work_pending:
    testb $_TIF_NEED_RESCHED, %cl  // 判斷是否須要調度
    jz work_notifysig   // 不須要則跳轉到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  // 是否全部工做都已經作完
    jz restore_all              // 是則退出
    testb $_TIF_NEED_RESCHED, %cl // 測試是否須要調度
    jnz work_resched            // 從新執行調度代碼

work_notifysig:             // 處理未決信號集
#ifdef CONFIG_VM86
    testl $X86_EFLAGS_VM, PT_EFLAGS(%esp) // 判斷是否在虛擬8086模式下
    movl %esp, %eax
    jne work_notifysig_v86      // 返回到內核空間
1:
#else
    movl %esp, %eax
#endif
    TRACE_IRQS_ON  // 啓動跟蹤中斷請求響應
    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  // 將信號投遞到進程
    jmp resume_userspace  // 恢復用戶空間

#ifdef CONFIG_VM86
    ALIGN
work_notifysig_v86:
    pushl_cfi %ecx          # save ti_flags for do_notify_resume
    call save_v86_state     // 保存VM86模式下的CPU信息
    popl_cfi %ecx
    movl %eax, %esp
    jmp 1b
#endif
END(work_pending)

首先是work_pending這段彙編邏輯:

  • 檢查是否須要進行調度
  • 若是須要,進行進程調度,而後再次進行判斷
  • 若是無需調度,那就去執行work_notifying,處理信號

而後是work_notifysig的這段彙編邏輯:

  • 先檢查是不是虛擬8086模式,即8086保護模式
  • 若是是,那麼須要先保存虛模式下的狀態信息
  • 而後跳轉到以前的代碼繼續執行
  • 將信號投遞到進程
  • 恢復用戶空間

最後返回系統調用


流程圖


實驗截圖



個人總結

系統調用中斷本質上是一個保存當前工做狀態,而後處理,最後返回而且恢復進程的過程.


參考資料

  • Understanding The Linux Kernel, the 3rd edtion
  • Linux內核設計與實現,第三版,Robert Love, 機械工業出版社

署名信息

吳欣偉 原創做品轉載請註明出處:《Linux內核分析》MOOC課程:http://mooc.study.163.com/course/USTC-1000029000
相關文章
相關標籤/搜索