Linux内存管理 | 三、虚拟地址空间管理

news2025/1/17 3:04:35
img
我的圈子: 高级工程师聚集地
我是董哥,高级嵌入式软件开发工程师,从事嵌入式Linux驱动开发和系统开发,曾就职于世界500强企业!
创作理念:专注分享高质量嵌入式文章,让大家读有所得!
img
img

上一节,我们主要了解了虚拟内存空间的布局情况,趁热打铁,我们直接从源代码的视角,来看一下Linux内核是如何管理虚拟内存空间的。

废话不多说,直接开始!

1、用户态空间管理

读完上一节我们知道,用户态的布局情况如下:

image-20231005160139650

我们运行的可执行程序,被加载进内存后,会作为一个进程存在,这个进程Linux内核会将其抽象成一个结构体。没错,它就是task_struct

1.1 task_struct结构体

task_struct结构体是进程的抽象,进程所涉及到的内容非常多,下面只列举出一些重要的数据结构,方面理解。

// include/linux/sched.h
struct task_struct {
	...
	pid_t				pid;		//	进程PID
	pid_t				tgid;		//	线程PID
    struct files_struct	*files;		//  进程打开的文件信息
	struct mm_struct	*mm;		//	进程虚拟内存空间的内存描述符
	...
}

如上,进程抽象为task_struct结构体,通过mm_struct结构体来管理虚拟内存空间。

1.2 mm_struct结构体

每个进程都有唯一的 mm_struct 结构体,也就是前边提到的每个进程的虚拟地址空间都是独立,互不干扰的。

mm_struct的结构体如下:

//	include/linux/mm_types.h
struct mm_struct {
	...
    struct {
        ...
        
		unsigned long task_size;	/* size of task vm space */
    	...
        
        unsigned long mmap_base;	/* base of mmap area */
        unsigned long total_vm;		/* Total pages mapped */
        unsigned long locked_vm;	/* Pages that have PG_mlocked set */
        unsigned long pinned_vm;	/* Refcount permanently increased */
        unsigned long data_vm;		/* VM_WRITE & ~VM_SHARED & ~VM_STACK */
        unsigned long exec_vm;		/* VM_EXEC & ~VM_WRITE & ~VM_STACK */
        unsigned long stack_vm;		/* VM_STACK */
        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;        
        ...
        
        struct vm_area_struct *mmap;		/* list of VMAs */
		struct rb_root mm_rb;
        ...
        
        
    }__randomize_layout;
    ...
}

 

1.3 内核态和用户态的划分

mm_struct里面定义的task_size变量,就是用来划分虚拟内存的用户空间和内核空间的。

unsigned long task_size;

task_size也就是两者的分界线,下面我们看下task_size是如何被赋值的。

 

当我们执行一个新的进程的时候,Linux内核会执行load_elf_binaryAPI接口,进而调用setup_new_exec函数来实现新进程的创建。

setup_new_exec函数中,会执行

current->mm->task_size = TASK_SIZE;

这个TASK_SIZE就是我们设置的内核空间地址和用户空间地址的分界线,由我们自定义配置。

#ifdef CONFIG_X86_32
/*
 * User space process size: 3GB (default).
 */
#define TASK_SIZE			PAGE_OFFSET
#define TASK_SIZE_MAX		TASK_SIZE
/*
config PAGE_OFFSET
        hex
        default 0xC0000000
        depends on X86_32
*/
#else
/*
 * User space process size. 47bits minus one guard page.
*/
#define TASK_SIZE_MAX	((1UL << 47) - PAGE_SIZE)
#define TASK_SIZE		(test_thread_flag(TIF_ADDR32) ? \
					IA32_PAGE_OFFSET : TASK_SIZE_MAX)
......

这里我们只需要知道TASK_SIZE默认值3为PAGE_OFFSET,并且默认为0xC0000000为分界线的,即用户空间3GB,内核空间1GB;当然这个可以由我们动态配置,可以配置PAGE_OFFSET0x80000000,即用户空间和内核空间均为2GB,取决于我们的应用场合,当你看到与我们讲解不同时,也不用大惊小怪。

