linux 0.11内存管理
linux 0.11内存管理
线性地址空间的格局
每个线程的虚拟地址空间并不重叠,每个进程不跨越自己的空间。如何防止进程跨越64M的空间进行非法访问?
非法跨越进程边界有两种情况:
一个进程非法跨越到内核
一个进程非法跨越到另一个进程

非法跨越问题
如何防止进程跨越到内核的非法访问
通过cpu硬件禁止从3特权级跳转到0特权级。通过设置LDT,GDT设定了特权级。进程能否自己修改LDT,GDT?不能,因为这两个都在内核数据段,是0特权级,进程无法访问。那进程能不能自己建一个LDT,GDT狸猫换太子?也不能,因为运行的时候CPU只将GDTR和LDTR寄存器指向的数据结构认定为LDT,GDT,进程自己没法伪造。而LDTR和GDTR是0特权级的进程没法把自己伪造的挂上去。
在寻址过程中我们会对这一内容做一个总结。详见下面的寻址章节。
进程怎么合理的跨越特权级
涉及进程通信和中断门,TSS进程切换
分页管理

重要寄存器:
CR3:指向页目录表的地址。CR3里面存放的是物理地址
CR0寄存器:
内核分页机制
建立过程:
1 | .org 0x1000 |
1 | movl $pg0+7,_pg_dir /* set present bit/user r/w */ |
上面的7代表111,这三位分别代表用户u,读写rw,存在p。如果是000代表内核,只读,不存在页。加7就是把后12位用于存权限问题。movl $pg0+7,_pg_dir
这行汇编指令的含义是将地址 pg0
加上 7 存储到
_pg_dir
地址处。因此上面这一段代码的意义就是让页目录表指向每个页表项,同时第一个地方指向页目录自己,每次递增4个字节即32位地址。也就是如下图所示,这里的_pg_dir就是head的起始地址,这里是在一边执行head.s的代码一边覆盖。
1 | movl $pg3+4092,%edi/*一个页表的最后一项在页表中的位置是1023*4=4092(一共1024项,第1024项的开始地址) */ |
edi指向了第一个页面,eax指向了最后一个页面
其中进程0用的是内核的页表。
地址32位4字节,一共页表4k,可以保存1k个线性地址间接指向下一级页表
页面管理
内核通过mem_map管理1M以上的内存空间。

目录项或页表项P位为1的时候说明已经和某页面建立了映射,如果为0说明没有建立映射机制。此时如果使用该页表项的页面会发送缺页中断。
进程管理自己的页
进程的页目录表是通过内核代码调用得到的,处于内核数据段。因此进程自己是没有权限修改自己的页目录表的,是由内核全权代理。
父子进程共享页面
copy pagetable:
进入copy_page_tables函数后,先为新的页表申请一个空闲页面,并把进程0中第一个页表里的前160个页表项复制到这个页面中(1个页表项控制一个页面4KB内存空间,160个页表项可以控制640KB内存空间)。进程0和进程1的页表暂时度指向了相同的页面,意味着进程1也可以操作进程0的页面。之后对进程1的页目录表进行设置。最后,用重置CR3的方法刷新页面变换高速缓存。进程1的页表和页目录表设置完毕。进程1此时是一个空架子,还没有对应的程序,它的页表又是从进程0的页表复制过来的,它们管理的页面完全一致,也就是它暂时和进程0共享一套页面管理结构。
1 | //from 父进程的段基地址,to子进程的段基地址 |

页面双指针管理
分配给进程的页面除了进程通过建立页表项的映射,内核本身也在管理

寻址
寻址过程

逻辑地址->线性地址
一个==逻辑地址==由一个 16 位的段选择子和一个 32 位的偏移组成
例如jmpi 0,8
,0是偏移,8是段选择子:==1000==
段选择子格式:

1000:

转换关系:

线性地址->物理地址
32位地址:CPU 在看到我们给出的内存地址后,首先把线性地址被拆分成
高 10 位:中间 10 位:后 12 位。

