简单理解程序地址空间:Linux 中的内存映射与页表解析

news2024/10/9 21:15:41

ps: Linux操作系统对于程序地址,物理地址的处理,对于源码,我也看不大懂,只是截取当我们进程发生正常缺页中断的时候的调用情况。本文中所有的源码都是进行截取过的,如果大家感兴趣可以去下载源码。

在Linux 操作系统 进程(1)-CSDN博客 我们在最后简单介绍了我们所写的C语言程序的地址都是虚拟地址,通过页表映射到物理地址,那么这篇文章,我们就深入一点,通过观察Linux源码中对于页面内容的填充,或者是当发生缺页中断的时候,如何获取到物理地址。

进程的起点 (task_struct)

当说到一个进程所有的属性的时候,必不可少的一个结构体就是task_struct结构体,那么当我们说到程序地址空间的时候,该结构体里一定会包含描述这个属性的相关字段。

struct task_struct {
	volatile long state;	/* -1 unrunnable, 0 runnable, >0 stopped */
	
        //就在这里
	struct mm_struct *mm, *active_mm;
}

通过这个结构体,让我们仔细看看Linux对于程序地址空间描述(截取)

struct mm_struct {
	struct vm_area_struct *mmap;        /* list of VMAs */
    struct rb_root mm_rb;               /* 红黑树,用于管理 VMA */
    struct vm_area_struct *mmap_cache;   /* 上一个查找的 VMA */
	
	unsigned long start_code, end_code, start_data, end_data;
	unsigned long start_brk, brk, start_stack;
	unsigned long arg_start, arg_end, env_start, env_end;
	unsigned long rss, anon_rss, total_vm, locked_vm, shared_vm;
	unsigned long exec_vm, stack_vm, reserved_vm, def_flags, nr_ptes;

    //虚拟地址空间的定义  stack ...
};

对于程序地址空间的定义有了,那么相对应的页表的描述不就在第一行 struct vm_area_struct *mmap;

vm_area_struct

struct vm_area_struct {
	struct mm_struct * vm_mm;	/* 所属的mm_struct. */
	unsigned long vm_start;		/* Our start address within vm_mm. */
	unsigned long vm_end;		/* The first byte after our end address
					   within vm_mm. */

	/* linked list of VM areas per task, sorted by address */
	struct vm_area_struct *vm_next; /*链接下一个进程的VMA空间*/

	unsigned long vm_flags;		/* 保存的数据的权限 -> 只读 读写... */

	struct rb_node vm_rb;  /*栈空间,堆空间... 的范围 */

页表的填充

mm_struct的初始化

mm_struct 结构体的初始化是由一个 init_mm的宏完成的

struct mm_struct init_mm = INIT_MM (init_mm);
#define INIT_MM(name) \
{			 					\
	.mm_rb		= RB_ROOT,				\
	.pgd		= swapper_pg_dir, 			\
	.mm_users	= ATOMIC_INIT(2), 			\
	.mm_count	= ATOMIC_INIT(1), 			\
	.mmap_sem	= __RWSEM_INITIALIZER(name.mmap_sem),	\
	.page_table_lock =  SPIN_LOCK_UNLOCKED, 		\
	.mmlist		= LIST_HEAD_INIT(name.mmlist),		\
	.cpu_vm_mask	= CPU_MASK_ALL,				\
	.default_kioctx = INIT_KIOCTX(name.default_kioctx, name),	\
}

但这也只是对于虚拟地址空间的初始化,页表并没有填充任何内容,当我们进行读取程序内容的时候,一定会发生缺页中断,既然初始化并没有对于页表初始化,那也就是说,在缺页中断的过程中,会有对该情况的判断。那么让我们跳转到缺页中断时,系统执行的函数吧!

