- 論壇徽章:
- 13
|
本帖最后由 karma303 于 2016-08-28 10:33 編輯
網(wǎng)上開始關(guān)注bitfield的endian特性,似乎都是從linux源代碼的一個細節(jié)開始的。
- struct iphdr {
- #if defined(__LITTLE_ENDIAN_BITFIELD)
- __u8 ihl:4,
- version:4;
- #elif defined (__BIG_ENDIAN_BITFIELD)
- __u8 version:4,
- ihl:4;
- #else
- #error "Please fix <asm/byteorder.h>"
- ....
- }
復(fù)制代碼
一些朋友看到這段代碼,再品味一下"__BIG_ENDIAN_BITFIELD"這個宏,就會有種被顛覆三觀的感覺。因為在我們心目中,cpu只有byte order之說,沒有bit order之說。
一個byte內(nèi)部的bit order也分big endian和little endian嗎?
可以肯定的是,對CPU來說,也就是軟件編程中,我們不需要關(guān)心bit order的endianness,它是硬件層的事情。
Bit endianness or bit-level endianness refers to the transmission order of bits over a serial medium. The bit-level analogue of little-endian (least significant bit goes first) is used in RS-232, Ethernet, and USB ... Usually the order of bits is transparently managed by the hardware and only relevant on this very low level ...
上面提到了以太網(wǎng),有些人肯定心想,果然,我確實在linux的網(wǎng)絡(luò)模塊的源碼里,看到很多的ntohx和htonx。
其實那仍然是字節(jié)序的轉(zhuǎn)換,跟這里的bit order不相干。這里的Ethernet指的是硬件層的,連驅(qū)動都不需要關(guān)心bit order。
并且,真正到了硬件層,到了關(guān)心Bit Endianness的地方,大家反而不叫它" Bit Endianness"了,而是直接喚作LSB first和MSB first。
thus terms like LSB first and MSB first are more descriptive than the concept of endianness.
我們用下面這段話再堅定一下我們的三觀:
Within a byte, for all processors, bit 7 is the most significant bit. So the big end byte looks the same for both byte orderings. Usually in printed material this bit is shown at the left, as in 00010010.
好了,既然沒有所謂的bit order,那怎么還會那樣子呢?(你還記得是哪樣子嗎)
事情是這樣的,大多數(shù)人在閱讀linux源碼的時候,特別是習(xí)慣了inte體系結(jié)構(gòu)的讀者,腦子中只有一種內(nèi)存模型。隨便說一個數(shù)字,0xabcdef00,我們腦海里立刻就浮現(xiàn)出一個畫面:(假設(shè)這個int位于0x7c00地址處)
![]()
上面是經(jīng)典的x86的內(nèi)存視圖。假如機器換做MIPS呢,我們會不假思索的構(gòu)畫出big endian下的布局:
![]()
一切看上去正常。
現(xiàn)在,假如在MIPS機器上,有這么一個結(jié)構(gòu)體。
- -------------m.c---------------
- struct abcdef {
- char ab;
- int d: 4;
- int c: 4;
- char ef;
- char oo;
- }abcdefoo = { ab: 0xab, d: 0xd, c: 0xc, ef: 0xef, oo: 0};
- -----------------------------------------------------
復(fù)制代碼 我們會覺得,這只不過是一個寫的比較啰嗦的0xabcdef00。出來的值還是一樣的。
但我們錯了?匆幌。
wws@localhost:~/lab/test$ mips-linux-gnu-gcc -c -o m.o m.c
wws@localhost:~/lab/test$ objdump -s m.o|grep data -A1
Contents of section .data:
0000 00000102 abdcef00 00000000 00000000 ...............
在MIPS眼中,這個結(jié)構(gòu)體的value是0xabdcef00。兩個bitfield的位置顛倒了。
這就是問題所在,gcc開發(fā)者眼中的big endian的內(nèi)存模型,其實是這樣:
![]()
其實在大端模式下,本來就該這樣看內(nèi)存,這樣非常自然。
現(xiàn)在問題就明白了,在gcc編譯器作者眼中,你指定的結(jié)構(gòu)體的布局是這樣來的:
>>>>>>>>>memory address growth >>>>>>>>>>>>>
(char ab) (int d:4) (int c:4) (char ef) (char oo)
那么,編譯器當(dāng)然會生成 0xabdcef00這個value。
總結(jié)一下,這個問題,不是bit order的問題。是編譯器的問題。更確切說,是編譯器作者的問題。
為什么bitfield為什么會受 byte endian的影響呢。因為byte endian一變,編譯器作者看待內(nèi)存的眼光(他心中用的內(nèi)存模型)也就變了,所以bitfield的生長方向也就變了。
----------------------7月1日更新-----------------------
【當(dāng)bitfield跨字節(jié)。。!
下面展示(short *)0x7c00 = 0xABCD 這條代碼分別在MIPS和X86機器上執(zhí)行后,內(nèi)存中的布局。
up.png (25.5 KB, 下載次數(shù): 220)
下載附件
2016-07-01 14:28 上傳
上面兩張圖,就是gcc編譯器作者眼中的內(nèi)存模型。注意兩點:
1,它們的內(nèi)存地址生長的方向不同。MIPS向右,X86向左。
2,它們的比特位都是(注意,都是)從右往左對應(yīng)7~0。
我想說的是第二點,比特位的生長順序非常重要。
就這一點,我們要與gcc編譯器的作者達成共識。它們眼中的bit生長順序就是這樣。
然后,我們再看gcc對于大小端上bitfield的排布,就容易理解了。
這樣一段代碼:
- #include<stio.h>
- #pragma pack(1)
- struct ab{
- union{
- struct {
- int m: 13;
- int n: 3;
- };
- struct {
- unsigned char a;
- unsigned char b;
- };
- };
- };
- void main(void){
- struct ab ab;
- ab.m = (0x34<<5) + 1;
- ab.n = 1;
- printf("a:%x, b:%x\n", ab.a, ab.b);
- }
復(fù)制代碼
在x86和mips上的輸出分別是:
wws@localhost:~/lab/test$ gcc -o x86.elf a.c
wws@localhost:~/lab/test$ mips-linux-gnu-gcc -o mips.elf a.c -static
wws@localhost:~/lab/test$ ./x86.elf
a:81, b:26
wws@localhost:~/lab/test$ qemu-mips ./mips.elf
a:34, b:9
對照上面的內(nèi)存模型(內(nèi)存地址就不改了),很容易解釋:
bottom.png (25.32 KB, 下載次數(shù): 202)
下載附件
2016-07-01 14:27 上傳
===2016,7,28更新=======
還是說明一下為好,就像我的別的大部分文章,這一篇照例不是寫給新手的。甚至說,你得對字節(jié)序,比特序,bitfiled有相當(dāng)多的思考后,才適宜讀它。
參考資料:
chortle.ccsu.edu/AssemblyTutorial/Chapter-15/ass15_4.html
en.wikipedia.org/wiki/Endianness
下一篇:biefield與endian 2
|
|