\(2^{10}\)=1K,一个页目录表有1k个页表,一个页表包含1k个页表项,一个页表指向一个页面(4k)。
因此一个页表对应了4M的物理空间。这里一共建立了4个页表:16M空间,理论上可以更大。由于硬件中cpu是4k对齐的
因此低12位会是零,所以可以用高20位来表示4KB对齐的页表和页,因此可以用后12位设置权限。

代码示例:
以如下代码为例展示从线性地址转换到物理地址的过程
1 | void write_verify(unsigned long address) |
page = *((unsigned long *) ((address>>20) & 0xffc))

IA-32 特权级保护
linux 0.11的特权级保护是基于==段==的。
访问控制是针对==内存==的访问控制,是以字节为单位的。
首先,程序发生跳转指令的时候会有非法访问的嫌疑。短跳转不会涉及段的变化,不会非法访问。而长跳转例如 jmpi 0,8
怎么确保不会从低特权级跳到高特权级?
访问数据段
高特权级的代码段可以访问第特权级的数据段,低特权不允许访问高特权的数据

堆栈

访问代码段
程序控制从一个代码段转换到另一个代码段时,必须把目标代码段的段选择子装 入 CS 寄存器。在装载过程中,处理器会检查目标代码段的段描述符,对段界限、类型 及特权级进行检验。进程控制转移是用 JMP、CALL、RET、SYEENTER、SYSEXIT、INT n 和 IRET 指令或者异常和中断机制来实现的。
目标代码段描述符的一致性(C)标志(段描述符类型域),这个标志确定一个段是一个一致性代 码段(标志为 1)还是非一致性代码段(标志为 0)。
访问非一致性代码段
当访问非一致性代码段时,调用例程的 CPL 必须与目标代码段的 DPL 相等,否则 处理器会产生一个一般保护异常(#GP)。
访问一致性代码段
当访问一致性代码段时,调用例程的 CPL 可以在数值上等于或小于(较低特权级)目标代码段的 DPL。仅当 CPL 大于 DPL 的时候,处理器产生一个一般保护异常(#GP)。 (当目标代码段是一致性代码段时,不用检验目标代码段的 RPL。)
门调用
GDT的变迁
==详见:第一章和第二章笔记==

段描述符结构
段描述符:写在gdt表里面的那个内容也是段描述符。段描述符是 GDT 或 LDT 中的一个数据结构,它为处理器提供诸如段基地址、段大 小、访问权限及状态等信息。

G表示粒度:
如果粒度标志位为 0,则段大小可以从 1 字节到 1M 字节,段长 增量单位为字节。
如果粒度标志位为 1,则段大小可以从 4K 字节到 4G 字节,段长 增量单位为 4K 字节。
D/B(默认操 作数大小/默认 栈指针大小和/ 或上限)标志
根据段描述符所指的是可执行代码段、向下扩展的数据段还是堆栈 段,这个标志有不同的功能。(对 32 位的代码和数据段,这个标志 总是被置为 1,而 16 位的代码和数据段,这个标志总是被置为 0。)
P(段存在) 标志
指明段当前是否在内存中(1 表示在内存中,0 表示不在)。当指向 段描述符的段选择子被装进段寄存器时,如果这个标志为 0,处理器 会产生段不存在异常(#NP)。内存管理软件可以通过这个标志,来 控制在某个特定时间有哪些段是真正的被载入物理内存的。这是除 分页之外的另一个虚拟内存控制机制。 图 3-9 演示了段存在标志置 0 时段描述符的格式。当这个标志置 0 时,操作系统或者管理软件就可以随意去使用标明为“可用”的地 方(段描述符里)来存贮自己的数据,比如有关已消失段的所在位 置的信息。
DPL(描述符 特权级)域
指明段的特权级。特权级从 0 到 3,0 为最高特权级。DPL 用来控制 对段的访问。DPL:访问该段的权限设置
S(描述符类 型)标志
确定段描述符是系统描述符(S 标记为 0)或者代码、数据段描述符 (S 标记为 1)
类型域 指明段或者门的类型,确定段的访问权限和增长方向。如何解释这 个域,取决于该描述符是应用程序描述符(代码或数据)还是系统 描述符。代码段、数据段和系统段对类型域有不同的编码
