Linux:进程地址空间

目录

1.程序地址空间 

2.进程地址空间


1.程序地址空间 

我们在讲C/C++语言的时候,32位平台下,我们见过这样的空间布局图

我们来验证一下这张图的正确性:

  int un_gval;int init_gval=100;int main(int argc, char* argv[],char* env[]){//代码区printf("code addr: %p\n",main);//字符常量区const char *str = "hello Linux";//*str = 'h';//不能修改因为字符常量区是被写入到代码区的,而代码区不能被修改printf("read only char addr: %p\n",str);//已初始化全局变量区printf("init global value addr: %p\n",&init_gval);//所谓的静态区就是已初始化全局变量区static int a ;   printf("stack addr: %p\n",&a);//已初始化全局变量区printf("uninit global value addr: %p\n",&un_gval);//堆区char *heap1 = (char*)malloc(100);char *heap2 = (char*)malloc(100);char *heap3 = (char*)malloc(100);char *heap4 = (char*)malloc(100);char *heap5 = (char*)malloc(100);            printf("heap1 addr: %p\n",heap1);//向地址增大方向增长    printf("heap2 addr: %p\n",heap2);    printf("heap3 addr: %p\n",heap3);    printf("heap4 addr: %p\n",heap4);    printf("heap5 addr: %p\n",heap5);                                                                                                                   //栈区                 printf("stack1 addr: %p\n",&heap1);    printf("stack2 addr: %p\n",&heap2);printf("stack3 addr: %p\n",&heap3);    printf("stack4 addr: %p\n",&heap4);   printf("stack5 addr: %p\n",&heap5);//命令行参数int i = 0;for(;argv[i];i++){printf("argv[%d]:%p\n",i,argv[i]);}//环境变量for(i=0;env[i];i++){printf("env[%d]:%p\n",i,env[i]);}return 0;}

运行结果:

通过观察静态变量的位置,可以认为静态变量就是全局变量,只是静态变量只初始化一次,有作用域的限制。

这里栈区还有一个特点:我们平时定义结构体对象时,我们取地址都是返回整个结构体最低的地址,内部是使用低地址向高地址排列,使用的是起始地址加偏移量的访问方式,但是栈区整体还是先使用高地址在使用低地址。

那么这里就有一个问题了,这张图是真实的物理内存吗?

我们再来验证一下:

  #include <stdio.h>#include <stdlib.h>#include <unistd.h>int g_val = 100;int main(){pid_t id = fork();if(id==0){//子进程int cnt = 5;while(1){printf("child, pid:%d, ppid:%d, g_val:%d ,&g_val:%p\n",getpid(),getppid(),g_val,&g_val);sleep(1);if(cnt == 0){g_val = 200;printf("child change g_val: 100->200\n");}cnt--;}}else {//父进程while(1){printf("father, pid:%d, ppid:%d, g_val:%d ,&g_val:%p\n",getpid(),getppid(),g_val,&g_val);                                                   sleep(1);}}return 0;}

运行上面代码的结果:

什么意思呢?就是我们定义了一个全局变量 g_val,然后我们通过 fork() 创建了一个子进程,让子进程修改了全局变量。我们之前文章中提到过,因为进程之间要保证数据的独立性,父进程的数据子进程也要有一份,而Linux采用写时拷贝,所以在子进程没有修改全局变量值时,父进程和子进程的全局变量地址相同可以理解。但是子进程对全局变量做修改后,写时拷贝应该重新申请一块空间来存放修改后的值,但是根据运行结果我们发现地址还是相同的,子进程全局变量的地址并没有改变,同一个地址竟然读出不同的值?所以我们可以大胆推测我们看到的地址并不是真正的物理地址。

得出结论:

  1. 变量内容不一样,所以父子进程输出的变量绝对不是同一个变量
  2. 但地址值是一样的,说明,该地址绝对不是物理地址!
  3. 在Linux地址下,这种地址叫做 虚拟地址
  4. 我们在用C/C++语言所看到的地址,全部都是虚拟地址/线性地址!物理地址,用户一概看不到,由OS统一管理,OS必须负责将 虚拟地址 转化成 物理地址。

2.进程地址空间

2.1 操作系统如和将虚拟地址转换为物理地址

所以之前说“程序的地址空间”是不准确的,准确的应该说成 进程地址空间 ,那该如何理解呢?

每一个进程运行之后,都会有一个进程地址空间的存在!都要在系统层面都要有自己的页表映射结构

在C/C++中,变量在编译形成可执行程序后,就没有变量名的概念了,都是地址。

2. 什么是地址空间?什么是区域划分?

地址空间也要被OS管理起来!!每一个进程都要有地址空间,系统中,一定要对地址空间做管理。如何管理地址空间呢? 也是通过之前文章提过的先描述,在组织。所以地址空间最终一定是内核的数据结构对象,就是一个内核结构体。

在这个结构体中,分别有每个空间如栈区,堆区的开始和结束位置。

在Linux中,这个进程/虚拟地址空间的东西,叫做:struct mm_struct

struct mm_struct
{long code start;long code_end;long data_start;long data_end;long heap_start;long heap_end; long stack_start;long stack_end;// ...
}

进程PCB Linux 中的struct task_struct 中也是有指针指向mm_struct的。 

3.为什么要有地址空间

  1. 让进程以统一的视角看待内存,所以任意一个进程,可以通过地址空间+页表可以将乱序的内存数据,变成有序,分门别类的规划好,使得无序边有序
  2. 存在虚拟地址空间,因为页表中有访问权限字段,可以有效的进行进程访问内存的安全检查,比如我们无法修改字符常量的内容,是因为页表访问权限是只读。
  3. 将进程管理和内存管理进行解耦
  4. 通过页表让进程映射到不同的物理内存处,从而实现进程的独立性!所以每一个进程都认为自己可以使用4GB的空间,但是真实的物理空间只有4GB,一个进程并不知道其他进程的存在。
  5. CPU中也有一个CR3寄存器来保存页表的地址,这个地址是真实的物理地址。

扩展问题

我们如果在玩一些大型游戏时,游戏所需要的内存非常大,我们之前学习过 进程 = 内核数据结构体PCB+程序的代码和数据,我们把游戏加载到内存中时,是把所有的代码和数据都拷贝过来吗?根据我们呢的常识,显然不是这样的,因为我们得内存很小,为什么游戏还是可以运行的呢?因为页表中还有是否分配空间和是否有内容的字段,00,表示既没有分配空间也没有内容,我们游戏一次只加载一部分代码和数据,当CPU执行完这段代码时,要执行下面代码,操作系统就会将上面字段改为00,出现缺页中断,然后再去磁盘中拷贝接下来的代码和数据,释放执行完的代码和数据,这样就可以使得我们得游戏可以正常运行。

本篇结束!

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

springMVC-模型数据的处理

一、数据放入到request域当中 1、把获取的数据放入request域中&#xff0c; 方便在跳转页面去显示 <a>添加主人信息</a> <form action"vote/vote04" method"post" >主人id&#xff1a;<input type"text" name"id&q…

Vue学习笔记-Vue3中的customRef

作用 创建一个自定义的ref&#xff0c;并对其依赖项的更新和触发进行显式控制 案例 描述&#xff1a;向输入框中输入内容&#xff0c;在下方延迟1秒展示输入内容 代码&#xff1a; <template><input type"text" v-model"keyword"><h3&…

设计模式——组合模式(结构型)

引言 组合模式是一种结构型设计模式&#xff0c; 你可以使用它将对象组合成树状结构&#xff0c; 并且能像使用独立对象一样使用它们。 问题 如果应用的核心模型能用树状结构表示&#xff0c; 在应用中使用组合模式才有价值。 例如&#xff0c; 你有两类对象&#xff1a; ​…

用23种设计模式打造一个cocos creator的游戏框架----(十五)策略模式

1、模式标准 模式名称&#xff1a;策略模式 模式分类&#xff1a;行为型 模式意图&#xff1a;定义一系列的算法&#xff0c;把它们一个个封装起来&#xff0c;并且使它们可以相互替换。此模式使得算法可以独立于使用它们的客户而变化 结构图&#xff1a; 适用于&#xff1…

贝锐蒲公英解决方案:企业海外分部高效、稳定访问国内办公系统

某电气技术有限公司是一家专注数字电源研发、制造和销售的企业。公司致力于为大数据和新能源行业提供智慧能源解决方案, 数字电源产品在数据与算力中心、网络基础设施、电池储能换电、家庭能源系统中均有广泛应用。目前在国内以及东南亚、欧洲、美国等地设有分公司/办事处&…

STM32F407-14.3.18-01连接霍尔传感器

连接霍尔传感器 可通过用于生成电机驱动 PWM 信号的高级控制定时器&#xff08;TIM1 或 TIM8&#xff09;以及图 114 中称为 “接口定时器”的另一个定时器 TIMx&#xff08;TIM2、TIM3、TIM4 或 TIM5&#xff09;&#xff0c;实现与霍尔传感器的连接。3 个定时器输入引脚&…

Android动画(四)——属性动画ValueAnimator的妙用

目录 介绍 效果图 代码实现 xml文件 介绍 ValueAnimator是ObjectAnimator的父类&#xff0c;它继承自Animator。ValueAnimaotor同样提供了ofInt、ofFloat、ofObject等静态方法&#xff0c;传入的参数是动画过程的开始值、中间值、结束值来构造动画对象。可以将ValueAnimator看…

LOF基金跟股票一样吗?

LOF基金&#xff0c;全称为"上市型开放式基金"&#xff0c;是一种可以在上海证券交易所认购、申购、赎回及交易的开放式证券投资基金。投资者可以通过上海证券交易所场内证券经营机构或场外基金销售机构进行认购、申购和赎回基金份额。 LOF基金的特点是既可以像股票…

1852_bash中的find应用扩展

Grey 全部学习内容汇总&#xff1a; https://github.com/GreyZhang/toolbox 1852_bash中的find应用扩展 find这个工具我用了好多年了&#xff0c;但是是不是真的会用呢&#xff1f;其实不然&#xff0c;否则也不会出现这种总结式的笔记。其实&#xff0c;注意部分小细节之后…

爬虫练习-获取imooc课程目录

代码&#xff1a; from bs4 import BeautifulSoup import requests headers{ User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:94.0) Gecko/20100101 Firefox/94.0, }id371 #课程id htmlrequests.get(https://coding.imooc.com/class/chapter/id.html#Anchor,head…

Netty网络基础的通俗理解(网络操作系统)

写在前面 说来惭愧&#xff0c;最近半年没怎么学习技术&#xff0c;时间基本都花在工作以及去熟悉了解金融领域的知识去了。从大一到现在&#xff0c;我一直有个持续学习技术的习惯&#xff0c;如果太久没学习技术&#xff0c;我心里就开始有点焦虑或者说不充实&#xff0c;所…

单元测试计划、用例、报告、评审编制模板

单元测试支撑文档编制模板&#xff0c;具体文档如下&#xff1a; 1. 单元测试计划 2. 单元测试用例 3. 单元测试报告 4. 编码及测试评审报告 软件项目相关资料全套获取&#xff1a;软件项目开发全套文档下载-CSDN博客 1、单元测试计划 2、单元测试用例 3、单元测试报告 4、编码…