 do_page_fault

/*datammu : 错误类型
* esr0 : 错误信息
* ear0 ; 错误的虚拟地址
*/


asmlinkage void do_page_fault(int datammu, unsigned long esr0, unsigned long ear0)
{
	struct vm_area_struct *vma;
	struct mm_struct *mm;
	unsigned long _pme, lrai, lrad, fixup;
	siginfo_t info;
	pgd_t *pge;
	pud_t *pue;
	pte_t *pte;
	int write;

    mm = current->mm;  //获取错误页的 mm_struct

    //...
    vma = find_vma(mm, ear0);
    switch (handle_mm_fault(mm, vma, ear0, write))
    // ...

}

在这个函数的前面数据的定义中,我们发现了几个之前并未出现的参数  pgd_t *pge;  pud_t *pue;   pte_t *pte;  这几个参数是操作系统对自己页表访问的具体描述,等下再说。当这个函数正常执行时,我们会发现他调用了这个函数 vma = find_vma(mm, ear0);  获取到发生缺页终端的虚拟地址所在的vma

/* Look up the first VMA which satisfies  addr < vm_end,  NULL if none. */
struct vm_area_struct * find_vma(struct mm_struct * mm, unsigned long addr)
{
	struct vm_area_struct *vma = NULL;

	if (mm) {
		/* Check the cache first. */
		/* (Cache hit rate is typically around 35%.) */
		vma = mm->mmap_cache;
		if (!(vma && vma->vm_end > addr && vma->vm_start <= addr)) {
			struct rb_node * rb_node;

			rb_node = mm->mm_rb.rb_node;
			vma = NULL;

			while (rb_node) {
				struct vm_area_struct * vma_tmp;

				vma_tmp = rb_entry(rb_node,
						struct vm_area_struct, vm_rb);

				if (vma_tmp->vm_end > addr) {
					vma = vma_tmp;
					if (vma_tmp->vm_start <= addr)
						break;
					rb_node = rb_node->rb_left;
				} else
					rb_node = rb_node->rb_right;
			}
			if (vma)
				mm->mmap_cache = vma;
		}
	}
	return vma;
}

在这个函数中,我们可以很清楚的看到,查找vma的时候,先去查找上一次使用过的vma(我们所写的程序的都具有局部性),然后在使用红黑树结构查找。那么有同学就有疑问了,为什么我们已经得到了出现错误的虚拟地址,为什么还要去查找他所在的vma范围呢?

unsigned long vm_flags;		/* 保存的数据的权限 -> 只读 读写... */

在我们所写的程序中,有只读的变量,可以读写的变量,或者是需要申请内存的堆空间的变量,如果我们只有虚拟地址,什么不知道,那么这个数据是需要重新申请内存呢,或者说是不可更改呢,vm_flags保存的权限,和vma结构体中的其他字段就起到了作用。 

页表填充

找到我们地址的其他属性后,就应该去找到页表了,handle_mm_fault(mm, vma, ear0, write),为什么说是找页表呢? 在do_page_fault 函数中,我们没有见过的那几个变量其实就是Linux系统的三级页表结构,通过一级一级的转变才得到最后的页表,分别就是 页目录 , 页中间目录 ,页表,最后通过偏移量才得到虚拟地址所在的页框。

int handle_mm_fault(struct mm_struct *mm, struct vm_area_struct * vma,
		unsigned long address, int write_access)
{
	pgd_t *pgd;
	pud_t *pud;
	pmd_t *pmd;
	pte_t *pte;

	__set_current_state(TASK_RUNNING); //更改进程为运行态

	inc_page_state(pgfault);

    //通过页目录去获得页表

	pgd = pgd_offset(mm, address);  
	spin_lock(&mm->page_table_lock);

	pud = pud_alloc(mm, pgd, address);
	if (!pud)
		goto oom;

	pmd = pmd_alloc(mm, pud, address);
	if (!pmd)
		goto oom;

	pte = pte_alloc_map(mm, pmd, address);
	if (!pte)
		goto oom;
	
	return handle_pte_fault(mm, vma, address, write_access, pte, pmd);

 oom:
	spin_unlock(&mm->page_table_lock);
	return VM_FAULT_OOM;
}

我们终于获得进程的页表,进入到了最后一个函数,页表第一次映射的处理就出现了,以及后续对于正常缺页中断的处理。

static inline int handle_pte_fault(struct mm_struct *mm,
	struct vm_area_struct * vma, unsigned long address,
	int write_access, pte_t *pte, pmd_t *pmd)
{
	pte_t entry;

	entry = *pte;
    
    // 判断页框存在不存在  不存在就是第一次映射 
	if (!pte_present(entry)) {
		/*
		 * If it truly wasn't present, we know that kswapd
		 * and the PTE updates will not touch it later. So
		 * drop the lock.
		 */
		if (pte_none(entry))
			return do_no_page(mm, vma, address, write_access, pte, pmd);
		if (pte_file(entry))
			return do_file_page(mm, vma, address, write_access, pte, pmd);
		return do_swap_page(mm, vma, address, pte, pmd, entry, write_access);
	}

	if (write_access) {
		if (!pte_write(entry))
			return do_wp_page(mm, vma, address, pte, pmd, entry);

		entry = pte_mkdirty(entry);
	}
	entry = pte_mkyoung(entry);
	ptep_set_access_flags(vma, address, pte, entry, write_access);
	update_mmu_cache(vma, address, entry);
	pte_unmap(pte);
	spin_unlock(&mm->page_table_lock);
	return VM_FAULT_MINOR;
}

