Linux中的分段:
微处理器的分段鼓励程序员将程序划分成逻辑上的实体,如子程序或者全局与局部数据区,然而Linux以非常有限的方式分段,实际上,分段和分页在某种程度上有点多余,因为他们可以划分进程的物理地址空间:分段可以给每个进程分配不同的线性地址空间,而分页可以把同一个线性地址空间映射到不同的物理空间。Linux更喜欢使用分页的方式,因为:
①当所有进程使用相同的段寄存器值时,内存管理变得更简单,也就是说它们能共享同样的一组线性地址。
②Linux设计目标之一是可以把它一直到绝大多数流行的处理器平台上,但是RISC体系结构对分段的支持有限。
运行在用户态的所有Linux进程都是用一对相同的段来对指令和数据寻址(用户代码段和用户数据段),运行在内核态的Linux进程都使用一对相同的段来对指令和数据寻址,它们分别叫做内核代码段和内核数据段。
四个主要的Linux段的段描述符字段的值:
相应的段选择符由宏__USER_CS, __USER_DS,__KERNEL_CS, __KERNEL_DS,为了对内核代码段寻址,内核只需要把__KERNEL_CS宏产生的值装进cs段寄存器。
与段相关的线性地址从0开始,达到2^31-1寻址限长,用户态或内核态下的所有进程可以使用相同的逻辑地址。所有的段都从0x00开始,得到的结论是,Linux下逻辑地址与线性地址是一致的,即逻辑地址的偏移量字段的值与相应的线性地址的值总是一致的。
CPL
CPL反应进程是在用户态还是内核态,并存放在cs寄存器中的段选择符的RPL字段指定,当特权级被改变时,一些寄存器必须相应地更新,例如当CPL=3时,ds寄存器必须含有用户数据段的段选择符,当CPL=0时,ds寄存器必须含有内核数据段的段选择符。ss寄存器也是如此,CPL=0,ss必须指向内核数据段中的一个内核栈,CPL=3,必须指向用户数据段的一个用户栈。当从用户态切换到内核态时,要确保ss寄存器里有内核数据。
对于指向指令(code)或数据结构(data)指针,不需要设置逻辑地址的段选择符,因为cs寄存器包含了,如内核调用一个函数时,他执行一条call汇编语言指令制定了逻辑地址偏移量,你用指定段选择符,因为在内核状态的段只有一种,代码段,由宏__KERNEL_CS定义,同样的也适用于内核数据结构指针及用户数据结构指针。
Linux GDT
操作系统的内存关系:
虚拟内存,这个概念是面向软件程序而言,他们希望程序是连续的地址,一段一段的,注意,在32位系统里,一个program仅有3G,(还有1G OS程序占据)
因为程序中一般将一个程序所使用的部分分为程序段,数据段,堆段和栈段等,这里面每一个段也就会有相应的Base,执行中也会Offset(即Linear Address),段通过段描述(Segment Descriptor)保存段的信息,每一个 Segment Descriptor 作为一个 entry 保存到 GDT(全局段表)中;
每个cpu对应一个GDT,所有GDT,它的地址和大小都存放在arch/架构名/kernel/head.S中被定义,(如x86:arch/i386/kernel/head.S,cpu_gdt_table和cpu_gdt_descr)(section?)
每个GDT包含18个段描述符和14个空的,未使用的项目的是为了使经常一起访问的描述符能处于同一个32字节高速缓存中。
18个段:
①用户态和内核态的代码段和数据段共四个
②任务状态段(TSS),每个处理器一个,每个TSS的线性地址空间都是内核数据段相应一个线性空间的一个子集,所有的任务状态段都顺序的存放在init_tss数组,第n个cpu的TSS描述符的Base字段指向init_tss数组,G=0,Limit=0xeb(236字节),Type=9或11,(进程能否在CPU下运行),DPL=0,不允许用户态下的进程访问TSS段。
③一个包括缺省的局部描述符表的段,这个段通常是被所有进程共享的段
④3个局部线程存储(TLS):允许多线程应用程序使用最多3个局部于线程的数据段。系统调用set_thread_area和get_thread_area分别创建和撤销一个TLS段
⑤高级电源管理:BIOS代码使用段,当Linux APM驱动程序调用BIOS函数来获取或者设置APM设备的状态,就可以自定义的代码段和数据段
⑥支持即插即用功能的BIOS服务程序 :由于BIOS例程使用段,当Linux的PnP设备驱动调用BIOS函数来检测PnP设备使用的资源时,可以使用它自定义代码段和数据段。
⑦被内核用来处理“双重错误(处理一个异常时可能引发另一个异常)”异常的特殊TSS段
Linux LDT
LDT内部存储的是进程相关的段的基址内容,GDT存储的是内核代码的段的基址。
大多数用户态的Linux程序都不使用局部描述表,所以定义一个缺省的LDT供大多数进程共享,一般放在default_ldt数组中,他有5个项,内核只使用2个,用于iBCS执行文件的调用门和Solaris/x86可执行文件的调用门。
(调用门是80x86微处理器提供的一种机制,用于在调用预定义函数时改变CPU的特权级)
在某些情况下,进程需要创建自己的局部描述符表,像Wine那样的程序,它们执行面向段的微软Windows应用程序。modify_ldt系统调用允许进程创建自己的局部描述符表,它需要自己的段,当处理器开始执行自定义局部描述符表的进程时,CPU的GDT副本中的LDT对应的就被修改了。
用户态下的程序同样也利用modify_ldt来分段,但是内核不会使用这些段,也不用了解相应的段描述符,这些段描述符被包含在进程自定义的局部描述符表中了
*************************
总结:
系统会给程序自动分配程序段,代码段等,这些段以及偏移组成了逻辑地址,而逻辑地址通过GDTR/LDTR,找到位于虚拟内存的段基址,最后得到虚拟地址.
***************************************************************************