Linux内核学习笔记——页表的那些事。
目录
- 页表什么时候创建
- 内核页表变化什么时候更新到用户页表
- 源码分析
 
- 常见问题解答
- 问题一:页表到底是保存在内核空间中还是用户空间中?
- 问题2:页表访问,软件是不是会频繁陷入内核?
- 问题3:内存申请,软件是不是会频繁陷入内核创建新页表条目
- 问题4:那内核页表和普通的页表到底有什么区别?
 
接上两文,本文补充一下内核页表和用户页表创建、更新时机说明。
 Linux内核学习笔记——内核页表隔离KPTI机制
 Linux内核学习笔记——内核页表隔离KPTI机制(源码分析)
KPTI中每个进程有两套页表——内核态页表与用户态页表(两个地址空间)。
内核态页表只能在内核态下访问,可以创建到内核和用户的映射(不过用户空间受SMAP和SMEP保护)。
-  内核页表:即书上说的主内核页表,在内核中其实就是一段内存,存放在主内核页全局目录 init_mm.pgd(swapper_pg_dir)中,硬件并不直接使用。
-  进程页表:每个进程自己的页表,放在进程自身的页目录 task_struct.pgd中。
在保护模式下,从硬件角度看,其运行的基本对象为“进程”(或线程),而寻址则依赖于“进程页表”,在进程调度而进行上下文切换时,会进行页表的切换:即将新进程的pgd(页目录)加载到CR3寄存器中。从这个角度看,其实是完全没有用到“内核页表”的,那么“内核页表”有什么用呢?跟“进程页表”有什么关系呢?
页表什么时候创建
内核页表中的内容为所有进程共享,每个进程都有自己的“进程页表”,“进程页表”中映射的线性地址包括两部分:
- 用户态
- 内核态
 其中,内核态地址对应的相关页表项,对于所有进程来说都是相同的(因为内核空间对所有进程来说都是共享的),而这部分页表内容其实就来源于“内核页表”,即每个进程的“进程页表”中内核态地址相关的页表项都是“内核页表”的一个拷贝(进程创建时候就产生了)。