以上,表达的概念很简单,如下图:

image.png

 

1.4 位置信息描述

我们知道用户态内存空间分为几个区域:代码段、数据段、BSS段、堆、文件映射和匿名映射区、栈等几个部分,同样在mm_struct中,定义了这些区域的统计信息和位置。

unsigned long mmap_base;	/* base of mmap area */
unsigned long total_vm;		/* Total pages mapped */
unsigned long locked_vm;	/* Pages that have PG_mlocked set */
unsigned long pinned_vm;	/* Refcount permanently increased */
unsigned long data_vm;		/* VM_WRITE & ~VM_SHARED & ~VM_STACK */
unsigned long exec_vm;		/* VM_EXEC & ~VM_WRITE & ~VM_STACK */
unsigned long stack_vm;		/* VM_STACK */
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;
  • total_vm:总映射页面的数目。(这么大的虚拟内存空间,不可能全部映射到真实的物理内存,都是按需映射的,这里表示当前映射的页面总数目)

由于物理内存比较小,当内存吃紧的时候,就会发生换入换出的操作,即将暂时不用的页面换出到硬盘上,有的页面比较重要,不能换出。

  • locked_vm:被锁定不能换出的页面
  • pinned_vm :不能换出、也不能移动的页面
  • data_vm:存放数据页的页的数目
  • exec_vm:存放可执行文件的页的数目
  • stack_vm:存放堆栈信息页的数目
  • start_codeend_code:表示可执行代码开始和结束的位置
  • start_dataend_data:表示已初始化数据的开始位置和结束位置
  • start_brkbrk:堆的起始地址,结束地址
  • start_stack:是栈的起始位置,在 RBP 寄存器中存储,栈的结束位置也就是栈顶指针,在 RSP 寄存器中存储。在栈中内存地址的增长方向也是由高地址向低地址增长。
  • arg_startarg_end:参数列表的起始位置和结束位置
  • env_startenv_end:环境变量的起始位置和结束位置

 

整体的布局情况如下

image.png

 

1.5 区域属性描述

尽管已经有了一些变量来描述每一个段的信息,但是Linux内核在mm_struct结构体里面,还有一个专门的数据结构vm_area_struct来管理每个区域的属性。

struct vm_area_struct *mmap;		/* list of VMAs */
struct rb_root mm_rb;

mmap:为一个单链表,将所有的区域串联起来

mm_rb:为一个红黑树,方便查找和修改内存区域。

 

下面看一下vm_area_struct数据结构:

struct vm_area_struct {
	/* The first cache line has the info for VMA tree walking. */
	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, *vm_prev;
	struct rb_node vm_rb;
	struct mm_struct *vm_mm;	/* The address space we belong to. */
	struct list_head anon_vma_chain; /* Serialized by mmap_sem &
					  * page_table_lock */
	struct anon_vma *anon_vma;	/* Serialized by page_table_lock */
	/* Function pointers to deal with this struct. */
	const struct vm_operations_struct *vm_ops;
	struct file * vm_file;		/* File we map to (can be NULL). */
	void * vm_private_data;		/* was vm_pte (shared mem) */
} __randomize_layout;
  • vm_startvm_end:为该区域在用户空间的起始和结束地址

  • vm_nextvm_prev:将该区域添加到链表上,便于管理。

  • vm_rb:将这个区域放到红黑树上

  • vm_ops:对该区域可以进行的内存操作

  • anon_vma:匿名映射

  • vm_file:文件映射

 

用户态空间的每个区域都由该结构体来管理,最终形成下面的这个结构:

image-20231008184824770

 

顺便介绍一下 我的圈子:高级工程师聚集地,期待大家的加入。

2、内核态空间管理

上面,我们从源码角度了解了用户态空间管理,下面我们看内核态空间管理。

回顾一下,我们内核态的布局情况是怎么样的呢,还记得吗?

image-20231005155942462

