浅谈大小端(Endian)与位域
目錄
字節(jié)序
位序
常見位序的錯誤理解
以太網(wǎng)的大小端
以太網(wǎng)的字節(jié)序
以太網(wǎng)的位序
位域中的大小端
位域遇上大小端以太網(wǎng)通信
大端CPU發(fā)送
小端CPU發(fā)送
位域大小端問題的解決措施
大小端問題浪費了太多應(yīng)用、驅(qū)動、邏輯工程師太多的時間,無時無刻都有人正在被大小端坑,正好比此時的我在浪費時間談?wù)摯笮《恕?/strong>
如果學過計算機原理、編程等知識,都知道課本里對大小端的一段描述:
小端模式:數(shù)據(jù)的高字節(jié),存放在高地址中。計算機讀取數(shù)據(jù)的方向,是從高地址開始讀取的;
大端模式:數(shù)據(jù)的高字節(jié),存放在低地址中。計算機讀取數(shù)據(jù)的方向,是從低地址開始讀取的;
注意這里只提到了字節(jié)序,并未提及到位序問題。
常見大小端CPU如下:
大端cpu:PowerPC
小端cpu:x86、arm(內(nèi)核支持大端但從未見過大端)
注:
·MSB/LSB:
????????用于描述寄存器的各個位域含義時:MSB(Most Significant Bit 最高有效位),LSB(Least Significant Bit 最低有效位)。0b10010100,無論在大端還是小端機中,MSB為1,LSB為0。
????????用于描述整形數(shù)據(jù)時:指數(shù)學層面的高權(quán)重字節(jié)與低權(quán)重字節(jié),如0x12345678,無論在大端還是小端機中,MSB都是指的0x12,LSB指0x78。
·Big Endian/Little Endian:指兩種端序。
·用[a:b]的方式表示多個bit位,如:[3:0]表示bit3?bit2 bit1?bit0,[0:3]表示bit0?bit1?bit2 bit3。
·為了視覺效果會把0寫成00,把1寫成01。
·文章中提到的高位低位均指內(nèi)存或寄存器的地址的高位與低位,如果是數(shù)字的高字節(jié)與低字節(jié),會強調(diào)為數(shù)學概念高權(quán)重的位與低權(quán)重的位,以防止教科書上的數(shù)字字節(jié)、存儲器字節(jié)傻傻分不清楚。
字節(jié)序
從字節(jié)序的視角一個多字節(jié)數(shù)0x12345678。
存放在大端CPU的RAM時: Byte0????????Byte1????????Byte2????????Byte3 0x12 ????????0x34? ? ? ? 0x56 ????????0x78存放在小端CPU的RAM時: Byte3????????Byte2????????Byte1???????Byte0 0x12 ????????0x34? ? ? ? 0x56 ???????0x78位序
?從位序的視角看一個單字節(jié)數(shù)0x12。
存放在大端CPU的RAM時,[0:7] = 0x12(0b00010010): bit0 bit1 bit2 bit3 bit4 bit5 bit6 bit7 0? ? 0? ??0?? ?1 ? ?0? ??0? ? 1? ?0存放在小端CPU的RAM時,[7:0] = 0x12(0b00010010): bit7 bit6 bit5 bit4 bit3 bit2 bit1 bit0 0? ?0? ??0?? ?1? ??0? ??0?? ?1 ? ?0?從位序的視角看一個多字節(jié)數(shù)0x12345678。
存放在大端CPU的RAM時,[0:31] = 0x12345678(0b0001 0010 0011 0100 0101 0110 0111 1000) bit: 00 01 02 03? ?04 05 06 07? ?08 09 10 11? ?12 13 14 15? ?16 17 18 19? ?20 21 22 23? ?24 25 26 27? ?28 29 30 31 val:? 0? 0? 0? 1? ??0??0??1??0? ?0? 0??1??1 ? ?0 ?1??0 ?0? ? 0? 1? 0??1?? ?0 ?1??1? 0? ??0??1? 1??1?? ?1? 0??0??0存放在小端CPU的RAM時,[31:0] = 0x12345678(0b0001 0010 0011 0100 0101 0110 0111 1000) bit: 31 30 29 28? ?27 26 25 24? ?23 22 21 20? ?19 18 17 16? ?15 14 13 12? ?11 10 09 08? ?07 06 05?04? ?03 02 01 00 val:? 0? 0??0 ?1? ?0? 0? 1??0? ?0 ?0??1??1? ?0 ?1??0??0? ??0? 1??0? 1? ?0??1? 1??0 ? ?0??1??1 ?1 ? ?1??0??0 ?0常見位序的錯誤理解
如果不知道位序這個概念,很可能會把多字節(jié)在RAM中存儲的順序弄錯,如對多字節(jié)數(shù)0x12345678的錯誤理解
在大端系統(tǒng)中,存儲順序錯理解為: bit:??07 06 05?04? ?03 02 01 00 ?15 14 13 12? ?11 10 09 08? ?23 22 21 20? ?19 18 17 16? ?31 30 29 28? ?27 26 25 24 val:? 0? 0? 0? 1? ??0??0??1??0? ?0? 0??1??1 ? ?0 ?1??0 ?0? ? 0? 1? 0??1?? ?0 ?1??1? 0? ??0??1? 1??1?? ?1? 0??0??0在小端系統(tǒng)中,存儲順序錯理解為: bit: 24 25 26 27? ?28 29 30 31? ?16 17 18 19? ?20 21 22 23? ?08 09 10 11? ?12 13 14 15? ?00 01 02 03? ?04 05 06 07 val:? 0? 0??0 ?1? ?0? 0? 1??0? ?0 ?0??1??1? ?0 ?1??0??0? ??0? 1??0? 1? ?0??1? 1??0 ? ?0??1??1 ?1 ? ?1??0??0 ?0以太網(wǎng)的大小端
以太網(wǎng)的字節(jié)序
無論大端CPU還是小端CPU,一定是先收發(fā)低字節(jié),再收發(fā)高字節(jié)。
以太網(wǎng)的位序
參考文章:Ethernet下字節(jié)序和bit序的總結(jié)_shaohui973的博客-CSDN博客_bit0和bit7的順序,根據(jù)個人理解有所修改。(由于筆者未專門研究以太網(wǎng)物理層邏輯,若文章中存在錯誤,請一定要幫忙指出)
網(wǎng)絡(luò)接口要遵從的一個原則:在發(fā)送bit數(shù)據(jù)的時候,先發(fā)送MSB,最后發(fā)送LSB,即最先發(fā)送數(shù)學層面上權(quán)重最大的位,即最左邊的bit數(shù)據(jù);接收bit數(shù)據(jù)的時候,先收到MSB,最后收到LSB,即將最先收到的bit數(shù)據(jù),移位存儲到數(shù)學層面上權(quán)重最大的位所在存儲器中,即最左邊的bit中。在計算機中表現(xiàn)如下:(注意此處提到的MSB和LSB并非存儲意義上的大小端,而是數(shù)字層面上高位與低位,權(quán)重高的為高位,權(quán)重低的為低位。計算機的存儲空間并不存在上下左右,此處的“左邊”,指人類對數(shù)字或地址的書寫習慣上的描述,如數(shù)字0x112233中,0x11就是該數(shù)字權(quán)重最高的字節(jié),位于最左邊)
在大端系統(tǒng)中,網(wǎng)卡發(fā)送bit數(shù)據(jù)bit[0:7] = 0x55(0b01010101)時,依次發(fā)送bit0, bit1, bit2, bit3, bit4, bit5, bit6, bit7,即發(fā)送01010101。
在小端系統(tǒng)中,網(wǎng)卡發(fā)送bit數(shù)據(jù)bit[7:0] = 0x55(0b01010101)時,依次發(fā)送bit7, bit6, bit5, bit4, bit3, bit2, bit1, bit0,即發(fā)送01010101。
在大端系統(tǒng)中,網(wǎng)卡收到bit數(shù)據(jù)時,依次存放在bit0, bit1, bit2, bit3, bit4, bit5, bit6, bit7,得到數(shù)據(jù)bit[0:7] = 0x55(0b01010101) 。
在小端系統(tǒng)中,網(wǎng)卡收到bit數(shù)據(jù)時,依次存放在bit7, bit6, bit5, bit4, bit3, bit2, bit1, bit0,得到數(shù)據(jù)bit[7:0] = 0x55(0b01010101)。
同樣是以太網(wǎng)發(fā)送端口,在大端系統(tǒng)與小端系統(tǒng)中邏輯電路設(shè)計并不相同,才能滿足大小端系統(tǒng)通信發(fā)送的數(shù)據(jù)和收到的數(shù)據(jù)值相同的基本要求。
不僅以太網(wǎng)接口原理如此,很多單通道串行通信都是如此,在大端系統(tǒng)與小端系統(tǒng)中收發(fā)的位序并不相同,在這些標準通信協(xié)議規(guī)范中提到的先發(fā)送高位中的“位”并不是指地址的位,而是數(shù)字含義中的位置,這里的高位指高權(quán)重的數(shù)字位,即左邊的值。
位域中的大小端
在寄存器定義、通信協(xié)議中經(jīng)常會看到位域定義,在C/C++的結(jié)構(gòu)體中可定義與之對應(yīng)的位域結(jié)構(gòu)體,以方便解析及組包。位域定義方式如下:
struct 位域結(jié)構(gòu)名?
{
? ? type [member_name] : width ;
};
本文為了方便,將存儲到同一個標準數(shù)據(jù)類型的所有成員稱為一個域,同一個域內(nèi)的成員稱為域成員。如下面的結(jié)構(gòu)體中a1和a2屬于同一個域,a1和a2是這個域的域成員;b1、b2、b3、b4屬于同一個域,b1、b2、b3、b4是這個域的域成員;
有如下C/C++結(jié)構(gòu)體struct st1及變量data:
struct st1 {uint8_t? a1 : 2;uint8_t? a2 : 6;uint16_t b1 : 3;uint16_t b2 : 4;uint16_t b3 : 5;uint16_t b4 : 4; };struct st1 data ={.a1 = 0x1,.a2 = 0x3,.b1 = 0x4,.b2 = 0x8,.b3 = 0xb,.b4 = 0x3 };結(jié)構(gòu)體成員.a1和.a2屬于同一個域,域大小為8bit。.b1、.b2、.b3、.b4屬于同一個域,域大小為16bit。
域的定義一般要遵循如下原則:域大小可以是8/16/32/64bit,域之間不可交叉,域的所有成員總大小必須等于所屬域大小,必要時加入保留(reserved)位來填補。
C/C++的結(jié)構(gòu)體有一個規(guī)定,無論大端還是小端,先定義的成員一定是低字節(jié)和低位。
在大端系統(tǒng)中,結(jié)構(gòu)體變量及其成員的存儲情況如下:
? ? ? ? .a1? ? ? ?? ?.a2? ? ? ???? ?.b1? ? ? ? ?.b2? ?? ? ? ?.b3? ? ? ? ?.b4 bit? ? ? [00:01]? ? ?[02:07]? ? ? ? ?[08:10]? ? ? [11:14]? ? ? [15:19]? ? ? [20:23] data? ?01? ? ? ? ? 000011? ? ? ? 100? ?? ? ? ?1000? ? ? ? ?01011? ? ? ?0011.a1[0:1] = 0b01(0x1); .a2[0:5] = 0b000011(0x3); .b1[0:2] = 0b100(0x4); .b2[0:3] = 0b1000(0x8); .b3[0:4] = 0b01011(0xb); .b4[0:3] = 0b0011(0x3);?在小端系統(tǒng)中,各個結(jié)構(gòu)體成員的存儲情況如下:
? ? ? ? ?.a1? ? ? ? ?.a2? ? ? ? ? ? ?.b1? ? ? ? ??.b2? ? ? ? ? .b3? ? ? ? ? ?.b4 bit? ? ? [01:00]? ? ?[07:02]? ? ? ? ?[10:08]? ? ? [14:11]? ? ? [19:15]? ? ? [23:20] data? ? 01? ? ? ? ? 000011? ? ? ? 100? ? ? ? ?1000? ? ? ? ?01011? ? ? ?0011.a1[1:0] = 0b01(0x1); .a2[5:0] = 0b000011(0x3); .b1[2:0] = 0b100(0x4); .b2[3:0] = 0b1000(0x8); .b3[4:0] = 0b01011(0xb); .b4[3:0] = 0b0011(0x3)位域&大小端&以太網(wǎng)通信
位域可以2bit、5bit等不規(guī)則的多個bit來描述一個成員,但單通道串行通信接口只能以8bit為單位進行逐位發(fā)送,且從高bit發(fā)送還是低bit發(fā)送取決于CPU的大小端模式,大端CPU先發(fā)送bit0,小端CPU先發(fā)送bit7.
使用上文提到的結(jié)構(gòu)體struct st1 data進行網(wǎng)絡(luò)發(fā)送時會出現(xiàn)如下有趣現(xiàn)象:
大端CPU發(fā)送
發(fā)送上述數(shù)據(jù),網(wǎng)絡(luò)端口發(fā)送的順序?qū)?從左到右的發(fā)):01? 000011? 100? 1000? 01011? 0011(為方便區(qū)分字節(jié),同一個byte的位背景顏色相同;為方便區(qū)分結(jié)構(gòu)體成員,成員間用空格隔開)。
如果接收端為大端CPU,針對每個字節(jié)的接收,收到的第一個bit放到bit0,最后一個bit放到bit7,最終收到的數(shù)據(jù)將是:bit[0:23]??01? 000011? 100? 1000? 01011? 0011。如果接收端程序定義的結(jié)構(gòu)體與發(fā)送端完全相同,則每個成員剛好對應(yīng)上,數(shù)據(jù)解析正確。
如果接收端為小端CPU,針對每個字節(jié)的接收,收到的第一個bit放到bit7,最后一個bit放到bit0,最終收到的數(shù)據(jù)將是:bit[23:0]? 1011? 00111? 0010? 000??010000? 11。如果接收端程序定義的結(jié)構(gòu)體與發(fā)送端完全相同,則由于發(fā)送位序顛倒,數(shù)據(jù)已經(jīng)完全紊亂。根據(jù)C/C++的規(guī)定:無論大端還是小端CPU,先定義的結(jié)構(gòu)體成員一定位于低位或低字節(jié),解析出來的數(shù)據(jù)如下:
.a1[1:0] = 0b11(0x3);
.a2[5:0] = 0b010000(0x10);
.b1[2:0] = 0b000(0x0);
.b2[3:0] = 0b0010(0x2);
.b3[4:0] = 0b00111(0x7);
.b4[3:0] = 0b1011(0xb);
和源數(shù)據(jù)相比,已經(jīng)面目全非了。
小端CPU發(fā)送
小端發(fā)送問題與大端發(fā)送原理相同,讀者可自己分析,加深印象。
位域大小端問題的解決措施
問題原因:位序的顛倒使得域內(nèi)部數(shù)據(jù)存儲順序顛倒;如果域大于1個字節(jié),且域成員間存在跨字節(jié)情況,則位序顛倒及字節(jié)序不顛倒特性,會使得域成員存在數(shù)據(jù)出現(xiàn)重組情況,進一步加重數(shù)據(jù)的紊亂。分析起來比較燒腦。
筆者總結(jié)了一套解決此問題的方法:
1、大端CPU程序與小端CPU程序中的位域結(jié)構(gòu)體中各個域間順序相同;
2、大端CPU程序與小端CPU程序中的位域結(jié)構(gòu)體中相同域內(nèi)部的域成員順序相反;
3、大端CPU或小端CPU在發(fā)送或收到數(shù)據(jù)時,任意一方做域的大小端字節(jié)序轉(zhuǎn)換,如域為uint16_t則按照2字節(jié)大小端轉(zhuǎn)換,為uint32_t則按照4字節(jié)大小端轉(zhuǎn)換。
具體是大端CPU還是小端CPU做結(jié)構(gòu)體定義修正及發(fā)送時域大小端轉(zhuǎn)換,取決于項目定義的報文是大端模式還是小端模式。如果報文定義的文檔規(guī)定使用大端定義,則大端CPU按照文檔定義結(jié)構(gòu)體既可,且發(fā)送時無需做大小端轉(zhuǎn)換;而小端CPU的程序則需要根據(jù)上述解決方法來處理。
驗證:
?假若報文協(xié)議規(guī)定使用大端模式,則大端CPU的結(jié)構(gòu)體定義如下:
struct st1 {uint8_t? a1 : 2;uint8_t? a2 : 6;uint16_t b1 : 3;uint16_t b2 : 4;uint16_t b3 : 5;uint16_t b4 : 4; };struct st1 data ={.a1 = 0x1,.a2 = 0x3,.b1 = 0x4,.b2 = 0x8,.b3 = 0xb,.b4 = 0x3 };?小端CPU的結(jié)構(gòu)體定義如下:
struct st1 {uint8_t? a2 : 6;uint8_t? a1 : 2;uint16_t b4 : 4;uint16_t b3 : 5;uint16_t b2 : 4;uint16_t b1 : 3; };char buff[3]; struct st1 *p_data = (struct st1 *)buff;大端CPU通過以太網(wǎng)發(fā)送數(shù)據(jù)順序:01? 000011? 100? 1000? 01011? 0011
小端CPU通過以太網(wǎng)收到數(shù)據(jù):bit[23:0]? 101100111001000001000011
數(shù)據(jù)用存入buff后做域大小端轉(zhuǎn)換,得到數(shù)據(jù)bit[23:0]? 100100001011001101000011
p_data進行解析解析得到數(shù)據(jù)bit[23:0]? 100? 1000? 01011? 0011??01? 000011,各成員值如下:
.a1[1:0] = 0b01(0x1);
.a2[5:0] = 0b000011(0x3);
.b1[2:0] = 0b100(0x4);
.b2[3:0] = 0b1000(0x8);
.b3[4:0] = 0b01011(0xb);
.b4[3:0] = 0b0011(0x3);
解析得到的數(shù)據(jù)和發(fā)送端的數(shù)據(jù)完全一樣,數(shù)據(jù)解析正確。
大端ARM
ARM Cortex-M3的端模式。
ARM的小端模式和前面所講完全相同,但它的大端模式比較特殊,32bit的數(shù)據(jù)的位序并不是bit0-bit31,而是bit[7:0][15:8][23:16][31:24]。
CM3 同時支持小端模式和大端模式。但是,單片機其它部分的設(shè)計,包括總線的連接,內(nèi)存控制器以及外設(shè)的性質(zhì)等, 一定要先在單片機的數(shù)據(jù)手冊上查清楚可以使用的端。在絕大多數(shù)情況下,基于 CM3 的單片機都使用小端模式——為了避免不必要的麻煩,在這里推薦讀者清一色地使用小端模式。
CM3中對大端模式的定義還與ARM7的不同(小端的定義都是相同的)。在ARM7中,大端的方式被稱為“字不變大端”,而在CM3中,使用的是“字節(jié)不變大端”。如表5.4所示。
?請注意:在 AHB 總線上的 BE‐8 模式下,數(shù)據(jù)字節(jié) lane 的傳送格式是與小端模式一致的。
這是不同于 ARM7TDMI 的行為,它在大端模式下會有另一種總線 lane 安排,如表 5.6
所示。
在CM3中,是在復位時確定使用哪種端模式的,且運行時不得更改。指令預(yù)取永遠使用小端模式,在配置控制存儲空間的訪問也永遠使用小端模式(包括NVIC,FPB之流)。另外,外部私有總線地址區(qū)0xE0000000至0xE00FFFFF也永遠使用小端模式。
當 SoC 設(shè)計不支持大端模式,卻有一些外設(shè)包含了大端模式時,可以輕易地使用REV/REVH指令來完成端模式的轉(zhuǎn)換。
對ARM的大端不理解也并不影響我們對大小端的理解與實際應(yīng)用,因為我還沒使用過大端ARM內(nèi)核的芯片(個人能力有限)。
總結(jié)
以上是生活随笔為你收集整理的浅谈大小端(Endian)与位域的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 软件项目管理第七章笔记---人力资源管理
- 下一篇: 考研英语一历年真题写作(小作文+大作文)