linux内核源码分析之虚拟内存

news2025/1/13 7:50:22

 

目录

虚拟地址空间划分(用户空间)

32位系统虚拟地址空间分配

64位系统虚拟地址空间分配

内存管理

内核布局虚拟地址空间

虚拟内存区域在内核中组织

内存访问权限

调用malloc 申请内存,

虚拟地址空间布局(内核)

直接映射区:范围地址3G-3G+896M

高端内存896M以上,ZONE_HIGHMEM,

虚拟内存 vmalloc 动态映射区

虚拟内存 永久映射区

虚拟内存 固定映射区

临时映射区:



虚拟地址空间划分(用户空间)


1:代码段
存放二进制文件机器码的虚拟内存


2:数据段
在代码中被我们制定了初始值的全局变量和静态变量在虚拟内存中的存储区
没有指定初始值的全局变量和静态变量在虚拟内存空间中的存储区域叫做BSS段;这些未初始化的全局变量被加载进内存知乎会被初始化为0.

上面介绍的全局变量和静态变量都是在编译期间确定的;

3:堆
程序运行时申请的内存,所以在虚拟内存中需要一块区域存放动态申请的内存,叫做堆。

4:文件映射与匿名映射区
动态链接库.so glibc 这些动态链接库也有自己对应的代码段,数据段BSS段 也需要被加载内存
文件映射mmap 映射的内存
5、栈
调用函数以及过程中的局部变量和函数参数也需要一块区域保存。

 

32位系统虚拟地址空间分配


寻址范围2^32 ,虚拟内存空间4GB 0x0000 0000 - 0xFFFF FFFF

其中用户态虚拟地址空间为3GB,虚拟内存范围0x0000 0000 - 0xC000 000
内核态地址空间1GB ,虚拟内存范围0XC000 000 -0xFFFF FFFF

用户态地址不是从0x0000 0000 开始 而是从0x08048000地址开始,因为大多数操作系统中数值比较小被认为是一个不合法的地址,不允许访问小地址。




内核使用start_brk标识堆的起始地址,brk标识当前堆的结束位置。当堆申请新的内存空间时,只需要将brk指针增加对应的大小,回收地址时减少对应的大小即可。如malloc申请内存就是通过改变brk位置实现。

文件映射区域的地址增长方向 从高地址向低地址增长;

栈空间的地址增长方向从高地址向低地址增长,每次进行申请新的栈地址时,其地址在减少。Start_stack标识起始地址,RSP 寄存器保存栈顶指针stack pointer,RBP寄存器中保存栈基地址。

64位系统虚拟地址空间分配

2^64 虚拟内存空间为16EB
内核空间与用户空间有空洞 叫做 canonical address空洞

内存管理


内核的结构体 task_struct -> mm_struct

在do_fork中 copy_mm 完成子进程虚拟内存空间mm_struct结构的创建和初始化
dup_mm函数将父进程的虚拟内存空间以及相关页表拷贝到子进程的mm_struct中,再赋值给task_struct

如果设置CLONE_VM,将父进程的虚拟内存空间以及相关页表直接赋值给子进程,父子进程共享

内核线程和用户线程区别,内核线程没有mm_struct,内核线程之间调度不涉及地址空间切换。

内核和用户地址空间划分 分界线

TASK_SIZE 大小计算 :task_size_max 1 左移47位 减 PAGE_SIZE (默认为4k) 就是
0x00007FFFFFFFF000,共 128T


内核空间的起始地址0xFFFF 8000 0000 0000

struct mm_struct {
    unsigned long task_size; /* size of task vm space */
...
}

task_size 定义了用户态地址空间与内核态地址空间之间的分界线

  • 32 位系统中用户地址空间和内核地址空间的分界线在 0xC000 000 地址处,task_size 为 0xC000 000。
#define TASK_SIZE  __PAGE_OFFSET
  • 64 位系统中用户地址空间和内核地址空间的分界线在 0x0000 7FFF FFFF F000 地址处,task_size 为0x0000 7FFF FFFF F000 


/arch/x86/include/asm/page_64_types.h

#define TASK_SIZE  (test_thread_flag(TIF_ADDR32) ? \
     IA32_PAGE_OFFSET : TASK_SIZE_MAX)

#define TASK_SIZE_MAX  task_size_max()

#define task_size_max()  ((_AC(1,UL) << __VIRTUAL_MASK_SHIFT) - PAGE_SIZE)

#define __VIRTUAL_MASK_SHIFT 47
 

计算 TASK_SIZ与页大小有关,在 task_size_max() 的计算逻辑中 1 左移 47 位得到的地址是 0x0000800000000000,然后减去一个 PAGE_SIZE (默认为 4K),就是 0x00007FFFFFFFF000,共 128T。
所以在 64 位系统中的 TASK_SIZE 为 0x00007FFFFFFFF000

