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