《LINUX3.0内核源代码分析》第二章:中断和异常 【转】
轉自:http://blog.chinaunix.net/uid-25845340-id-2982887.html
摘要:第二章主要講述linux如何處理ARM cortex A9多核處理器的中斷、異常。介紹了中斷向量表的入口、通用的中斷處理代碼、中斷和軟中斷、延遲處理、中斷異常的返回過程。
第二章內容較多,會分幾個部分講述。本部分主要講進入、退出中斷的過程,這部分代碼涉及的都是匯編部分。
?
法律聲明:《LINUX3.0內核源代碼分析》系列文章由謝寶友(scxby@163.com)發表于http://xiebaoyou.blog.chinaunix.net,文章中的LINUX3.0源代碼遵循GPL協議。除此以外,文檔中的其他內容由作者保留所有版權。謝絕轉載。
?
本連載文章并不是為了形成一本適合出版的書籍,而是為了向有一定內核基本的讀者提供一些linux3.0源碼分析。因此,請讀者結合《深入理解LINUX內核》第三版閱讀本連載。
?
由于我的主要工作不是BSP,對CPU體系結構不算太熟悉。如果非要說熟悉哪種CPU的話,應該是對MIPS熟悉一點。ARM方面純粹是臨陣磨槍,為了寫本系列文章,前兩個月臨時看了一下相關書籍。如有不清楚或者錯誤的地方,敬請大家指出,先謝過了^-^。
?
請讀者先看看《ARM嵌入式開發》第9章,對ARM的6種異常有所了解。并明白在進入中斷和異常時,硬件都完成了哪些事情。
1.1.1??????中斷向量和簡單中斷處理
ARM中斷向量表在entry-armv.S中,如下:
__vectors_start:
?ARM(??????????swi???SYS_ERROR0????)/* reset異常?*/
?THUMB(????svc???#0??????????????)
?THUMB(????nop?????????????????????)
????W(b)??????????vector_und + stubs_offset/*?未定義指令?*/
????W(ldr)???????pc, .LCvswi + stubs_offset/*?系統調用?*/
????W(b)??????????vector_pabt + stubs_offset/*?指令預取異常?*/
????W(b)??????????vector_dabt + stubs_offset/*?數據訪問中止異常?*/
????W(b)??????????vector_addrexcptn + stubs_offset/*?保留?*/
????W(b)??????????vector_irq + stubs_offset/*?中斷?*/
????W(b)??????????vector_fiq + stubs_offset/*?快速中斷?*/
?
????.globl????????__vectors_end
__vectors_end:
?
不象MIPS,ARM中斷向量表中每一個中斷向量只能存儲一條指令,因此必須使用一條跳轉指令,跳轉到各自的處理程序。當然,為了提高快速中斷的處理速度,可以將它的處理代碼直接跟隨在中斷向量表后面。但是linux沒有這樣實現。
在這8個向量中,?vector_addrexcptn和vector_fiq比較簡單:
vector_fiq:
????disable_fiq????????/*?簡單的禁止fiq,這樣,中斷處理退回后,不會再次產生fiq中斷了。也就是說,FIQ中斷只可能產生一次。?*/
????subs?pc, lr, #4???????????/* lr指向當前異常地址+8的地方,這里將其減去4,即是退出異常時,要返回的地址。這里直接返回。?*/
?
/*=============================================================================
?* Address exception handler
?*-----------------------------------------------------------------------------
?* These aren't too critical.
?* (they're not supposed to happen, and won't happen in 32-bit data mode).
?*/
/**
?*?根據注釋,這里是處理地址異常,它不但不重要,而且不大可能產生。因此就是一個死循環,將系統掛死在這里。
?*?根據《ARM嵌入式系統開發》所述,這是一個保留異常。可能真的不大可能發生。
?*/
vector_addrexcptn:
????b???????vector_addrexcptn
?
reset異常更簡單,它僅僅是模擬調用一次SYS_ERROR0,但這應該是內核初始化完成之后,才這樣簡單。在flash上的復位異常是整個初始化的入口,應該非常復雜。
ARM(???swi???SYS_ERROR0????)/* reset異常,簡單的調用SYS_ERROR0系統調用即可?*/
?THUMB(????svc???#0??????????????)/*?應該不會運行到這里,呵呵,這僅僅是我的猜想?*/
?THUMB(????nop?????????????????????)
?
這8個中斷異常入口,除系統調用異常外,都是使用b指令進行跳轉。系統調用異常使用是這樣的:
W(ldr)??pc, .LCvswi + stubs_offset
由于系統調用異常的代碼編譯在其他文件中,其入口地址與異常向量相隔較遠,使用b指令無法跳轉過去。?因此將其地址存放到LCvswi中,并從內存地址中加載其入口地址。這樣,系統調用的速度稍微慢一點。
?
1.1.2??????從匯編跳轉到C代碼
未定義指令異常、指令預取異常、數據訪問中止異常、中斷的處理代碼分別是vector_und、vector_pabt、vector_dabt和vector_irq。這幾個函數是由以下代碼生成的:
vector_stub?????????irq, IRQ_MODE, 4
vector_stub?????????dabt, ABT_MODE, 8
vector_stub?????????pabt, ABT_MODE, 4
vector_stub?????????und, UND_MODE
?
我們以vector_stub????irq, IRQ_MODE, 4為例,看看vector_stub生成了什么代碼:
?
?????/**
??????*?生成通用中斷、異常處理代碼的宏。
??????* correction用于調整lr的值。這是因為進入異常時,pc指針是發生異常時的指針后面8個字節或者12個字節處。
??????*?不同的異常需要跳轉到不同的返回地址。有的需要重新執行指令,有的則需要跳到下一條指令處。
??????*/
????.macro?????vector_stub, name, mode, correction=0
????/*?將異常入口強制進行32字節對齊,32字節是一個緩存行的大小。這應當是出于性能的考慮。?*/
????.align????????5
?
vector_\name:
????/*?需要調整返回值,則遞減lr寄存器?*/
????.if \correction?
????sub???lr, lr, #\correction
????.endif
?
????@
????@ Save r0, lr_?(parent PC) and spsr_
????@ (parent CPSR)
????@
????/**
?????*?將r0,lr保存到堆棧中。這里并沒有移動堆棧指針。
?????*?這是因為:每種處理器模式都有自己的堆棧。接下來系統會切換到svc模式,將堆棧切換到每個任務的系統堆棧去。
?????*?執行后,[sp] = r0, [sp+4]=lr,這里保存r0和lr是因為后面要使用這兩個寄存器,即這兩個寄存器會被破壞。
?????*?請注意:中斷和異常并不會保存所有寄存器。
?????*/
????stmia????????sp, {r0, lr}???????????????????@ save r0, lr
????/**
?????* spsr是異常發生前的狀態寄存器,退出異常后,需要根據它恢復現場,因此需要將它保存起來。
?????*?首先將它裝載到lr寄存器,再將它存儲到[sp+8]處。
?????*/
????mrs??lr, spsr
????str????lr, [sp, #8]???????????????????@ save spsr
?
????@
????@ Prepare for SVC32 mode.??IRQs remain disabled.
????@
????/**
?????*?以下三句,是準備將處理器模式設置為SVC32模式。這樣,當前堆棧也會切換到SVC32模式下的堆棧。
?????*/
????mrs??r0, cpsr
????eor???r0, r0, #(\mode ^ SVC_MODE | PSR_ISETSTATE)
????msr??spsr_cxsf, r0
?
????@
????@ the branch table must immediately follow this code
????@
????/**
?????* lr中保存了異常前的狀態,與0x0f and后,可以得到異常前的處理器模式。
?????*/
????and??lr, lr, #0x0f
????/* 1f就是宏生成的代碼后面的跳轉表,這里根據異常前的處理器模式,決定跳轉到哪一個處理代碼?*/
?THUMB(????adr???r0, 1f???????????????????????????)
?THUMB(????ldr????lr, [r0, lr, lsl #2]?????????)
???/* sp是SVC32模式下的堆棧指針,這里將它移到r0中,就可以作為C函數的第一個參數,即C函數中的pt_regs參數?*/
????mov?r0, sp????????????????
????/* pc指針此時指向了1f,即跳轉表,因此將它加上lr<<2,就可以按處理器模式進行跳轉了?*/
?ARM(??????????ldr????lr, [pc, lr, lsl #2]?????????)
???/*?這條指令是從異常返回,由于我們修改了spsr寄存器,因此會進入SVC32模式,并不是真的從異常返回了?*/
????movs?????????pc, lr????????????????????????????@ branch to handler in SVC mode
ENDPROC(vector_\name)
?
????.align????????2
????@ handler addresses follow this label
1:
????.endm
?
分析完vector_stub宏代碼,我們再看看中斷處理函數是如何生成的:
/**
?*?借助宏vector_stub生成vector_irq主體代碼
?*/
vector_stub?????irq, IRQ_MODE, 4
??????????????/**
??????????????*?下面的跳轉表必須緊跟在vector_stub宏后面,參見前文對vector_stub的分析。
???????????????*/
????/*?從用戶態進入中斷的處理函數?*/
????.long?????????__irq_usr???????????????????@??0??(USR_26 / USR_32)
????/*?錯誤,不應該從FIQ狀態進入IRQ狀態?*/
????.long?????????__irq_invalid???????????????????????@??1??(FIQ_26 / FIQ_32)
????.long?????????__irq_invalid???????????????????????@??2??(IRQ_26 / IRQ_32)
????/*?從SVC模式進入中斷?*/
????.long?????????__irq_svc???????????????????@??3??(SVC_26 / SVC_32)
????.long?????????__irq_invalid???????????????????????@??4
????.long?????????__irq_invalid???????????????????????@??5
????.long?????????__irq_invalid???????????????????????@??6
????.long?????????__irq_invalid???????????????????????@??7
????.long?????????__irq_invalid???????????????????????@??8
????.long?????????__irq_invalid???????????????????????@??9
????.long?????????__irq_invalid???????????????????????@??a
????.long?????????__irq_invalid???????????????????????@??b
????.long?????????__irq_invalid???????????????????????@??c
????.long?????????__irq_invalid???????????????????????@??d
????.long?????????__irq_invalid???????????????????????@??e
?????????.long?????????__irq_invalid???????????????????????@??f
?
接下來我們看看__irq_invalid,這段代碼一般情況不應當被調用。
__irq_invalid:
????/**
?????*?將所有寄存器保存到堆棧中,并將BAD_IRQ作為錯誤原因寫入r1寄存器。
?????*/
????inv_entry BAD_IRQ
????/**
?????*?跳轉到通用錯誤處理
?????*/
????b???????common_invalid
ENDPROC(__irq_invalid)
?
common_invalid代碼如下:
common_invalid:
????/**
?????*?如果需要棧幀,就將fp設置為0,這樣在進行堆棧回溯時,就可以知道這里的堆棧是一個中斷的棧幀了。
?????*/
????zero_fp
?
????/**
?????* r0保存的是中斷棧開始的地方,將中斷前的r0-r2寄存器現場恢復到r4-r6中。
?????*/
????ldmia????????r0, {r4 - r6}
????/**
?????*?調整r0,使其指向中斷現場的PC
?????*/
????add??r0, sp, #S_PC?????????????@ here for interlock avoidance
????mov?r7, #-1?????????????????????????@??""???""????""????????""
????/**
?????*?將中斷前的r0存到sp中。
?????*/
????str????r4, [sp]???????????????@ save preserved r0
????/*?這里沒有看清楚,飄過。清楚的同學發一個郵件給我scxby@163.com */
????stmia????????r0, {r5 - r7}?????????????????@ lr_,
?????????????????????????????????????????@ cpsr_, "old_r0"
?
????/* sp是SVC32模式上的堆棧地址,指向pt_regs,即中斷前的寄存器現場?*/
????mov?r0, sp
????/**
?????*?跳轉到C處理函數,這里編譯腳本應當有處理,這樣才能確保bad_mode與當前指令相近。否則b指令跳不過去。
?????*/
????b???????bad_mode
ENDPROC(__und_invalid)
?
1.1.1.1?????????從用戶態進入中斷
_irq_usr函數的第一步是保存用戶態寄存器現場到svc32堆棧中,這是通過調用usr_enry來實現的:
????.macro?????usr_entry
?UNWIND(.fnstart??????)
?UNWIND(.cantunwind??????)????????@ don't unwind the user space
???/**
????*?將svc32堆棧指針向低地址方向移動一個pt_regs結構大小,用于保存寄存器現場。
????*/
????sub???sp, sp, #S_FRAME_SIZE
????/**
?????*?向svc32堆棧中保存寄存器現場。
?????*/
?ARM(??????????stmib????????sp, {r1 - r12}?????)
?THUMB(????stmia????????sp, {r0 - r12}?????)
?
????/**
?????* r0是中斷棧指針,從其中取出中斷前的r0-r2現場放到r1-r4中。
?????*/
????ldmia????????r0, {r1 - r3}
????add??r0, sp, #S_PC?????????????@ here for interlock avoidance
????mov?r4, #-1?????????????????????????@??""??""?????""????????""
?
????/**
?????*?從中斷棧中取出真實的r0存放到pt_regs->r0中。
?????*/
????str????r1, [sp]???????????????@ save the "real" r0 copied
?????????????????????????????????????????@ from the exception stack
?
????@
????@ We are now ready to fill in the remaining blanks on the stack:
????@
????@??r2 - lr_, already fixed up for correct return/restart
????@??r3 - spsr_
????@??r4 - orig_r0 (see pt_regs definition in ptrace.h)
????@
????@ Also, separately save sp_usr and lr_usr
????@
????/**
?????*?將中斷異常棧中取出中斷前ARM_pc、ARM_cpsr保存到svc32棧中。
?????*/
????stmia????????r0, {r2 - r4}
????/**
?????*?將棧指針和lr壓入棧
?????*/
?ARM(??????????stmdb???????r0, {sp, lr}^??????????????????????????)
?THUMB(????store_user_sp_lr r0, r1, S_SP - S_PC???????)
?
????@
????@ Enable the alignment trap while in kernel mode
????@
????alignment_trap r0
?
????@
????@ Clear FP to mark the first stack frame
????@
????/**
?????*?將fp設置為0,這樣可以標示一個中斷棧幀。
?????*/
????zero_fp
????.endm
?
中斷處理的主要過程如下:
/**
?*?從用戶態進入中斷。
?*/
__irq_usr:
????/**
?????*?將寄存器現場保存起來。
?????*/
????usr_entry
????/**
?????*?對低版本的ARM核來說,用戶態無法實現原子比較交換。如果用戶態在處理原子比較交換的過程中發生中斷,需要特殊處理,略過。
?????*/
????kuser_cmpxchg_check
?
????/**
?????*?如果打開了IRQSOFF_TRACER檢測開關,則在這里記錄下關中斷的時間。這在實時系統中比較有用。
?????*?請記住,系統運行到這里,仍然是處于關中斷狀態的。
?????*/
#ifdef CONFIG_IRQSOFF_TRACER
????bl??????trace_hardirqs_off
#endif
?
????/**
?????*?根據當前sp指針,將該指針最右邊13位清0,獲得當前任務的thread_info。
?????*/
????get_thread_info tsk
#ifdef CONFIG_PREEMPT
????/**
?????*?遞增任務的搶占計數
?????*/
????ldr????r8, [tsk, #TI_PREEMPT]????????????@ get preempt count
????add??r7, r8, #1????????????????????@ increment it
????str????r7, [tsk, #TI_PREEMPT]
#endif
?
????irq_handler
#ifdef CONFIG_PREEMPT
????/**
?????*?獲得當前的搶占計數
?????*/
????ldr????r0, [tsk, #TI_PREEMPT]
????/**
?????*?并將r8中的值保存回去。相當于將前一步遞增的搶占計數減回去了。
?????*/
????str????r8, [tsk, #TI_PREEMPT]
????/**
?????* r0,r7是調用irq_handler前后的搶占計數,這里進行比較,是防止驅動的ISR程序沒有配對操作搶占計數導致系統錯誤。
?????*/
????teq???r0, r7
????/**
?????*?如果搶占計數被破壞,則強制寫入0.
?????*/
?ARM(??????????strne?????????r0, [r0, -r0]???????)
?THUMB(????movne??????r0, #0?????????????????)
?THUMB(????strne?????????r0, [r0]??????)
#endif
?
????/**
?????*?從中斷退回用戶態。
?????*/
????mov?why, #0
????b???????ret_to_user_from_irq
?UNWIND(.fnend??????????????????)
ENDPROC(__irq_usr)
?
在沒有配置MULTI_IRQ_HANDLER?的情況下,irq_handler的邏輯很簡單,就是簡單的調用arch_irq_handler_default。
????.macro?????arch_irq_handler_default
????get_irqnr_preamble r5, lr
????/**
?????*?將中斷號讀取到r0寄存器。
?????*/
1:??????????get_irqnr_and_base r0, r6, r5, lr
????/**
?????*?如果還存在中斷,就將sp作為第二個參數,調用asm_do_IRQ。sp目前指向pt_regs。
?????*/
????movne??????r1, sp
????@
????@ routine called with r0 = irq number, r1 = struct pt_regs *
????@
????/**
?????*?這里將lr設置為get_irqnr_and_base的第二條指令,因為第二次循環時,不必執行其第一條指令(加載寄存器基址)
?????*/
????adrne????????lr, BSYM(1b)
????/**
?????*?將中斷號、pt_regs(中斷前的寄存器現場)傳遞給asm_do_IRQ。
?????*?請注意,當asm_do_IRQ返回時,會返回到get_irqnr_and_base處,這里相當于是一個循環處理,直到所有中斷都已經處理完畢才退出循環。
?????*/
????bne??asm_do_IRQ
?
#ifdef CONFIG_SMP
????/*
?????* XXX
?????*
?????* this macro assumes that irqstat (r6) and base (r5) are
?????* preserved from get_irqnr_and_base above
?????*/
????/**
?????*?這里是從寄存器中讀取ipi標志
?????*/
????ALT_SMP(test_for_ipi r0, r6, r5, lr)
????ALT_UP_B(9997f)
????movne??????r1, sp
????/**
?????*?同理,這里也是將返回地址設置為ALT_SMP的第二條指令,構造成一個循環。
?????*/
????adrne????????lr, BSYM(1b)
????/**
?????*?只要存在IPI就調用do_IPI,并循環直到處理完所有IPI。
?????*/
????bne??do_IPI
?
#ifdef CONFIG_LOCAL_TIMERS
????/**
?????*?同理,這里循環處理多核系統中的本地時鐘中斷。
?????*/
????test_for_ltirq r0, r6, r5, lr
????movne??????r0, sp
????adrne????????lr, BSYM(1b)
????bne??do_local_timer
#endif
#endif
9997:
????.endm
?
至此,我們已經將匯編部分分析完畢。在跳轉到C代碼前,匯編代碼會將中斷前的現場保存到堆棧中,并形成一個pt_regs結構傳給C函數。最終,會循環調用asm_do_IRQ、do_IPI、do_local_timer。僅僅在多核下,才可能處理IPI和local_timer。
?
1.1.1.1?????????退回用戶態
從中斷返回到用戶態是由ret_to_user_from_irq進行處理的,在恢復寄存器現場前,需要處理搶占、檢查信號等等。
/**
?*?從中斷返回用戶態,在軟中斷或者中斷處理函數退出時,系統確保已經關閉了中斷。
?*/
ENTRY(ret_to_user_from_irq)
????/**
?????*?從任務的TI_FLAGS標志判斷是否需要處理搶占或者信號。
?????*/
????ldr????r1, [tsk, #TI_FLAGS]
????tst????r1, #_TIF_WORK_MASK
????/**
?????*?處理搶占或者信號
?????*/
????bne??work_pending
????/**
?????*?運行到這里,說明沒有搶占或者信號需要處理,或者已經處理完畢,開始退回用戶態了。
?????*/
no_work_pending:
????/**
?????*?退回用戶態時,必然會打開中斷,因此這里記錄下打開中斷的事實,供調試用。
?????*/
#if defined(CONFIG_IRQSOFF_TRACER)
????asm_trace_hardirqs_on
#endif
????/* perform architecture specific actions before user return */
????/**
?????*?在返回用戶態前,處理各個體系結構的鉤子,對我們分析的單板來說,沒有鉤子需要處理。
?????*/
????arch_ret_to_user r1, lr
?
????/**
?????*?恢復寄存器現場,并切回用戶態。
?????*/
????restore_user_regs fast = 0, offset = 0
ENDPROC(ret_to_user_from_irq)
?
在切換回用戶態前,需要處理搶占和信號:
work_pending:
????/**
?????*?檢查任務的_TIF_NEED_RESCHED,如果置位,則說明需要處理任務搶占,在這里調度到高優先級任務。
?????*/
????tst????r1, #_TIF_NEED_RESCHED
????bne??work_resched
????/**
?????*?接著處理信號。
?????*/
????tst????r1, #_TIF_SIGPENDING|_TIF_NOTIFY_RESUME
????/**
?????*?沒有信號需要處理,則跳轉到no_work_pending并退回用戶態。
?????*/
????beq??no_work_pending
????mov?r0, sp????????????????????????????????????@ 'regs'
????mov?r2, why?????????????????????????????????@ 'syscall'
????tst????r1, #_TIF_SIGPENDING?????????????@ delivering a signal?
????movne??????why, #0????????????????????????????????@ prevent further restarts
????/**
?????*?這里處理信號
?????*/
????bl??????do_notify_resume
????/**
?????*?然后重新關中斷并判斷是否有更多任務需要處理。
?????*/
????b???????ret_slow_syscall???????????????@ Check work again
?
/**
?*?這里處理搶占,注意這里可以調用schedule,而不用調用preempt_schedule。這是有原因的。
?*?另外,這個標號也不能隨意移到其他地方。因為調用schedule后,流程會轉到ret_slow_syscall。
?* ret_slow_syscall上會關中斷,然后將中斷、異常返回流程重新處理一次。
?*?需要關中斷的原因,是schedule函數會強制將中斷打開。
?*/
work_resched:
????bl??????schedule
/*
?* "slow" syscall return path.??"why" tells us if this was a real syscall.
?*/
ENTRY(ret_to_user)
ret_slow_syscall:
????disable_irq???????????????????????????????????@ disable interrupts
?
?
1.1.1.2?????????從svc32模式進入中斷
當中斷嵌套或者中斷打斷系統調用等異常時,中斷會從svc32模式進入中斷。在開始中斷處理前,系統仍然需要保存寄存器現場。這是通過調用宏svc_entry來實現的。
/**
?*?當從svc模式進入中斷處理程序時,使用本宏保存寄存器現場到堆棧中,并形成pt_regs結構傳遞給C函數。
?*/
????.macro?????svc_entry, stack_hole=0
?UNWIND(.fnstart???????????????)
?UNWIND(.save {r0 - pc}??????????????)
???/**
????*?將當前指針向低地址移動,以保存寄存器現場。這里減去4是為了將sp指向pt_regs中r1的位置。
????*/
????sub???sp, sp, #(S_FRAME_SIZE + \stack_hole - 4)
#ifdef CONFIG_THUMB2_KERNEL/*?新內核支持將內核編譯為THUMB-2,以節省代碼空間,我們的系統不支持這個功能?*/
?SPFIX(?????????str????r0, [sp]?????)????????@ temporarily saved
?SPFIX(?????????mov?r0, sp?????????????????)
?SPFIX(?????????tst????r0, #4?????????????????)????????@ test original stack alignment
?SPFIX(?????????ldr????r0, [sp]?????)????????@ restored
#else
?SPFIX(?????????tst????sp, #4?????????????????)
#endif
?SPFIX(?????????subeq???????sp, sp, #4?)
?
???/**
????*?將r1-r12保存到堆棧中。
????*/
????stmia????????sp, {r1 - r12}
?
????/**
?????* r0,sp,lr,spsr已經被匯編代碼使用,因此需要根據r0從中斷棧(我們目前正在使用的是svc棧)中取出
?????*/
????ldmia????????r0, {r1 - r3}
????/**
?????* r5指向pt_regs的ARM_sp即r13
?????*/
????add??r5, sp, #S_SP - 4????????@ here for interlock avoidance
????mov?r4, #-1?????????????????????????@??""??""??????""???????""
????/**
?????*?將r0調整到剛進入宏的位置
?????*/
????add??r0, sp, #(S_FRAME_SIZE + \stack_hole - 4)
?SPFIX(?????????addeq???????r0, r0, #4?)
???/**
????*?保存r0,同時將sp向下調整4字節,現在sp指向pt_regs了。
????*/
????str????r1, [sp, #-4]!??????????????@ save the "real" r0 copied
?????????????????????????????????????????@ from the exception stack
?
????mov?r1, lr
?
????@
????@ We are now ready to fill in the remaining blanks on the stack:
????@
????@??r0 - sp_svc
????@??r1 - lr_svc
????@??r2 - lr_, already fixed up for correct return/restart
????@??r3 - spsr_
????@??r4 - orig_r0 (see pt_regs definition in ptrace.h)
????@
???stmia????????r5, {r0 - r4}/*?將中斷棧中的數據保存到pt_regs */
????.endm
?
當不是從用戶態進入中斷時,中斷處理代碼要稍顯復雜一點,主要是需要處理搶占:
/**
?*?從svc32模式進入中斷的處理過程
?*/
__irq_svc:
????/**
?????*?首先保存寄存器現場。
?????*/
????svc_entry
?
????/**
?????*?進入中斷后,系統自動將中斷關閉,這里調用trace_hardirqs_off記錄下中斷被關閉的事實,用于跟蹤調試。
?????*/
#ifdef CONFIG_TRACE_IRQFLAGS
????bl??????trace_hardirqs_off
#endif
#ifdef CONFIG_PREEMPT
????/**
?????*?對可搶占內核來說,這里將任務的搶占計數加1,在整個中斷處理過程中,進程都不能被搶占。
?????*/
????get_thread_info tsk
????ldr????r8, [tsk, #TI_PREEMPT]????????????@ get preempt count
????add??r7, r8, #1????????????????????@ increment it
????str????r7, [tsk, #TI_PREEMPT]
#endif
?
????irq_handler
#ifdef CONFIG_PREEMPT
????/**
?????*?恢復搶占計數。
?????*/
????str????r8, [tsk, #TI_PREEMPT]????????????@ restore preempt count
????/**
?????*?將任務的TI_FLAGS標志加載到r0中,這樣后面會根據r0判斷_TIF_NEED_RESCHED,以處理任務搶占
?????*/
????ldr????r0, [tsk, #TI_FLAGS]???????????????????@ get flags
????/**
?????*?如果在進入中斷前,系統處于系統調用狀態,那么搶占計數就可能為0.
?????*?這里比較搶占計數是否為0,如果為0,則進行搶占處理。
?????*/
????teq???r8, #0????????????????????????????????????@ if preempt count != 0
????/**
?????*?如果系統關搶占了,那么強制針r0清0,這樣就不可能調用svc_preempt
?????*/
????movne??????r0, #0????????????????????????????????????@ force flags to 0
????/**
?????*?如果系統沒有關搶占,并且任務存在_TIF_NEED_RESCHED標志,則調用svc_preempt處理搶占。
?????*/
????tst????r0, #_TIF_NEED_RESCHED
????blne?svc_preempt
#endif
????/*?在此中斷處于關閉狀態,從pt_regs中獲得中斷前的SPSR寄存器,接下來將會用這個寄存器恢復狀態。?*/
????ldr????r4, [sp, #S_PSR]?????????????????@ irqs are already disabled
????/*?如果恢復狀態后,將會打開中斷,則調用trace_hardirqs_on進行跟蹤?*/
#ifdef CONFIG_TRACE_IRQFLAGS
????tst????r4, #PSR_I_BIT
????bleq?trace_hardirqs_on
#endif
????/**
?????*?退回svc32模式
?????*/
????svc_exit r4????????????????????????????????????@ return from exception
?UNWIND(.fnend??????????????????)
ENDPROC(__irq_svc)
?
處理搶占的代碼并不復雜,如下:
#ifdef CONFIG_PREEMPT
svc_preempt:
????mov?r8, lr
????/**
?????*?這里調用preempt_schedule_irq處理搶占調度,今后在分析調度時,將會詳細介紹這個函數。
?????*/
1:??????????bl??????preempt_schedule_irq?????????????@ irq en/disable is done inside
????/**
?????* preempt_schedule_irq返回時,會重新將中斷關閉,這里加載TI_FLAGS標志是安全的。
?????*/
????ldr????r0, [tsk, #TI_FLAGS]???????????????????@ get new tasks TI_FLAGS
????tst????r0, #_TIF_NEED_RESCHED
????/**
?????*?如果任務沒有搶占標志,那么退回上層繼續處理,恢復寄存器現場,返回上層中斷。
?????*/??
????moveq??????pc, r8????????????????????????????????????@ go again
????/**
?????*?否則表示任務再次被搶占,循環處理搶占。
?????*/
????b???????1b
#endif
?
?
1.1.1.3?????????退回svc32模式
從中斷退出的代碼如下:
#ifndef CONFIG_THUMB2_KERNEL
????.macro?????svc_exit, rpsr
????msr??spsr_cxsf, \rpsr /*?恢復rpsr */
#if defined(CONFIG_CPU_V6)
????/**
?????*?恢復r0寄存器
?????*/
????ldr????r0, [sp]
????/**
?????*?由于發生了中斷,需要執行strex指令,這樣上層中斷中的spinlock會認為排它性裝載失效,重啟spinlock循環。
?????*?在mips等體系結構中,這是由硬件完成的。可能ARM硬件不能完成這件事。
?????*/
????strex?????????r1, r2, [sp]???????????????????????????@ clear the exclusive monitor
????/**
?????*?恢復所有寄存器,并恢復cpsr。將處理器狀態切回中斷前。
?????*/
????ldmib????????sp, {r1 - pc}^???????????????????????@ load r1 - pc, cpsr
#elif defined(CONFIG_CPU_32v6K)
????clrex???????????????????????????????????????????????@ clear the exclusive monitor
????ldmia????????sp, {r0 - pc}^???????????????????????@ load r0 - pc, cpsr
#else
????ldmia????????sp, {r0 - pc}^???????????????????????@ load r0 - pc, cpsr
#endif
????.endm
?
至此,進入中斷和退出中斷的基本流程已經梳理完成。我們將在下一部分講中斷處理函數的C語言部分。包含ISR和軟中斷的處理。
轉載于:https://www.cnblogs.com/sky-heaven/p/5321563.html
總結
以上是生活随笔為你收集整理的《LINUX3.0内核源代码分析》第二章:中断和异常 【转】的全部內容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: 工作流设计
- 下一篇: android开发全然退出activit