 当然,为了加快这个过程,cpu中汇集成一个MMU(内存管理单元)用来处理这些事情

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2200034.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Linux】wsl2安装ubuntu并移动安装位置

本文首发于 ❄️慕雪的寒舍 1.启用wsl 首先是启用你的wsl&#xff0c;参考本站wsl安装centos8中的教程; 启用wsl后&#xff0c;更新一下&#xff0c;并设置版本为2&#xff1b; wsl --update wsl --set-default-version 2 # 设置wsl版本为2&#xff0c;不然可能安装失败2.安…

【FPGA开发】Modelsim仿真精度的坑

问题所在 最近在使用黑金的AXU3EG板卡对着正点原子ZYNQ7020的例程进行移植学习。但在编写tb代码以及使用modelsim进行仿真时出了问题&#xff0c;发现我的实际波形与正点的对不上&#xff0c;仔细测量一下波形发现&#xff0c;我的系统时钟是6ns周期&#xff0c;而不是理想中的…

某象异形滑块99%准确率方案

注意,本文只提供学习的思路,严禁违反法律以及破坏信息系统等行为,本文只提供思路 如有侵犯,请联系作者下架 该文章模型已经上线ocr识别网站,欢迎测试!!,地址:https://yxlocr.windy-rain.cn/ocr/slider/6 所谓的顶象异形滑块,是指没有采用常规的缺口,使用各种形状的…

20.安卓逆向-frida基础-hook分析调试技巧2-hookDES

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 内容参考于&#xff1a;图灵Python学院 本人写的内容纯属胡编乱造&#xff0c;全都是合成造假&#xff0c;仅仅只是为了娱乐&#xff0c;请不要盲目相信。 工…

MEMS 课本习题(1)

Chapter 5 Lump Modeling 为了将机械系统转换为等效电路&#xff0c;我们需要将各个机械元件转换为相应的电气元件。以下是机械元件和其电气等效元件的对照关系&#xff1a; 质量&#xff08;m&#xff09; - 转换为 电感&#xff08;L&#xff09;弹簧&#xff08;k&#xff…

SD入门教程一:Stable Diffusion 基础(技术篇)

前言 在开篇的时候就大致讲了SD和VAE&#xff0c;那么今天我们具象化地再来讲讲Stable Diffusion&#xff08;稳定扩散&#xff09;。 严格说来它是一个由几个组件&#xff08;模型&#xff09;构成的系统&#xff0c;而非单独的一个模型。我以最常见的文生图为例&#xff0c;…

PCL 计算3DSC并可视化

目录 一、概述 1.1原理 1.2实现步骤 1.3应用场景 二、代码实现 2.1关键函数 2.1.1 法线计算 2.1.2 3DSC特征计算 2.1.3 可视化3DSC直方图 2.2完整代码 三、实现效果 PCL点云算法汇总及实战案例汇总的目录地址链接&#xff1a; PCL点云算法与项目实战案例汇总&#…

【C++】——继承【上】

P. S.&#xff1a;以下代码均在VS2019环境下测试&#xff0c;不代表所有编译器均可通过。 P. S.&#xff1a;测试代码均未展示头文件stdio.h的声明&#xff0c;使用时请自行添加。 博主主页&#xff1a;Yan. yan.                        …

MPLS解决BGP路由黑洞问题

文章目录 MPLS应用实验实验配置 MPLS应用实验 实验目的&#xff1a;解决BGP中IBGP邻居之间存在的问题 MPLS解决BGP路由黑洞问题实验 配置完基本的MPLS和BGP操作之后&#xff0c;只有R2和R5上面有两个私网的路由&#xff0c;中间的设备没有私网路由&#xff0c;这时候默认还是走…

Linux源码阅读笔记-USB驱动分析

基础层次详解 通用串行总线&#xff08;USB&#xff09;主要用于连接主机和外部设备&#xff08;协调主机和设备之间的通讯&#xff09;&#xff0c;USB 设备不能主动向主机发送数据。USB 总线采用拓扑&#xff08;树形&#xff09;&#xff0c;主机侧和设备侧的 USB 控制器&a…

IT招聘乱象的全面分析

近年来&#xff0c;IT行业的招聘要求似乎越来越苛刻&#xff0c;甚至有些不切实际。许多企业在招聘时&#xff0c;不仅要求前端工程师具备UI设计能力&#xff0c;还希望后端工程师精通K8S服务器运维&#xff0c;更有甚至希望研发经理掌握所有前后端框架和最新开发技术。这种招聘…

RAG测评关键指标

解读RAG测评&#xff1a;关键指标与应用分析 ©作者|CodeDan 来源|神州问学 一、RAG介绍 1.1 简介 RAG&#xff08;Retrieval-Augmented Generation&#xff09;是一种结合信息检索与文本生成的技术&#xff0c;旨在提高大型语言模型&#xff08;LLM&#xff09;在回答复…

ROS理论与实践学习笔记——4 ROS的常用组件之TF坐标变换

tf:TransForm Frame,坐标变换 坐标系:ROS 中是通过坐标系统开标定物体的,确切的将是通过右手坐标系来标定的。 作用:在 ROS 中用于实现不同坐标系之间的点或向量的转换。 说明:在ROS中坐标变换最初对应的是tf,不过在 hydro 版本开始, tf 被弃用,迁移到 tf2,后者…

docker 搭建 vue3 + vite

vue3发布了,今天就分享一下我使用docker 搭建 vue3 vite 开发环境。至于为什么使用docker搭建&#xff0c;因为多版本可以快速切换&#xff0c;和本地环境避免冲突。好了话不多说我们开始吧。 1. 准备资料 Docker Desktop wsl2 ubuntu 下载地址 : https://www.docker.…

实验室认证需要准备哪些文件材料?

实验室认证需要准备的文件材料通常包括以下几类&#xff1a; 一、法律地位文件 实验室成立文件及营业执照&#xff1a;包括实验室的成立证明文件、单位营业执照等&#xff0c;以证明实验室的法律地位和合法性。 人员任命文件&#xff1a;最高管理者&#xff08;如总经理&…

QT 实现QMessageBox::about()信息自定义显示

这是我记录Qt学习过程的第四篇心得文章&#xff0c;主要是方便自己编写的应用程序显示“关于信息”&#xff0c;对QMessageBox::about()输入信息进行规范&#xff0c;可以设置应用程序名称&#xff0c;通过定义宏从pro文件获取应用程序版本号&#xff0c;以及编译程序的QT版本、…

关于C语⾔内存函数 memcpy memmove memset memcmp

memcpy使⽤和模拟实现 void * memcpy ( void * destination, const void * source, size_t num ); 函数memcpy从source的位置开始向后复制num个字节的数据到destination指向的内存位置。 这个函数在遇到 \0 的时候并不会停下来。 如果source和destination有任何的重叠&am…

Linux环境通过APT 仓库安装版PostgreSQL 数据库实战

Linux环境通过APT 仓库安装版PostgreSQL 数据库是运维人员常见的需求之一&#xff0c;今天我们一步一步演示一下&#xff1a; 1、添加 PostgreSQL APT 仓库 确保你的系统更新&#xff0c;然后添加 PostgreSQL 的官方 APT 仓库。 sudo apt update sudo apt install -y wget w…

原来机器学习那么简单——决策树回归

引言&#xff1a; 在正文开始之前&#xff0c;首先给大家介绍一个不错的人工智能学习教程&#xff1a;https://www.captainbed.cn/bbs。其中包含了机器学习、深度学习、强化学习等系列教程&#xff0c;感兴趣的读者可以自行查阅。 一、算法介绍 回归树是决策树的一种&#xff…

[已完结] Authentication Lab —— 靶场笔记合集

Authentication Labhttps://authlab.digi.ninja/ 0x01&#xff1a;Authentication Lab 靶场简介 Authentication Lab 是由 DigiNinja 提供的&#xff0c;一个专注于身份验证和授权漏洞的实验平台。该网站旨在提供一个可以让用户探索和实践各种常见与不常见的身份验证与授权漏…