天天看點

(4.3)uboot詳解——異常和異常向量

(4.3)uboot詳解——異常和異常向量

中斷是一個較難掌握知識,因為它是一個過程,而不是一個結果,其中的步驟都建立在理論的層面上,需要了解。比如按下按鍵1會使led1亮,這個“起因-結果”的操作我想小孩子也能掌握,因為它是一個現象,但是要掌握“起因-過程-結果”卻需要花一些功夫,因為這個過程需要了解。如果你認真的了解了前面兩節的内容,那麼現在就該到了實作“過程”的時候了。

前面兩節分析了外部中斷和内部中斷相關的内容,這篇文章将對處理器的異常情況作一個總結。

可以參考第二片文章(處理器工作模式),ARM處理器有七種工作模式,除了使用者模式和系統模式以外,其他5中模式都是異常模式。

(4.3)uboot詳解——異常和異常向量

人會生病,生病時我們可以依靠我們的免疫系統來恢複,計算機也會“生病”,它“生病”的時候就會進入異常工作模式,依靠異常處理程式讓cpu恢複過來,那麼計算機在哪些情況下會“生病”呢?

(1)複位異常:當cpu重新上電或者接收到reset信号的時候,處理器就會識别到有複位異常發生了,它就要進入管理模式,執行對應的異常處理函數,直到恢複正常為止。比如重新開機按鈕被按下,或者cpu複位引腳電平發生變化,或者軟體設定複位寄存器的相應位。

(2)一般中斷/快速中斷請求:cpu和外圍裝置是分别獨立運作的硬體執行單元,cpu對全部裝置進行管理和資源排程處理,cpu要想知道外圍裝置的運作狀态,要麼cpu定時的去檢視外圍裝置的特定寄存器,要麼外圍裝置自己告訴cpu發生了什麼,第二種方式就是我們常說的中斷請求操作,中斷請求分為一般中斷請求和快速中斷請求,快速中斷請求有最高的優先級和最小的中斷延遲(看前面文章的中斷處理過程就知道了),通常用于處理高速資料傳輸及通道中的資料恢複處理,如DMA等,但是絕大部分外設使用的是一般中斷請求。是以當有中斷請求的時候,處理器将進入中斷模式,執行中斷處理程式,直到恢複正常。

(3)預取指令中止異常:這個異常發生在cpu流水線的取指階段,如果目标指令位址是非法位址,處理器就會進入中止模式,執行中止異常處理程式。比如使用bl指令跳轉到某個位址讀取指令,但是這個位址是不可讀的,或者這個位址不存在。

(4)資料中止通路異常:這個異常發生在要通路的資料位址不存在或者非法位址時,這種情況,cpu也會進入中止模式,執行對應的處理程式。比如mov r0, A ; A中存放的位址是不存在的或者不可通路的

(5)未定義指令異常:這個異常發生在流水線技術的譯碼階段,如果目前指令不能識别為有效指令,就會産生未定義指令異常,cpu就會進入未定義指令終止模式,執行異常處理程式。比如mov r0,#1的下一條指令是abc,這是一條非法指令。

(6)軟體中斷指令異常:把這個異常放到最後,是因為這個異常的産生原因比較特殊,他是應用程式自己産生的,用于使用者程式申請通路硬體資源時,通過作業系統核心代碼來通路外設硬體資源的一種方式。比如應用程式需要将資料列印到螢幕上,就需要使用顯示器這個硬體資源,但是應用程式是沒有權限使用外設硬體的,外設都是有核心進行管理的,那麼怎麼辦呢?那麼隻能使用軟體中斷指令切換到核心态,通過作業系統核心代碼來通路外設硬體,核心态是工作在特權模式下的,作業系統在特權模式下完成将使用者資料列印到螢幕,是以軟體中斷的異常時在管理模式下進行處理的。

上面分析了處理器在哪些情況下會進入異常模式(注:這些模式都是cpu硬體層面的),第1,2,6種異常對于我們往往是有用的,工作中經常會使用這三種異常來實作我們想要的功能,但是第3,4,5種異常我們一般是不想看到的,這往往表示我們寫了不規範的代碼,或者執行了錯誤的操作。

從上面的分析可以發現,中斷其實隻是cpu異常的一種,中斷處理有一個過程,其實它也是異常處理過程的一個子集。下面将對異常的處理過程進行分析:

(1)保護執行狀态:當cpu發生異常的時候,硬體首先會将目前的工作模式記錄下來,以便後面異常處理完成後可以恢複回來,異常發生時,會将目前CPSR的内容複制到發生的異常模式下的SPSR中,比如在使用者模式時發生了未定義指令異常,則會将目前的CPSR儲存在SPSR_und中。

(2)模式切換:硬體自動根據目前的異常類型,将異常碼寫入CPSR裡的M[4:0]模式位,這樣cpu就進入了對應的模式下,同時cpu會關閉中斷IRQ(CPSR的I位置1),防止中斷進入,如果目前異常是快速中斷,則關閉快速中斷。

(3)儲存傳回位址:目前程式被異常打斷,切換到異常處理程式裡,異常處理程式處理完成後,需要傳回到目前模式的對應位置繼續執行,是以必須儲存目前執行指令的下一條指令的位址到LR_excep(字尾用于區分,無實際意義)中,至于儲存的到底是什麼值,是pc-4,pc-8還是pc,請參考這篇文章關于ARM的PC指針(什麼時候PC+8,PC+4,PC-4,PC-8)

(4)跳入異常向量表:該操作是cpu硬體自動完成的,當異常發生時,cpu強制将pc的值修改為一個固定記憶體位址,這塊固定位址稱作異常向量。

後面我們将分析每一種異常的異常處理程式是怎麼将cpu“治”好的,但是在這之前,需要弄明白,當cpu進入異常模式時,它是怎麼确定要執行哪一種異常處理程式的?是誰指引它去“尋醫問藥”的呢?這個訓示器就是異常向量。

(4.3)uboot詳解——異常和異常向量

上面這張圖檔就是一個異常向量表,從圖中可以看到,每種ARM異常對應一個字長空間,正好是一條32位指令長度,比如bl指令在一個字長中的存放如下圖:

(4.3)uboot詳解——異常和異常向量

當異常發生的時候,cpu強制将pc值設定為目前異常對應的固定記憶體位址,比如當運作0x345678位址處的指令時,發生一般中斷異常,cpu就會将目前的pc值強制設定為0x00000018,并把0x345678儲存到lr寄存器中,然後下一條指令将會從0x00000018處取指,如果0x00000018處的指令是bl HandleIRQ, 那麼cpu将會跳轉到HandleIRQ處執行一般中斷處理函數,這個函數是由我們編寫的,記得中斷處理函數完成時,要跳回0x34567d處繼續執行原來的程式。

分析到這來,也許你會有意外,為什麼異常向量表的存放位址可以是SDRAM的前4k呢?那裡不是存放uboot的地方嗎?如果這裡存放了異常向量表,那麼uboot不就被破壞掉了嗎?

(4.3)uboot詳解——異常和異常向量

我們再看看第一篇文章((1)uboot詳解——闆子剛上電時都幹了些什麼)中介紹的這張記憶體映射圖,當闆子從norflash啟動的時候,記憶體映射圖如左半部分,這時沒有存放uboot,uboot放在norflash中,是以存放異常向量表是沒有影響的;然而當從nandflash啟動的時候,記憶體映射圖如右半部分,記憶體的前4k在剛上電的時候是會存放uboot的,如果再存放異常向量,必然會破壞uboot的資料!但是即使如此,為什麼我們的闆子能夠正常運作呢?這個就涉及到了代碼重定向的内容(将再後面重點講解),因為在uboot初始化異常向量表的時候,cpu已經将uboot拷貝到SDRAM中執行了,也就是說uboot已經在0x30000000位址處了,即使在初始化中斷向量時有破壞uboot,對闆子的正常啟動是沒有影響的。

從中斷向量表中可以看出,複位異常的向量指向0x0000000處,開發闆的啟動位址也是0x0000000處,這樣要巧妙的設計是為了處理這種異常時,不用再寫異常處理函數了,因為收到reset信号的時候,硬體會自動将uboot從nandflash中拷貝到stepping stone中,這樣,uboot則成了複位異常的處理函數。至于norflash啟動,其實過程也一樣,請參考第一篇文章描述。

不僅如此,因為快速中斷的中斷向量位址是0x0000001c,它是最後一個向量,後面的空間可以被使用,是以可以将快速中斷的處理函數直接以這個位址為開始進行編寫了,而不用像其他異常處理函數一樣,需要bl跳轉到其他位址進行實作,這樣可以節省一個bl指令的執行時間,也就是一個時鐘周期(為什麼是一個時鐘周期?請參考arm指令流水線技術)。

下面是通常異常向量表中存放的指令:

bl reset

bl HandleUndef

bl HandleSWI

