深入淺出大端和小端

點擊打開鏈接

(2008-5-23 22:40) 文章一: 

端模式(Endian)的這個詞出自Jonathan Swift書寫的《格列佛遊記》。這本書根據將雞蛋敲開的方法不同將所有的人分爲兩類,從圓頭開始將雞蛋敲開的人被歸爲Big Endian,從尖頭開始將雞蛋敲開的人被歸爲Littile Endian。小人國的內戰就源於吃雞蛋時是究竟從大頭(Big-Endian)敲開還是從小頭(Little-Endian)敲開。在計算機業Big Endian和Little Endian也幾乎引起一場戰爭。在計算機業界,Endian表示數據在存儲器中的存放順序。下文舉例說明在計算機中大小端模式的區別。


如果將一個32位的整數0x12345678存放到一個整型變量(int)中,這個整型變量採用大端或者小端模式在內存中的存儲由下表所示。爲簡單起見,本書使用OP0表示一個32位數據的最高字節MSB(Most Significant Byte),使用OP3表示一個32位數據最低字節LSB(Least Significant Byte)。




地址偏移
 大端模式
 小端模式
 
0x00
 12(OP0)
 78(OP3)
 
0x01
 34(OP1)
 56(OP2)
 
0x02
 56(OP2)
 34(OP1)
 
0x03
 78(OP3)
 12(OP0)
 






如果將一個16位的整數0x1234存放到一個短整型變量(short)中。這個短整型變量在內存中的存儲在大小端模式由下表所示。




地址偏移
 大端模式
 小端模式
 
0x00
 12(OP0)
 34(OP1)
 
0x01
 34(OP1)
 12(OP0)
 






由上表所知,採用大小模式對數據進行存放的主要區別在於在存放的字節順序,大端方式將高位存放在低地址,小端方式將高位存放在高地址。採用大端方式進行數據存放符合人類的正常思維,而採用小端方式進行數據存放利於計算機處理。到目前爲止,採用大端或者小端進行數據存放,其孰優孰劣也沒有定論。


有的處理器系統採用了小端方式進行數據存放,如Intel的奔騰。有的處理器系統採用了大端方式進行數據存放,如IBM半導體和Freescale的PowerPC處理器。不僅對於處理器,一些外設的設計中也存在着使用大端或者小端進行數據存放的選擇。


因此在一個處理器系統中,有可能存在大端和小端模式同時存在的現象。這一現象爲系統的軟硬件設計帶來了不小的麻煩,這要求系統設計工程師,必須深入理解大端和小端模式的差別。大端與小端模式的差別體現在一個處理器的寄存器,指令集,系統總線等各個層次中。


1.1.1        從軟件的角度理解端模式
從軟件的角度上,不同端模式的處理器進行數據傳遞時必須要考慮端模式的不同。如進行網絡數據傳遞時,必須要考慮端模式的轉換。有過Socket接口編程經驗的程序員一定使用過以下幾個函數用於大小端字節序的轉換。


¨          #define ntohs(n)     //16位數據類型網絡字節順序到主機字節順序的轉換


¨          #define htons(n)     //16位數據類型主機字節順序到網絡字節順序的轉換


¨          #define ntohl(n)      //32位數據類型網絡字節順序到主機字節順序的轉換


¨          #define htonl(n)      //32位數據類型主機字節順序到網絡字節順序的轉換


其中互聯網使用的網絡字節順序採用大端模式進行編址,而主機字節順序根據處理器的不同而不同,如PowerPC處理器使用大端模式,而Pentuim處理器使用小端模式。


大端模式處理器的字節序到網絡字節序不需要轉換,此時ntohs(n)=n,ntohl = n;而小端模式處理器的字節序到網絡字節必須要進行轉換,此時ntohs(n) = __swab16(n),ntohl = __swab32(n)。__swab16與__swab32函數定義如下所示。


#define ___swab16(x)


{


            __u16 __x = (x);


            ((__u16)(


                        (((__u16)(__x) & (__u16)0x00ffU) << 8) |


                        (((__u16)(__x) & (__u16)0xff00U) >> 8) ));


}