内核页表变化什么时候更新到用户页表
“内核页表”由内核自己维护并更新,在vmalloc区发生page fault时,将“内核页表”同步到“进程页表”中。以32位系统为例,内核页表主要包含两部分:
- 线性映射区
- vmalloc区
其中,线性映射区即通过TASK_SIZE偏移进行映射的区域,对32系统来说就是0-896M这部分区域,映射对应的虚拟地址区域为TASK_SIZE~TASK_SIZE+896M。这部分区域在内核初始化时就已经完成映射,并创建好相应的页表,即这部分虚拟内存区域不会发生page fault。
vmalloc区,为896M~896M+128M,这部分区域用于映射高端内存,有三种映射方式:vmalloc、固定、临时,这里就不详细展开了。
以vmalloc为例(最常使用),这部分区域对应的线性地址在内核使用vmalloc分配内存时,其实就已经分配了相应的物理内存,并做了相应的映射,建立了相应的页表项,但相关页表项仅写入了“内核页表”,并没有实时更新到“进程页表中”,内核在这里使用了“延迟更新”的策略,将“进程页表”真正更新推迟到第一次访问相关线性地址,发生page fault时,此时在page fault的处理流程中进行“进程页表”的更新。
源码分析
/** 缺页地址位于内核空间。并不代表异常发生于内核空间,有可能是用户* 态访问了内核空间的地址。*/if (unlikely(fault_in_kernel_space(address))) {if (!(error_code & (PF_RSVD | PF_USER | PF_PROT))) {//检查发生缺页的地址是否在vmalloc区,是则进行相应的处理if (vmalloc_fault(address) >= 0)return;/** 对于发生缺页异常的指针位于vmalloc区情况的处理,主要是将* 主内核页表向当前进程的内核页表同步。*/
static noinline __kprobes int vmalloc_fault(unsigned long address)
{unsigned long pgd_paddr;pmd_t *pmd_k;pte_t *pte_k;/* Make sure we are in vmalloc area: *//* 区域检查 */if (!(address >= VMALLOC_START && address < VMALLOC_END))return -1;WARN_ON_ONCE(in_nmi());/** Synchronize this task's top level page-table* with the 'reference' page table.** Do _not_ use "current" here. We might be inside* an interrupt in the middle of a task switch..*//*获取pgd(最顶级页目录)地址,直接从CR3寄存器中读取。*不要通过current获取,因为缺页异常可能在上下文切换的过程中发生,*此时如果通过current获取,则可能会出问题*/pgd_paddr = read_cr3();//从主内核页表中,同步vmalloc区发生缺页异常地址对应的页表pmd_k = vmalloc_sync_one(__va(pgd_paddr), address);if (!pmd_k)return -1;//如果同步后,相应的PTE还不存在,则说明该地址有问题了pte_k = pte_offset_kernel(pmd_k, address);if (!pte_present(*pte_k))return -1;return 0;
}常见问题解答
问题一:页表到底是保存在内核空间中还是用户空间中?
创建和删除页表的确是在内核空间操作的。页表不能在用户空间进行操作一点都不奇怪,你要知道页表的作用不仅仅是虚拟地址到物理地址的映射,还有关键的权限访问控制和页面属性的记录。下图是armv8中level 1的页表格式,类似于x86中的PUD的结构:
 
 可以看到该页表中只有"Outlook block address"是在表示下一级页表的地址,"Upper attributes"和"Lower attributes"是内核空间用到权限的控制位和页属性标志。
问题2:页表访问,软件是不是会频繁陷入内核?
这个需要结合场景分析。访问页表是否会陷入内核,这要看你是:
- CPU地址翻译的过程中的页表访问;
- 增加修改页表项。
如果是第一种,CPU地址翻译,那么这种访问是硬件完成的,整个过程不需要代码参与,没有任何性能上的损失。
如果是第二种,是会慢一些。这种慢是为了安全,如果页表在用户空间,那么用户就可能自己修改页表,映射任意的内存地址,访问任何内存,甚至是直接操作硬件,进程间、内核的隔离保护就失去了意义。
问题3:内存申请,软件是不是会频繁陷入内核创建新页表条目
你以为在用户进程中分配内存的时候,就马上通过系统调用陷入内核,然后进行页表操作吗?这个理解是不对的。
应用程序虽然可能频繁的malloc或者free,但在页表层面上,并不会频繁的创建、删除页表项,主要原因是,malloc/free操作的接口都是C库的接口,在C库里,还有另外一层次的封装,来保证不会频繁的提交页表的操作申请。
内核如今已经发展的很成熟了,当然不会这么傻。在你兴高采烈的分配好一块内存后,内核只是给你找了一块独一无二的虚拟内存空间,并没有映射到物理内存,所以根本没有页表的操作。只有你真正用到你的内存时,MMU发现无法进行虚拟内存到物理内存的转换,只好抛出page fault异常,然后进入内核进行物理内存的分配过程,接着就给你把页表创建好了,这个整个过程叫做惰性分配。
更重要的是,其实libc库在进程创建的时候,就已经把堆空间用内存池的方式管理起来,在进程分配小于128kb的内存时,根本不需要内核进行任何操作,因为堆这个段的虚拟内存早就映射好了物理内存。
问题4:那内核页表和普通的页表到底有什么区别?
对于所有进程来说它们页表中的内核空间页表部分都是一模一样的,它们都是从1号进程的init_mm结构中copy的,只有用户空间的页表不尽相同。用户空间的页表是用来进行不同进程地址空间隔离的,所以相同的虚拟地址可以映射到不同的物理地址,当然一般情况下这也是必须的,而内核只有一个。
相关文章:
 
Linux内核学习笔记——页表的那些事。
目录页表什么时候创建内核页表变化什么时候更新到用户页表源码分析常见问题解答问题一:页表到底是保存在内核空间中还是用户空间中?问题2:页表访问,软件是不是会频繁陷入内核?问题3:内存申请,软…...
 
C++,Qt分别读写xml文件
XML语法 第一行是XML文档声明,<>内的代表是元素,基本语法如以下所示。C常见的是使用tiny库读写,Qt使用自带的库读写; <?xml version"1.0" encoding"utf-8" standalone"yes" ?> <根元素>…...
 
WebStorm安装教程【2023年最新版图解】一文教会你安装
文章目录引言一、下载WebStorm三、WebStorm激活配置及创建项目Active Code安装完成尝试新建一个项目引言 今天发现了一个专注前端开发的软件,相比VSCode的话,这个好像也不错,为了后续做个API接口项目做准备。 对于入门JavaScript 开发的者&am…...
用户态和内核态,系统调用
特权指令:具有特殊权限的指令,比如清内存,重置时钟,分配系统资源,修改用户的访问权限 由于这类指令的权限最大,所以使用不当会导致整个系统崩溃 系统调用:是操作系统提供给应用程序的接口(供应…...
Java 包装类
Java 中有些类只能操作对象,因此 Java 的基本数据类型都有一个对应的包装类。 byte:Byteshort:Shortint:Integerlong:Longfloat:Floatdouble:Doublechar:Characterbooleanÿ…...
 
Raspberry Pi GPIO入门指南
如果您想使用 Raspberry Pi 进行数字输入/输出操作,那么您需要使用 GPIO(通用输入/输出)引脚。在这篇文章中,我们将为您提供 Raspberry Pi GPIO 的基础知识,包括如何访问和操作 GPIO 引脚。 0.认识GPIO 树莓派上的那…...
 
汇编语言程序设计(三)之汇编程序
系列文章 汇编语言程序设计(一) 汇编语言程序设计(二)之寄存器 汇编程序 经过上述课程的学习,我们可以编写一个完整的程序了。这章开始我们将开始编写完整的汇编语言程序,用编译和连接程序将它们连接成可…...
 
用二极管和电容过滤电源波动,实现简单的稳压 - 小水泵升压改装方案
简而言之,就是类似采样保持电路,当电源电压因为电机启动而骤降时,用二极管避免电容电压跟着降低,从而让电容上连接的低功耗芯片有一个比较稳定的供电电压。没什么特别的用处,省个LDO 吧,电压跌幅太大的时候…...
 
【数据结构与算法】数据结构有哪些?算法有哪些?
1. 算法与数据结构总览图 2.常用的数据结构 2.1.数组(Array) 数组是一种聚合数据类型,它是将具有相同类型的若干变量有序地组织在一起的集合。数组可以说是最基本的数据结构,在各种编程语言中都有对应。一个数组可以分解为多个数…...
 
使用Element-UI展示数据(动态查询)
学习内容来源:视频P4 本篇文章进度接着之前的文章进行续写 精简前后端分离项目搭建 Vue基础容器使用 目录选择组件修改表格组件修改分页组件增加后端接口前端请求数据接口页面初始化请求数据点击页码请求数据选择组件 在官方文档中选择现成的组件,放在页…...
lamda 表达式例子全集
1、List 转 map 1.1、key(Model属性) value Model Map<String, Model> modeMap List.stream().collect(Collectors.toMap(Model1::属性get方法, v -> v, (p1, p2) -> p1)); 1.2、key(Model1属性) value Model2 Map<String, Model1> model2Map List.stream…...
 
计算机网络第八版——第一章课后题答案(超详细)
第一章 该答案为博主在网络上整理,排版不易,希望大家多多点赞支持。后续将会持续更新(可以给博主点个关注~ 【1-01】计算机网络可以向用户提供哪些服务? 解答:这道题没有现成的标准答案,因为可以从不同的…...
 
嵌入式和Python(二):python初识及其基本使用规则
目录 一,python基本特点 二,python使用说明 ● 两种编程方式 ① 交互式编程 ② 脚本式编程 ● python中文编码 ● python行和缩进 ● python引号 ● python空行 ● python等待用户输入 ① 没有转换变量类型 ② 转换变量类型 ● python变…...
 
C语言详解双向链表的基本操作
目录 双链表的定义与接口函数 定义双链表 接口函数 详解接口函数的实现 创建新节点(BuyLTNode) 初始化双链表(ListInit) 双向链表打印(ListPrint) 双链表查找(ListFind) 双链…...
 
面试必须要知道的常见排序算法
以下排序均为升序 1.直接插入排序 具体思想 把待排序的数据按大小比较插入到一个已经排序好的有序序列中,直到所有的待排序数据全部插入到有序序列中为止.实际生活中,我们平常斗地主摸牌时,就用到了插入排序的思想. 当插入第n个数据时,前面n-1个数据已经有序;第n个数据依次与前…...
 
Kubernetes之服务发布
学了服务发现后,svc的IP只能被集群内部主机及pod才可以访问,要想集群外的主机也可以访问svc,就需要利用到服务发布。 NodePort Nodeport服务是外部访问服务的最基本方式。当我们创建一个服务的时候,把服务的端口映射到kubernete…...
【第二章】谭浩强C语言课后习题答案
1. 什么是算法?试从日常生活中找3个例子,描述它们的算法 算法:简而言之就是求解问题的步骤,对特定问题求解步骤的一种描述。 比如生活中的例子: 考大学首先填报志愿表、交报名费、拿到准考证、按时参加考试、收到录取通知书、按照日期到指定学校报到。 去北京听演唱会首先…...
PostgreSQL和PostGISWGS84和CGCS2000与GCJ02和BD09坐标系与之间互转
– 如果转换后结果为null,查看geom的srid是否为4326或者4490 WGS84转GCJ02 select geoc_wgs84togcj02(geom) from test_table GCJ02转WGS84 select geoc_gcj02towgs84(geom) from test_table WGS84转BD09 select geoc_wgs84tobd09(geom) from test_table BD09转WGS84 select …...
 
数据结构——链表讲解(2)
作者:几冬雪来 时间:2023年3月5日 内容:数据结构链表讲解 目录 前言: 剩余的链表应用: 1.查找: 2.改写数据: 3.在pos之前插入数据: 4.pos位置删除: 5.在pos的后…...
 
Elasticsearch:图片相似度搜索的 5 个技术组成部分
作者:Radovan Ondas,Bernhard Suhm 在本系列博文的第一部分中,我们介绍了图像相似度搜索,并回顾了一种可以降低复杂性并便于实施的高级架构。 此博客解释了实现图像相似性搜索应用程序所需的每个组件的基本概念和技术注意事项。 学…...
电脑插入多块移动硬盘后经常出现卡顿和蓝屏
当电脑在插入多块移动硬盘后频繁出现卡顿和蓝屏问题时,可能涉及硬件资源冲突、驱动兼容性、供电不足或系统设置等多方面原因。以下是逐步排查和解决方案: 1. 检查电源供电问题 问题原因:多块移动硬盘同时运行可能导致USB接口供电不足&#x…...
 
如何将联系人从 iPhone 转移到 Android
从 iPhone 换到 Android 手机时,你可能需要保留重要的数据,例如通讯录。好在,将通讯录从 iPhone 转移到 Android 手机非常简单,你可以从本文中学习 6 种可靠的方法,确保随时保持连接,不错过任何信息。 第 1…...
 
让AI看见世界:MCP协议与服务器的工作原理
让AI看见世界:MCP协议与服务器的工作原理 MCP(Model Context Protocol)是一种创新的通信协议,旨在让大型语言模型能够安全、高效地与外部资源进行交互。在AI技术快速发展的今天,MCP正成为连接AI与现实世界的重要桥梁。…...
 
【JavaWeb】Docker项目部署
引言 之前学习了Linux操作系统的常见命令,在Linux上安装软件,以及如何在Linux上部署一个单体项目,大多数同学都会有相同的感受,那就是麻烦。 核心体现在三点: 命令太多了,记不住 软件安装包名字复杂&…...
 
算法:模拟
1.替换所有的问号 1576. 替换所有的问号 - 力扣(LeetCode) 遍历字符串:通过外层循环逐一检查每个字符。遇到 ? 时处理: 内层循环遍历小写字母(a 到 z)。对每个字母检查是否满足: 与…...
JavaScript基础-API 和 Web API
在学习JavaScript的过程中,理解API(应用程序接口)和Web API的概念及其应用是非常重要的。这些工具极大地扩展了JavaScript的功能,使得开发者能够创建出功能丰富、交互性强的Web应用程序。本文将深入探讨JavaScript中的API与Web AP…...
scikit-learn机器学习
# 同时添加如下代码, 这样每次环境(kernel)启动的时候只要运行下方代码即可: # Also add the following code, # so that every time the environment (kernel) starts, # just run the following code: import sys sys.path.append(/home/aistudio/external-libraries)机…...
 
GO协程(Goroutine)问题总结
在使用Go语言来编写代码时,遇到的一些问题总结一下 [参考文档]:https://www.topgoer.com/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/goroutine.html 1. main()函数默认的Goroutine 场景再现: 今天在看到这个教程的时候,在自己的电…...
苹果AI眼镜:从“工具”到“社交姿态”的范式革命——重新定义AI交互入口的未来机会
在2025年的AI硬件浪潮中,苹果AI眼镜(Apple Glasses)正在引发一场关于“人机交互形态”的深度思考。它并非简单地替代AirPods或Apple Watch,而是开辟了一个全新的、日常可接受的AI入口。其核心价值不在于功能的堆叠,而在于如何通过形态设计打破社交壁垒,成为用户“全天佩戴…...
 
通过MicroSip配置自己的freeswitch服务器进行调试记录
之前用docker安装的freeswitch的,启动是正常的, 但用下面的Microsip连接不上 主要原因有可能一下几个 1、通过下面命令可以看 [rootlocalhost default]# docker exec -it freeswitch fs_cli -x "sofia status profile internal"Name …...
