big-endian-vs-small-endian
Table of Contents
Big endian VS Little endian
- big endian 和 little endian是计算机体系中最常见的问题,首先我们要明白这个问题 是"不同计算机系统在byte排布上的分歧"
- 因为在"bit位置的排布"上是不存在"分歧的". 比如一个二进制序列00001001无论在什么
机器系统上面都会被如下排布
Addr. 00 01 02 03 04 05 06 07 +--+--+--+--+--+--+--+--+ | 0| 0| 0| 0| 1| 0| 0| 1| +--+--+--+--+--+--+--+--+ x x
- 按照低地址的bit,数值更高的"理解", 上面这个序列结果为9
2 ** (7 - 4) + 2 ** (7 - 7) = 9
- Bits的解释是"从右到左",也就是Bit0是rightmost和smallest,这个和阿拉伯十进制数 字的处理方法是一样的.
- 好了,那么后面我们就可以把byte看成是一个不可分割的整体了,我们把每个byte的内容 解释成"汉字"好了,比如00001001就是汉字"九", 那么00001000就是"八"
- 好了,如果我们想表示9876这个概念(我们以32-bit系统为例,四个byte是一个地址,那么
不同endian会有什么不同呢:
- 对于big endian来说,要把数字大的放在前面(地址小的byte),也就是现代写汉字的办 法"从左向右"书写: "九八七六"
- 对于little endian来说,要把数字大的放在后面(地址大的byte),也就是现代写汉字 的办法"从右向左"书写: "六七八九"
- 我们以0x01020304在内存中的布局来看一下两个endian:
- 首先是big endian
Addr. 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 +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | 0| 0| 0| 0| 0| 0| 0| 1| 0| 0| 0| 0| 0| 0| 1| 0| 0| 0| 0| 0| 0| 0| 1| 1| 0| 0| 0| 0| 0| 1| 0| 0| +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ x x x x x
- 从布局上来看,big-endian似乎更加符合人们"认知"的需求.实际上也是这样,一开始计算
机中出现的endian就一种,那就是big endian, 如果你真的想计算上面的数值多大,就和
计算刚才一个byte的大小一样. big-endian沿袭了bit级别的价值观:地址小的byte是
most-significant的位置,值1的话,数字更大
2 ** (31-07) + 2 ** (31-14) + 2 **(31-22) + 2 ** (31-23) + 2 ** (31-29) = 16909060
- 如果是little-endian,你就要时刻记着.它把数值小的byte放到地址小的byte位置,所以
效果是这样的
Addr. 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 +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | 0| 0| 0| 0| 0| 1| 0| 0| 0| 0| 0| 0| 0| 0| 1| 1| 0| 0| 0| 0| 0| 0| 1| 0| 0| 0| 0| 0| 0| 0| 0| 1| +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ x x x x x
- 这个如果计算真实值的话,对于人来说,有一点绕, 当然了,对于计算机来说,只要公式固
定了,差距几乎没有
2 ** (0 + 7 - 5 ) + 2 ** (8 + 15 - 14) + 2 ** (8 + 15 - 15) + 2 ** (16 + 23 - 22) + 2 ** (24 + 31 - 31) = 16909060
- little-endian这种东西,就是intel发明的了,之所以发明这种对于人类来说难以阅读的
新endian, 其出发点,是为了能够把一个32bit的地址按照8bit, 16bit, 32bit三种方式
来解析,而不需要换地址.在原来内存比较稀缺的,使用汇编的时代,这会带来很大的效率
提升
The benefit of little endianness is that a variable can be read as any length using the same address. For example a 32 bit variable can be read as an 8 bit or 16 bit variable without changing the address. This may have limited benefit these days, but in the days of assembler and limited memory it could be a significant advantage
- 我们用下面一个c语言的例子来说明little在"利用相同地址表达不同bit位数据"方面的
优点
#include <stdio.h> int main(int argc, char *argv[]){ int num = 0x01020304; int* pt = # char* cpt = (char*)pt; printf("num is %d\n", *cpt); cpt++; printf("num is %d\n", *cpt); cpt++; printf("num is %d\n", *cpt); cpt++; printf("num is %d\n", *cpt); return 0; } /****************************************************/ /* <===================OUTPUT===================> */ /* > result on Little-Endian Machine (Intel i7 CPU) */ /* num is 4 */ /* num is 3 */ /* num is 2 */ /* num is 1 */ /* > result on Big-Endian Machine (IBM Power CPU) */ /* num is 1 */ /* num is 2 */ /* num is 3 */ /* num is 4 */ /****************************************************/
- 如果我们的例子是0x00000001这种数据, 那么以16-bit或者32-bit读取这个地址,都会
得到1, 而在big-endian则在把这个地址当16bit数据读取中,会读成0
#include <stdio.h> int main(int argc, char *argv[]){ int num = 0x00000001; int* pt = # printf("num is %d\n", *pt); short* spt = (short*)pt; printf("num is %d\n", *spt); return 0; } /****************************************************/ /* <===================OUTPUT===================> */ /* > result on Little-Endian Machine (Intel i7 CPU) */ /* num is 1 */ /* num is 1 */ /* > result on Big-Endian Machine (IBM Power CPU) */ /* num is 1 */ /* num is 0 */ /****************************************************/
Memory Segmentation only show in Linux 32-bit arch
X86-32
- 内存的分页和分段都是将"不同进程"的内存映射到不同物理内存的实践,而Linux更喜欢 分页的方法,但是分段的方法在x86的机器上面又绕不过去,怎么办呢?
- Linux的办法就是所有的进程只用一个segmentation item.kernel自己也就一个segmentation item.
- 而这个分段也是"徒有其表",因为user和kernel的这个分段的可寻址地址都是0到4G
- 我们来看看3.2版本的Kernel对于user CS和DS寄存器的设置
/* File => arch/x86/include/asm/segment.h */ #define GDT_ENTRY_DEFAULT_USER_CS 14 #define GDT_ENTRY_DEFAULT_USER_DS 15 #define GDT_ENTRY_KERNEL_BASE (12) #define GDT_ENTRY_KERNEL_CS (GDT_ENTRY_KERNEL_BASE+0) #define GDT_ENTRY_KERNEL_DS (GDT_ENTRY_KERNEL_BASE+1)
- 也就是说User CS的位置是14, CS的index前十五项就是1110
- 而CS的后三项是TI和RPL:
- TI是0, 因为没使用LDT
- RPL是3, 因为是用户态
- 所以组合起来就是1110011, 换算成16进制就是73
- 同理可得User DS的值为1111011, 换算成16进制是7B, 在随便实验了两个x86Linux版本
3.2的程序之后,gdb打印出来的cs ds都如我们所设想的
(gdb) info registers ... cs 0x73 115 ss 0x7b 123 ds 0x7b 123 ...
X86-64
- 当然了,这个是x86-32的设置, 而x86-64则有不同的设置, 首先需要理解的是ia64是安
腾处理器的架构,所以去arch/ia64下面是找不到任何x86-64bit的配置的.安腾系列就
根本没有segmentation的设置,如下
/* File ==> arch/ia64/include/asm/segment.h */ #ifndef _ASM_IA64_SEGMENT_H #define _ASM_IA64_SEGMENT_H /* Only here because we have some old header files that expect it.. */ #endif /* _ASM_IA64_SEGMENT_H */
- 真正的64bit设置还是在文件arch/x86/include/asm/segment.h里面,只不过32位和64位
通过一个`#ifdef CONFIG_X86_32 `和`#else`分开了, 在else的部分,我们就看到了下
面的代码
#else #include <asm/cache.h> #define GDT_ENTRY_KERNEL32_CS 1 #define GDT_ENTRY_KERNEL_CS 2 #define GDT_ENTRY_KERNEL_DS 3 #define __KERNEL32_CS (GDT_ENTRY_KERNEL32_CS * 8) /* * we cannot use the same code segment descriptor for user and kernel * -- not even in the long flat mode, because of different DPL /kkeil * The segment offset needs to contain a RPL. Grr. -AK * GDT layout to get 64bit syscall right (sysret hardcodes gdt offsets) */ #define GDT_ENTRY_DEFAULT_USER32_CS 4 #define GDT_ENTRY_DEFAULT_USER_DS 5 #define GDT_ENTRY_DEFAULT_USER_CS 6
- 这里我们发现CS有两个值,分别是4和6, 看起来6更像一点,毕竟是64位么,不是32位
- 如果是6的话,那么cs的value就应该是<110>+<011>, 也就是16位的0x33, 我们在ubuntu
12.04版本的64位机器上面运行的话结果如下
cs 0x23 35
- Great!,那么那个4是什么意思呢?那个4是在64位上面运行32位的程序的时候,GDT_for_32 的那个item在GDT数组4的位置!, cs的value应该是<100>+<011>,也就是0x23.
- 在64机器上面编译代码为32位,以ubuntu为例,需要下载如下lib
$ sudo apt-get install g++-multilib libc6-dev-i386
- 我们再来写一个检测long长度的c代码
/* Filename long.c */ #include <stdio.h> int main() { long z; printf("Long int size is %d bytes long!\n", sizeof(z)); }
- 编译不同的native code:
- 32bit native code
gcc -m32 -o -g output32 long.c
- 64bit native code
gcc -m64 -o -g output64 long.c
- 32bit native code
- gdb来进行测试:
- 32bit native code: 果真为0x23!
(gdb) info registers eax 0xf7fc30a0 -134467424 ecx 0xffffd4c0 -11072 edx 0xffffd4e4 -11036 ebx 0xf7fc1000 -134475776 esp 0xffffd4a0 0xffffd4a0 ebp 0xffffd4a8 0xffffd4a8 esi 0x0 0 edi 0x8048320 134513440 eip 0x804842c 0x804842c <main+17> eflags 0x286 [ PF SF IF ] cs 0x23 35 ss 0x2b 43 ds 0x2b 43 es 0x2b 43 fs 0x0 0 gs 0x63 99
- 64bit native code: 也如我们所预测的是0x33
(gdb) info registers rax 0x400536 4195638 rbx 0x0 0 rcx 0x0 0 rdx 0x7fffffffe438 140737488348216 rsi 0x7fffffffe428 140737488348200 rdi 0x1 1 rbp 0x7fffffffe340 0x7fffffffe340 rsp 0x7fffffffe340 0x7fffffffe340 r8 0x7ffff7dd4dd0 140737351863760 r9 0x7ffff7de99d0 140737351948752 r10 0x833 2099 r11 0x7ffff7a2f950 140737348041040 r12 0x400440 4195392 r13 0x7fffffffe420 140737488348192 r14 0x0 0 r15 0x0 0 rip 0x40053a 0x40053a <main+4> eflags 0x246 [ PF ZF IF ] cs 0x33 51 ss 0x2b 43 ds 0x0 0 es 0x0 0 fs 0x0 0
- 32bit native code: 果真为0x23!