#define ___swab32(x)


{


            __u32 __x = (x);


            ((__u32)(


                        (((__u32)(__x) & (__u32)0x000000ffUL) << 24) |


                        (((__u32)(__x) & (__u32)0x0000ff00UL) << 8) |


                        (((__u32)(__x) & (__u32)0x00ff0000UL) >> 8) |


                        (((__u32)(__x) & (__u32)0xff000000UL) >> 24) ));


}
 




PowerPC處理器提供了lwbrx,lhbrx,stwbrx,sthbrx四條指令用於處理字節序的轉換以優化__swab16和__swap32這類函數。此外PowerPC處理器中的rlwimi指令也可以用來實現__swab16和__swap32這類函數。在Linux PowerPC中,定義了一系列有關字節序轉換的函數,其詳細定義在./include/asm-powerpc/byteorder.h文件中。


程序員在對普通文件進行處理也需要考慮端模式問題。在大端模式的處理器下對文件的32,16位讀寫操作所得到的結果與小端模式的處理器不同。讀者單純從軟件的角度理解上遠遠不能真正理解大小端模式的區別。事實上,真正的理解大小端模式的區別,必須要從系統的角度,從指令集,寄存器和數據總線上深入理解,大小端模式的區別。


1.1.2        從系統的角度理解端模式
除了4.2.1節中,軟件上對不同端模式編程上的差異,處理器在硬件上也由於端模式問題在設計中有所不同。從系統的角度上看,端模式問題對軟件和硬件的設計帶來了不同的影響,當一個處理器系統中大小端模式同時存在時,必須要對這些不同端模式的訪問進行特殊的處理。


PowerPC處理器主導網絡市場,可以說絕大多數的通信設備都使用PowerPC處理器進行協議處理和其他控制信息的處理,這也可能也是在網絡上的絕大多數協議都採用大端編址方式的原因。因此在有關網絡協議的軟件設計中,使用小端方式的處理器需要在軟件中處理端模式的轉變。而Pentium主導個人機市場,因此多數用於個人機的外設都採用小端模式,包括一些在網絡設備中使用的PCI總線,Flash等設備,這也要求硬件工程師在硬件設計中注意端模式的轉換。


本書中的小端外設是指這種外設中的寄存器以小端方式進行存儲,如PCI設備的配置空間,NOR FLASH中的寄存器等等。


對於有些設備,如DDR顆粒,沒有以小端方式存儲的寄存器,因此從邏輯上講並不需要對端模式進行轉換。在設計中,只需要將雙方數據總線進行一一對應的互連,而不需要進行數據總線的轉換。


如果從實際應用的角度說,採用小端模式的處理器需要在軟件中處理端模式的轉換,因爲採用小端模式的處理器在與小端外設互連時,不需要任何轉換。


而採用大端模式的處理器需要在硬件設計時處理端模式的轉換。大端模式處理器需要在寄存器,指令集,數據總線及數據總線與小端外設的連接等等多個方面進行處理,以解決與小端外設連接時的端模式轉換問題。


在寄存器和數據總線的位序定義上,基於大小端模式的處理器有所不同。


一個採用大端模式的32位處理器,如基於E500內核的MPC8541,將其寄存器的最高位msb(most significant bit)定義爲0,最低位lsb(lease significant bit)定義爲31;而小端模式的32位處理器,將其寄存器的最高位定義爲31,低位地址定義爲0。


與此向對應,採用大端模式的32位處理器數據總線的最高位爲0,最高位爲31;採用小端模式的32位處理器的數據總線的最高位爲31,最低位爲0。如圖4.5所示。






OP0
 
OP1
 
OP2
 
OP3
 
OP0
 
OP1
 
OP2
 
OP3
 
31
 
0
 
31
 
0
 
圖4.5大小端模式處理器的寄存器的定義
 
大端模式處理器寄存器位序定義
 
小端模式處理器寄存器位序定義
 




