生活随笔
收集整理的這篇文章主要介紹了
kernel 3.10内核源码分析--中断--中断和异常返回流程
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
一、問題
1、內核調度與中斷/異常/系統調用的關系如何?
2、信號處理與中斷/異常/系統調用的關系如何?
3、內核搶占與中斷/異常/系統調用的關系如何?
4、內核線程的調度有何特別之處?中斷/異常/系統調用返回時,內核線程會發生調度嗎?
?這些問題都需要分析清楚中斷/異常的返回流程,才能解答。
二、中斷/異常的返回流程
1、中斷/異常的返回內核軟件流程
中斷、異常(包括系統調用)和fork返回的處理流程如下:
?
關鍵點:
1)中斷返回和異常返回的流程基本一致,差別主要在于異常返回時,需要先關一次中斷。因為Linux實現中,異常使用的是陷阱門,通過時不會自動關中斷;而中斷使用的是中斷門,通過時會自動關中斷。
2)中斷/異常(包括系統調用)返回時,是進行調度(schedule)的重要時機點,其中,中斷(時鐘中斷)返回時調度依賴的最主要的時機點,時鐘中斷處理函數中不會直接進行調度,只是根據相應的調度算法,決定是否需要調度,以及調度的next task,如果需要調度,則設置NEED_RESCHED標記。調度(schedule)的實際執行是在中斷返回的時候,檢查NEED_RESCHED標記,如果設置則進行調度。
3)信號處理是在當前進程從內核態返回用戶態時進行的,在發生中斷、異常(包括系統調用)、或fork時,都有可能從內核態返回用戶態,都是處理信號的時機。注意:只有current進程的信號才能在此時得到處理。其它非正在運行的進程的信號無法處理。
4)關于內核搶占。中斷/異常發生在內核態時,也就是說中斷/異常返回時,需要返回內核態,走resume_kernel流程,此時,如果內核支持內核搶占,則此時是個關鍵的調度時機點,如果內核不支持搶占,則不會發生調度。也就是說:如果當前進程上下文處于內核態,當不支持內核搶占時,則無論進程的優先級和時間片如何,都是不能發生調度的,只能在返回用戶態時,才能發生調度。從這點可以看出,當不支持內核搶占時,Linux的實時性很差(開啟內核搶占后稍好),當在內核態(中斷、軟中斷、其它內核流程)執行時間或流程太長時,可能導致進程調度饑餓,極端情況下,當在內核態發生死鎖時,會直接導致整個系統因無法調度而死鎖,當然針對這種情況(softlockup),內核提供了專門的watchdog機制來檢測。
5)關于內核線程的調度,跟普通線程相比,從原理和機制上看,沒有特別之處。但關鍵的不同在于:內核線程始終運行在內核態,當沒有開啟內核搶占時,設想當一個內核線程被中斷/異常打斷,此時從中斷/異常返回時會發生調度嗎?答案是不會,因為當前進程的上下文處于內核態,在沒有開啟內核搶占的情況下,是不會發生調度行為的,除非該內核線程主動調用schedule()釋放CPU控制權。也就是說,內核線程觸發主動調用schedule,否則會一直占用CPU。所以在編寫內核線程時,需要在相關任務處理結束后,主動調用schedule,這點需要注意。
?
2、中斷/異常返回時硬件完成的處理流程
中斷或異常返回時,必然會執行iret指令,然后將控制器交回給之前被中斷打斷的進程,硬件自動完成如下操作:
1)從當前棧(內核棧)中彈出cs、eip和eflag,并load到相應的寄存器中寄存器。(如之前有硬件錯誤碼入棧,需要先彈出這個錯誤碼)。?
2)權限檢查。比對ISR的CPL是否等于cs中的低兩位的值。如果是,iret終止返回;否則,轉入下一步。?
3)從當前棧(內核棧)中彈出之前壓入的用戶態堆棧相關的ss和esp,并load到相應寄存器,至此,即完成了從內核棧到用戶棧的切換。?
4)后續處理。主要包括:檢查ds、es、fs及gs段寄存器,如果其中一個寄存器包含的選擇符是一個段描述符,并且其DPL值小于CPL,那么,清相關的段寄存器。目的是為了防止用戶態的程序利用內核以前所用的段寄存器,以防止惡意用戶程序利用其訪問內核地址空間。
三、代碼分析
中斷、異常(包括系統調用)、fork返回時,會分別跳轉到entry_32.S匯編代碼中的ret_from_intr、ret_from_exception、ret_from_fork標號處執行。相應代碼分析如下:
1、中斷返回(ret_from_intr)
1)主流程
點擊(此處)折疊或打開
/*從中斷返回*/
ret_from_intr:
/*將當前進程的thread_info結構體的指針存入%ebp幀寄存器中*/
GET_THREAD_INFO(%ebp)
#ifdef CONFIG_VM86
/* 取中斷之前寄存器EFLAGS的高16位和段寄存器CS的內容構成的32位長整數放入eax中,其目的是檢驗:
?* 1.中斷之前CPU是否運行于VM86模式
?* (EFLAGS的高16位中的第二位用來標識CPU運行在VM86模式下)
?* 2.中斷之前CPU運行于用戶空間還是系統空間
?*(CS的低兩位代表著中斷發生時CPU的運行級別CPL。若是CS的低兩位為1,表示中斷發生于用戶空間,)
????????*/
movl PT_EFLAGS(%esp), %eax????# mix EFLAGS and CS
movb PT_CS(%esp), %al
andl $(X86_EFLAGS_VM | SEGMENT_RPL_MASK), %eax
#else
/*
* We can be coming here from child spawned by kernel_thread().
*/
movl PT_CS(%esp), %eax
andl $SEGMENT_RPL_MASK, %eax
#endif
// 判斷是否返回用戶態或者v8086模式,如果不是,則轉入resume_kernel,否則進入resume_userspace
cmpl $USER_RPL, %eax
jb resume_kernel????# not returning to v8086 or userspace 2)返回用戶態 點擊(此處)折疊或打開
// 如果是返回用戶態
ENTRY(resume_userspace)
LOCKDEP_SYS_EXIT
/*
* 前面已經關了中斷了,這次再關的原因是,還有其它流程會自己跳轉
* 到這里,比如system_call
*/
?????DISABLE_INTERRUPTS(CLBR_ANY)????# make sure we don 3)調度和信號處理: 點擊(此處)折疊或打開
work_pending:
????# 返回用戶態時,只需要判斷need_resched是否置位,不需要判斷preempt_count
????# 如果need_resched置位,則發生調度,否則跳轉到work_notifysig
????testb $_TIF_NEED_RESCHED, %cl
????# 進行信號處理
????jz work_notifysig
work_resched:
????# 需要調度,調用schedule函數
????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 irq功能
????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
????# 如果有,再次檢查是否需要調度,如果需要,則再次跳轉到work_resched進行重新調度
????testb $_TIF_NEED_RESCHED, %cl
????jnz work_resched????
????# 如果不需要調度,則繼續到work_notifysig,進行信號處理了,也就是說如果這里發生調度,也是有機會處理信號的。
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
1:
#else
????movl %esp, %eax
#endif
????# 開trace
????TRACE_IRQS_ON
????# 開中斷(前面關了),意味著信號處理是開中斷執行的,還是中斷優先級高
????ENABLE_INTERRUPTS(CLBR_NONE)
????# 再次判斷CS低兩位,當為1時,表示中斷/異常之前處于用戶態,否則為內核態,據此再次確認是否需要返回內核態
????movb PT_CS(%esp), %bl
????andb $SEGMENT_RPL_MASK, %bl
????cmpb $USER_RPL, %bl
????# 返回內核態
????jb resume_kernel
????# edx清零
????xorl %edx, %edx
????# 調用C函數,其中進行通知鏈即信號的處理
????call do_notify_resume
????# 信號處理完后,重新跳轉到resume_userspace,此時如果沒有新的信號產生,則會在前面就通過restore_all恢復了,不會再到這里了
????jmp resume_userspace
#ifdef CONFIG_VM86
????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
#endif
END(work_pending) 4)返回內核態
點擊(此處)折疊或打開
/*如果配置了內核搶占*/
#ifdef CONFIG_PREEMPT
ENTRY(resume_kernel)
/*
* 前面已經關了中斷了,這次再關的原因是,還有其它流程會自己跳轉
* 到這里,比如system_call
*/
DISABLE_INTERRUPTS(CLBR_ANY)
/*判斷是否可以搶占*/
cmpl $0,TI_preempt_count(%ebp)????# non-zero preempt_count ?
/*搶占計數非0,不能搶占,則不產生調度,直接恢復上下文*/
jnz restore_all
/*可以搶占,則需要調度*/
need_resched:
/*判斷need_resched是否被設置*/
movl TI_flags(%ebp), %ecx????# need_resched set ?
testb $_TIF_NEED_RESCHED, %cl
/*沒設置need_resched,則不需要調度,直接恢復上下文*/
jz restore_all
/*
*判斷發生中斷時(因為PT_EFLAGS(%esp)中保存的是進入中斷時的EFLAGS值,這是由CPU硬件自動壓棧的,中斷走中斷門,會自動關中斷,異常走陷阱門,不自動關中斷)是否關中斷了,
*如果關了,表示是異常上下文(Fixme:應該是中斷吧),則直接恢復上下文。
*/
testl $X86_EFLAGS_IF,PT_EFLAGS(%esp)????# interrupts off (exception path) ?
jz restore_all
/*如果沒關中斷,表示為中斷上下文?則調用preempt_schedule_irq,進行調度*/
call preempt_schedule_irq
jmp need_resched
END(resume_kernel) 2、異常返回(ret_from_exception)
異常返回跟中斷返回流程基本一致,差別主要在于異常返回時,需要先關一次中斷。因為Linux實現中,異常使用的是陷阱門,通過時不會自動關中斷;而中斷使用的是中斷門,通過時會自動關中斷。
點擊(此處)折疊或打開
/*從異常返回*/
ret_from_exception:
/*
* 這里為什么要關中斷?而從中斷返回不需要? 因為異常走的是陷阱門,
* 默認是不關中斷執行的,而中斷走的是中斷門,默認是關中斷執行的?
*
*/
/*關中斷*/
preempt_stop(CLBR_ANY)
/*從中斷返回*/
ret_from_intr:
... 3、fork返回(
ret_from_fork)
fork返回的后半部分處理跟異常/中斷返回一致,前面一部分有單獨的處理:包括調用schedule_tail和跳轉syscall_exit進行相關處理
點擊(此處)折疊或打開
#fork返回,單獨處理
ENTRY(ret_from_fork)
CFI_STARTPROC
pushl_cfi %eax
#進行調度收尾處理,包括回收DEAD(X)狀態的進程
call schedule_tail
#獲取thread_info放入ebp中
GET_THREAD_INFO(%ebp)
popl_cfi %eax
#重設kernel eflags
pushl_cfi $0x0202????# Reset kernel eflags
popfl_cfi
#跳轉到syscall_exit進行系統調用退出相關的處理。
jmp syscall_exit
CFI_ENDPROC
END(ret_from_fork) 原文地址: http://blog.chinaunix.net/uid-14528823-id-4761421.html
總結
以上是生活随笔為你收集整理的kernel 3.10内核源码分析--中断--中断和异常返回流程的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。