开始研究linux内核了。。。:)分段简单点,分页要好好理解,虽然还是存理论上的,但是总算比以前要强点了。
linux 分段原理:
三种地址:
a逻辑地址, 段基地址 + 偏移量
b线性地址,寻址空间地址
c物理地址
分段实际上指的就是逻辑地址这个内涵。
段寄存器(16字节),存放了段的标志,比如0,1,2,3。。。
每个段标志0,1,2,3对应了一个段描述符(8字节)
从段寄存器到段描述符的方法:找到存放段描述符的起始地址,通过gdt寄存器获得, 再加上 8×(0,1,2,3..)等
等偏移量就是该段描述符的地址。段描述符里头主要有
a。 base地址,该段的起始地址
b。 G粒度位,单位大小
c。 limit。段大小
d。 S 系统标志
e。 段类型
f。 描述符访问特权级
g。 。。。
常用的段寄存器有 cs ds ss es fs gs(一个进程以用户态执行的时候,cs ds分别存放了用户代码段和用户数据段 当进入内核态的
时候 将cs ds存入内存,然后将内核代码段和内核数据段分别存入cs ds)
linux的段:
a。 内核代码段(所有进程共享)
b。 内核数据段(所有进程共享)
c。 用户代码段(所有进程共享)
d。 用户数据段(所有进程共享)
e。 每个进程各有一个的tss 进程状态段(根据进程号可以计算出它在gdt中的位置获得该段描述符,该段描述符的base域指向进程
描述符(tss)的地址)
f。 所有进程共享一个默认的ldt 段
其中abcd的base字段的值都是0x00000000,方便管理,地址转换通过分页机制完成
tss段的base字段的值指向tss寄存器的地址
(一句话,段就是从逻辑地址到线性地址的一种寻址方式,可以相同的逻辑地址映射成不同的线性地址。但是linux使用了分页机制
,每个进程都使用同样的线性地址,所有段使用并不多,内核代码段、内核数据段、用户代码段、用户数据段的base域都指向0,也
就是忽略了段的基址功能仅使用了其的系统标志访问权限等其他功能。不过每个进程的tss段描述符的base域并不相同,因为他们指
向了不同的进程描述符的地址)
要计算 GDT 中最多可以存储多少条目,必须先理解 NR_TASKS(这个变量决定了 Linux 可支持的并发进程数 —— 内核源代码中的
默认值是 512,最多允许有 256 个到同一实例的并发连接)。
GDT 中可存储的条目总数可通过以下公式确定:
GDT 中的条目数 = 12 + 2 * NR_TASKS。
正如前所述,GDT 可以保存的条目数 = 2^13 -1 = 8192。
在这 8192 个段描述符中,Linux 要使用 6 个段描述符,另外还有 4 个描述符将用于 APM 特性(高级电源管理特性),在 GDT 中
还有 4 个条目保留未用。因此,GDT 中的条目数等于 8192 - 14,也就是 8180。
任何情况下,GDT 中的条目数 8180,因此:
2 * NR_TASKS = 8180
NR_TASKS = 8180/2 = 4090
(为什么使用 2 * NR_TASKS?因为对于所创建的每个进程,都不仅要加载一个 TSS 描述符 —— 用来维护上下文切换的内容,另外
还要加载一个 LDT 描述符。)
这种 x86 架构中进程数量的限制是 Linux 2.2 中的一个组件,但自 2.4 版的内核开始,这个问题已经不存在了,部分原因是使用
了硬件上下文切换(这不可避免地要使用 TSS),并将其替换为进程切换。
linux的分页机制(常规分页,扩展分页类似不多说了)
linux的分段机制很简单,并且只用了少量的段,从逻辑地址到线性地址是非常简单的,因为一般的段描述符的base域都是0.分页机
制是关键。也是linux的重要的地方
从i386开始,intel处理4KB的页。每个4KB的物理页可以存放有 数据(该地址通过页表项获得)、若干个页表(该地址通过目录表项
获得)、目录表(该地址通过CR3寄存器获得)。32位线性地址(linux的段很少很简单很容易转化成线性地址)被划分为10 + 10 +
12 分别表示10位的页目录,10位的页,和12位的偏移量。同时进程维护着若干张表,一个页目录表(1024*4B),若干个页表
(n*1024*4B),都是存放在物理一个个的物理页里面的。
例子:访问0x20020002线性地址对应的物理内存地址。前10位页目录 128(十进制), 中间10位页表 32(十进制),最后12位偏移
量2(十进制)。
1.首先查找页目录表物理地址(通过CR3寄存器获得),将该物理地址加上128*4字节就找到了这个线性地址所对应的目录表项。
2.根据目录表项前20位的值求出 该页表存放的物理地址。将该物理地址加上32*4字节就找到了这个线性地址的所在的页表项。
3.根据页表项的前20位的值求出该页 在物理内存的起始地址。将这个物理地址加上偏移量2 就得到了线性地址的对应物理地址。
以上情况均假定线性地址所在的页已经在物理内存中。下面是缺页的情况。
1.计算得到的目录表项为0的话。说明该页表不在内存中,便产生一个缺页中断分配一个物理页4KB(存放页表)并且把分配的物理地
址赋值给该目录表项。于是可以进一步访问所在的页表了。
2.页表的物理地址找到了,但是计算出来的所在页表项的值为0。说明该页不在内存中。便产生一个缺页中断分配一个物理页4KB(存
放数据)并且把分配的物理地址赋值给该页表项。于是可以进一步访问所在的页了。
3.不会再中断了,根据页表项算出的物理地址 加上 偏移量就是该线性地址的物理地址了。
总结:线性地址到物理地址的转换就是通过 CR3寄存器 -> 目录表 -> 页表 -> 偏移量 不断查找计算得出。进程创建时唯一的目录
表要存放到内存中,还有若干个页表也存放到内存中 ,还有若个个页(数据)存在到内存中。在需要的时候才会分配空间用来存放
页表 和 数据。
系统通过二级或者多级方式进行地址转换的原因是为了节省内存。试想 线性地址结构为 20 + 12。简单的只有一个页表的话
(常规寻址是1024个页表),为了表示4G的寻址空间,必须分配一个拥有1024*1024个页表项的超大页表放入物理内存,所占内存为
1024*1024*4B=4M而且是连续的内存,查找页表项的需要。无论是从资源浪费上还是技术实现上都是不可取的。
ps:
目录表项和页表项的结构都是一样的。占了4个字节。
其中高位的20位存放页表的物理地址或者页的物理地址。(因为页表地址和页地址的低12位都是0,物理内存是连续分配的,并且每
块4KB,页表地址和页地址都是指向的物理内存的每个页块的起始地址,所以低12位都是0)
present位,该目录表项所指的页表或者该页表项所指的页是否在内存中,不在的话就引起缺页中断
dirty位,在页表项中使用。当对该页写操作的时候设置这个标志。
read/write 读写权限
user/supervisor 访问页或页表所需要的特权级
page size 目录表项使用。如果设置位1,则目录表项所指的是大小为4MB的页框(一级页表寻址),而不是页表(二级页表寻址)
accessed 页表使用。访问页时,需要设置。如果该页被调度出物理内存的话,共操作系统使用。
pcd pwt 硬件高速缓存处理页或者页表的控制方法
阅读(1195) | 评论(0) | 转发(0) |