Linux 进程虚拟地址空间与虚拟内存

news2024/11/24 2:53:08

Linux 进程虚拟地址空间与虚拟内存

本文主要介绍Linux进程虚拟地址空间和虚拟内存的概念,学习可用物理内存中的页帧与所有的进程虚拟地址空间中的页之间的关联:

逆向映射(reverse mapping) 技术有助于从虚拟内存页追踪到对应的物理内存页

缺页处理(page fault handliing 则允许从块设备按需读取数据填充虚拟地址空间。

1. 简介

无论是何种体系结构,虚拟地址空间的分布方式都有以下几个成分

  • 当前运行的二进制代码
  • 程序使用的动态链接库的代码
  • 存储全局变量和动态产生的数据的堆
  • 用于保存局部变量和实现函数/过程调用的栈
  • 环境变量和命令行参数的栈
  • 将文件内容映射到虚拟地址空间中的内存映射

在这里插入图片描述

系统中的各个进程都具有一个struct mm_struct的实例,可以通过task_struct访问,这个实例保存了进程的内存管理信息

struct mm_struct {
    unsigned long (*get_unmapped_area) (struct file* filp,
                    			unsigned long addr, unsigned long len,
                          unsigned long pgoff, unsigned long flags);
...
    unsigned long mmap_base; /* mmap 区域的基地址 */
    unsigned long task_size; /* 进程虚拟内存空间的长度 */
...
    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;
...
}

可执行代码占用的虚拟地址空间区域,其开始和结束分别通过start_code和end_code标记,类似的,start_data和end_data标记了包含已初始化数据的区域。在ELF二进制文件映射到地址空间后,这些区域的长度不再改变。

堆的起始地址保存在start_brk,brk表示堆区域当前的结束地址,尽管堆的起始地址在进程生命周期中保持不变,但是堆的长度会发生变化,因此brk也会发生变化。

参数列表和环境变量的位置分别由arg_start和arg_end、env_start和env_end描述。两个区域都位于栈中最高区域。

mmap_base表示虚拟地址空间中用于内存映射的起始地址,可调用get_unmapped_area在mmap区域中为新映射区为新映射找到适当的位置。

task_size顾名思义,存储了对应进程的地址空间长度。

test段如何映射到虚拟地址空间中由ELF标准确定,例如IA-32系统起始于0x0804800,AMD64则使用0x000000000400000。堆紧接着text段开始,向上生长。栈起始于STACK_TOP ,如果设置PF_RANDOMIZE,则起始点会减少一个小的随机值。每个体系结构都必须定义STACK_TOP ,大多数都设置为TASK_SIZE,即户地址空间中的最高可用地址。进程的参数列表和环境变量都是栈的初始数据。

用于内存映射区域起始于mm_stuct->mm_base,通常设置为TASK_UNMAPPED_BASE,每个体系结构都需定义,几乎所有情况下,都为TASK_SIZE/3。

2. 内存映射原理

由于所有的用户进程总的虚拟地址空间比可用的物理内存大得多,因此只有最常用的部分才与物理页帧关联。内核利用address_space数据结构,提供一组方法从后备存储器(例如文件系统功能)读取数据。因此,address_space形成了一个辅助层,将映射的数据表示为连续的线性区域,提供给内存管理子系统。
在这里插入图片描述

  1. 试图访问用户地址空间中的一个内存地址,但是用页表无法确定物理地址
  2. 处理器接下来会触发一个缺页异常,发送到内核
  3. 内核会检查负责缺页区域的进程地址空间的数据结构,找到适当的后备存储器,或者确认该访问实际上是不正确的
  4. 分配物理内存页,并从后备存储器读取所需数据填充
  5. 借助页表将物理内存页并入到用户进程的地址空间,应用恢复执行。

struct mm_struct出了前面提到的成员,还包括下列成员,用于管理用户进程在虚拟地址空间中的所有内存区域

struct mm_struct {
    struct vm_area_struct * mmap; /* 虚拟内存区域列表 */
    struct rb_root mm_rb;
    struct vm_area_struct * mmap_cache; /* 上次find_vma的结果 */
...
}

每个区域都通过一个vm_area_struct实例描述,进程的各区域按两种方法排序

  1. 在一个单链表上(开始于mm_struct -> mmap)
  2. 在一个红黑树中,根结点位于mm_rb

内存映射也分好几种类型

  • 按照物理页的类型来分

    • 文件映射

      把文件的一个区间映射到进程的虚拟地址空间,数据源是存储设备上的文件

    • 匿名映射

      没有文件支持的内存映射,把物理内存映射到进程的虚拟地址空间,没有数据源

​ 通常把文件映射的物理页称为文件页,把匿名映射的物理页称为匿名页。

  • 按照对其他进程是否可见来分

    • 共享映射

      修改数据时映射相同区域的其他进程可以看见,如果是文件支持的映射,修改会传递到底层文件

    • 私有映射

      第一次修改数据时会从数据源复制一个副本,然后修改副本,其他进程不可见,不影响数据源

两个进程可以使用共享的文件映射实现共享内存,匿名映射通常为私有映射,共享的匿名映射只可能出现在父进程和子进程之间,在进程的虚拟地址空间中,代码段和数据段是私有的文件映射,未初始化的数据段、堆栈是私有的匿名映射。

虚拟内存区域的表示

每个内存区域表示为vm_area_struct的一个实例,简化定义如下:

struct vm_area_struct {
    struct mm_struct * vmm; /* 所属地址空间 */
    unsigned long vm_start; /* vm_mm内的起始地址 */
    unsigned long vm_end; /* 在vm_mm内结束地址后的第一个字节地址 */
    
    /* 各进程的虚拟内存区域链表,按地址排序 */
    struct vm_area_struct * vm_next;
  
  	pgprot_t vm_page_prot; /* 该虚拟内存区域的访问权限 */
    unsigned long vm_flags; /* 标志,如下列出 */
    
    struct rb_node vm_rb;
  
    /* 
     * 对于有地址空间和后备存储器的区域来说
     * shared连接到address_space->i_mmap优先树
     * 或连接到悬挂在优先树节点外、类似的一组虚拟内存区域的链表
     * 或连接到address_space->i_mmap_nonlinear链表中的虚拟内存区域 */
    union {
        struct  {
				    struct list_head list;
            void *parent; /* 与prio_tree_node的parent成员在内存中位于同一位置 */
            struct vm_area_struct *head;
        } vm_set;
        
        struct raw_prio_tree_node prio_tree_node;
    } shared;
  
    /*
     * 在文件的某一页经过写时复制之后,文件的MAP_PRIVATE虚拟内存区域可能同时在i_mmap树和
     * anon_vma链表中,MAP_SHARED虚拟内存区域只能在i_mmap树中,匿名的MAP_PRIVATE、栈
     * 或者brk虚拟内存区域(file指针为NULL)只能处于anon_vma链表中 */
    struct list_head anon_vma_node; /* 对该成员的访问通过anno_vma->lock串行化 */
    struct anon_vma *anon_vma;      /* 对该成员的访问通过page_table_lock串行化 */
    
    /* 用于处理该结构的各个函数指针 */
    struct vm_operation_struct *vm_ops;
  
    /* 后备存储器的相关信息 */
    unsigned vm_pgoff; /* 文件映射的偏移量,以页为单位 */
    struct file *vmfile; /* 所映射到的文件,也有可能为null */
    void * vm_privat_data; /* vm_pte即共享内存 */
};

vm_ops是一个指针,指向许多方法的集合,这些方法用于在区域上执行各种标准操作

struct vm_operation_struct {
		void (*open) (struct vm_area_struct *area);
  	void (*close) (struct vm_area_struct *area);
    int (*fault) (struct vm_area_struct *vma, struct vm_fault *vmf);
    struct page * (*nopage) (struct vm_area_struct *area, unsigned long
                             addresss, int * type);
}

在创建和删除区域时,分别调用open和close,这两个接口通常不使用,设置为NULL指针。

但是fault是非常重要的,如果地址空间中某个虚拟内存页不在物理内存中,自动触发的缺页异常处理程序会调用该函数,将对应的数据读取到一个映射在用户地址空间的物理内存页中。

nopage是内核原来用于响应缺页异常的办法,不如fault灵活,出于兼容考虑,该成员依然保留,但不应该用于新的代码。
在这里插入图片描述

优先查找树

优先查找树(priority search tree)用于建立文件中的一个区域与该区域映射到的所有虚拟地址空间之间的关联。

struct address_space {
		struct inode *host;
...
    struct prio_tree_root i_mmap;
    struct list_head i_mmap_nonlinear;
}

struct file {
...
		struct address_space *f_mapping;
...
}

struct inode {
...
		struct address_space *i_mapping;
...
}

在这里插入图片描述

3. 对区域的操作

将虚拟地址关联到区域

通过虚拟地址,find_vma可以查找到用户地址空间中结束地址在给定地址后的第一个区域,即满足addr < vm_area_struct->vm_end条件的第一个区域

struct vm_area_struct * find_vma(struct mm_struct *mm, unsigned long addr) 
{
  	struct vm_area_struct *vma = NULL;
  
    if (mm) {
    		/* 首先检查缓存 */
      	/* 缓存命中概率通常为35% */
      	vma = mm->mmap_cache;
      	if (!(vma && vma->end > addr && vma->vm_struct <= 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;
}

添加区域

删除区域

插入区域

4. 内存映射

C语言标准库提供了mmap函数建立映射,在内核测,提供了两个系统调用mmap和mmap2

#include <sys/mman.h>

void *mmap(void *addr, size_t length, int prot, int flags,
           int fd, off_t offset);
int munmap(void *addr, size_t length);

mmap在调用进程的虚拟地址空间中创建一个新的映射,新映射的起始地址为addr,映射长度为length

prot定义了映射的内存保护类型

  • PROT_EXEC:Pages may be executed.

  • PROT_READ:Pages may be read.

  • PROT_WRITE:Pages may be written.

  • PROT_NONE:Pages may not be accessed.

最重要的几个标志:

  1. MAP_FIXED除了给定地址之外,不能将其他地址用于映射,如果没有设置该标志,内核可以在受阻时随意改变目标地址
  2. 如果一个对象(通常是文件)在几个进程之间共享时,则必须使用MAP_SHARED
  3. MAP_PRIVATE创建一个与数据源分离的私有映射,对映射区域的写操作不影响文件源数据
  4. MAP_ANONYMOUS创建与任何数据都不相关的匿名映射,内容被初始化为0,fd和off参数被忽略

通过前面的介绍,可以建立虚拟地址物理地址 之间的联系(通过页表),也可以建立一个进程的内存区域 和其虚拟内存页地址 之间的联系,但是仍然缺乏物理内存页和该页所属进程(更精确的说,所有使用该页的进程的对应页表项)之间的联系。

逆向映射

5. 缺页异常

在这里插入图片描述

6. 虚拟地址空间与虚拟内存的联系与不同

很多人都以为虚拟地址空间和虚拟内存是一个概念,但严格意义上来讲,二者的差别就像Java和Javascript

虚拟地址空间指的是操作系统给每个进程准备的地址空间,上面解释的,包括代码段、数据段、堆、栈等,是进程逻辑上使用的地址空间。虚拟地址空间的大小并不等于物理内存的大小,而是由操作系统总线宽度决定,32位操作系统那一半就是4G大小,操作系统会为每个进程分配特定的地址空间,并维护映射关系,将虚拟地址映射到物理地址上。当进程访问虚拟地址空间时,操作系统会通过MMU(可以去看我文件系统那篇博客)将虚拟地址转换为对应的物理地址,并进行访问。

而虚拟内存是指操作系统提供的一种机制,使得进程能够访问超出物理内存大小的地址空间。虚拟内存的实现方式是将物理内存分为许多大小相等的页(Page),并将虚拟地址空间也分为大小相等的页。当进程访问一个虚拟页时,操作系统会将该页从磁盘中加载到物理内存中的一个页框(Page Frame)中,并将虚拟地址映射到这个页框上。如果物理内存中的页框数量不足,操作系统会进行页面置换(Page Replacement),将不再需要或者使用频率较低的页从物理内存中移除,并将新的页加载到空出的页框中。

虚拟地址空间是进程在逻辑上使用的地址空间,而虚拟内存则是操作系统在物理内存和磁盘之间建立的一层抽象,使得进程能够访问超出物理内存大小的地址空间。虚拟地址空间则是通过对地址空间进行划分和映射来实现的,而虚拟内存是通过将虚拟地址转换为物理地址来实现的。没有虚拟内存这层抽象,虚拟地址空间根本无从发力。

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

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

相关文章

ASS字幕 中的阴影 如何去除,三秒解决

有些外挂的ass字幕&#xff0c;总是自带一层浓浓的 阴影&#xff0c;看着就很不舒服&#xff0c;如下截图 解决方法&#xff1a; 鼠标右键&#xff0c;用记事本打开ass字幕文件&#xff0c;然后搜索关键字 ScaledBorderAndShadow&#xff0c;将其后面的 yes 改为 no&#xff0…

JSP在线小说系统用eclipse定制开发mysql数据库BS模式java编程jdbc

一、源码特点 JSP 在线小说系统是一套完善的web设计系统&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境为 TOMCAT7.0,eclipse开发&#xff0c;数据库为Mysql5.0&#xff0c;使用ja…

C. Insert Zero and Invert Prefix - 构造+思维

分析&#xff1a; 数组b的最后一个元素永远不可能使1&#xff0c;因为即使在最后一个位置操作&#xff0c;也只会把前n-1个元素反转&#xff0c;最后一个元素只能为0.然后可以发现只要a[i]0就可以直接输出0&#xff0c;当a[i]1时一连串的1只需要最后一个1的位置改变成1的字串长…

微信小程序基础语法

微信小程序 文章目录 微信小程序[toc]一、初识微信小程序1.什么是微信小程序2.小程序可以做什么3.小程序与普通网页开发的区别 二、开发准备1.注册小程序开发账号2.安装开发者工具3.登陆我们的开发者工具 三、小程序构成1.小程序的基本组成结构2.小程序的页面组成结构3.小程序组…

OSI(开放系统互连参考模型)知识点详细介绍!!

开放系统互连参考模型分七层&#xff0c;从低到高是物理层&#xff0c;数据链路层&#xff0c;网络层&#xff0c;传输层&#xff0c;会话层&#xff0c;表示层和应用层 一.物理层&#xff08;Physical Layer&#xff09; 物理层位于 OSI/RM 参考模型的最底层&#xff0c;为数…

6.18、Java初级异常

1. 异常概述 1.1 什么是生活的异常 男主角小明每天开车上班&#xff0c;正常车程 1 小时。但是&#xff0c;可能会出现意外&#xff0c;出现意外&#xff0c;即为异常情况。我们会做相应的处理。如果不处理&#xff0c;到不了公司。 处理完了&#xff0c;就可以正常开车去公司…

浪涌保护器的标准和应用领域综合方案

浪涌保护器是一种用于防止电力系统或电子设备受到雷击或其他暂态过电压的损坏的装置。根据国家标准GB/T 18802.11-20201&#xff0c;低压电涌保护器 (SPD) 应符合IEC 61643-11:2011的性能要求和试验方法。浪涌保护器的产品参数包括&#xff1a;额定工作电压、最大连续工作电压、…

【kubernetes系列】kubernetes之kube-proxy的工作模式

概述 从kubernetes最早开始&#xff0c;kube-proxy到现在总共支持三种模式&#xff0c;在v1.8之前我们使用的是iptables 以及 userspace两种模式&#xff0c;iptables 模式从 v1.2 版本开始引入并作为kube-proxy 默认的操作模式。在kubernetes 1.8之后引入了ipvs模式&#xff…

!!!已解决: Linux操作系统登录,输入正确账号密码显示却显示:Sorry, that didn‘t work. Please try again.

&#xff01;&#xff01;&#xff01;已解决&#xff1a; Linux操作系统登录&#xff0c;明明输入密码正确却显示&#xff1a;Sorry, that didn’t work. Please try again. 先给大家复现一下我的问题&#xff1a; 为什么出现这个问题&#xff1f;&#xff1f;&#xff1f; …

知识梳理for CDGA/CDGP——第九章 ​文件和内容管理

第九章在CDGA分值占比较少&#xff0c;CDGP不考核&#xff0c;主要考点包括&#xff1a;定义、目标、原则、活动、工具、度量指标等基本概念、记住精心管理档案特点、GARP原则等。因此本章建议不需要花大量时间研究&#xff0c;熟悉历史真题&#xff0c;聚焦关键考点即可&#…

Leetcode-每日一题【1290. 二进制链表转整数】

题目 给你一个单链表的引用结点 head。链表中每个结点的值不是 0 就是 1。已知此链表是一个整数数字的二进制表示形式。 请你返回该链表所表示数字的 十进制值 。 示例 1&#xff1a; 输入&#xff1a;head [1,0,1]输出&#xff1a;5解释&#xff1a;二进制数 (101) 转化为…

没有BuildConfig

Android Gradle 插件8.0.0&#xff08;2023年4月&#xff09; 刚刚发现&#xff0c;新创建的一个Android项目&#xff0c;成功运行到手机上了&#xff0c;然后在代码中想使用一下BuildConfig这个类&#xff0c;发现没有&#xff0c;按以前的经验&#xff0c;项目刚创建时Build…

优雅码住!ChatGPT的五大开源替代方案

自去年11月发布以来&#xff0c;ChatGPT吸引了全球各行业人士的注意力和想象力。人们将它用于各种任务和应用程序&#xff0c;而且它有可能改变流行的应用程序并创建新的应用程序。 但ChatGPT也引发了微软和谷歌等科技巨头之间的人工智能竞赛&#xff0c;使得该行业在大型语言模…

Docker服务编排之Docker Compose的使用

Docker服务编排 概念&#xff1a;按照一定的业务规则批量的管理容器 微服务架构的应用系统中一般包含很多微服务&#xff0c;一个微服务中又包含很多的实例&#xff0c;每个微服务都要手动管理&#xff0c;维护的工作量很大。 拉去镜像&#xff0c;创建多个容器&#xff0c;分…

使用YOLOv5实现实时目标检测结果保存

本文将分享保存实时目标检测结果的方法&#xff0c;包括将目标信息逐帧保存到.txt文件中、逐帧输出检测结果图片、以及如何保存所有检测图片&#xff08;包括视野中无目标的帧&#xff09;。 目录 0.准备 1.目标信息保存 2.检测图片保存 3.保存所有帧 0.准备 本文以单摄像…

基于卷积变分自动编码器的3D数据处理与重建【CVAE】

在这个项目中&#xff0c;我们将学习如何使用卷积变分自动编码器 (CVAE) 来处理和重建 3D 湍流数据。 我们使用计算流体动力学 (CFD) 方法生成 3D 湍流立方体&#xff0c;每个 3D 立方体沿着三个速度分量携带物理信息&#xff08;与图像数据类似&#xff0c;被视为单独的通道&…

elk中kibana使用

1.前言 kibana是一款作为elasticsearch可视化的一款软件&#xff0c;将elasticsearch中的数据以可视化的状态展现出来&#xff0c;kibana也提供了查询、统计、修改索引等功能 2.kibana使用 索引管理 在索引管理中&#xff0c;可以看到所有索引的状态、运行状况、主分片、副本…

76-基于51单片机家庭红外人体检测震动报警系统(程序+原理图+元件清单全套资料)...

资料编号&#xff1a;076 功能介绍&#xff1a;采用51单片机作为主控CPU&#xff0c;采用红外接触传感器采集当前是否有人&#xff0c;采用震动传感器采集当前是否有震动&#xff0c;起到家庭防盗效果&#xff0c;采用按键设置当前布防/撤防状态&#xff0c;布防状态下&#xf…

Binder系列--获取ServiceManager

获取ServiceManager hongxi.zhu 2023-7-1 以SurfaceFlinger为例&#xff0c;分析客户端进程如何获取ServiceManager代理服务对象 主要流程 SurfaceFlinger中获取SM服务 frameworks/native/services/surfaceflinger/main_surfaceflinger.cpp // publish surface flingersp<…

适合初中生用的台灯有哪些?这样的台灯最适合学生!

对于学生而言台灯主要的点就是能够护眼、缓解眼睛疲劳&#xff0c;因为学生需要长时间的学习和用眼而且可以休息放松的时间比较少&#xff0c;导致眼睛过度疲劳&#xff0c;这也是为什么这么多中小学生近视的原因。那么我们应该怎么样选好一款台灯呢&#xff1f; 要想台灯能护眼…