大小端模式處理器外部總線的位序也遵循着同樣的規律,根據所採用的數據總線是32位,16位和8位,大小端處理器外部總線的位序有所不同。


¨          大端模式下32位數據總線的msb是第0位,MSB是數據總線的第0~7的字段;而lsb是第31位,LSB是第24~31字段。小端模式下32位總線的msb是第31位,MSB是數據總線的第31~24位,lsb是第0位,LSB是7~0字段。


¨          大端模式下16位數據總線的msb是第0位,MSB是數據總線的第0~7的字段;而lsb是第15位,LSB是第8~15字段。小端模式下16位總線的msb是第15位,MSB是數據總線的第15~7位,lsb是第0位,LSB是7~0字段。


¨          大端模式下8位數據總線的msb是第0位,MSB是數據總線的第0~7的字段;而lsb是第7位,LSB是第0~7字段。小端模式下8位總線的msb是第7位,MSB是數據總線的第7~0位,lsb是第0位,LSB是7~0字段。


由上分析,我們可以得知對於8位,16位和32位寬度的數據總線,採用大端模式時數據總線的msb和MSB的位置都不會發生變化,而採用小端模式時數據總線的lsb和LSB位置也不會發生變化。


爲此,大端模式的處理器對8位,16位和32位的內存訪問(包括外設的訪問)一般都包含第0~7字段,即MSB。小端模式的處理器對8位,16位和32位的內存訪問都包含第7~0位,小端方式的第7~0字段,即LSB。


由於大小端處理器的數據總線其8位,16位和32位寬度的數據總線的定義不同,因此需要分別進行討論在系統級別上如何處理端模式轉換。


在一個大端處理器系統中,需要處理大端處理器對小端外設的訪問。


1.1.2.1    大端處理器對32位小端外設進行訪問
大端處理器採用32位總線與小端外設進行訪問時,大端處理器的32位數據總線的第0~7位用來處理OP0,第8~15位用來處理OP1,第16~23位用來處理OP2,第24~31位用來處理OP3。而32位的小端設備使用數據總線的第31~24位用來處理OP0,第23~16位用來處理OP1,第15~8位用來處理OP2,第7~0位用來處理OP3。


大端處理器,如MPC8541,使用stw,sth,stb和lwz,lhz,lbz指令對32位的外部設備進行訪問。在這些指令結束後,存放在外部設備的數據將被讀入MPC8541的通用寄存器中。爲保證軟件的一致性,當訪問結束後,存放在通用寄存器的字節序,即OP0,OP1,OP2和OP3必須要和存放在小端外設的字節序一致。此時在使用大端處理器的數據總線連接小端外設時必須要進行一定的處理,按照某種拓撲結構連接以保證軟件的一致性。大端處理器數據總線與小端外設進行連接的拓撲結構如圖4.6所示。




OP0
 
OP1
 
OP2
 
OP3
 
31
 
31
 
0
 
7
 
8
 
15
 
16
 
23
 
24
 
24
 
23
 
16
 
15
 
8
 
7
 
0
 
大端處理器的32位數據總線
 
小端設備的32位總線接口
 
圖4.6 大端處理器與小端外設的32位連接
 
OP0
 
OP1
 
OP2
 
OP3
 




如圖4.6所示,採用大端處理器訪問小端設備時,將各自的OP0~OP3字段直接相連。在大端處理器的32位數據總線的最高位爲0,最低位爲31;而小端設備的最高位爲31,最低位爲0。因此硬件工程師在進行信號連接時需要將採用大端處理器的0~31位分別與小端設備的31~0位一一對應,進行互連。


1.1.2.2    大端處理器對8,16位小端外設進行訪問
大端處理器使用8位,16位數據總線對8位,16位的小端外設進行連接。對於32位處理器,用來連接外設的總線一般是32位。因此體系結構工程師在進行大端處理器總線設計時有兩種選擇,是採用32位總線的高端部分(第0~15字段)還是低端部分(第16~31字段)連接小端設備。PowerPC處理器使用32位總線的高端部分,即數據總線的第0~15位連接16位的小端設備,使用0~7位連接8位的小端設備。


