bitfield与endian
本文是原「蜗窝讨论区」的历史存档(2016-06-24),来自版块「Linux kernel技术问答」,共 4 帖。讨论区已停止服务,此处仅供查阅。
看到这个博客觉得很好,把自己日常的笔记也贴出来,希望大家多多指教。(希望没违反版规) 今天鼓捣的是bitfield的endian特性,纠结了一天,还好有所领悟。
网上开始关注bitfield的endian特性,似乎都是从linux源代码的一个细节开始的。
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>"
....
} 一些朋友看到这段代码,再品味一下"__BIG_ENDIAN_BITFIELD"这个宏,就会有种被颠覆三观的感觉。因为在我们心目中,只有byte order之说,没有bit order之说。 CPU是否有bit order的区别,其实很好验证。(我感觉这一段写的不好,大家可以直接ctrl-F跳到【第二段】阅读) x86的bit order是little endian的,(这样说也许不对,但很多人就是这么认为的,包括我),例如下面一个情景: 内存中有如下两个字节的内存单元, 其中0x7c00处存放的是1,0x7c01处存放的是2 。
<<<[0x7c01]<<<< growth of memory address <<<[0x7c00]<<<<< 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 1
上图就是一个典型的intel风格的内存模型,RAM地址从右往左变大,每个字节内的bit也是从右向左“变高”。 这两个字节,(unsigned shor)0x7c00,在intel CPU眼中是0x0201,在MIPS眼中,是0x0102。对吧?其实这已经说明问题了,如果MIPS是有bit order的,那它读出来的,应该是0x8040 。 我们前面说要做实验,我们编译下面的C文件: ---- file m.c--- int abc = 0x0102
对,这个文件就一句话,我们把它交叉编译成MIPS机器下的目标文件: $ mips-linux-gnu-gcc -c -o m.o m.c 如果MIPS的bit order是big endian的话,那gcc编译器为了保证到时候MIPS读出来的abc是0x0102,它肯定会为abc创建这样的内存布局: <<<[0x7c01]<<<< growth of memory address <<<[0x7c00]<<<<< 0 1 0 0 0 0 0 0 1 0 0 0 0 0 0 0 因此,我们在x86下看到的abc的值就该是0x4080 。 我们看一下: wws@localhost:~/lab/test$ objdump -s m.o|grep data -A1 Contents of section .data: 0000 00000102 00000000 00000000 00000000 ................
并不是0x4080,而是0x0201,表明MIPS使用的是跟x86一样的bit order。 好像说了半天废话。。。。。
【第二段】 既然没有所谓的bit order,那怎么还会那样子呢?(你还记得是哪样子吗) 事情是这样的,大多数人在阅读linux源码的时候,特别是习惯了inte体系结构的读者,脑子中只有一种内存模型,就是我刚才画的0x7c00-->0x7c01的模型。如果有一个整数在MIPS眼中是0xabcdef00,在我们心中,它在内存里就长成这样子: <<<< growth of memory address <<<<< 00 ef cd ab
如果有这么一个MIPS上的结构体: -------------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};
我们会认为这个结构体的内存布局跟刚才的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眼中,这个结构体的value是0xabdcef00。两个bitfield的位置颠倒了。 这就是问题所在,gcc开发者眼中的big endian的内存模型,其实是这样: >>>>>>memory address growth>>>>>>>>> ab cd ef 00
其实在大端模式下,本来就该这样看内存,这样非常自然。 现在问题就明白了,在gcc编译器作者眼中,你指定的结构体的布局是这样来的: >>>>>>>>>memory address growth >>>>>>>>>>>>> (char ab) (int d:4) (int c:4) (char ef) (char oo)
那么,编译器当然会生成 0xabdcef00这个value。
总结一下,这个问题,不是bit order的问题。是编译器的问题。更确切说,是编译器作者的问题。 为什么bitfield为什么会受 byte endian的影响呢。因为byte endian一变,编译器作者看待内存的眼光(他心中用的内存模型)也就变了,所以bitfield的生长方向也就变了。
顺便分享MIPS交叉编译tools chain的下载,输入: wget -c -T 20 https://sourcery.mentor.com/public/gnu_toolchain/mips-linux-gnu/mips-2011.03-93-mips-linux-gnu-i686-pc-linux-gnu.tar.bz2 然后解压缩,里面的bin目录里的文件就可以直接用了。 这是我见过的最方便的软件。据说已经商业化了。 wget里的年份估计能用更新的,到它的网站上看看。 用浏览器下载很慢,wget反而很快。 一百多兆,把手机下停机了。
另外ARM工具链的安装,只需要一句话: sudo apt-get install gcc-arm-linux-gnueabi 装完了,命令是arm-linux-gnueabi-gcc 。 -c -o的时候,加-mbig-endian没问题,但是链接的时候就报错了。具体什么错,我也不关心。 我只需要编译出来big endian的目标文件,能看data段就够了。
好文章啊,语言生动、有趣、易懂,多谢分享! 另外可以整理一下在博客上发出来
实际上是 hardware/ABI 决定了Endianess和bitfiled packing的联系. Check https://gcc.gnu.org/onlinedocs/gcc/Structures-unions-enumerations-and-bit-fields-implementation.html The order of allocation of bit-fields within a unit (C90 6.5.2.1, C99 and C11 6.7.2.1). Determined by ABI.
