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

devtools/2024/10/18 14:14:43/

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 addresswithin 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;} elserb_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.ppmy.cn/devtools/121189.html

相关文章

828华为云征文|在Flexus X实例上安装JDK和Tomcat保姆教学

目录 一、Flexus云服务器X实例 1.1 Flexus X实例概述 1.2 Flexus X实例场景优势 1.3 其他型号与Flexus X实例比较 二、Flexus X实例上安装JDK 2.1 确定安装版本 2.2 yum命令直接安装 2.3 查看版本 三、Flexus X实例上安装tomcat 3.1 上传安装包到Flexus X实例服务器 …

9.29学习

1.线上问题rebalance 因集群架构变动导致的消费组内重平衡&#xff0c;如果kafka集内节点较多&#xff0c;比如数百个&#xff0c;那重平衡可能会耗时导致数分钟到数小时&#xff0c;此时kafka基本处于不可用状态&#xff0c;对kafka的TPS影响极大 产生的原因 ①组成员数量发…

移动端实现下拉刷新和上拉加载(内含案例)

在前端开发中&#xff0c;上拉加载和下拉刷新常用于实现内容的动态加载&#xff0c;尤其在移动端的应用中。下面我将提供一个简单的示例和逻辑说明。 1. 逻辑说明&#xff1a; 下拉刷新&#xff1a; 用户向下拖动页面顶部&#xff0c;触发一个事件&#xff0c;刷新当前内容。需…

什么是梯度爆炸?什么是梯度消失?怎么解决?

什么是梯度爆炸&#xff1f;什么是梯度消失&#xff1f;怎么解决&#xff1f; 梯度爆炸梯度消失解决办法 梯度爆炸 梯度爆炸是指在神经网络训练过程中&#xff0c;梯度值变得非常大&#xff0c;超出了网络的处理范围&#xff0c;导致权重更新变得不稳定甚至不收敛的现象。当梯…

云原生(四十四) | 远程连接ECS服务器

文章目录 远程连接ECS服务器 一、自带连接工具连接ECS云服务器 二、为什么要使用远程连接工具 三、远程连接ECS服务器四要素 1、用户名 密码 2、IP地址&#xff08;公网IP&#xff09; 3、SSH端口号 4、阿里云安全组 四、使用MobaXterm远程连接ECS云服务器 五、ECS云…

根据给定的相机和镜头参数,估算相机的内参。

1. 相机分辨率和传感器尺寸 最高分辨率&#xff1a;6000 4000 像素传感器尺寸&#xff1a;22.3 mm 14.9 mm 2. 计算像素大小 需要计算每个像素对应的实际尺寸&#xff08;mm/pixel&#xff09;&#xff1a; 水平方向像素大小&#xff1a; 垂直方向像素大小&#xff1a; …

饿了么 ui表单 有滚动条的时候 右上角多一节

// 当没有滚动条的时候 :deep(.el-table__body-wrapper.is-scrolling-none~.el-table__fixed-right) {right: 0px !important;}// 当有滚动条的时候 默认偏移距离:deep(.el-table--scrollable-y .el-table__fixed-right) {right: 13px !important;}修改完 不显示滚动条

python Scrapy 框架 demo

文章目录 前言python Scrapy 框架 demo1. 安装2. 百度热搜爬取demo2.1. 初始化项目2.2. 修改 items.pyitems.py2.3. 创建 spiders/baidu_spider.py2.4. 修改 pipelines.py2.5. 修改 settings.py 3. settings.py 相关配置说明4. 启动爬虫测试 前言 如果您觉得有用的话&#xff0…