PowerPC處理器採用16位總線與16位的小端外設進行訪問時,PowerPC處理器的16位數據總線的第0~7位用來處理OP0,第8~15位用來處理OP1。而16位的小端設備使用數據總線的第15~8位用來處理OP0,第7~0位用來處理OP1。


PowerPC處理器採用8位總線與8位的小端外設進行訪問時,PowerPC處理器的8位數據總線的第0~7字段用來處理OP0。而8位的小端設備使用數據總線的第7~0位用來處理OP1。大端處理器與小端外設的連接關係如圖4.7所示。 


OP0
 
OP1
 
OP0
 
OP1
 
15
 
0
 
7
 
8
 
15
 
8
 
7
 
0
 
大端處理器的8/16位數據總線
 
小端設備的8/16位總線接口
 
圖4.7 大端處理器與小端外設的8/16位連接
 
OP0
 
OP0
 
7
 
0
 
7
 
0
 




與32位總線接口類似,PowerPC處理器可以使用stw,sth,stb和lwz,lhz,lbz指令對32位的外部設備進行訪問,並將數據存放在相應的通用寄存器中。當訪問結束後,存放在通用寄存器的字節序,即OP0,OP1必須要和存放在小端外設的字節序一致。


PowerPC處理器對8位的小端外設進行訪問時,一個總線週期只能訪問8位數據,如果處理器使用stw或者lwz指令訪問8位的小端設備內的32位數據時,在數據總線上將OP0,OP1,OP2和OP3依次傳遞到PowerPC的通用寄存器中。


PowerPC處理器對16位的小端外設進行訪問時,一個總線週期只能訪問16位數據,如果處理器使用stw或者lwz指令訪問16位的小端設備內的32位數據時,在數據總線上將OP0~1和OP2~3依次傳遞到PowerPC的通用寄存器中。


PowerPC處理器使用sth或者lhz指令訪問16位的小端設備時,16位的小端設備將數據的第15~0位,傳遞到PowerPC處理器的總線的第0~15位,然後再將數據最終傳遞給相應的通用寄存器。這裏有許多讀者會感到困惑,因爲爲了保證軟件的一致性,PowerPC處理器使用lhz指令訪問16位的小端設備的16位寄存器時,需要將結果保存在通用寄存器的第16~31位,而不是0~15位。究竟PowerPC處理器是如何將系統總線中0~15位的數據搬移到寄存器的第16~31位中的呢?爲此我們需要對lhz指令進行分析。


lhz rD,d(rA)


if rA = 0 then b ← 0


else b ← (rA)


EA ← b + EXTS(d)


rD ← (24)0 || MEM(EA, 1)
 




由lhz指令的以上描述得知lhz指令將來自數據總線上的OP0與OP1直接存入寄存器的第16~31位,而將第0~15位直接清零。


PowerPC處理器使用stb或者lbz指令訪問8位的小端設備時,8位的小端設備將數據的第7~0位,傳遞到PowerPC處理器的總線的第0~7位,然後再將數據最終傳遞給相應的通用寄存器,lbz指令的描述如下所示。


lbz rD,d(rA)


if rA = 0 then b ← 0


else b ← (rA)


EA ← b + EXTS(d)


rD ← (24)0 || MEM(EA, 1)
 




由lhz指令的以上描述得知lhz指令將來自數據總線上的OP0直接存入寄存器的第24~31位,而將第0~23位清零。


本節分別描述了大端處理器的32位,16位及8位數據總線與32位,16位和8位的小端設備進行連接。如果大端處理器的數據總線需要同時支持小端設備的32位,16位及8位的數據傳送方式,端模式的處理將會更加複雜。IC的設計人員在設計PCI總線橋片的時候將會遇到這一類問題,此時設計人員將使用多路總線開關來解決這一問題。端模式問題的解決需要軟硬件協調處理,並在指令集上加以支持。對於小端處理器而言,需要使用軟件轉換的方法實現大小端模式的匹配;對於大端處理器而言,在外部數據總線與小端外設的連接時必須要考慮數據總線連接的拓撲結構。
轉載:http://hi.baidu.com/boshenshen/blog/item/8c9d1e647e8e2ef4f6365452.html


