全面解析Linux 内核 3.10.x - 内存管理 - 内存模型

来源:互联网 发布:淘宝开店卖捕鱼游戏币 编辑:程序博客网 时间:2024/06/06 06:40

From: 全面解析Linux 内核 3.10.x - 内存管理

若想无可替代,必须与众不同 - 每日一句

一、甚么是页框?

从Intel的经典4K页框大小说起:
intel
我们都中断intel多数处理器基本都是以4K为业基准,主要是因为以下两个原因:
- 1.由分页单元引发的缺页异常很容易得到解释,或者由于请求的页存在但是不允许进程对其访问,或者是由于请求的页不存在,第二种情况下,内核分配器必须找到一个4K的空闲页框,并将其分配给进程。
- 2.虽然4K是磁盘块大小的倍数,但是在大多数情况下,在主存和磁盘之间传输小块数据时候更高效!

MIPS 中的页框简单规划

二、Linux 内核中的分页

因为当前内核基本上主要是针对于Linux以及Linux衍生版本,故而下面的分页机制基本适用于所有的Linux系统!
Linux 采用一种同时适用于32位和64位系统的普通分页模型,两级页表在32位系统上已经够用了,但是64位系统需要更多数量的的分页级别!在2.6.10 版本中内核首次适用了三级分页模型,在2.6.11中则使用了更为霸气的四级分页模型!
三级页表机制,包括:
页全局目录(Page Global Directory),即pgd,是多级页表的抽象最高层.每一级的页表都处理不同大小的内存 —— 这个全局目录可以处理4MB的区域.每项都指向一个更小目录的低级表,因此pgd 就是一个页表目录.当代码遍历这个结构时(有些驱动程序就要这样做),就称为是在“遍历”页表。
页中间目录 (Page Middle Directory),即 pmd,是页表的中间层。在 x86 架构上,pmd 在硬件中并不存在,但是在内核代码中它是与 pgd 合并在一起的。
页表条目(Page Table Entry),即pte,是页表的最低层,它直接处理页(参看PAGE_SIZE),该值包含某页的物理地址,还包含了说明该条目是否有效及相关页是否在物理内存中的位。
为了支持大内存区域,Linux 也采用了这种三级分页机制。在不需要为大内存区域时,即可将 pmd 定义成“1”,返回两级分页机制。
分页级别是在编译时进行优化的,我们可以通过启用或禁用中间目录来启用两级和三级分页(使用相同的代码)。32 位处理器使用的是pmd分页,而64位处理器使用的是pgd分页.
三级分页模型

Page

如您所知,在64位处理器中:
21 MSB 保留未用
13 LSB 由页面偏移量表示
其余的 30 位分为:
10 位用于页表
10 位用于页全局目录
10 位用于页中间目录
我们可以从架构中看到,实际上使用了 43 位进行寻址。因此在 64 位处理器中,可以有效使用的内存是 2 的 43 次方。
每个进程都有自己的页目录和页表。为了引用一个包含实际用户数据的页框,操作系统(在 x86 架构上)首先将 pgd 加载到 cr3 寄存器中。Linux 将 cr3 寄存器的内容存储到 TSS 段中。此后只要在 CPU 上执行新进程,就从 TSS 段中将另外一个值加载到 cr3 寄存器中。从而使分页单元引用一组正确的页表。
pgd 表中的每一条目都指向一个页框,其中中包含了一组 pmd 条目;pdm 表中的每个条目又指向一个页框,其中包含一组 pte 条目;pde 表中的每个条目再指向一个页框,其中包含的是用户数据。如果正在查找的页已转出,那么就会在 pte 表中存储一个交换条目,(在缺页的情况下)以定位将哪个页框重新加载到内存中
进程页表

32位系统中0x00000000 - 0xbfffffff, 无论用户态还是内核态都可以寻址!0xc0000000 - 0xffffffff, 只有内核态的进程才能寻址!64位系统中0x0000000000000000 - 0xffffffffbfffffff0xffffffffc0000000 - 0xffffffffffffffff 

示例驱动模块演示流程

    /*Base on Mips*/    unsigned long virt_addr,virt_addr_k;    unsigned char *addr,addr_k;    for (i = 0; i < 5; i++){        addr = vmalloc(50);        for(j = 0;j < 50;j++)            *(u8 *)(addr + j) |= 1;        virt_addr = (u64)addr;        printk("\nvirt_addr=%p,phy_addr=%p\n",virt_addr,virt_to_phys(virt_addr));        vfree(addr);    }    for (i = 0; i < 5; i++){        addr_k = kmalloc(50,GFP_KERNEL);        for(j = 0;j < 50;j++)            *(u8 *)(addr_k + j) |= 1;        virt_addr_k = (u64)addr_k;        printk("\nvirt_addr=%p,phy_addr=%p\n",virt_addr_k,virt_to_phys(virt_addr_k));        kfree(addr_k);    }/*-------- 打印结果 --------*/[   57.888000] virt_addr=c000000000780000,phy_addr=1800000000780000[   57.888000] virt_addr=c0000000007a0000,phy_addr=18000000007a0000[   57.888000] virt_addr=c0000000007c0000,phy_addr=18000000007c0000[   57.888000] virt_addr=c0000000007e0000,phy_addr=18000000007e0000[   57.888000] virt_addr=c000000000800000,phy_addr=1800000000800000[   57.888000] virt_addr=a80000016418ddc0,phy_addr=000000016418ddc0[   57.888000] virt_addr=a80000016418ddc0,phy_addr=000000016418ddc0[   57.888000] virt_addr=a80000016418ddc0,phy_addr=000000016418ddc0[   57.888000] virt_addr=a80000016418ddc0,phy_addr=000000016418ddc0[   57.888000] virt_addr=a80000016418ddc0,phy_addr=000000016418ddc0

1、页描述符
页描述符其实就是描述页的状态信息,从而使得内核清晰的知道每个页表的状态!
内核需要知道的是:
a.页框的当前状态
b.页框包含的是进程的页,还是内核数据或内核代码
c.确定动态内存的页框是否空闲
Ps. 如何判断页框是否空闲?
以下是不空闲的:
包含用户进程的数据,某个软件高速缓存的数据,动态内核分配的内核数据结构,
设备驱动程序缓冲的数据,内核模块的代码等!
*内核中使用一个结构体 page来做页描述符结构存储!*

三、内核页表

内核映像被载入内存后,CPU仍然处于实模式中,分页功能暂时没有倍启用!

第一阶段:
内核创建一个有限的地址空间,包括内核代码段和数据段,初始化页表和用于存放动态数据结构的128K空间,这个最小限度的空间仅够将内核装入RAM和对其初始化核心的核心数据结构!

第二个阶段:
内核充分利用剩余的RAM并适当地建立页表!
临时内核页表的建立
临时页全局目录是在内核编译过程中静态的初始化,而临时页表是由startup_32
swapper_pg_dir

Ps.

2、页对齐 ?

By: Keven - 点滴积累

0 0