張建幫 原創作品轉載請註明出處 《Linux內核分析》MOOC課程http://mooc.study.163.com/course/USTC-1000029000
關於linux系統調用的具體流程,這一篇文章其實講得比較清楚:鏈接在這裏 。但是它只敘述了整體的過程,沒有深入下去,因此這裏我們將以
getpid()
系統調用爲例,分析其中具體的函數調用的實現。這裏我們放上之前文章的系統調用過程的圖片:
庫函數中的
getpid()
調用int $0x80
指令後,系統就從用戶態切換到內核態,並跳轉到了0x80
中斷向量號所對應的中斷向量服務程序system_call
,該服務程序的代碼位置是/linux-3.18.6/arch/x86/kernel/entry_32.S
文件中的 490 行處的ENTRY(system_call)
處。由於這部分的彙編代碼涉及到比較多的知識,所以下面的代碼是作了一些精簡,只保留最核心的那部分代碼:(引用自這裏)
/* 代碼文件路徑:/linux-3.18.6/arch/x86/kernel/entry_32.S */
# system call handler stub
# 系統調用匯編代碼的起點
ENTRY(system_call)
# step 1:
SAVE_ALL # 進入中斷處理程序前,保存內核態下的現場
......
cmpl $(NR_syscalls), %eax # 比較傳入的系統調用號是否大於最大的系統調用號
jae syscall_badsys # 如果傳入的系統調用號太大,則跳轉到處理無效系統調用號的處理程序
# step 2:
syscall_call:
call *sys_call_table(,%eax,4) # 根據 eax 中傳入的系統調用號來調用對應的系統調用服務程序,在我們的例子中就是調用 sys_getpid,4表示的是sys_call_table中每個元素的大小是4個字節
# step 3:
syscall_after_call:
movl %eax,PT_EAX(%esp) # store the return value, 保存系統調用後返回的值到 eax 中
# step 4:
syscall_exit: # 會檢測要不要執行syscall_exit_work,正常情況會有一些需要處理的工作,比如當前進程有一些信號要處理,系統需要進行調度
......
movl TI_flags(%ebp), %ecx
testl $_TIF_ALLWORK_MASK, %ecx # current->work
jne syscall_exit_work
# step 5:
restore_all:
restore_nocheck:
RESTORE_REGS 4 # 恢復系統調用前的上下文
irq_return:
INTERRUPT_RETURN # 這是一個宏定義,本質上是一條 iret 指令
# system_call 結束位置
......
ENDPROC(system_call)
由於代碼比較長,所以我們在上面的代碼中加入了 “step n” 的註釋,將代碼分成 5 個步驟進行解析。下面通過一個流程圖對內核源碼中系統調用的處理過程進行一個概要性地描述。
引用自這裏
從上圖中我們可以看到:系統調用完成後並不是立刻返回到系統中斷處理程序,而是首先要判斷在執行系統調用函數的函數中,有沒有信號要處理,要不要進行調度等,這些都處理完成後,才返回到上一級的的中斷處理程序中。