PAGE_SIZE 定义在 /arch/x86/include/asm/page_types.h文件中

/* PAGE_SHIFT determines the page size */
#define PAGE_SHIFT  12
#define PAGE_SIZE  (_AC(1,UL) << PAGE_SHIFT)
 

内核布局虚拟地址空间


task_struct->mm_struct

struct mm_struct {
    unsigned long task_size;    /* size of task vm space */
    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 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 */

       ..............
}
  • task_size 来划分用户态虚拟内存和内核态虚拟内存空间。
  • total_vm 表示进程虚拟内存空间中与物理内存映射的页的总数,只是将虚拟内存与物理内存建立管理关系,并不代表真正的分配物理内存。当内存吃紧的时候,有些页可以换出到硬盘上,有些不能换出。
  • Locked_vm 就是被锁定不能换出的内存页总数;
  • pinned_vm表示既不能换出,也不能移动的内存页总数
  • data_vm 表示数据段中映射的内存页数目,
  • exec_vm是代码段中存放可执行文件内存的页数目。
  • stack_vm是栈中映射内存数目

虚拟内存区域在内核中组织

struct vm_area_struct, 每个结构体对应于虚拟内存空间中的唯一虚拟内存区域VMA [vm_start,vm_end)

内存访问权限

vm_flags访问权限
VM_READ可读
VM_WRITE可写
VM_EXEC可执行
VM_SHARD可多进程之间共享
VM_IO可映射至设备 IO 空间
VM_RESERVED内存区域不可被换出
VM_SEQ_READ内存区域可能被顺序访问
VM_RAND_READ内存区域可能被随机访问

  • VM_SHARD 用于指定这块虚拟内存区域映射的物理内存是否可以在多进程之间共享,以便完成进程间通讯。设置这个值即为 mmap 的共享映射,不设置的话则为私有映射。这个等后面我们讲到 mmap 的相关实现时还会再次提起。
  • VM_IO 的设置表示这块虚拟内存区域可以映射至设备 IO 空间中。通常在设备驱动程序执行 mmap 进行 IO 空间映射时才会被设置。
  • VM_RESERVED 的设置表示在内存紧张的时候,这块虚拟内存区域非常重要,不能被换出到磁盘中。
  • VM_SEQ_READ 的设置用来暗示内核,应用程序对这块虚拟内存区域的读取是会采用顺序读的方式进行,内核会根据实际情况决定预读后续的内存页数,以便加快下次顺序访问速度。
  • VM_RAND_READ 的设置会暗示内核,应用程序会对这块虚拟内存区域进行随机读取,内核则会根据实际情况减少预读的内存页数甚至停止预读。

我们可以通过 posix_fadvise,madvise 系统调用来暗示内核是否对相关内存区域进行顺序读取或者随机读取。

虚拟内存映射可以映射到文件上,也可以映射到物理内存上。

映射到物理内存上称之为 匿名映射,映射到文件中 称之为 文件映射。

调用malloc 申请内存,

如果申请的是小块内存(低于128k),则会调用do_brk(),通过调整堆中的brk指针大小来增加或者回收堆内存;

如果申请的是大块内存(大于128k),调用mmap,在文件映射与匿名映射区域中创建一块VMA内存区域。这块映射区域用 struct anon_vma 表示。

当调用 mmap 进行文件映射时,vm_file 属性就用来关联被映射的文件。这样一来虚拟内存区域就与映射文件关联了起来。vm_pgoff 则表示映射进虚拟内存中的文件内容,在文件中的偏移。

  • 对虚拟内存区域操作 vm_ops
  • 虚拟内存在内核中的组织方式:双向链表与红黑树
  • 红黑树用于查找特定内存区域
  • 载入elf文件,在内核中完成这个映射过程的函数是 load_elf_binary 

虚拟地址空间布局(内核空间)

32位体系结构,1G虚拟内存空间

 

直接映射区:范围地址3G-3G+896M

这块连续的虚拟内存地址会映射到物理地址0~896M这块连续的物理内存上。

  • 前1M 已经在系统启动的时候被系统占用;
  • 1M之后的物理内存存放的是内核代码段,数据段,BSS段。(ELF文件)可以通过cat /proc/iomem命令查看具体物理内存布局情况。
  • 在使用fork创建进程时,会创建task_struct, mm_struct, vm_area_struct 这些数据结构会放在896M内存中,会被映射到内核虚拟地址,3G到3G+896M;
  • 进程创建好,在内核运行过程,涉及内核栈的分配,内核为每个进程分配一个固定大小的内核栈,也是在直接映射区;

物理内存直接映射区的前16M,ZONE_DMA:用于为DMA分配内存;

