1. 虚拟地址和物理地址:
虚拟地址关系到进程的用户空间和内核空间,而物理地址则用来寻址实际可用的内存。
linux用页表来为物理地址分配虚拟地址。
进程的虚拟地址空间,都被内核划分为很多等长的部分,这样的部分成为页。物理内存也划分为同样大小的页。
2. 页表
页表是一种数据结构,用来将虚拟地址空间映射到物理地址空间。
实现两个地址空间关联最容易的方法是使用数组,对虚拟地址空间的每一页都分配一个数组项,同时该数组项指向与之关联的物理地址页。
因为虚拟地址空间的大部分区域没有使用,所以可以使用内存用量少的多级分页模型。
3. 多级分页模型:
为减少页表的大小并忽略不需要的区域,linux把虚拟地址划分为多个部分。
举个例子:
我们将虚拟地址划分为4个部分,这样就需要一个三级的页表。
a. 虚拟地址的第一个部分称为全局页目录(Page Global Directory, PGD) --- PGD 用于索引进程中的一个数组,每个进程有且只有一个PGD,PGD的数组项指向另一些数组的起始地址,这些数组称为中间页目录。
b. 虚拟地址的第二部分称为中间页目录(Page Middle Directory) --- 在通过PGD中的数组找到对应的PMD之后,PMD的数组项也是一个指针,指向下一级数组,即页表或页目录。
c. 虚拟地址的第三部分称为PTE(Page Table Entry, 页表数组) ---PTE 用来索引页表,页表的数组项指向物理内存页。
虚拟内存页和物理内存页之间的映射就此完成,
d. 虚拟地址的最后一部分称为偏移量,它指定了页内部的一个字节位置,每个地址都指向地址空间的某个字节。
页表的特色在于,对虚拟地址空间中不需要的区域,不必创建中间页目录或页表,所以多级页表节省了大量内存。
多级页表的缺点就是每次访问内存时,必须逐级访问多个数组后,才能将虚拟地址转换为物理地址。linux使用下面两种方法来加速该过程:
4. 内存访问加速方法:
a. 使用了MMU(Memory Management Unit,内存管理单元)。
b. 地址转换中出现最频繁的那些地址,保存到 地址转换后备缓冲器(Translation Lookaside Buffer, TLB)的高速缓存中。这些地址无需访问页表即可从高速缓存中直接获得地址数据。
5. 内存映射:
将任意来源的数据传输到进程的虚拟地址空间中。
用户可以像普通内存一样访问作为映射目标的地址空间区域,但任何修改都会自动传输到数据源。
例如:文件的内容可以映射到内存中,只需读取相应的内存即可访问文件内容,或者向内存写入数据来修改文件的内容。内核会保证任何修改都会自动同步到文件中。
内核在实现设备驱动程序时直接使用了内存映射,外设的I/O可以映射到虚拟地址空间中,对相应内存区域的读写会由系统重定向到设备,从而大大简化了驱动程序的实现。
6. 逻辑地址到线性物理地址的映射:
逻辑地址指的是机器语言指令中,用来指定一个操作数或一条指令的地址。
一种是使用段地址+段内偏移量做逻辑地址。段的描述符保存在GDT(global descriptor table)或者LDT(local descriptor table)中。每个线程有不同的逻辑地址(不同的段地址),再映射到不同的线性物理地址
另一种是使用paging机制。每个线程有相同的逻辑地址,由paging机制将逻辑地址映射到线性物理地址。
Linux倾向于使用paging机制,因为不同机器架构的分段机制不同。
kernel不使用LDT,尽管有一个系统调用允许线程创建自己的LDT。在执行segment-oriented的windows应用时,这个调用是有用的,例如Wine。