我们要知道:

  1. 内核态的虚拟空间和任何一个进程都没有关系,所有的进程看到的内核态虚拟空间都是一样的。
  2. 在内核态,我们直接操作的依旧是虚拟地址,而非物理地址
  3. 不同CPU结构下,内核态空间的布局格式是不变的,但是大小会有所调整,比如ARMX86的大小空间有所不同。

 

内核态空间管理并不像用户态那样使用结构体来统一管理,而是直接使用宏来定义每个区域的分界线,

下面我们以x86架构来分析内核态空间的管理

2.1 分界线定义

/*
 * User space process size: 3GB (default).
 */
#define TASK_SIZE		PAGE_OFFSET

/* PAGE_OFFSET - the virtual address of the start of the kernel image */
#define PAGE_OFFSET		((unsigned long)__PAGE_OFFSET)

#define __PAGE_OFFSET		__PAGE_OFFSET_BASE

#define __PAGE_OFFSET_BASE	_AC(CONFIG_PAGE_OFFSET, UL)

config PAGE_OFFSET
	hex
	default 0xB0000000 if VMSPLIT_3G_OPT
	default 0x80000000 if VMSPLIT_2G
	default 0x78000000 if VMSPLIT_2G_OPT
	default 0x40000000 if VMSPLIT_1G
	default 0xC0000000
	depends on X86_32

TASK_SIZE:内核态空间与用户态空间的分界线

PAGE_OFFSET:该宏表示内核镜像起始的虚拟地址。

CONFIG_PAGE_OFFSET:这个宏定义的值,根据实际情况自行设定,默认为0XC0000000,可以设置为0X80000000等。

以上,TASK_SIZE就被定义为0XC0000000作为用户态空间和内核态空间的分界线,将4G虚拟内存分配为3G/1G结构。

image-20231010072937276

2.2 直接映射区定义

直接映射区是定义在PAGE_OFFSEThigh_memory之间的区域。

  • PAGE_OFFSET:表示内核镜像的起始地址,上文已经说明。
  • high_memory也是表示的就是896M这个值,表示高端内存的分界线。

顺便说明以下,TASK_SIZEPAGE_OFFSET在不同架构下是不同的,在ARM架构下,两者并不相等,本文以X86架构为例

image-20231010073949813

2.3 安全保护区定义

系统会在high_memoryVMALLOC_START之间预留8M的安全保护区,防止访问越界。

VMALLOC_OFFSET表示的是内核动态映射区的偏移,也就是所谓的安全保护区。

#define VMALLOC_START		(((unsigned long)high_memory + VMALLOC_OFFSET) & ~(VMALLOC_OFFSET-1))

#define VMALLOC_OFFSET		(8*1024*1024)

可以很清楚的看到VMALLOC_OFFSET定义了8M的空间,VMALLOC_STARThigh_memory基础上,偏移了VMALLOC_OFFSET 8M空间大小作为安全保护区,以防越界访问。

image-20231010074810831

2.3 动态映射区定义

VMALLOC_STARTVMALLOC_END之间称为内核动态映射区。

和用户态进程使用 malloc 申请内存一样,在这块动态映射区内核是使用 vmalloc 进行内存分配。

#define VMALLOC_START		(((unsigned long)high_memory + VMALLOC_OFFSET) & ~(VMALLOC_OFFSET-1))

#ifdef CONFIG_HIGHMEM
# define VMALLOC_END	(PKMAP_BASE - 2 * PAGE_SIZE)
#else
# define VMALLOC_END	(LDT_BASE_ADDR - 2 * PAGE_SIZE)
#endif

PKMAP_BASE:是永久映射区的起始地址。

VMALLOC_END:在永久映射区的起始地址下,偏移2个PAGE_SIZE作为安全保护区。

image-20231010075717944

2.4 永久映射区定义

PKMAP_BASEFIXADDR_START 的空间称为永久内核映射,在内核的这段虚拟地址空间中允许建立与物理高端内存的长期映射关系。