bl HandlePrefetchAbt

bl HandleDataAbt

bl HandleNoUsed

bl HandleIRQ

bl HandleFIQ

這些Handle則是存放着異常處理函數的入口位址

(5)通常找到異常向量表以後,異常向量表就會指引cpu去哪裡執行異常處理程式,下面就來分析一下異常處理函數的實作過程。

異常處理函數一般都是有程式員寫的,也往往是處理異常時最有發揮空間的地方,因為有了能夠給我們編寫異常處理函數的餘地,才能讓我們實作多種多樣的功能,讓我們的鍵盤能夠快速的輸入,我們的滑鼠能自由的晃動,讓各種傳感器高速的捕捉信号,cpu也能及時的高效的處理這些請求。

A. 儲存執行現場

異常處理程式最開始,要儲存被打斷程式的執行現場,程式的執行無非就是儲存目前操作寄存器裡的資料,可以通過下面的棧操作指令實作現場儲存現場:

STMFD SP_excep!, {r0 - r12, LR_excep}

注:LR_abt,SP_excep分别對應異常模式下LR和SP

需要注意的是,在跳轉到異常處理程式入口時,已經切換到對應異常模式下了,是以這裡的SP是異常模式下的SP_excep了,是以被打斷程式現場(寄存器資料)是儲存在異常模式下的棧裡,上述指令将r0~r12全部都儲存到了異常模式棧,最後将修改完的被打斷程式傳回位址入棧儲存;之是以儲存該傳回位址就是将來可以通過mov pc,lr 的指令傳回使用者程式繼續執行。

B. 異常處理過程

這個是異常處理的核心部分,具體問題需要具體分析和解決,将在執行個體中分析。但是編寫時應該注意一下幾點:

ISR不能有傳回值;ISR不能傳遞參數;ISR應該是短而高效的,在ISR中做浮點運算是不明智的;ISR中不應該有重入和性能上的問題,是以不應該使用pintf()函數。

C.異常處理傳回

異常處理完成之後,傳回被打斷程式繼續執行,具體操作如下:

l 恢複被打斷程式運作時寄存器資料

l 恢複程式運作時狀态CPSR

l 通過進入異常時儲存的傳回位址,傳回到被打斷程式繼續執行

異常發生後,進入異常處理程式時,将使用者程式寄存器R0~R12裡的資料儲存在了異常模式下棧裡面,異常處理完傳回時,要将棧裡儲存的的資料再恢複回原先R0~R12裡,毫無疑問在異常處理過程中必須要保證異常處理入口和出口時棧指針SP_excep要一樣,否則恢複到R0~R12裡的資料不正确,傳回被打斷程式時執行現場不一緻,出現問題,雖然将執行現場恢複了,但是此時還是在異常模式下,CPSR裡的狀态是異常模式下狀态,是以要恢複SPSR_excep裡的儲存狀态到CPSR裡,SPSR_excep是被打斷程式執行時的狀态,在恢複SPSR_excep到CPSR的同時,CPU的模式和狀态從異常模式切換回了被打斷程式執行時的模式和狀态。此刻程式現場恢複了,狀态也恢複了,但PC裡的值仍然指向異常模式下的位址空間,我們要讓CPU繼續執行被打斷程式,是以要再手動改變PC的值為進入異常時的傳回位址,該位址在異常處理入口時已經計算好,直接将PC = LR_excep即可。

上述操作可以一步一步實作,但是通常我們可以通過一條指令實作上述全部操作:

LDMFD SP_excp!,  {r0-r12,  pc}^

注:SP_excep為對應異常模式下SP,^符号表示恢複SPSR_excep到CPSR

中斷異常處理操作可以用下圖來描述:

(4.3)uboot詳解——異常和異常向量

下面将通過一個小小的項目來總結第4章1,2,3節所分析的内容,主要功能包括:

a. 開機時将cpu模式設定為管理模式

b.關閉看門狗

c.關閉中斷

d.開啟中斷,并初始化中斷向量表,設定棧

e.初始化按鍵中斷,按下不同的按鈕,點亮不同的led燈

代碼較多,不友善貼出來,請到這裡下載下傳

總結:這裡用了三節内容分析了中斷和異常的相關内容,包括中斷的産生,中斷的處理過程和中斷的配置等,由于這些知識不僅是重點也是難點,後面将會在分析uboot源代碼的時候繼續講解。

經曆一段小艱難後,我們将了解一下較輕快的話題——時鐘分頻。

(4.3)uboot詳解——異常和異常向量