2009年7月3日 星期五

CPU exception vector 淺談

因為再深入我也不懂,所以就淺談吧~
有錯請指正~

話說,身為一個程式設計人員,常常遇到一些程式跑著跑著就當掉的狀況也很合理的。
例如:『segmentation fault』。簡單講就是程式寫錯,然後它就當了。

大家也應該都聽過,『開機之後CPU就會到固定位置去拿指令,然後開始跑,通
常這個位置都是放BIOS,而這個位置通常是0』。

那這個第一行指令究竟是長怎樣呢?這跟程式當掉有什麼關係?跟程式碼寫錯
又有什麼關係?

我們來看通常一般冷開機之後開始跑的程式碼都是怎樣的
( 通常就是跳到bootload or BIOS )

b start
ldr pc, _undefined_instruction
ldr pc, _software_interrupt
ldr pc, _prefetch_abort
ldr pc, _data_abort
ldr pc, _not_used
ldr pc, _irq
ldr pc, _fiq


上面是組語,我們只看兩行,第一行是『b start』,『b』的意思就是branch的
意思,也就是jump到start這個地方開始執行。換句換說,CPU拿到手的第一個
指令就是要它去找別人,顯然是相當不負責任的行為。讓人不禁懷疑那第二行第
三行到底是幹麼的。

我們接著看『ldr pc, _undefined_instruction』,『ldr』的意思是說,將
_undefined_instruction這個位址,放到pc這個暫存器當中。大家一定也聽過
program counter這個東西,一個正常的CPU就是靠program counter所指
的位置去抓指令進來執行。換句話說,第二行程式居然也是要CPU跳到別的地方
執行。而且前面七八行都是做這件事情。很怪吧?!!!

到這裡我們解答了上面第一個疑問,第一行程式其實就是一個Jump的指令,跳到
真正的開機程序。

看了程式碼之後,我們不禁好奇這些看起來排列很有規則的程式碼到底是做啥用
的?其實答案就是『exception vector』。

什麼是exception vector呢?其實就是CPU出錯了(其實不完全是出錯),它就會
來這個vector找看相對應的處理函式,例如遇到看不懂得指令,他就固定會抓0x4
位址的指令,也就是『ldr pc, _undefined_instruction』,接著就會跳到處理undefined instruction的函式去處理。

到這邊我們回答了第二個問題,程式當掉的時候,就是會跑來這個地方,接著再
Jump到處理的程式碼。同時我們也發現,一個CPU就是靠著這個vector在處理
各種不同的exception。那....萬一這些vector被改掉了呢?
例如:你程式碼拿到一個null pointer,然後又對這個null pointer開始做寫入動
作。

char *ptr = NULL;
memcpy( ptr, 0x0, 10 );

那整個OS就完全不能動了。可是我們也知道我們現在這樣寫,頂多 segmentation
fault,並不會怎樣,OS還是照樣活著。why why why?

原因很簡單,就是這些區段被一些方法保護住了。一般常見的有兩種方法,一種是
MMU,一種是cpu有兩個exception vector存上的位址。

MMU可以設定某個區段的存取權力,只能readable的話,那就不會有被改掉的問
題,另外一個方式是可以將exception vector的位址擺到不容易改到的地方,通常
是往高位址的地方擺(high vector address),例如,0xffff0000接近4GB的位
置。(32bit 最多只能定址4GB)

第二種方式,通常是一開始開機vector還是從0x0那邊抓,可是經過設定CPU,可
以改變,在不斷電的狀態下,發生exception就會跳到高位位址去找exception
vector。

p.s. 關於定址模式可能有些人沒感覺,可能要懂整個CPU的memory map之後,
會比較有感覺。

p.s. 眼尖的人一定覺得很怪,為什麼vector第一行用『b』,其它卻是用『ldr』

上面貼文本來的用意是要點出,為什麼一個CPU需要有擺放兩個exception vector的
功能。如果有看懂的話,答案很簡單,就是怕被程式人員亂改改到了,這樣會讓整個OS
當掉,很容易一個null pointer亂填,整個系統就掛了。可是一開始不知道怎麼帶出這
麼問題,所以文章就變長了。恰巧,也提到一些其他的東西,我們就稍微聊一下CPU
的模式吧。

從 exception vector 看得出來,CPU本身除了拿到指令一直run以外,還會接受一些
exception發生,發生之後呢,便會到這個vector來拿相對應處理的指令。

舉例來說,一個程式碼被compile成使用某個硬體的特殊指令,可是卻剛好被放到沒支
援這個指令的 CPU 上執行,那CPU跑著跑著發現一個自己看不懂得指令,就會發出一
個undefined instruction 的 exception,exception發出後,依照CPU的設計,以
ARM來說,就會抓位址在0x4地方的指令開始執行。

而這時候,我們的處理函式就有機會可以透過這個exception hanlder去模擬這個指令
,利用多個指令或運算計算出那個特殊硬體指令的值。這是其中一種undefined
instruction exception 的應用。

另外,我們要提的是,這許多的exception其實隱含著CPU的運行模式,例如我們以前
常聽到x86 CPU切到什麼保護模式之類的,其實就是因為發生了一些exception,為了
處理這些exception,所以切到相對應的模式去做處理。

切換模式聽起來很單純,其實對實作OS來說,牽涉到的事情還不少,例如切換模式之
後,為了要讓處理完exception之後,原本的程式能夠繼續執行,相對的exception
handler必須將目前狀態存起來,所以就必須實現stack來存放目前的register status
(context),以便將來可以resume回來。

看到這邊,應該有人會問想說,了解exception vector的意義何在?當你要了解如何
實做一個OS的時候,其實就是要去控制CPU,而CPU的運作,其實就是控制這些模
式。而exception vector就是這些模式的進入點。當你對一個OS可以有透徹的了解
的時候,對於系統表現出來的行為,會有更新的了解和解釋,對於debug application
和 driver 其實是有幫助的。

對於各個cpu mode的說明,例如一些有趣的問題:有哪些模式?每個模式有什麼不
同?它是專門處理那些問題?有哪些種應用?這些因為要牽涉到很多assembly的程
式片段,所以就先不深入探討,有興趣的朋友可以一起討論。

沒有留言:

張貼留言

搜尋此網誌