文章二:


大端(big-endian)和小端(little-endian)<轉>
2007-12-07 20:36
補:x86機是小端(修改分區表時要注意),單片機一般爲大端


    今天碰一個關於字節順序的問題,雖然看起來很簡單,但一直都沒怎麼完全明白這個東西,索性就找了下資料,把它弄清楚. 
    因爲現行的計算機都是以八位一個字節爲存儲單位,那麼一個16位的整數,也就是C語言中的short,在內存中可能有兩種存儲順序big-endian和 litte-endian.考慮一個short整數0x3132(0x32是低位,0x31是高位),把它賦值給一個short變量,那麼它在內存中的存 儲可能有如下兩種情況:
大端字節(Big-endian):
----------------->>>>>>>>內存地址增大方向
short變量地址
       0x1000                  0x1001
_____________________________
|                           |
|         0x31             |       0x32
|_______________ | ________________
高位字節在低位字節的前面,也就是高位在內存地址低的一端.可以這樣記住(大端->高位->在前->正常的邏輯順序)
小端字節(little-endian):
----------------->>>>>>>>內存地址增大方向
short變量地址
       0x1000                  0x1001
_____________________________
|                           |
|         0x32             |       0x31
|________________ | ________________
低位字節在高位字節的前面,也就是低位在內存地址低的一端.可以這樣記住(小端->低位->在前->與正常邏輯順序相反)
可以做個實驗
在windows上下如下程序
#include <stdio.h>
#include <assert.h>
void main( void )
{
        short test;
        FILE* fp;
         
        test = 0x3132; //(31ASIIC碼的’1’,32ASIIC碼的’2’)
        if ((fp = fopen ("c:\\test.txt", "wb")) == NULL)
              assert(0);
        fwrite(&test, sizeof(short), 1, fp);
        fclose(fp);
}
    然後在C盤下打開test.txt文件,可以看見內容是21,而test等於0x3132,可以明顯的看出來x86的字節順序是低位在前.如果我們把這段 同樣的代碼放到(big-endian)的機器上執行,那麼打出來的文件就是12.這在本機中使用是沒有問題的.但當你把這個文件從一個big- endian機器複製到一個little-endian機器上時就出現問題了.
    如上述例子,我們在big-endian的機器上創建了這個test文件,把其複製到little-endian的機器上再用fread讀到一個 short裏面,我們得到的就不再是0x3132而是0x3231了,這樣讀到的數據就是錯誤的,所以在兩個字節順序不一樣的機器上傳輸數據時需要特別小 心字節順序,理解了字節順序在可以幫助我們寫出移植行更高的代碼.
正因爲有字節順序的差別,所以在網絡傳輸的時候定義了所有字節順序相關的數據都使用big-endian,BSD的代碼中定義了四個宏來處理:
#define ntohs(n)     //網絡字節順序到主機字節順序 n代表net, h代表host, s代表short
#define htons(n)     //主機字節順序到網絡字節順序 n代表net, h代表host, s代表short
#define ntohl(n)      //網絡字節順序到主機字節順序 n代表net, h代表host, l代表 long
#define htonl(n)      //主機字節順序到網絡字節順序 n代表net, h代表host, l代表 long
舉例說明下這其中一個宏的實現:
#define sw16(x) \
    ((short)( \
        (((short)(x) & (short)0x00ffU) << 8) | \
        (((short)(x) & (short)0xff00U) >> 8) ))
這裏實現的是一個交換兩個字節順序.其他幾個宏類似.


我們改寫一下上面的程序
#include <stdio.h>
#include <assert.h>


#define sw16(x) \
    ((short)( \
        (((short)(x) & (short)0x00ffU) << 8) | \
        (((short)(x) & (short)0xff00U) >> 8) ))