比如内核通过 alloc_pages() 函数在物理内存的高端内存中申请获取到的物理内存页,这些物理内存页可以通过调用 kmap 映射到永久映射区中。

#define PKMAP_BASE		\
	((LDT_BASE_ADDR - PAGE_SIZE) & PMD_MASK)
	
#define LDT_BASE_ADDR		\
	((CPU_ENTRY_AREA_BASE - PAGE_SIZE) & PMD_MASK)

#define CPU_ENTRY_AREA_BASE						\
	((FIXADDR_TOT_START - PAGE_SIZE * (CPU_ENTRY_AREA_PAGES + 1))   \
	 & PMD_MASK)

#define FIXADDR_TOT_START	(FIXADDR_TOP - FIXADDR_TOT_SIZE)

#define FIXADDR_TOP	((unsigned long)__FIXADDR_TOP)

#define FIXADDR_TOT_SIZE	(__end_of_fixed_addresses << PAGE_SHIFT)

unsigned long __FIXADDR_TOP = 0xfffff000;

#define PMD_MASK	(~(PMD_SIZE - 1))

#define PAGE_SHIFT		12
#define PAGE_SIZE		(_AC(1,UL) << PAGE_SHIFT)

#define CPU_ENTRY_AREA_PAGES	(NR_CPUS * 40)

#define FIXADDR_START		(FIXADDR_TOP - FIXADDR_SIZE)

  • PKMAP_BASE:是永久映射区的起始地址,它经过一系列的计算得到,具体可以看上面的宏定义,我们大概了解就行了,不同体系结构的定义位置还不一样。
  • FIXADDR_START:是固定映射区的起始地址,也是永久映射区的结束地址。

image-20231011194447450

2.5 固定映射区定义

FIXADDR_STARTFIXADDR_TOP的空间称为固定映射区,主要用于满足特殊的需求。

#define FIXADDR_TOP	((unsigned long)__FIXADDR_TOP)

unsigned long __FIXADDR_TOP = 0xfffff000;

固定映射区中的虚拟地址,可以自由映射到物理内存的高端地址空间上,特点是其映射的虚拟地址是不变的,物理地址是可以改变的。

image-20231011195006927

2.6 临时映射区定义

最后FIXADDR_TOP0xFFFFFFFF之间的区域称为临时映射区。

它主要用来做什么呢,网上举的一个例子,大家参考以下。

假设用户态的进程要映射一个文件到内存中,先要映射用户态进程空间的一段虚拟地址到物理内存,然后将文件内容写入这个物理内存供用户态进程访问。

给用户态进程分配物理内存页可以通过 alloc_pages(),分配完毕后,按说将用户态进程虚拟地址和物理内存的映射关系放在用户态进程的页表中,就完事大吉了。这个时候,用户态进程可以通过用户态的虚拟地址,也即 0 至 3G 的部分,经过页表映射后访问物理内存,并不需要内核态的虚拟地址里面也划出一块来,映射到这个物理内存页。

但是如果要把文件内容写入物理内存,这件事情要内核来干了,这就只好通过 kmap_atomic 做一个临时映射,写入物理内存完毕后,再 kunmap_atomic 来解映射即可。

image-20231011195939077

以上,就是内核态空间的布局以及管理。

3、总结

该篇文章,主要从源码角度来了解用户态空间和内核态空间是如何管理的,挪用大佬的一个图片,结合上面所讲的,相信很快就能茅塞顿开。

image-20231011200638903

点赞+关注,永远不迷路

img
欢迎关注 公号&星球【嵌入式艺术】,董哥原创!

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

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

相关文章

解决微信小程序导入项目报错: [app.json文件内容错误]app.json未找到

目录 场景描述 原因分析 解决方法 场景描述 使用微信开发者工具导入项目后&#xff0c;打开控制台&#xff0c;出现报错提示&#xff1a;[app.json文件内容错误]app.json 未找到&#xff0c;如下图&#xff1a; 原因分析 一级文件目录里确实找不到app.json文件&#xff0c…

