初级内存管理单元
关于内存的分页
以往的物理页是按照 4KB 进行分配和管理的, 而在 Linux 之后流行的就是 2MB 大小的物理页的分配和管理, 整个物理内存管理单元也是 2MB 物理页管理的
先获取基本的物理地址空间信息
在 bootloader 程序中, 已经调用了 BIOS 的 int 15h 中断将物理内存地址的结构体放置到了 1MB 之下的物理地址 0x7e00 处, 我们需要将其提取出来
每一条物理空间信息 BIOS 加载到内存时 20B, 因此我们要获取该数据, 也需要定义一个结构体也占用 20B 的物理内存大小, 获取 0x7e00 地址上的数据, 但是在 IA-32e 模式下, 我们能够使用的是线性地址, 不能直接访问物理地址, 访问物理地址需要通过我们已经在 head.S 程序中定义好的页表进行页的映射, 在 head.S 中我们只进行了 10MB 的映射, 这对于我们目前来说已经足够了, 不过我们还是要知道物理地址 0 对应的线性地址时多少, 这样我们才能进行编码
物理地址空间的大小为 32 个, 我们一开始先打印出这些信息, 输出物理内存哪些是可用的 (type == 1), 如果多了脏数据(type> 4) 证明这个地方的数据经不明数据污染了, 也就是说有数据将 BIOS 写到 0x7e00 地址上的数据覆盖了, 那我们就不要在读取了, 因为已经是错误的了, 没有意义了, 打印出来我们总共可用的内存大小
在分配可用物理页之前先获取可用物理页的页数
在这个部分, 我们需要对复制 BIOS 的全部物理地址空间信息到一个 memory_management_struct 中, 这个结构体就是我们内存管理的核心, 它保存着所有的内存页的信息, 包括不可用的
接下来我们计算出可用的物理内存页数, 获取一个可用的物理内存段之后, 我们对其起始地址尽心 2MB 的物理页的对齐, 返回的就是对齐后的物理地址, 接着计算出这个物理内存段的结束地址 end, (end - start)>> page_2m_shift 计算出在这个物理段中有几个物理页, 也就一个分隔游戏, start 就是对齐之后的物理地址, 在操作系统中一般有两个可用的物理地址段, 即为 A 和 B, 他们一般不在一起, 第一个就是从我们的物理地址 0 开始的物理内存段, 显然我们的 bootloader 和内核代码都在这里, 这里肯定是可用的物理内存, 其实地址就是 0, 对齐后的地址也是 0, 因为 2MB 对齐就是将我们所有的内存分成一个一个的 2MB, 但是另外一个可用的物理地址段它的 start 地址就不一定就是在一个矩形的 2MB 的起始位置了, 也就是说地址不对齐了, 这个时候我们就需要进行物理地址的对齐, 一般来说经过运算后, 我们原来的物理地址 start 的物理地址会增加一点到一个 2MB 的起始地址, 虽然这样会浪费一小段可用的物理地址空间, 但是我们完成了 2MB 的分隔, 更加方便我们的管理
分配可用物理内存页
需要用到的逻辑上的结构体
struct page --> 代表的就是我们说的 2MB 的物理页, 但是它本身不是 2MB, 而是他管理的物理内存时 2MB 的, 这里所谓的管理就是通过属性保存地址, phyaddr 就是管理的物理页的物理起始地址
struct zone --> 区域空间结构体, 它与 page 联系紧密, 它标志一个可用物理地址段, 就是我们在上面讲到了两个可用的物理地址段, 他代表着一个段, 怎么代表的呢, 也是通过属性 startaddr 和 endaddr, startaddr 保存着一个可用物理内存段的起始地址, endaddr 保存着一个可用物理内存段的结束地址, 我们已经知道另一个一个物理段是很大的, 有多个 2MB 的物理页, 因此这里的 endaddr - startadd 的值也大于 2MB, 所以会有多个 page 结构体引用着一个 zone, 用来在逻辑层面上模拟分配一个页
上面已经提到过的 memory_management_struct(在后面简称为 mm), 他包含了从 BIOS 获取的物理地址信息, bitsmap(就是一个整数, 和 ext3 文件系统一样一样的)用来方便索引空闲的物理页 page, 一个 page 结构体数组(在逻辑上属于 zone), 一个 zone 结构体数组, 内核代码结束位置, 自己的结束位置, 注意 page 和 zone 结构体分配在了内核代码之后, zone 在 page 之后
开始分配可用物理内存页 (类似与 Python 中的__new__() 魔法方法的功能, 但是与 Java 中的 new 关键字的功能不同, 这里只是为 page 和 zone 结构体分配了内存, 这里到处了实质: 可用物理页的分配就是在为结构体创建内存空间, 但是不进行初始化)
初始化 bitmap
mm.bits_size 属性赋值为我们之前计算出来的可用物理内存页的个数, 这样才能确定 bitmap 的大小
将 bitmap 的值置为 0, 虽然在这个时候我们的内核代码已经在内存中了, 我们理应将对应的 bitmap 中的一个位置位, 但是我们现在的内存管理的数据结构体还不完善, 所以我们将这个往后推
初始化 page struct 结构体数组
mm.pages_size 等都记录下来, page 结构体采用的 4K 物理页的对齐方式, 反正这些元数据结构体会比较特殊
分配内存空间, 将所有的值初始化为 0
初始化 zone 结构体数组
分配内存空间, 将所有的值初始化为 0
第二次初始化 (此时数据结构体的内存大致已经分配好了, 就是属性需要进行赋值, 类似于 Python 中的__init__() 方法)
先为 zone 结构体进行属性的初始化, 从 mm 中的 bios 的物理地址空间信息中读取 type == 1 的地址, 对齐, 赋值该 zone 的 start, end 和上面的一样计算, 这样一个物理段就初始化完毕了, 此时在这个物理段 zone 的基础上初始上 page 的属性(但是这里的 zone 和 page, 并不是所有的属性都被初始化了, 有一些需要在函数 page_init 中进行初始化), 我们知道 page 都是属于 zone 的, 通过循环将 zone 所代表的物理段分成多个 2MB 大小的物理页, 当然是使用 page 结构体的 phyaddr 和 length 来表示了, 接着这样 page 指向该 zone, 表示 page 的所属是谁
现在我们也为 page 和 zone 的属性都赋值了, 现在我们就要通过一个 page_init 函数来初始化内核代码所在的内存, 还记得上面提到了在初始化 bitmap 的时候, 我说过的要将这个事情往后推吗!
在初始化内存的时候, page 的属性 refcount++, page 指向的 zone 的 freepages--, pageusing++, 并且置位 bitmap 表示已用
内存管理需要的东西完成了, 下面就是通过一个函数接口来通过访问这里的内存管理机制分配到内存了
通过 alloc_pages 函数返回一个 struct page 数组内核层和应用层使用
判断向内存中的哪个区域要物理页
通过 bitmap 找到指定连续数量的未被使用的位, 通过该位计算得出这个 page 数组的首地址, 将连续的 page 数据返回, 同时标志 bitmap 对应的位已用
来源: https://www.cnblogs.com/megachen/p/9806914.html