對於位元組序小端和大端的思考,位元組序大端
最近公司處理器要換核,由小端處理器ARM換成大端處理器POWERPC,bootloader以及kernel的移植工作交給了我,這是一個很有挑戰的工作,自己也非常興奮。
如此一來,當今主流的嵌入式處理器(MIPS ARM PPC)也都算接觸過啦。
這幾天開始動手做移植,首先要解決的是大小端的差異,進過學習思考,感覺大小端還是很有研究的必要,自己的思考總結記錄在此,與大家分享,以備後用。
從網上可以查到的大小端的解釋,小端是低端資料存放在低端地址,大端是高端資料存在低端地址。大小端真的就這麼簡單嗎,不是這樣的。
位元組序大端小端是針對超過一個byte的資料類型在記憶體中的儲存布局來講的。對象是資料類型的儲存布局,為什麼要超過一個byte呢,這涉及到記憶體定址,記憶體定址的最小單位就是byte,一個byte內的資料排布順序是固定的(小端)。
打個比方,拿我們自己來說,有一句4個字的話,我們是從右向左讀,還是從左向右讀,這就是我們的位元組序啊。
所以對於處理器在操作超過一個byte的資料類型時,如何排布資料在記憶體中的順序,就由其位元組序來決定。
可以這樣理解:
對於小端處理器,如果要定址一個word型資料,處理器首先由地址匯流排發出地址,之後對於由32位元據匯流排(32位處理器)返回的資料,小端處理器認為0-7位元據線是低端資料,而24-31位元據線為高端資料。
相反,對於大端處理器,定址一個word型資料,處理器對於資料線返回的資料,認為24-31位元據線為低端資料,而0-7位元據線為高端資料。
位元組序大小端還遠沒有這麼簡單,對於位元組序的理解,我覺得可以分為2種情況:
(1)操作記憶體,首先說明記憶體本身是沒有位元組序一說的,記憶體中同樣一段資料,小端處理器讀出來的資料意義和大端處理器讀出來的資料意義是不同的,所以其儲存資料的順序是由處理器位元組序來決定的。
操作記憶體,無非就是讀和寫。那這裡又可以分為2種情況,
一種是處理器讀處理器寫,也就是處理器主動發起對於記憶體的讀寫,因為使用同一處理器,採用同樣的位元組序去讀寫同一資料(修改其值,進行位操作等)是沒有差別的,唯一的差別是在對於同一段記憶體,讀寫時操作了不同的資料類型。這種情況就不細說了,因為現在街是大小端的文章都會解釋這個問題,這也是驗證處理器是大端還是小端很好的方法。
另一種是另一主裝置與處理器非同步操作了記憶體,如DMA,假如處理器由小端改為大端,而外設是小端(我這次的移植就是這種情況),在外圍硬體設計不變的情況下(處理器0-31資料線與外設0-31資料線一一對應),外設DMA寫入記憶體的資料,由處理器讀取回來,資料就已經被翻轉了,已經失去了原來的意義。這也是做大小端移植需要注意的一點。
(2)操作寄存器,這跟DMA一樣,也是一個非同步作業,現在大部分外設控制器寄存器都是小端設計(這裡的設計是指寄存器描述是小端的,資料排布也是小端的),寄存器也可以分為2種。
一種是位意義的寄存器,這種寄存器的每一位都有意義,可能是某種功能的開關,這種寄存器在外設中非常常見。對於這類寄存器,小端處理器操作沒有差別,因為位元組序一致,但是對於大端處理器,其獲得寄存器資料是翻轉的,所以對於每一位的定義也是翻轉的,不過我們可以通過修改軟體上(如kernel)對寄存器的位宏定義來擷取其正確的位意義,這一點在做大小端移植時需要注意。
另一種是資料意義的寄存器,這種寄存器上儲存的是有意義的資料,如串口收發資料寄存器,網卡DMA描述符首地址寄存器等,對於大端處理器,該類寄存器是無法通過修改位宏定義來保證正確,因為其是一個整體資料,這種寄存器只能是在擷取其值後將資料再翻轉(大端轉小端),來擷取寄存器中原有意義的資料,在進行操作。
但是如果是這樣的修改,就有一個原子操作的問題了,因為讀寫寄存器本來是由一條指令完成的,但是現在卻添加了翻轉操作,在進行讀寫指令,這就不能保證寄存器讀寫的原子性。在高效能,中斷頻發,不停切換的作業系統下,就有可能產生問題。
對於這個問題,也可以在硬體上進行協作,如將處理器的(0-7)(8-15)(16-23)(24-31)與外設的(0-7)(8-15)(16-23)(24-31)進行反接,但是這樣也可能有問題,如對於大於1byte卻小於4byte的資料,處理器如何擷取的問題。
這些在由小端到大端移植的問題我還在探索和學習中,還是很有意思的。
不過對於本來設計為大端,寄存器描述也是大端的外設,與大端處理器相連,就不會有這些問題。也就是說外設與處理器位元組序同一,這些棘手的問題都可以避免。