上市公司专利申请、创新绩效测算(2000-2022年)

参照王治等&#xff08;2022&#xff09;的做法&#xff0c;团队对上市公司-创新绩效进行测算。应用企业当年的专利申请数量&#xff08;Apply&#xff09;和企业当年的发明专利申请数量&#xff08;IApply&#xff09;衡量企业创新绩效 一、数据介绍 数据名称&#xff1a;上市…

如何处理前端路由懒加载?

聚沙成塔每天进步一点点 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 欢迎来到前端入门之旅&#xff01;感兴趣的可以订阅本专栏哦&#xff01;这个专栏是为那些对Web开发感兴趣、刚刚踏入前端领域的朋友们量身打造的。无论你是完全的新手还是有一些基础的开发…

C++初阶--C++入门(1)

文章目录 C语言与C命名空间命名空间的定义和使用 C的输入输出缺省参数函数重载引用赋值与引用引用在参数上的使用以及注意事项函数返回值的引用引用与值的时间效率比较常引用 C语言与C 很多初学者都会把这两门语言进行混淆&#xff0c;但其实这是两种不同的语言&#xff0c;C相…

零基础学习CSS

01-CSS初体验 层叠样式表 (Cascading Style Sheets&#xff0c;缩写为 CSS&#xff09;&#xff0c;是一种 样式表 语言&#xff0c;用来描述 HTML 文档的呈现&#xff08;美化内容&#xff09;。 书写位置&#xff1a;title 标签下方添加 style 双标签&#xff0c;style 标签…

2023网络工程毕业设计选题推荐 - 计算机毕业设计题目大全

文章目录 0 简介1 如何选题2 最新网络工程选题2.1 Java web - SSM 系统2.2 大数据方向2.3 人工智能方向2.4 其他方向 4 最后 0 简介 学长搜集分享最新的网络工程专业毕设毕设选题&#xff0c;难度适中&#xff0c;适合作为毕业设计&#xff0c;大家参考。 学长整理的题目标准…

从文字到视频:借助ChatGPT与剪映轻松生成高质量视频(文末送书)

&#x1f935;‍♂️ 个人主页&#xff1a;艾派森的个人主页 ✍&#x1f3fb;作者简介&#xff1a;Python学习者 &#x1f40b; 希望大家多多支持&#xff0c;我们一起进步&#xff01;&#x1f604; 如果文章对你有帮助的话&#xff0c; 欢迎评论 &#x1f4ac;点赞&#x1f4…

ChatGPT AIGC 实现Excel 交叉查找 Index+match 函数

行与列交叉多条件查找需求如下: 这个需求要使用Excel中最经典的组合函数Index+match函数。 函数公式可以交给ChatGPT AIGC来实现。 Prompt: 有一个表格A列为品牌,B列为月份,C列为销量,61行数据,请写出Excel函数公式根据E3单元格的品牌与F2单元格的月份查找对应的销量,…

合规合规,合规法规的挑战与解决方案

在当前数据安全威胁日益加剧的时代&#xff0c;无论是来自企业内部还是外部&#xff0c;您都需要采取积极主动的态度。政府方面也希望出于公民数据安全的考虑&#xff0c;确保对企业的IT操作进行监管。为了实现这一目标&#xff0c;政府或主管法定机构发布了关于企业IT操作的法…

《golang设计模式》第三部分·行为型模式-01-责任链模式(Chain of Responsibility)

文章目录 1 概念1.1 角色1.2 类图 2. 代码示例2.1 设计2.2 代码2.3 类图 1 概念 责任链&#xff08;Chain of Responsibility&#xff09;是指将客户端请求处理的不同职责对象组成请求处理链。 客户端只需要将请求交付到该链上&#xff0c;而不需要关心链上含有哪些对象。请求…

5款CSS3选项框单选按钮样式(走过路过不要错过)

5款CSS3选项框单选按钮样式是一款创意好看的选项单选按钮样式特效。 样式如下&#xff1a; 文章顶部就是源码&#xff0c;如果下载不了&#xff08;如果被弄成收费or要VPI才能下载&#xff09;私一下我&#xff0c;第一次弄csdn的资源绑定&#xff0c;不行我重新弄成网盘的 百…

【QT开发笔记-基础篇】| 第四章 事件QEvent | 4.7 拖放事件

本节对应的视频讲解&#xff1a;B_站_链_接 【QT开发笔记-基础篇】 第4章 事件 4.7 拖动事件 本章要实现的整体效果如下&#xff1a; QEvent::DragEnter ​ 当拖动文件进入到窗口/控件中时&#xff0c;触发该事件&#xff0c;它对应的子类是 QDragEnterEvent QEvent::DragLe…

IDEA2023.1版本新建Web项目并配置本地Tomcat

IDEA2023.1版本新建Web项目并配置本地Tomcat 一、新建Web项目 一、新建Web项目 由于我最初是新建了一个空项目作为工作空间的&#xff0c;所以这里选择直接新建module&#xff0c;如下所示。&#xff08;这里使用的是idea的newUI&#xff09; 新建module&#xff0c;输入信息…

基于RuoYi-Flowable-Plus的若依ruoyi-nbcio支持自定义业务表单流程(五)

更多ruoyi-nbcio功能请看演示系统 gitee源代码地址 前后端代码&#xff1a; https://gitee.com/nbacheng/ruoyi-nbcio 演示地址&#xff1a;RuoYi-Nbcio后台管理系统 今天讲一下wf_demo表单的一些修改 1、demo的实现类修改如下&#xff1a; 主要是增加一个服务名称&#…

学信息系统项目管理师第4版系列30_信息系统管理

1. 管理方法 1.1. 技术底座构成了几乎所有业务模式的支柱 1.2. 信息系统是为组织用来生产和管理信息&#xff08;数据&#xff09;的技术&#xff08;“什么”&#xff09;、人员&#xff08;“谁”&#xff09;和过程&#xff08;“如何”&#xff09;的组合 1.3. 信息系统…

【nginx学习笔记】

1.正向代理&#xff1a;代理的是客户端&#xff0c;一般有明确的访问对象 比如&#xff1a;我现在通过v-p-n去访问YouTube&#xff0c;那么就是正向代理。 2.反向代理&#xff1a;代理的是服务器 最常见的就是web中&#xff0c;nginx去代理一群后端的服务器。 3.负载均衡&…

mac虚拟机,无法从apple store下载软件

问题&#xff1a;vmware版本为16pro&#xff0c;mac版本为10.14.6&#xff0c;网络可以正常访问互联网&#xff0c;apple id也正常登录了&#xff0c;但是从apple store下载软件&#xff0c;转了一会圈&#xff0c;就停掉了。 解决&#xff1a;后面使用了一个网上看到的方法&a…

ESDA in PySal (4):shape-measures:形状测量

ESDA in PySal (4)&#xff1a;shape-measures&#xff1a;形状测量 1.Measures of shape esda.shape 模块提供文献中使用的统计数据来测量多边形的结构和规则性。 这些测量值从非常简单&#xff08;例如长宽差&#xff09;到非常复杂&#xff08;例如归一化转动惯量&#xf…

以太网诊断协议DoIP(Ethernet Diagnostic Protocol DoIP)

系列文章目录 C技能系列 Linux通信架构系列 C高性能优化编程系列 深入理解软件架构设计系列 高级C并发线程编程 设计模式系列 期待你的关注哦&#xff01;&#xff01;&#xff01; 现在的一切都是为将来的梦想编织翅膀&#xff0c;让梦想在现实中展翅高飞。 Now everythi…

【4】c++11新特性(稳定性和兼容性)—>final关键字

c中增加了final关键字来限制某个类不能被继承&#xff0c;或者某个虚函数不能被重写。如果使用final修饰函数&#xff0c;只能修饰虚函数&#xff0c;并且放在类或者函数的后面。 修饰函数 #include <iostream> using namespace std;class Base { public:virtual void t…