当前位置: 首页 > news >正文

【Linux】虚拟地址空间

前言:

        上文我们讲到了Linux中的环境变量【Linux】环境变量-CSDN博客

        本文我们来讲一下Linux中的虚拟地址空间

 初识空间分布

        下面是虚拟地址空间的大致分布图:

 

        我们可以先通过代码来验证是否正确:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>int g_unval;
int g_val = 100;int main(int argc, char* argv[], char* env[])
{const char* str = "helloworld";printf("code addr: %p\n", main);printf("init global addr: %p\n", &g_val);printf("uninit global addr: %p\n", &g_unval);static int test = 10;char* heap_mem = (char*)malloc(10);char* heap_mem1 = (char*)malloc(10);char* heap_mem2 = (char*)malloc(10);char* heap_mem3 = (char*)malloc(10);printf("heap addr: %p\n", heap_mem);printf("heap addr: %p\n", heap_mem1);printf("heap addr: %p\n", heap_mem2);printf("heap addr: %p\n", heap_mem3);printf("test static addr: %p\n", &test);printf("stack addr: %p\n", &heap_mem);printf("stack addr: %p\n", &heap_mem1);printf("stack addr: %p\n", &heap_mem2);printf("stack addr: %p\n", &heap_mem3);printf("read only string addr: %p\n", str);for (int i = 0; i < argc; i++){printf("argv[%d]: %p\n", i, argv[i]);}for (int i = 0; env[i]; i++){printf("env[%d]: %p\n", i, env[i]);}return 0;
}
hyc@hcss-ecs-4ce7:~/linux/虚拟地址$ ./test
code addr: 0x55da1c103189
init global addr: 0x55da1c106010
uninit global addr: 0x55da1c10601c
heap addr: 0x55da1d1f06b0
heap addr: 0x55da1d1f06d0
heap addr: 0x55da1d1f06f0
heap addr: 0x55da1d1f0710
test static addr: 0x55da1c106014
stack addr: 0x7ffcf95ad350
stack addr: 0x7ffcf95ad358
stack addr: 0x7ffcf95ad360
stack addr: 0x7ffcf95ad368
read only string addr: 0x55da1c104004
argv[0]: 0x7ffcf95ad71f
env[0]: 0x7ffcf95ad726
env[1]: 0x7ffcf95ad736
env[2]: 0x7ffcf95ad744
env[3]: 0x7ffcf95ad75f
env[4]: 0x7ffcf95ad780
env[5]: 0x7ffcf95ad78c
env[6]: 0x7ffcf95ad7a1
env[7]: 0x7ffcf95ad7b0
env[8]: 0x7ffcf95ad7bf
env[9]: 0x7ffcf95ad7d0
env[10]: 0x7ffcf95addbf
env[11]: 0x7ffcf95addf5
env[12]: 0x7ffcf95ade17
env[13]: 0x7ffcf95ade2e
env[14]: 0x7ffcf95ade39
env[15]: 0x7ffcf95ade59
env[16]: 0x7ffcf95ade62
env[17]: 0x7ffcf95ade6a
env[18]: 0x7ffcf95ade7e
env[19]: 0x7ffcf95ade9a
env[20]: 0x7ffcf95adebe
env[21]: 0x7ffcf95adecf
env[22]: 0x7ffcf95adf10
env[23]: 0x7ffcf95adf78
env[24]: 0x7ffcf95adfab
env[25]: 0x7ffcf95adfbe
env[26]: 0x7ffcf95adfd1
env[27]: 0x7ffcf95adfe8

虚拟地址

        打印不同进程下的相同变量的地址与值

#include <stdio.h>
#include <unistd.h>int g_val = 0;int main()
{pid_t id = fork();if (id < 0){perror("fork");return 0;}else if(id == 0){ printf("child[%d]: %d : %p\n", getpid(), g_val, &g_val);}else{printf("parent[%d]: %d : %p\n", getpid(), g_val, &g_val);}sleep(1);return 0;
}

        我们可以看到父子进程的输出结果都是一样的,很好理解没有问题。

        但如果子进程对变量进行修改会发生什么?

#include <stdio.h>
#include <unistd.h>int g_val = 0;int main()
{pid_t id = fork();if (id < 0){perror("fork");return 0;}else if (id == 0){ g_val=100;printf("child[%d]: %d : %p\n", getpid(), g_val, &g_val);}else{printf("parent[%d]: %d : %p\n", getpid(), g_val, &g_val);}sleep(1);return 0;
}

        这时我们就会发现输出变量的地址是一模一样的,但是变量的值却不一样!这就很奇怪了。

总结:

        相同的地址值却不一样,说明我们所打印出来的绝对不可能是真实的地址。而这就是我们说所的虚拟地址

        我们在使用C/C++看到的地址,全部都是虚拟地址!物理地址,用户一概都看不到,由OS统一管理。

        换言之,OS负责讲虚拟地址转化为物理地址

虚拟地址空间

