前言
上电启动流程如下:
boot/目录下的三个汇编语言文件(bootsect.S 和 setup.S 是实模式下运行的 16位代码程序,采用近似于 Intel 的汇编语言语法,并且需要使用 8086 汇编编译器和连接器 as86 和 1d86。而 head.s 则使用一种 AT&T 的汇编语法格式,并且运行在保护模式下,需要用 GNU 的 as(gas)汇编器进行编译)。
操作系统第一阶段如下:
(剖析操作系统,一开始就是面向芯片编程,按照芯片手册,当涉及了进程调度则面向多任务处理)
操作系统说白了就是在内存中放置各种数据结构来实现“管理”的功能
内核对内存的管理机制主要在于分段与分页机制
虚拟地址到物理地址的转换是由硬件完成的,而页目录表和页表是由操作系统负责管理的
分段
程序员在代码中给出一个内存地址(应用程序所使用的是逻辑地址),在保护模式下要先经过分段机制的转换,才能最终变成物理地址(当没有开启分页机制)
在内存分段中,一个程序的逻辑地址通过分段机制自动地映射(变换)到中间层的4GB(2^32)线性地址空间中。程序每次对内存的引用都是对内存段中内存的引用。当程序引用一个内存地址时,通过把相应的段基址加到程序员看得见的逻辑地址上就形成了一个对应的线性地址。此时若没有启用分页机制,则该线性地址就被送到 CPU的外部地址总线上,用于直接寻址对应的物理内存。
CPU进行地址变换(映射)的主要目的是为了解决虚拟内存空间到物理内存空间的映射问题。虚拟内存空间的含义是指一种利用二级或外部存储空间,使程序能不受实际物理内存量限制而使用内存的一种方法。通常虚拟内存空间要比实际物理内存量大得多。
分页
setup_paging 这个标签处的代码开启分页机制
setup_paging:mov ecx,1024*5xor eax,eaxxor edi,edipushfcldrep stosdmov eax,_pg_dirmov [eax],pg0+7mov [eax+4],pg1+7mov [eax+8],pg2+7mov [eax+12],pg3+7mov edi,pg3+4092mov eax,00fff007hstd
L3: stosdsub eax,00001000hjge L3popfxor eax,eaxmov cr3,eaxmov eax,cr0or eax,80000000hmov cr0,eaxret
(这段代码,就是把页表和页目录表在内存中写好,之后开启 cr0 寄存器的分页开关)
linux-0.11 认为,总共可以使用的内存不会超过 16M,也即最大地址空间为 0xFFFFFF。 而按照当前的页目录表和页表这种机制,1 个页目录表最多包含 1024 个页目录项(也就是 1024 个页表),1 个页表最多包含 1024 个页表项(也就是 1024 个页),1 页为 4KB(因为有 12 位偏移地址),因此,16M 的地址空间可以用 1 个页目录表 + 4 个页表搞定。
4(页表数)* 1024(页表项数) * 4KB(一页大小)= 16MB
所以,主要完成将页目录表放在内存地址的最开头
_pg_dir 标签 之后紧挨着这个页目录表,放置 4 个页表
.org 0x1000 pg0:
.org 0x2000 pg1:
.org 0x3000 pg2:
.org 0x4000 pg3:
.org 0x5000
最终将页目录表和页表填写好数值,来覆盖整个 16MB 的内存。随后,开启分页机制。此时内存中的页表相关的布局如下。
(这些页目录表和页表放到了整个内存布局中最开头的位置,就是覆盖了开头的 system 代码了,不过被覆盖的 system 代码已经执行过了,所以无所谓)
同时也需要通过一个寄存器告诉 CPU 把这些页表放在了哪里
xor eax,eax
mov cr3,eax
(相当于告诉 cr3 寄存器,0 地址处就是页目录表,再通过页目录表可以找到所有的页表,也就相当于 CPU 知道了分页机制的全貌了。)
CPU 首先把线性地址被拆分成 高 10 位:中间 10 位:后 12 位
高 10 位负责在页目录表中找到一个页目录项,这个页目录项的值加上中间 10 位拼接后的地址去页表中去寻找一个页表项,这个页表项的值,再加上后 12 位偏移地址,就是最终的物理地址。
而这一切的操作,都由计算机的一个硬件叫 MMU,中文名字叫内存管理单元,有时也叫 PMMU,分页内存管理单元。由这个部件来负责将虚拟地址(线性地址)转换为物理地址。这种页表方案叫做二级页表,第一级叫页目录表 PDE,第二级叫页表 PTE。(所以整个过程不用过多介入,作为操作系统这个软件层,只需要提供好页目录表和页表即可)
开启分页机制的开关。其实就是更改 cr0 寄存器中的一位(31 位)
页目录表和页表的具体结构如下
setup_paging:...mov eax,_pg_dirmov [eax],pg0+7mov [eax+4],pg1+7mov [eax+8],pg2+7mov [eax+12],pg3+7mov edi,pg3+4092mov eax,00fff007hstd
L3: stosdsub eax, 1000hjpe L3...
(前五行表示,页目录表的前 4 个页目录项,分别指向 4 个页表)
比如页目录项中的第一项 [eax] 被赋值为 pg0+7,也就是 0x00001007,根据页目录项的格式,表示页表地址为 0x1000,页属性为 0x07 表示改页存在、用户可读写。
后面几行表示,填充 4 个页表的每一项,一共 4*1024=4096 项,依次映射到内存的前 16MB 空间。
(每2^12B (4KB) 的物理空间需要一个页表项,每个页表项需要4KB。所以不管物理内存多大,页表项装满时总是要占用大约千分之一的内存;4个页表,每个页表1024项,所以4个页表装满需要占用16MB的空间)
1个页目录表最多包含1024个页目录项(也就是1024个页表),但是16M的地址空间所对应的这个页目录表中只包含了4个页目录项(也就是4个页表)
经过这套分页机制,线性地址将恰好和最终转换的物理地址一样,如下图:
(存在一点错误的结构图,计算的应该是13M而非15M)
地址转换过程
Intel 体系结构的内存管理可以分成两大部分,分段和分页。 分段机制,其目的是为了为每个程序或任务提供单独的代码段(cs)、数据段(ds)、栈段(ss),使其不会相互干扰。 分页机制,开机后分页机制默认是关闭状态,一般需要手动开启,并且设置好页目录表(PDE)和页表(PTE)。其目的在于可以按需使用物理内存,同时也可以在多任务时起到隔离的作用。 在 Intel 的保护模式下,分段机制是没有开启和关闭一说的,它必须存在,而分页机制是可以选择开启或关闭的。
逻辑地址:程序员写代码时给出的地址叫逻辑地址,其中包含段选择子和偏移地址两部分。
线性地址:通过分段机制,将逻辑地址转换后的地址,叫做线性地址。而这个线性地址是有个范围的,这个范围就叫做线性地址空间,32 位模式下,线性地址空间就是 4G。
物理地址:就是真正在内存中的地址,它也是有范围的,叫做物理地址空间。那这个范围的大小,就取决于你的内存有多大了。
虚拟地址:如果没有开启分页机制,那么线性地址就和物理地址是一一对应的,可以理解为相等。如果开启了分页机制,那么线性地址将被视为虚拟地址,这个虚拟地址将会通过分页机制的转换,最终转换成物理地址。
在Intel的一手芯片手册中,逻辑地址到线性地址再到物理地址的转换过程如下
Intel手册的内存管理原文是如下描述的
启用了分页机制的内存布局:
(此时的栈顶指针地址,应该是在system所在位置的内存里)
之后便是跳到内核的main函数进行操作系统的具体工作,诸如与内存相关的规划内存(内存边界划分)及更加细致的内存管理(内存分配相关的数据结构的使用)
对于操作系统main函数中的内存框架部分,将在未来另外整理一篇!!!!!!