页表工作原理详解

Ⅰ. 前言

​ 进入了线程这部分内容,我们需要了解更多的知识,大体就是线程概念,线程与进程的区别和联系、线程控制、线程创建、线程终止、线程等待、线程分离、线程安全、线程同步,除此之外我们还得学习互斥量、条件变量、POSIX信号量以及读写锁,最后我们还会介绍一些关于多进程的设计模式比如单例模式等,然后还会了解一下线程池的概念!

​ 首先我们不会马上来讲线程的概念,因为不太好理解,我们先引入一个知识点,就是我们之前一直在谈论的页表,它的工作方式大概是怎么样的呢,我们一起来了解一下!

Ⅱ. 页表的工作方式

Linux从头学15:【页目录和页表】-理论 + 实例 + 图文的最完全、最接地气详解

地址的简单解释:

物理地址: 物理地址就是内存单元的绝对地址,比如一个 128MB 的 DRAM 内存条插在计算机上,物理地址 0x0000 就表示内存条的第 1 个存储单元,0x0010 就表示内存条的第 17 个存储单元,不管 CPU 内部怎么处理内存地址,最终访问的都是内存单元的物理地址。

虚拟地址:虚拟地址是操作系统给运行在用户态的应用程序看到的地址,每一个虚拟地址,如果有一个对应的物理地址,那么就是一个合法的虚拟地址,应用程序实际访问的是其对应的物理地址;否则就是一个非法的虚拟地址。一旦应用程序访问非法的虚拟地址,CPU 当然就会产生异常了。一旦出现这样的异常,操作系统就会及时进行处理,甚至是杀死掉这个应用程序。虚拟地址物理地址的对应关系,一般是通过页表来实现。


​ 简单的说,页表就是一个存储物理页地址的表,我们知道,现在的程序使用的都是虚拟内存CPU 在取指令或者取数据的时候使用的是虚拟地址,为了能够从内存中取得数据,需要将虚拟地址转换为物理地址,虚拟地址和物理地址之间的映射关系就保存在页表中。每个进程都有自己的 页目录页表

如何看待页表:

  • 地址空间是进程能看到的资源窗口。
  • 页表决定进程真正拥有资源的情况。
  • 合理的对地址空间+页表进行资源划分,我们就可以对一个进程所有的资源进行分类。

​ 仔细思考一下,我们的虚拟内存一共有多大,假设这是一台 32 位机器,那么就有 2 32 2^{32} 232 这么多大的寻址范围,如果我们的页表是通过记录每个虚拟内存对应的物理内存的转化地址的话,那么我们一共得有 2 32 2^{32} 232 这么多个地址,一共算下来就是 4GB,除此之外还得加上一些属性比如是否命中、RWX权限、U/K权限等等假设一共 6 个字节,那么一共就是 24GB,光光一个页表就得 24GB,我们多开几个进程,那么不会直接崩了❓❓❓

在讲解页表工作原理之前,我们先来完善一些知识:

  • 物理内存其实也是需要被管理的,而它肯定也是一种数据结构,被操作系统维护的,通常来说物理内存被划分为一页一页,每页大小是 4KB,最后通过 mem 数组就能来维护这些页,而这些页我们称之为页框!如下:

    struct Page {
        // 物理内存的属性 -- 一般为4KB
    }
    struct Page mem[size]; // 通过mem数组来维护被划分出来的每一页
    
  • 磁盘上的文件其实也是被划分为每一页每一页来进行管理,比如说一个可执行文件,其实不是简简单单的从文本文件编译成二进制文件的,首先,这个可执行文件里面所采用的地址其实都是虚拟地址,其次,我们编译好的程序其实都得按 4KB 为一个单位进行划分,一般这个 4KB 的区域我们叫做页帧

  • 这也就是为什么我们之前在讲 IO 的时候谈到 磁盘与物理内存的交互单位为 4KB,其实就是因为它们都是通过页框和页帧来交互的!

  • 除此之外,因为有了管理物理内存的数据结构,所以因此有了对应的管理物理内存的算法,比如说伙伴系统,这里了解即可!【全网首发】深度剖析 Linux 伙伴系统的设计与实现

页表的知识铺垫:

  • 操作系统在加载用户程序的时候,不仅仅需要分配物理内存,来存放程序的内容;而且还需要分配物理内存,用来保存程序的页目录和页表。
  • 其实页表不是单一的,而是以 多级页表的方式存在的。第一层就是一个页目录,而页目录的每一个表项指向对应的页表,叫做 二级页表。也就是说每个表项中存放着每个页表的起始地址!
  • 一个页表中一共有 1024 个表项,每一个页表项占用 4 个字节,所以一个页表就占用 4KB 的物理内存空间,正好是一个物理页的大小。所以一个 页表/页目录 可以覆盖 4MB 的物理内存空间(1024 * 4 KB)
  • 一个应用程序是不可能完全使用全部的 4GB 空间的,也许只要几十个页表就可以了。例如:一个用户程序的代码段、数据段、栈段,一共就需要 10MB 的空间,那么使用 3 个页表就足够了,加上页目录,一共需要 16KB 的空间。
  • 虚拟存储的实现需要依靠硬件的支持,对于不同的 CPU 来说是不同的,但是几乎所有的硬件都采用一个加 MMU(Memory Management Unit) 的部件来进行页映射!