结论一:

        1.一个进程,对应拥有一个虚拟地址空间

        2.一个进程,也对应拥有一个页表。页表的作用是建立虚拟地址与物理地址的映射关系,用于虚拟地址与物理地址的转化。

        3.虚拟地址空间单位为:1字节,既一个地址指向的空间大小为1字节。32位机器有2^32个地址,64位机器有2^64个地址。32位机器的虚拟空间大小就有4G(2^32*1字节)其中3G是用户可用空间,1G是内核空间(暂时不用管)

        如果我们创建一个子进程,我们知道子进程的task_struct是拷贝父进程的,而子进程与父进程共享代码与数据(没有新继承加载进来的情况下)这也就意味了子进程的页表映射关系与父进程一模一样

        但如果子进程要修改数据呢?会发生什么?

结论二:

        子进程修改数据会发生写时拷贝:在物理空间上重新开辟,并在页表中重新建立映射关系。注意:虚拟地址并没有改变,仅仅改变了物理地址!!!

        这也就解释了最开始的代码中,为什么明明打印出来的地址相同,但是值却不同。 

写时拷贝的意义

有的同学可以困惑,为什么不创建进程的时间直接拷贝一份呢?

1.减少了创建进程的时间

        如果创建时就直接拷贝,会增加进程创建的时间:并不会进程中每一个变量都会修改

2.减少了内存的浪费

        对于不需要修改的变量,依旧拷贝就会产生冗余数据,浪费内存空间

虚拟地址空间的本质 

mm_struct

        虚拟地址空间的本身其实就是一个结构体!(mm_struct)

        我们知道虚拟空间中有许多区域划分,而如果确定区域的位置呢?只需要知道各个区域的起始位置以及结束位置就可以了。所以mm_struct的主要成员变量是各个区域的位置信息

        想要对区域进行调整,也只需要修改对应区域的start、end即可

struct mm_struct
{unsigned long start_code,end_code;unsigned long start_data,end_data;......
}

        我们说一个进程对应一个虚拟地址空间,既对应一个mm_struct。操作系统中有多个进程,必然也有多个mm_struct。那么就如何管理的呢?

        1.当数量较少时,采用单链表管理

        2.当数量较多时,采用红黑数管理

vm_area_struct

        mm_struct中还有一重要成员:vm_area_struct(存放其指针)

        vm_area_struct用来表示一个独立的虚拟内存区域,由于每个不同的虚拟内存区域功能和内部机制都不同,因此⼀个进程使⽤多个vm_area_struct结构来分别表示不同类型的虚拟内存区域,方便进程快速访问。

为什么要有虚拟地址

1.让无序的物理地址,变为有序

2.在地址的转化过程中,可以对地址以及操作进行判定是否合法,进而保护了物理内存

        例如:char* str="abc";  *str="a",为什么会报错?因为在查找页表时被拦截了!

3.让进程管理和内存管理,进行一定程度的解耦合

        不让虚拟地址与物理地址有强相关性

补充:

        1.我们可以不加载代码和数据,只有task_struct、mm_struct、页表

        2.再次理解挂起:保留虚拟地址空间以及页表中的虚拟地址,清空页表中的物理地址,将物理地址空间唤入到磁盘的swap分区中

http://www.dtcms.com/a/308884.html

相关文章:

  • 智能图书馆管理系统开发实战系列(三):前端工程化实践 - Electron + React + TypeScript
  • docker运行时目录/var/lib/docker 学习
  • 面试笔记【16:9区域问题】
  • diffusion原理和代码延伸笔记1——扩散桥,GOUB,UniDB
  • 如何提前识别项目风险?主要方法分享
  • MemoRizz:AI的“超级大脑”工具,实现持久记忆与上下文智能管理
  • 【智能体agent】入门之--2.2框架---autoGen
  • 第十三天:蛇形矩阵
  • SpringBoot3.x入门到精通系列:1.1 简介与新特性
  • 【网络安全】gcc和gdb是什么-GNU Compiler Collection和GNU Debugger?
  • 钢筋计数误差↓78%!陌讯多模态融合算法在建筑地产AI质检的落地实践
  • ACL 2024 大模型方向优秀论文:洞察NLP前沿​关键突破
  • window怎么安装pyqt6以及 安装 pythonqt6 会遇到的问题和怎么解决
  • Linux基础复习:字符输入与输出
  • 17-C语言:第18天笔记
  • 文法中的间接左递归
  • [echarts]多个柱状图及图例
  • 《Java 程序设计》第 16 章 - JDBC 数据库编程
  • SpringBoot实战:高效Web开发
  • SpringBoot中异常的全局处理
  • 学习曲线之TS
  • SQL Server DATEADD()函数详解:时间计算的终极指南与实战案例
  • 可计算存储(Computational Storage)与DPU(Data Processing Unit)的技术特点对比及实际应用场景分析
  • 免费语音识别(ASR)服务深度指南​
  • 动态配置实现过程
  • 《黑马笔记》 --- C++ 提高编程
  • Winform C# 热力图制作要点
  • HOOPS Exchange技术架构全解析:打造高效CAD/BIM数据导入与导出引擎
  • 【go】格式化的输入和输出
  • 计算机网络知识【推荐!!!】按照OSI七层模型梳理