16M-896M,ZONE_NORMAL

高端内存896M以上,ZONE_HIGHMEM,

32位4G 高端内存区域4G-896M = 3200M,这块区域如何映射到内核虚拟内存中?

内核虚拟内存空间 1G - 896M = -128M,两者大小不一致不能通过直接映射

需要采用动态映射

虚拟内存 vmalloc 动态映射区

VMALLOC_START到VMALLOC_END之间的这块区域成为动态映射区,动态方式映射到物理内存中的高端内存。

用户使用malloc 申请内存,在内核中使用vmalloc 进行内存分配。vmalloc分配的内存在虚拟地址上是连续的,但是在物理内存上是不连续的。

虚拟内存 永久映射区

PKMAP_BASE到PKMAP_START之间的这段空间成为永久映射区。允许这段虚拟内存映射到物理高端内存长期映射关系。 如通过alloc_pages函数在物理内存高端区域申请到物理页,这些物理内存通过调用kmap映射到永久映射区中。

页数限制 PKMAP_BASE

虚拟内存 固定映射区

范围:FIXADDR_START 到 FIXMAP_TOP

此处虚拟地址可以自由映射到物理内存的高端地址上,虚拟地址是固定的,而被映射的物理地址是可以改变的。

作用:在内核启动过程中,有些模块需要使用虚拟内存并映射到指定的物理地址上,而且这些模块也没有办法等待完整的内存管理模块初始化后再进行地址映射。因此,内核固定分配了一些虚拟地址,这些地址有固定用途,使用该地址的模块在初始化的时候,将这些固定分配的虚拟地址映射到指定的物理地址上去。

临时映射区:

数据拷贝过程,kmap_atomic 创建映射;kunmap_atomic 解除映射

 

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

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

相关文章

动静态库生成使用

&#x1f525;&#x1f525; 欢迎来到小林的博客&#xff01;&#xff01;       &#x1f6f0;️博客主页&#xff1a;✈️林 子       &#x1f6f0;️博客专栏&#xff1a;✈️ Linux       &#x1f6f0;️社区 :✈️ 进步学堂       &#x1f6f0…

2000-2022年上市公司行业异质性数据(技术密集型、劳动密集型、资本密集型)(含原始数据和处理代码)

2000-2022年上市公司行业异质性数据&#xff08;技术密集型、劳动密集型、资本密集型&#xff09;&#xff08;含原始数据和处理代码&#xff09; 1、时间&#xff1a;2000-2022年 2、指标&#xff1a;股票代码、年份、股票简称、统计日期、行业名称、行业代码、成立日期、上…

React复习日志大纲

文章目录 创建项目启动项目项目目录说明调整项目src剩余目录01基本使用02 列表渲染03 条件渲染04 样式处理05 函数和类组件创建和渲染06 事件绑定07 事件对象e08 传递额外参数09 组件状态修改10 受控组件11 非受控组件12 组件通信父传子13 Props说明14 组件通信子传父15 组件通…

【已解决】您所使用的密钥ak有问题,不支持jsapi服务,可以访问该网址了解如何获取有效密钥。

您所使用的密钥ak有问题&#xff0c;不支持jsapi服务&#xff0c;可以访问该网址了解如何获取有效密钥。详情查看&#xff1a;http://lbsyun.baidu.com/apiconsole/key#。 问题 百度密钥过期 思路 注册成为开发者 如果还没注册百度地图api账号的&#xff0c;点击以后就进入…

LRU算法之我见

文章目录 一、LRU算法是什么&#xff1f;二、使用原理三、代码实现总结 一、LRU算法是什么&#xff1f; LRU算法又称最近最少使用算法&#xff0c;它是是大部分操作系统为最大化页面命中率而广泛采用的一种页面置换算法。是一种缓存淘汰策略&#xff0c;根据使用频率来淘汰无用…

华清远见第六课程day4作业

仿照string类&#xff0c;完成myString 类 #include <iostream> #include <cstring>using namespace std;class myString{ private:char *str;int size; public:myString():size(10){str new char[size];strcpy(str,"");}myString(const char*s){size …

mysql leetcode打题记录

文章目录 完成度基本语法高级语法连接日期 函数编写函数聚合函数 因为上过的数据库课实在太水了&#xff0c;所以打算先在菜鸟教程/CSDN/leetcode先学一下基本语法&#xff0c;然后去做Stanford数据库原理的课程CS145。 小目标&#xff1a;把leetcode上不用钱的mysql的题先做一…

RabbitMQ死信队列与延迟队列