页表其实是这样子工作的:

​ 假设这是一个 32 位机器,那么我们的虚拟内存中每个虚拟地址就是 32 个比特位也就是 4 个字节,但是这 4 个字节不是简简单单的存放地址的,而是==①将高位的 10 个比特位作为一部分==,这部分的数据大小范围就是 2 10 2^{10} 210 字节也就是 1KB ,称为 页目录索引,首先这个索引会根据去页目录中索引到对应的位置,因为在页目录中,它有1024 个页表项,每一个表项指向一个页表的起始地址也就是物理地址。

​ 通过页目录和高位的十个比特位找到对应的页表,可以索引到对应的二级页表,每个二级页表中又有 1024 个页表项,其中每个页表项中存放的是对应的物理内存中某个页框的物理起始地址。这个时候就轮到 ②虚拟地址从高位第 11~20 位一共 10 个比特位作为一部分,这一部分称为 页表索引指向当前二级页表中的某个页表项的具体位置!

​ 通过二级页表和第二部分比特位,可以索引到物理内存某一页框的起始地址,这个时候第三部分就要登场了,③虚拟地址低位的 12 个比特位作为一部分,这一部分称为 页内偏移,起始也就是在这个页框中的 偏移量,因为我们定位的目的是 寻找物理地址也就是这 n 个字节(具体是多少字节得看使用的是什么类型的数据),所以肯定要具体到这个偏移量位置处。这就是页表的工作流程了!

​ 我们还可以发现,为什么我们对物理内存和磁盘都是按 4KB 作为单位划分呢 ❓❓❓

​ 其实就是因为这个页内偏移的大小范围是 12 个比特位,就是 4096 也就是 4KB,所以这都是为了结构而产生的!

​ 下面我们通过图片的形式来大概描述一下页表的工作方式:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dCPKiRsF-1680185272079)(../../img/image-20230329201417223.png)]

​ 但是其实页表页目录的关系更像是多叉树的关系:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-D15GA8cH-1680185272079)(../../img/image-20230329211451119.png)]

​ 上图中少画了一个寄存器的步骤,下面我们也可以稍微来了解一些关于页表的寄存器!

Ⅲ. CR3寄存器

​ 现在,所有页表的物理地址被页目录表项指向了,那么页目录的物理地址,处理器是怎么知道的呢❓❓❓

​ 答案就是通过 CR3 寄存器,也叫做: PDBR(Page Table Base Register)这个寄存器中,保存了当前正在执行的那个任务的页目录地址。

​ 每个任务(程序)都有自己的页目录和页表,页目录表的地址被记录在任务的 TSS 段中。

​ 当操作系统调度任务的时候,处理器就会找到即将执行的新任务的 TSS 段信息,然后把新任务的页目录开始地址更新到 CR3 寄存器中。当新任务的指令开始被执行时,处理器在获取指令、操作数据时,操作的是线性地址。

​ 页处理单元就会从 CR3 寄存器中保存的页目录表开始,把这个线性地址最终转换成物理地址。

当然,处理器中还有一个快表,用来加快从线性地址到物理地址的转换过程。

CR3寄存器的格式如下:


我的博客即将同步至腾讯云开发者社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=xuxptecz0rfn

  • 7
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
页表操作系统中用来记录虚拟内存地址与物理内存地址映射关系的数据结构,其实现方式因操作系统不同而异。以下是一个简单的页表代码实现示例,供参考: ```c #define PAGE_SIZE 4096 // 页大小为 4KB #define PAGE_NUM 1024 // 页表项数为 1024 struct page_table_entry { unsigned int present : 1; // 页是否存在 unsigned int writeable : 1; // 页是否可写 unsigned int user_access : 1; // 用户是否有访问权限 unsigned int write_through : 1; // 是否写直达 unsigned int cache_disabled : 1; // 是否禁用缓存 unsigned int accessed : 1; // 是否被访问过 unsigned int dirty : 1; // 是否被修改过 unsigned int zero : 1; // 保留位 unsigned int global : 1; // 全局页标志位 unsigned int available : 3; // 空闲可用位 unsigned int page_frame : 20; // 物理页框号 }; struct page_table_entry page_table[PAGE_NUM]; // 初始化页表 // 将虚拟地址映射到物理地址 void map_virtual_to_physical(unsigned int virtual_address, unsigned int physical_address) { unsigned int page_index = virtual_address / PAGE_SIZE; // 计算页号 page_table[page_index].present = 1; // 设置页存在 page_table[page_index].writeable = 1; // 设置页可写 page_table[page_index].user_access = 1; // 设置用户可访问 page_table[page_index].write_through = 0; // 不写直达 page_table[page_index].cache_disabled = 0; // 不禁用缓存 page_table[page_index].accessed = 0; // 未被访问过 page_table[page_index].dirty = 0; // 未被修改过 page_table[page_index].zero = 0; // 保留位清零 page_table[page_index].global = 0; // 非全局页 page_table[page_index].available = 0; // 空闲可用位清零 page_table[page_index].page_frame = physical_address / PAGE_SIZE; // 计算页框号 } ``` 以上代码实现了将虚拟地址映射到物理地址的功能,具体实现方式可能会因操作系统的不同而有所差异。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

利刃大大

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值