本章学习目标
了解页目录、页表的结构和作用
清楚为什么当我们访问一个段的某单元时,处理器能准确的知道它在哪个页,以及页内位置的基本原理
1.分页机制概述。
1.1 简单的分页模型
分段的内存管理模式依靠的是段部件。段地址加上偏移量就是线性地址,分页模式没有开启的时候这就是物理地址。
分页的内存管理模式依靠的是页部件。分页功能开启之后,段部件发出的线性地址会被会被送到页部件,经过页部件的转换之后才是物理地址,才可以用于物理内存的访问。
分页功能开启后,我们会假想一个4G的虚拟内存空间,在这个虚拟的内存空间里进行分段,然后把段拆分并映射到物理内存的页。每个任务都有自己独立的4G虚拟内存。
1.2 页目录、页表、页
第一个支持分页内存管理模式的intel处理器是80386.
处理器设计的是层次化的分页结构,手段是采用了页目录表和页表。
页目录表,page directory table,PDT,是一个表,表中有1024个表项,表项的内容是页表的物理地址。因为单个表项的大小是4字节,所以整个页目录表的大小是4KB。每个任务有一个页目录表,每个任务自己的页目录表的物理地址记录在该任务的TSS中的CR3域,这个CR3又称为页目录基址寄存器,page directory base register,PDBR。
页表也是一个表,表中有1024个表项,每个表项记录的是一个页的物理地址。因为每个表项的大小是4字节,因此一个页表的大小是4KB。
分页模式启动的时候,内存会按照页的大小被均分成若干份,比如内存为4GB,页的大小为4KB的时候,内存就被分成了1048576个页。
1.3 地址变换的具体过程
地址变换的具体过程
线性地址0x00801050
把线性地址分成3段,第1段10 bit,第2段10 bit,第3段12 bit。
0000 0000 1000 0000 0001 0000 0101 0000
0000000010 0000000001 000001010000
这3段的16进制表示形式分别是0x02, 0x01, 0x50
线性地址的第1段用作页目录表的索引,找到页表的物理地址,0x08001000
线性地址的第2段用作页表索引,找到页的物理地址,0x0000C000
线性地址的第3段作为偏移地址,在找到的物理地址上再加上偏移量就得到了实际要访问的物理地址,所以最后要访问的物理地址是0x0000C050
2.使内核在分页机制下工作
2.1 创建内核的页目录表和页表
进入内核代码开始执行之后,首先初始化中断系统,包括创建中断描述符表IDT、设置实时时钟中断处理过程、加载IDTR、设置8259A中断控制器、设置和时钟中断相关的硬件。
接下来就要开启页功能。开启页功能之前需要做些准备工作,包括创建PDT和页表,已经初始化这2个表的表项。
因为当前内存已经被加载到内存中了,此时再创建页目录表和页表就要使得段部件发出的线性地址和页部件发出的物理地址在数值上相同。
我们自己编写的微内核大小不超过1MB,所以需要管理的内存大小不超过1MB。又一个页表对应着1024个页,每个页的大小为4KB,所以一个页表管理的内存大小就可以达到4MB,所以这里我们只需要创建一个页目录表和一个页表即可。页表和页目录表所占的内存都需要在一个完整的页上,因此这里我们给页目录表和页表分配的物理地址分别是0x00020000和0x00021000。
这里就一个页目录表,页目录表只有一个表项被初始化,指向唯一的那个页表。将页目录表的最后一个表项的值设置为该页目录表的物理地址。将页目录表的第一个表项的值设置为页表的物理地址,即0x00021000,当然,表项的前20 bit是页表的物理地址的前20 bit,后12 bit是页表的属性值。
另外,因为页都是4KB对齐的,即其物理地址的低12位都是0,因此只需要20位就可以表示页的物理地址,但是页目录表和页表的表项都是32位大小,剩余的12位用来表示该页的属性。
一个页的大小是4KB,1MB对应于256个页,因此只需要初始化页表的前256个表项,因为内核程序加载的时候页功能还没有打开,线性地址就是物理地址,因此页表项就被初始化为内存的前256个页的物理地址,当然制取页地址的前20 bit。
2.2 任务全局空间和局部空间的页面映射
这里介绍一下如何把内核映射到虚拟内存的高2G处。
当前内核的线性地址是0~0xFFFFF,即低1MB。需要把它映射到高2G处,即地址0x80000000开始的地方
映射的方法是,将内核的各个段的基地址加上0x80000000
需要修改的地方有页目录表,GDT中的各个段描述符的基地址
页目录表的修改
前面我们把页目录表的物理地址写在了页目录表的最后一个表项里
段部件发出的线性地址为0xFFFFF800,在对线性地址进行转换的过程中,把线性地址分成3段:
第1段10位,这里是0x3FF,用作页目录表的索引,取出了页目录表的最后一项,值为0x00020003,其中前20 bit是要进一步访问的页表的物理地址的前20 bit;
第2段10位,用作页表的索引,这里是0x3FF,用作页表的索引,页表就是刚才取出的在物理地址0x00020003处的表,实际上就是页目录表,这里要访问页目录表的最后一项,值为0x00020003,即最终会访问到的页就是页目录表自己。
第3段12位,用作页内偏移地址,前面获取到了要访问的页就是页目录表自己,即基址为0x00020000,加上这里的页内偏移,则最终要访问的位置的物理地址为0x00020800。页目录表中偏移为0x0800的地方对应的正式虚拟地址0x80000000的页。
GDT的修改
光修改页目录表是不够的,还需要修改GDT,使得全局空间的段对应的线性地址都在高2G。修改的方法就是将GDT中每个表项里的段描述符的基地址的最高位都置位。
GDT本身的线性地址也要映射到内存的高端
3.创建内核任务
3.1内核的虚拟内存分配
内核作为一个任务来执行的话,就需要自己的任务控制块TCB,内核任务的TCB所在的地址空间是指定的,即0x0001F800,还在1MB范围内,映射到高地址之后就是0x8001F800。
将内核任务的TCB中的任务状态域设置为忙,即值为0xFFFF。
这里TCB新增了一个域,在偏移0x46的地方,为下一个可用于分配的线性地址。
开启分页功能之后,每个任务都有自己独立的虚拟内存。因此分配内存的时候都只在任务自己的虚拟内存中进行。因为刚才已经分配了1M的内存空间,因此下一个可用于分配的线性地址就是0x00100000,又因为在内核分配内存应从高地址处开始,因此将0x80100000写入到TCB对应的域。
创建好了TCB之后,将它链到任务链上。
接下来就是要创建内核任务的任务状态段TSS。先是分配内存,从内核任务自己的虚拟内存中分配。参见调用过程task_alloc_memory。
3.2页面位映射串和空闲页的查找
使用位串来指示页的分配情况,这个位串称为页映射位串。
4G内存,一个页的大小是4KB,这样一共有1048576个页。一个位指示一个页是否分配,则需要1048576个位,即128KB。位的值为0表示该页未分配,为1表示分配了。
3.3 内核任务的确立
为TSS分配了内存之后,需要:
初始化TSS的各个域的值
创建TSS的描述符并安装到GDT中,从而得到一个选择子
将TSS的选择子加载到TCB相应的域中
将TSS的选择子加载到TR,至此,内核任务就完全确立为当前任务。
4.用户任务的创建和切换
4.1 用户任务的虚拟内存分配策略
创建任务控制块
用户任务的初始状态是就绪(0)
用户任务的起始可分配线性地址是0
创建用户任务的工作,第一步,分配内存
内存分配包括创建用户任务自己的页目录表和页表,并分配对应的物理页
当前正在运行的是内核任务,使用的也是内核任务的页目录表和页表,给用户程序分配内存应该要在用户任务的虚拟内存中分配。
但是考虑到内核任务只用到了虚拟内存的低2G,而内核任务只用到虚拟内存的高2G,可以在内核任务的低2G中分配内存给用户任务使用,这里的分配内存包括从虚拟内存中分配内存和分配物理页。
在用户任务创建好之后可将内核任务的页目录表和页表整体复制过去,然后清空内核任务的页目录表的低2G部分即可。
4.2 用户任务的虚拟地址空间分配
刷新TLB
TLB,translation lookaside buffer,转换速查缓冲器
页目录表和页表中的一些内容会被存储到TLB中,TLB是高速缓存,处理器在访问内存的时候会直接使用TLB中缓存的数据来实现线性地址到物理地址的转换,只有在TLB中找不到对应的线性地址时,才从页目录表和页表中加载相应的内容,然后继续从TLB中获取转换后的结果。
页目录表和页表的内容被更新之后,不会第一时间反应到TLB上,这有可能会导致线性地址到物理地址转换出错。可以人为的重载一下CR3来实现TLB的刷新。
4.3 创建用户任务的LDT
4.4 用户程序的加载
4.5 重定位U-SALT并复制页目录表
4.6 切换到用户任务执行
将新建的用户任务链接到TCB链上
标签:分页,内核,19,任务,物理地址,地址,页表,目录,页面 From: https://blog.csdn.net/shiwei160714/article/details/140800917