这篇文章主要说一下linux对于分段机制的处理,虽然都说linux不使用分段机制,但是分段机制属于CPU的一个功能,即使linux不使用,也要通过代码想办法绕过它,况且linux也使用到了分段机制中的某些功能。
分段机制主要功能只有两点:
将物理内存划分为多个段,让操作系统可以使用大于其地址线对应的物理内存(比如正常情况下32位地址线可以访问4G大小的内存,但是有分段后则可访问大于4G的内存)。
权限控制,将每个段设置权限位,让不同的程序访问不同的段。
对于linux内核来说,它仅仅只使用了分段机制中的权限控制功能,具体我们可以一起看看是如何做的。
CPU的段寄存器
在CPU中,跟段有关的CPU寄存器一共有6个:cs,ss,ds,es,fs,gs,它们保存的是段选择符。而同时这六个寄存器每个都有一个对应的非编程寄存器,它们对应的非编程寄存器中保存的是段描述符。系统可以把同一个寄存器用于不同的目的,方法是先将其寄存器中的值保存到内存中,之后恢复。而在系统中最主要的是cs,ds,ss这三个寄存器。
CS 代码段寄存器:指向包含程序指令的段,在CS寄存器中RPL用于表示当前CPU的特权级(CPL),CPL为0是最高权限(内核态使用),CPL为3是用户态使用。
SS栈段寄存器:指向当前程序的栈的段。
DS 数据段寄存器:指向保存着静态数据和全局数据的段(静态区)。
在段寄存器中主要保存的是段选择符,它的长度是16位,具体如下:
索引号(index):所对应的段描述符处于GDT或LDT中的索引。
TI:TI=0表示对应段描述符保存在GDT(全局描述符表)中,TI=1表示对应的段描述符保存在LDT(局部描述符表)中。
RPL:当此对应的段选择符装入cs寄存器时,设置CPU当前的特权级的值为RPL,也就是cs寄存器中的RPL就是CPL。
段选择符主要用途就是根据段索引号和TI标志,去到GDT或者LDT中找到这个选择符对应的段描述符,比如我们在内核代码中常见的__KERNEL_CS,__KERNEL_DS,__USER_CS,__USER_DS就是段选择符,它们并不是段描述符。
全局描述符表与局部描述符表
全局描述符表和局部描述符表保存的都是段描述符,记住要把段描述符和段选择符区别开来,保存在寄存器中的是段选择符,这个段选择符会到描述符表中获取对于的段描述符,然后将段描述符保存到对应寄存器的非编程寄存器中。
系统中每个CPU有属于自己的一个全局描述符表(GDT),其所在内存的基地址和其大小一起保存在CPU的gdtr寄存器中。其大小为64K,一共可保存8192个段描述符,不过第一个一般都会置空,也就是能保存8191个段描述符。第一个置空的原因是防止加电后段寄存器未经初始化就进入保护模式而使用GDT。
而对于局部描述符表,CPU设定是每个进程可以创建属于自己的局部描述符表(LDT),当前被使用的LDT的基地址和大小一起保存在ldtr寄存器中。不过大多数用户态的liunx程序都不使用局部描述符