死信队列 死信队列的定义 死信队列&#xff08;Dead Letter Queue&#xff09;&#xff1a; 死信队列是一种特殊的队列&#xff0c;用于存放不能被消费的消息。当消息满足某些条件时&#xff0c;比如消息过期、消息被拒绝消费或消息达到最大重试次数等&#xff0c;RabbitMQ 会…

实现数组去重的七种方法

实现数组去重的 7 种方式 1. 方法一&#xff1a;利用两层循环数组的splice方法 通过两层循环对数组元素进行逐一比较&#xff0c;然后通过splice方法来删除重复的元素。此方法对NaN是无法进行去重的&#xff0c;因为进行比较时NaN ! NaN。 let arr [1, 2, 2, abc, abc, true,…

Mac系统 AndroidStudio Missing essential plugin:org.jetbrains.android报错

打开Android Studio,提示 Missing essential plugin:org.jetbrains.android错误&#xff0c;产生的原因是Kotlin被禁用。 解决的方法是删除disabled_plugins.txt&#xff0c;Mac OS对应的路径为&#xff1a; /Users/xzh/Library/Application Support/Google/AndroidStudio202…

C#中async/await的线程ID变化情况

一、简单的起步 Console.WriteLine($"主线程开始ID&#xff1a;{Thread.CurrentThread.ManagedThreadId}");//aawait Task.Delay(100);//cConsole.WriteLine($"主线程结束ID&#xff1a;{Environment.CurrentManagedThreadId}");//b 结果&#xff1a; …

MySQL复合查询(查询直接看这里)

回顾基本查询 查询工资高于500或岗位为TOM的雇员&#xff0c;同时还要满足他们的姓名首字母为大写的J select * from EMP where(sale > 500 or job TOM) and ename like J%;按照部门号升序而雇员的工资降序排序 select * from EMP order by deptno, sal desc;最后&#…

CANoe中的工作模式之争:由一段简单的代码引出的问题

1、引子 有网友问我一个CAPL中timer定时器的代码问题。他在CANoe工程中写了一段代码:每5秒循环触发一次定时器事件程序,输出一句文本信息到Write窗口。但是执行后发现并不是每5秒触发一次定时器事件程序,而是非常快的触发定时器事件程序。当他把这段代码复制到一个新的CANo…

【integrin + vWFa vWF】

CR3 ; CR4 ; 也有 vWFA CD11 CD18 vWF --Crystal structure and substrate-induced activation of ADAMTS13 神奇&#xff01; HGNC:12726 vWF 12p13.31 HGNC: 7 a2M 12p13.31

微服务之流控、容错组件sentinel

背景 2012年阿里巴巴研发的流量治理组件&#xff0c;核心功能流控、容错 有什么功能 流量控制 流量控制 网关控制 黑白名单 熔断降级 熔断 保护分布式系统防止因为调用下有服务时产生故障或者请求超时等异常影响上游服务&#xff0c;使用熔断方案&#xff0c;类似断路器…

城市内涝监测预警系统:有效降低内涝风险,保障城市安全

近日&#xff0c;受台风“海葵”的影响&#xff0c;福建广东多地遭遇了持续性强降雨的袭击&#xff0c;道路积水严重&#xff0c;“城市看海”模式再次开启&#xff0c;不少网友纷纷调侃房子已经升级为海景房。近年来受极端天气影响&#xff0c;城市内涝灾害越发凸显&#xff0…

vscode 画流程图

文章目录 1、安装插件 draw2、新建文件3、开始画图4、另存为图片 vscode可以画流程图了&#xff0c;只需要安装插件就可以了。 1、安装插件 draw 2、新建文件 3、开始画图 4、另存为图片

小程序中如何查看会员的等级及变更记录

会员等级变更记录是了解用户购买行为和消费习惯的重要依据。下面就将介绍如何怎么查看会员的等级以及等级变更记录。 1. 找到指定的会员卡。在管理员后台->会员管理处&#xff0c;找到需要查看等级和记录的会员卡。也支持对会员卡按卡号、手机号和等级进行搜索。在这个页面…

后端太卷,我不玩了!

作者&#xff1a;阿秀 InterviewGuide大厂面试真题网站&#xff1a;https://top.interviewguide.cn 这是阿秀的第「303」篇原创 小伙伴们大家好&#xff0c;我是阿秀。 校招岗位形势是一直在变化的&#xff0c;并不是一成不变的&#xff0c;从18、19、20年这三年里的算法岗大热…

1-2 AUTOSAR规范文档

目录 一、AUTOSAR文档下载 二、AUTOSAR文档分类 三、软件设计规范文档解读&#xff08;SWS&#xff09; 一、AUTOSAR文档下载 AUTOSAR规范文档下载可以到AUTOSAR官网&#xff08;Home AUTOSAR&#xff09;进行下载。 下载操作如下图所示&#xff1a; 二、AUTOSAR文档分类 AU…