// 因爲x86下面是低位在前,需要交換一下變成網絡字節順序
#define htons(x) sw16(x)
void main( void )
{
        short test;
        FILE* fp;
        
        test = htons(0x3132); //(31ASIIC碼的’1’,32ASIIC碼的’2’)
        if ((fp = fopen ("c:\\test.txt", "wb")) == NULL)
              assert(0);
        fwrite(&test, sizeof(short), 1, fp);
        fclose(fp);
}
    如果在高字節在前的機器上,由於與網絡字節順序一致,所以我們什麼都不幹就可以了,只需要把#define htons(x) sw16(x)宏替換爲 #define htons(x) (x).
    一開始我在理解這個問題時,總在想爲什麼其他數據不用交換字節順序?比如說我們write一塊buffer到文件,最後終於想明白了,因爲都是unsigned char類型一個字節一個字節的寫進去,這個順序是固定的,不存在字節順序的問題,夠笨啊.. 




http://hi.baidu.com/liyangzhao/blog/item/277e2ce7e105cf2db838200f.html


文章三:


big-endian和little-endian這兩個術語來自Jonathan Swift在十八世紀的嘲諷作品Gulliver’s Travels。 Blefuscu帝國的國民被根據吃雞蛋的方式劃分爲兩個部分:一部分在吃雞蛋的時候從雞蛋的大端(big end)開始,而另一部分則從雞蛋的小端(little end)開始。


x86的CPU使用的是LE(Windows中稱爲“主機字節序”),而SocksAddr中使用的則是BE(就是“網絡字節序”),所以在使用網絡編程時需要使用htns,htnl,nths,nthl來倒字節序。


其實對彙編熟了就清楚了,慘,我的彙編很慘的


LE little-endian 
最符合人的思維的字節序 
地址低位存儲值的低位 
地址高位存儲值的高位 
怎麼講是最符合人的思維的字節序,是因爲從人的第一觀感來說 
低位值小,就應該放在內存地址小的地方,也即內存地址低位 
反之,高位值就應該放在內存地址大的地方,也即內存地址高位 


BE big-endian 
最直觀的字節序 
地址低位存儲值的高位 
地址高位存儲值的低位 
爲什麼說直觀,不要考慮對應關係 
只需要把內存地址從左到右按照由低到高的順序寫出 
把值按照通常的高位到低位的順序寫出 
兩者對照,一個字節一個字節的填充進去 




例子:在內存中雙字0x01020304(DWORD)的存儲方式 


內存地址 
4000 4001 4002 4003 
LE 04 03 02 01 
BE 01 02 03 04 


MSDN中關於LE和BE的解釋
Byte Ordering Byte ordering Meaning 
big-endian The most significant byte is on the left end of a word. 
little-endian The most significant byte is on the right end of a word. 
這裏這個最重要的字節可以解釋成值的最高位,如果換成是錢的話就是最值錢的那一位 
比如我有1234元人民幣,最值錢的是1000元,最不值錢的是4元,那麼這個1就是最重要的字節




Big endian machine: It thinks the first byte it reads is the biggest.
Little endian machine: It thinks the first byte it reads is the littlest.
舉個例子,從內存地址0x0000開始有以下數據
0x0000    0x12
0x0001    0x34
0x0002    0xab
0x0003    0xcd
如果我們去讀取一個地址爲0x0000的四個字節變量,若字節序爲big-endian,則讀出
結果爲0x1234abcd;若字節序位little-endian,則讀出結果爲0xcdab3412.
如果我們將0x1234abcd寫入到以0x0000開始的內存中,則結果爲
                big-endian    little-endian
0x0000    0x12              0xcd
0x0001    0x23              0xab
0x0002    0xab              0x34
0x0003    0xcd              0x12
x86系列CPU都是little-endian的字節序.


http://blog.chinaunix.net/u/29331/showart_356837.html
本文來自:我愛研發網(52RD.com) - R&D大本營
詳細出處:http://www.52rd.com/Blog/Detail_RD.Blog_imjacob_14837.html
發佈了9 篇原創文章 · 獲贊 3 · 訪問量 8萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章