【Linux 0.12】Linux内核体系结构——Linux的内存管理

Linux内核体系结构

Linux内核对内存的管理和使用

物理内存

为了有效地使用物理内存,在 Linux0.12 内核中,将物理内存划分为几个区域:

img

内核模块占据物理内存的最开始部分。

高速缓冲区供软盘,硬盘等设备读写数据时使用(先读入高速缓冲,再写入块设备)。其中,640K~1M 空间用于显存和 BIOS ROM 。

主内存区供所有程序随时申请和使用。

由于主存有限, CPU 通常都提供了内存管理机制。386以后的处理器都提供了分段和分页两种方式。 Linux 同时使用了这两种方式。(被迫。不分段就没法用分页)

内存分段机制

Intel CPU 使用段的概念来对程序进行寻址。下面用实模式和保护模式做对比:

实模式下,寻址内存地址依靠 段地址+段偏移。段存放在段寄存器中,长度固定为64K。段内偏移存放在某寄存器(ESI,EDI等)中。

保护模式下,本质仍然是 段地址+段偏移,但段寄存器保存的不再是段的基地址,而是一个段描述符表的索引。索引在表中指定的段描述符包含了段的地址、长度、访问权限等信息。段的长度由段描述符指定。此时,段寄存器保存的索引,称为段选择符。(添加一个段描述符表的原因是保护模式下地址空间很大,段数量太多,不够用了。)

内存分页管理

如果采用分页,线性地址只是一个中间结果。CR0 的 PG 标志位决定是否开启分页。

采用分页时,进程可以使用比实际内存空间更大的地址空间。386 处理器采用了页目录(PDT)和页表(PT)。

CR3 保存当前页目录在物理内存的地址。(因此 CR3 也被称为 PDBR )

CPU 多任务和保护方式

每个任务都有自己的代码和数据段,保存于局部地址空间,所以其他任务不能访问。而内核代码和数据是所有任务共享的,他们保存在全局地址空间。

虚拟地址、线性地址和物理地址之间的关系

1. 内核代码和数据的地址

对于 Linux0.12 内核代码和数据来说,在 head.s 程序的初始化操作中已经把内核代码段和数据段都设置成长度为 16MB 的段。在线性地址空间中,这两个段的范围重叠。都是0~0xFFFFFF,共 16MB。这个范围内包含内核的所有代码,内核段表(GDT,IDT,TSS)、页目录表以及内核的二级页表、内核局部数据、内核临时堆栈(用于任务 0 的用户堆栈)。在页目录和页表的设置中,内核代码和内核数据直接映射到物理地址的开始上。所以对于内核段而言,线性地址可以等价于物理地址。

内核代码和数据在三种地址空间中的关系

所以默认情况下, Linux0.12 内核占据 16MB 的物理内存,4096 个页面(每个页面 4K)。总结以下:

  1. 内核代码段和数据段在线性地址空间和物理地址空间是一样的。这样可以简化内核的初始化操作。
  2. 由于 GDT 和 IDT 也在内核数据段中,所以他们的线性地址也等于物理地址。
  3. 除了任务 0 以外,所有其他任务使用的物理内存页面与线性地址中的页面至少有部分不同。因此内核需要动态地在主存区为它们做映射。

2. 任务 0 的地址对应关系

任务 0 是系统中人工启动的第一个任务。它的代码段和数据段长度被设置为 640KB ,且直接包含在内核代码和数据中。其对应的 TSS 也是手工预设好的,位于任务 0 的数据结构信息中。

任务0在3个地址空间中的相互关系

任务 0 由于已经被包含在内核代码中,所以不需要再另外为其分配内存页。它运行时需要的内核态堆栈和用户态堆栈也在内核代码区域中,且对用户可读可写并存在。

解释一下 0 号进程和 1 号进程:
系统允许一个进程创建新进程,即子进程。子进程还可以创建新的子进程,形成进程树。

基于此,内核态下执行的 0 号进程是所有进程的祖先,其对应信息都是手工预设的。

1 号进程由 0 号进程创建而来,位于内核态,负责执行内核的部分初始化工作以及进行系统预设,并创建若干个用于告诉缓存和虚拟主存管理的内核线程。随后,1 号进程调用 execve() 运行可执行程序 init,并演变为 1 号进程,即 init 进程。

3. 任务 1 的地址对应关系

与任务 0 类似,任务 1 也是一个特殊的任务。它的代码也在内核代码区域中。与任务 0 不同的是,系统在使用 fork() 创建任务 1(init 进程)时为了存放任务 1 的二级页表而在主内存区申请了一页内存来存放,并复制了父进程(任务 0 )的页目录和二级页表项。因此任务 1 有自己的页目录和页表项,将任务 1 占用的线性地址空间范围(64128MB)同样映射到了物理地址 0640KB处。并且任务 1 的代码段和数据段重叠,只占用一个页目录和一个二级页表。此外,系统还会为任务 1 在主存申请一页内存存放任务数据结构(PCB)和用作任务 1 的内核堆栈空间。PCB 包含 TSS 信息。

任务1在3种地址空间中的关系

4. 其他任务的地址对应关系

从任务 2 开始的其他任务,父进程都是任务 1 (init)进程。

用户申请内存的动态分配

用户程序使用 malloc() 申请内存时,动态申请的内存和容量均由库函数的内存分配器管理,内核不会插手。因为内核已经为每个进程在线性空间内分配了 64MB 的空间。只要进程执行时寻址空间在这 64MB 内,内核会通过缺页中断为其分配物理地址。

不过,内核会位进程使用的代码和数据空间维护一个当前位置值 brk ,它指明了进程代码和数据(包含动态分配的数据空间)在进程地址空间的末端位置。