1Linuxx86_64与i386区别之——内存寻址收藏21引子毫无疑问,不管是32位,还是64位处理器,所有进程(执行的程序)都必须占用一定数量的内存,它或是用来存放从磁盘载入的程序代码,或是存放取自用户输入的数据等等。不过进程对这些内存的管理方式因内存用途不一而不尽相同,有些内存是事先静态分配和统一回收的,而有些却是按需要动态分配和回收的。对任何一个普通进程来讲,它都会涉及到5种不同的数据段。稍有编程知识的朋友都该能想到这几个数据段种包含有“程序代码段”、“程序数据段”、“程序堆栈段”等。不错,这几种数据段都在其中,但除了以上几种数据段之外,进程还另外包含两种数据段。下面我们来简单归纳一下进程对应的内存空间中所包含的5种不同的数据区。代码段:代码段是用来存放可执行文件的操作指令,也就是说是它是可执行程序在内存种的镜像。代码段需要防止在运行时被非法修改,所以只准许读取操作,而不允许写入(修改)操作——它是不可写的。数据段:数据段用来存放可执行文件中已初始化全局变量,换句话说就是存放程序静态分配的变量和全局变量。BSS段:BSS段包含了程序中未初始化全局变量,在内存中bss段全部置零。堆(heap):堆是用于存放进程运行中被动态分配的内存段,它大小并不固定,可动态扩张或缩减。当进程调用malloc等函数分配内存时,新分配的内存就被动态添加到堆上(堆被扩张);当利用free等函数释放内存时,被释放的内存从堆中被剔除(堆被缩减)。栈:栈是用户存放程序临时创建的局部变量,也就是说我们函数括弧“{}”中定义的变量(但不包括static声明的变量,static意味这在数据段中存放变量)。除此以外在函数被调用时,其参数也会被压入发起调用的进程栈中,并且待到调用结束后,函数的返回值也回被存放回栈中。由于栈的先进先出特点,所以栈特别方便用来保存/恢复调用现场。从这个意义上将我们可以把堆栈看成一个临时数据寄存、交换的内存区。静态分配内存就是编译器在编译程序的时候根据源程序来分配内存.动态分配内存就是在程序编译之后,运行时调用运行时刻库函数来分配内存的.静态分配由于是在程序运行之前,所以速度快,效率高,但是局限性大.动态分配在程序运行时执行,所以速度慢,但灵活性高。术语BSS已经有些年头了,它是blockstartedbysymbol的缩写。因为未初始化的变量没有对应的值,所以并不需要存储在可执行对象中。但是因为C标准强制规定未初始化的全局变量要被赋予特殊的默认值(基本上是0值),所以内核要从可执行代码装入变量(未赋值的)到内存中,然后将零页映射到该片内存上,于是这些未初始化变量就被赋予了0值。这样做避免了在目标文件中进行显式地初始化,减少空间浪费(来自《Linux内核开发》)我们在x86_64环境上运行以下经典程序:#includestdio.h#includemalloc.h#includeunistd.hintbss_var;intdata_var0=1;intmain(intargc,char**argv){printf(belowareaddressesoftypesofprocess'smem\n);printf(Textlocation:\n);printf(\tAddressofmain(CodeSegment):%p\n,main);printf(____________________________\n);intstack_var0=2;printf(StackLocation:\n);printf(\tInitialendofstack:%p\n,&stack_var0);intstack_var1=3;printf(\tnewendofstack:%p\n,&stack_var1);printf(____________________________\n);printf(DataLocation:\n);printf(\tAddressofdata_var(DataSegment):%p\n,&data_var0);staticintdata_var1=4;printf(\tNewendofdata_var(DataSegment):%p\n,&data_var1);printf(____________________________\n);printf(BSSLocation:\n);printf(\tAddressofbss_var:%p\n,&bss_var);printf(____________________________\n);char*b=sbrk((ptrdiff_t)0);printf(HeapLocation:\n);printf(\tInitialendofheap:%p\n,b);brk(b+4);b=sbrk((ptrdiff_t)0);printf(\tNewendofheap:%p\n,b);return0;}运行结果:[root@kolleraupdilogs]#./memorybelowareaddressesoftypesofprocess'smemTextlocation:Addressofmain(CodeSegment):0x400568____________________________StackLocation:Initialendofstack:0x7fff0e0dc544newendofstack:0x7fff0e0dc540____________________________DataLocation:Addressofdata_var(DataSegment):0x600bfcNewendofdata_var(DataSegment):0x600c00____________________________BSSLocation:Addressofbss_var:0x600c14____________________________HeapLocation:Initialendofheap:0xb059000Newendofheap:0xb05900432x86_64体系新变化AMDx86_64的出现,给全新的64位的x86带来了很多结构上的变化:1)64位整型数在x86-64中,所有通用寄存器(GPRs)都从32位扩充到了64位,名字也发生了变化。8个通用寄存器(eax,ebx,ecx,edx,ebp,esp,esi,edi)在新的结构中被命名为rax,rbx,rcx,rdx,rbp,rsp,rsi,rdi,它们都是64位的。呵呵,想当年,从16位扩充到32位时,同样也有一次名字的变化。所有算术逻辑操作、寄存器到内存的数据传输现在都能以64位的整形类型进行操作。堆栈的压栈和弹出操作都以8字节的单位进行,而且指针类型也拥有了64位。2)新增寄存器在新的架构中,另外新增了8个通用寄存器:64位的r8,r9,r10,r11,r12,r13,r14,r15。这样就有利与编译器将函数参数、返回值等放在这些新增的GPR里面进行传递,从而提高了程序的运行速度。同时,128位的MMX寄存器也从原来的8个增加到了16个。3)增大的逻辑地址空间目前在新的架构中,应用程序可以拥有的逻辑地址空间从4GB增加到了256TB(2^48),而且这一逻辑地址空间在未来可能增加到16EB(2^64,1EB=1024PB,1PB=1024TB,1TB=1024GB)。4)增大的物理地址空间目前的x86-64架构,可以支持的物理内存扩展到了1TB(2^40),当然,在未来该数字可以扩展到4PB(2^52)。相比于经过PAE技术扩展的i386的64GB物理内存,新的架构带来了不小的飞跃。5)无缝使用SSE指令新的架构借鉴和吸收了Intel的SSE、SSE2的核心指令,并在2005年加入了SSE3。在这一新的架构下,可以不再需要x87浮点协处理器来完成浮点运算了。6)NX位跟PAE技术一样,新的x86-64架构也在页表项中增加了NX位,来帮助CPU判断该页包含的内容是否是可以执行的,从而避免借助“bufferoverrun”导致的病毒攻击。7)去除旧的机制在新架构的“长模式(longmode)”下,很多在IA32中被提出,但确不经常被操作系统用到的一些机制不再被支持。这些机制包括段式地址变化机制(FS和GS仍然被保留),任务转移门(TSS)机制,以及虚拟86模式。当然,出于向下兼容的考虑,x86-64在“传统模式”(Legacymode)下,仍然对这些机制进行了保留。43x86_64段式管理x86的两种工作模式:实地址模式和虚地址模式(保护模式)。Linux主要工作在保护模式下。在保护模式下,64位x86体系架构的虚地址空间可达2^48Byte,即256TB,这可比只能到达区区4GB的32位x86体系大多了。逻辑地址到线性地址的转换由x86分段机制管理。段寄存器CS、DS、ES、SS、FS或GS各标识一个段。这些段寄存器作为段选择器,用来选择该段的描述符。Linux中关于段描述符的宏定义集中在文件/arch/x86/include/asm/Segment.h中,我们先贴出部分代码:32位的:#defineGDT_ENTRY_KERNEL_BASE12/*0x0000000cc=1100*/#defineGDT_ENTRY_KERNEL_CS(GDT_ENTRY_KERNEL_BASE+0)/*0x0000000cc=1100*/#defineGDT_ENTRY_KERNEL_DS(GDT_ENTRY_KERNEL_BASE+1)/*0x0000000dc=1101*/64位的:#defineGDT_ENTRY_KERNEL32_CS1/*0x00000001*/#defineGDT_ENTRY_KERNEL_CS2/*0x00000002*/#defineGDT_ENTRY_KERNEL_DS3/*0x00000003*/#define__KERNEL32_CS(GDT_ENTRY_KERNEL32_CS*8)/*0x00000100*/#defineGDT_ENTRY_DEFAULT_USER32_CS4/*0x00000004*/#defineGDT_ENTRY_DEFAULT_USER_DS5/*0x00000005*/#defineGDT_ENTRY_DEFAULT_USER_CS6/*0x00000006*/#define__USER32_CS(GDT_ENTRY_DEFAULT_USER32_CS*8+3)/*0x00000403*/#define__USER32_DS__USER_DS不管32位还是64位的:(我们只关心64位)#define__KERNEL_CS(GDT_ENTRY_KERNEL_CS*8)/*0x00000200*/#define__KERNEL_DS(GDT_ENTRY_KERNEL_DS*8)/*0x00000300*/#define__USER_DS(GDT_ENTRY_DEFAULT_USER_DS*8+3)/*0x00000503*/#define__USER_CS(GDT_ENTRY_DEFAULT_USER_CS*8+3)/*0x00000603*/看见没有,我们熟悉的__USER_CS,__USER_DS,__KERNEL_CS,和__KERNEL_DS,就是传说中的段选择子。我们看到,内核代码段的描述子存放在以0x200为基地址的内存单元中,占8个字节。同样,内核数据段、用户代码段、用户数据段分别存放在以0x300、0x500、0x600为基地址的内存单元中。我们注意到,__USER_DS和__USER_CS的最低三位为3,也就是011,这正说明其CPL位为11,代表用户模式,TI为0,代表GDT。对于x86_64来说,虚拟地址由16位选择子和64位偏移量组成,段寄存器仅仅存放选择子。CPU的分段单元(S