【Linux】地址空间虚拟地址

个人主页 : zxctscl
如有转载请先通知

文章目录

  • 1. 虚拟地址
    • 1.1 虚拟地址引入
    • 1.2 虚拟地址理解
    • 1.3 虚拟地址细节问题
  • 2. 地址空间
    • 2.1 理解地址空间
    • 2.2 页表和写时拷贝
  • 3. 进程调度

1. 虚拟地址

1.1 虚拟地址引入

先先来一个测试代码:

  1 #include<stdio.h>
  2 #include<unistd.h>
  3 #include<string.h>
  4 #include<stdlib.h>
  5
  6 int g_val=100;
  7
  8 int main()
  9 {
 10   printf("father is running,pid:%d,ppid:%d\n",getpid(),getppid());
 11
 12
 13   pid_t id=fork();
 14   if(id==0)
 15   {
 16     int cnt=0;
 17     while(1)
 18     {
 19     printf("I am child process,pid:%d,ppid:%d,g_val:%d,&g_val:%p\n",getpid(),getppid(),g_val,&g_val);
 20     sleep(1);
 21     cnt++;
 22     if(cnt==5)
 23     {
 24       g_val=300;
 25       printf("I am child process,change %d->%d\n",100,300);
 26     }
 27     }
 28   }
 29   else{
 30     while(1)
 31     {
 32     printf("I am father process,pid:%d,ppid:%d,g_val:%d,&g_val:%p\n",getpid(),getppid(),g_val,&g_val);
 33     sleep(1);
 34   }
 35
 36 }
 37 }
 38

编译运行:
子进程把数据改了,父进程的数据没有改变,但是父子地址是一样的。
在这里插入图片描述

这个地址绝对不是物理地址,理论上修改了数据为300之后不可能在输出有100,访问一个地址怎么可能又是100也是300。这个地址在系统层面上称之为虚拟地址。

1.2 虚拟地址理解

每一个进程除了要把代码和数据加载到内存之外,对于当前的操作系统来讲,系统当中会为每一个进程创建一个地址空间。

地址空间在操作系统里面。在32位和64位下的地址空间大小是不一样的,为了方便这里使用32位来表述。32位从低到高一个有4GB的地址空间范围,实际上这个地址空间当中打印出来的地址,是该空间内对应的地址。进程是可以指向这个地址空间的。

地址空间
其实PCB和地址空间都是在物理内存里面的,只不过要访问初始化全局数据的时候,不在地址空间上保存,地址空间只会提供线性连续地址,让用户之后通过虚拟地址的地址空间,将虚拟地址转化到为了物理内存中。
此时计算机的体系结构中还存在一个页表,页表它的主要功能是负责将地址空间中的虚拟地址和物理地址之间建立映射关系。未来在用进程进行访问的时候,操作系统会自动用虚拟地址查页表转换为物理地址,然后让用户访问到数据。

在这里插入图片描述
父进程的代码可以通过页表地址映射转换到为了内存中代码,父进程通过连续的地址空间就可以访问到它的代码和数据。

在这里插入图片描述
假设在物理内存上存放一个全局变量g_val,默认内容是100,g_val在页表在地址空间中都要被找到,所以在地址空间的初始化数据中就有它的地址虚拟地址,页表的左侧也有它的虚拟地址,在页表右侧就有它对应的物理地址。
在这里插入图片描述

当创建了一个子进程,本质上是系统多了一个进程,它也有自己的task_truct,还有自己的虚拟地址空间,还有它所对应的页表。

每个进程都要有自己的虚拟的地址空间,也要有自己对应的页表。
每个进程都要有自己独立的地址空间,那么操作系统就得管理很多个进程的地址空间,而地址空间本质上就是内核中的一个数据结构对象。

子进程会把父进程的很多数据结构全拷贝一份,基本上子进程的PCB、地址空间和页表基本上和父进程的一致。
子进程的地址空间也会有一个虚拟地址,子进程对应的页表也来自父进程,所以页表保存的地址,从而子进程也会指向那个g_val。
在这里插入图片描述

所以子进程和父进程看到的虚拟地址是一样的,并且它们的页表也一样,指向的物理内存也一样,所以它们打印出来的地址也就是相同的了。

如果子进程进行写入,也是通过页表向物理内存处进行写入,写入的时候直接找到g_val把100改为300。可子进程一旦对数据做修改了,父进程就会看到。如果子进程直接修改了数据,就会导致程序运行本身问题。
而进程本身在运行的时候具有独立性,所以子进程对数据进行修改,就不能影响到父进程,所以当子进程尝试对数据进行修改时,操作系统发现父进程也有,就在在子进程修改之前,在物理内存中出现开辟一个空间,开辟完成之后。然后把修改之前的数据拷贝到新空间中,再把新的物理地址和之前的物理地址相比较,把新的物理地址放在子进程的页表中,重新构建映射,页表的右侧就指向新的物理地址空间,这个工作结束,才会就行让子进程执行写入操作,把100改为300。
在这里插入图片描述
重新开辟物理内存这些都是操作系统自己做,上面这个过程叫做写时拷贝。

修改的只是子进程的物理地址和页表,而地址空间里面的依然是虚拟地址。子进程和父进程的虚拟地址是一样的,只是映射到物理内存到不同区域,所以对应看到的地址是一样的,但内容却不一样。

1.3 虚拟地址细节问题

如果父子进程不写,未来一个全局变量,默认是被父子共享的,代码(只读)是共享的。

为什么会存在写时拷贝?
因为进程具有独立性,所以父子进程有自己的地址空间和页表。
但是代码是共享的,那么怎么不在创建子进程的时候,全部给子进程拷贝一份?
主要是在父进程中的数据子进程不一定都会修改,而这些占据的空间又很大,子进程程序拷贝一份就是在浪费空间,所以采用写时拷贝,就是为了按需申请。必须写时才能拷贝是为了保证进程的独立性
按需申请本质是通过调整拷贝时间顺序,达到有效节省空间的目的。

2. 地址空间

2.1 理解地址空间

地址空间本质是内核的一个struct结构体,结构体里面有各种各样的区域划分,内部有很多的属性都是表示start,end的范围。
来看看源码里面描写这个结构体:
在这里插入图片描述
并不是限定了某一个范围,而是这个范围之间它所对应地址空间都可以使用。这个范围可以根据页表映射到物理内存。

操作系统给每一个进程都划分一块进程地址空间。
在这里插入图片描述
为什么要有地址空间?
一个程序的代码和数据放在物理内存中,如果没有虚拟地址空间,要直接找到程序的代码和数据,就必须让进程的PCB把对应的代码和数据都记录下来。如果当前还有其他程序,都在物理内存中,每一个程序都在物理内存中加载的话,也就要求每一个进程所对应的代码和数据在物理内存的哪一个位置都得记录下来。这个记录对应进程而言负担是比较大的,也就是进程直接使用物理地址。
在这里插入图片描述
就有可能出现访问越界,或者访问到其他进程的代码和数据。所以用进程记录物理地址就比较混乱,不利于做统一管理。
实际物理内存中的代码区,数据区、堆区、栈区、共享区、命令行参数和环境变量,对一个进程来讲可能是乱序的,那么再加载其他进程也是乱序的。

进程在申请内存时,在地址空间上能申请就可以,在页表对应的左侧就可以了,右侧可以先不填,当用户真正用到的时候在申请。

地址空间和也表存在的好处就是:一、将无序变有序,让进程以统一的视角来看待物理内存以及自己运行的各个区域
二、进程管理模块和内存管理模块进行解耦

地址空间并不是百分百使用的,一般只使用一部分。比如在堆区,申请了五十个字节,可是遍历的时候计数器越界了,在地址空间里面就越界了,操作系统就直接拦截了这个请求,所有的非法请求都不能通过地址空间到物理内存上,也就是保护物理内存。
拦截非法请求就是对物理内存进行保护

2.2 页表和写时拷贝

在这里插入图片描述

查页表对内存地址进行访问是CPU,它里面包含CR3寄存器内,CPU的还有有一个叫做MMU硬件(内存管理单元),快速把虚拟地址结合页表转化为物理地址。
页表里面的一些选项来支持权限管理。就像是C语言中不能修改字符常量区,是因为页表里面没有给修改的权限。

在这里插入图片描述
操作系统支持写时拷贝,页表给父进程的权限是rw。当父进程创建子进程之后,子进程的页表权限是r。当父进程一旦创建子进程,父进程为了支持写时拷贝,因为父进程走到已初始化全局区本来就是可以写的,但创建子进程之后,操作系统会直接修改页表中该位置的权限,都修改为r。当父子进程中任何一个尝试写入时,此时系统就会直接识别到错误。
操作系统识别到错误就得判断:1.是不是数据不在物理内存;2.是不是数据想要写时拷贝;3.如果都不是,才能进行异常处理。
第一种解决就是缺页中断,第二种就发生写时拷贝。
在这里插入图片描述
上面的图就足矣说名问题,同一个变量,地址相同,其实是虚拟地址相同,内容不同其实是被映射到了不同的物理地址!

在这里插入图片描述
在最开始的时候,地址空间的页表里面的数据从哪里来?
程序一旦加载到内存就有地址。程序在变成二进制的时候本身就有地址。也就是说程序里面本身就有地址。
在这里插入图片描述
来看一下之前的代码:

  1 #include<stdio.h>
  2 #include<unistd.h>
  3 #include<string.h>
  4 #include<stdlib.h>
  5
  6
  7 int main()
  8 {
  9   pid_t id=fork();
 10   if(id==0)
 11   {
 12     while(1)
 13     {
 14       printf("child,%d,%p\n",id,&id);
 15       sleep(1);
 16     }
 17   }
 18   else if(id>0)
 19   {
 20     while(1)
 21     {
 22       printf("father,%d,%p\n",id,&id);
 23       sleep(1);
 24     }
 25    }
 26   return 0;
 27   }
 28

当fork()时候,不管是父进程还是子进程,都要return。在return时候,本质就是对id进行写入,而id本身是父进程定义的变量,不论是父进程还是子进程,谁先return,都得return两次,进程在return的时候,发生写时拷贝。所以当父进程用id的时候,它认为id大于0;子进程在返回的时候它认为id等于0。所以虚拟地址相同而物理地址不同。

3. 进程调度

Linux中的nice值并不是能任意调度的,而是从-20到19,这40个数字之间变换。

在操作系统中每一个CPU都会有一个运行队列:
在这里插入图片描述
来看看蓝色区域的部分,这里面有queue队列包含140项,它其实是task_struct *queue[140]
queue[140]: 一个元素就是一个进程队列,相同优先级的进程按照FIFO规则进行排队调度,所以,数组下标就是优先级!
nr_active: 总共有多少个运行状态的进程
在这里插入图片描述
从该结构中,选择一个最合适的进程,过程是怎么的呢?

  1. 从0下表开始遍历queue[140]
  2. 找到第一个非空队列,该队列必定为优先级最高的队列
  3. 拿到选中队列的第一个进程,开始运行,调度完成!
  4. 遍历queue[140]时间复杂度是常数!但还是太低效了!

bitmap[5]:一共140个优先级,一共140个进程队列,为了提高查找非空队列的效率,就可以用5*32个比特位表示队列是否为空,这样,便可以大大提高查找效率!

在这里插入图片描述
活跃进程的task_struct *queue[140]只出不进,过期进程的task_struct *queue[140]只进不出。

active指针和expired指针:active指针永远指向活动队列;expired指针永远指向过期队列。
可是活动队列上的进程会越来越少,过期队列上的进程会越来越多,因为进程时间片到期时一直都存在的。
没关系,在合适的时候,只要能够交换active指针和expired指针的内容,就相当于有具有了一批新的活动进程!

有问题请指出,大家一起进步!!!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Grass注册不了、按钮灰色的解决方案

近期相信grass挂机项目不少人有所有接触。还有不了解这个项目的可以看看博客&#xff1a; http://t.csdnimg.cn/bI4UO 但是不少人注册时遇到无法注册的问题&#xff0c;或者是注册按钮显示灰色&#xff0c;放上鼠标时显示禁止。这也是博主在尝试时遇到的问题。 经过探索&…

如何解决python安装mysqlclient失败问题

在使用Django等框架来操作MySQL时&#xff0c;实际上底层还是通过Python来操作的&#xff0c;首先需要安装一个驱动程序&#xff0c;在Python3中&#xff0c;驱动程序有多种选择&#xff0c;比如有pymysql以及mysqlclient等。使用pip命令安装mysqlclient失败应如何解决&#xf…

【Linux实践室】Linux高级用户管理实战指南:Linux用户与用户组编辑操作详解

&#x1f308;个人主页&#xff1a;聆风吟_ &#x1f525;系列专栏&#xff1a;Linux实践室、网络奇遇记 &#x1f516;少年有梦不应止于心动&#xff0c;更要付诸行动。 文章目录 一. ⛳️任务描述二. ⛳️相关知识2.1 &#x1f514;Linux查看用户属性命令2.1.1 &#x1f47b;…

测试用例的编写评审

1、什么叫软件测试用例 什么是测试用例 测试用例(TestCase) 是为项目需求而编制的一组测试输入、执行条件 以及预期结果&#xff0c;以便测试某个程序是否满足客户需求。–测试依据 可以总结为:每一个测试点的数据设计和步骤设计。–测试用例 2、测试用例的重要性(了解) 2.1…

社媒矩阵运营解决方案:海外云手机

在全球化的浪潮下&#xff0c;企业愈发认识到通过海外社交媒体平台扩大影响力、树立品牌形象及抢占国际市场的巨大机遇。因此&#xff0c;运营海外社交媒体账户已逐渐成为企业战略部署的重要组成部分。为了全面捕捉多渠道的流量&#xff0c;众多企业选择同时运营多个平台的多个…

基于Spring Boot的校园招聘系统

文章目录 项目介绍主要功能截图&#xff1a;部分代码展示设计总结项目获取方式 &#x1f345; 作者主页&#xff1a;超级无敌暴龙战士塔塔开 &#x1f345; 简介&#xff1a;Java领域优质创作者&#x1f3c6;、 简历模板、学习资料、面试题库【关注我&#xff0c;都给你】 &…

基于SkyEye运行Qt:著名应用程序开发框架

Qt是一个著名的跨平台的C图形用户界面应用程序开发框架&#xff0c;目前包括Qt Creator、Qt Designer等等快速开发工具&#xff0c;还支持2D/3D图形渲染、OpenGL&#xff0c;允许真正的组件编程&#xff0c;是与GTK、MFC、OWL、ATL一样的图形界面库。使用Qt开发的软件可以做到一…

抖音直播间没流量怎么办?如何快速提升直播间人气?

抖音直播间人气低迷&#xff0c;是否因为投入的资金不足或是数据表现不够抢眼而让你感到困惑&#xff1f;要提升抖音直播间的人气&#xff0c;首先需要深入了解抖音的推荐逻辑&#xff0c;探究直播间人气的真正来源。 抖音直播间的人气来源有哪些&#xff1f; 抖音直播间人气…

SpringMVC核心流程解析

SpringMVC核心流程解析 DispatcherServlet的继承关系请求流程分析获取HandlerChain(ControllrtMethod拦截器)获取HandlerAdapter handlerMappings的初始化过程 DispatcherServlet的继承关系 DispatcherServlet本质是一个servlet&#xff0c;既然是servlet&#xff0c;一个请求…

Pytorch-自动微分模块

&#x1f947;接下来我们进入到Pytorch的自动微分模块torch.autograd~ 自动微分模块是PyTorch中用于实现张量自动求导的模块。PyTorch通过torch.autograd模块提供了自动微分的功能&#xff0c;这对于深度学习和优化问题至关重要&#xff0c;因为它可以自动计算梯度&#xff0c…

视觉位置识别与多模态导航规划

前言 机器人感知决策是机器人移动的前提&#xff0c;机器人需要对周围环境实现理解&#xff0c;而周围环境通常由静态环境与动态环境构成。机器人在初始状态或者重启时需要确定当前所处的位置&#xff0c;然后根据用户的指令或意图&#xff0c;开展相应移动或抓取操作。通过视觉…

Mamba:使用选择性状态空间的线性时间序列建模

本文主要是关于mamba论文的详解~ 论文&#xff1a;Mamba: Linear-Time Sequence Modeling with Selective State Spaces 论文地址&#xff1a;https://arxiv.org/ftp/arxiv/papers/2312/2312.00752.pdf 代码&#xff1a;state-spaces/mamba (github.com) Demo:state-spaces (St…

Java 算法篇-深入了解 BF 与 KMP 算法

&#x1f525;博客主页&#xff1a; 【小扳_-CSDN博客】 ❤感谢大家点赞&#x1f44d;收藏⭐评论✍ 文章目录 1.0 BF 算法概述 1.1 BF 算法实际使用 2.0 KMP 算法概述 2.1 KMP 算法实际使用 2.2 相比于 BF 算法实现&#xff0c;KMP 算法的重要思想 2.3 为什么要这样设计&#x…

C++面向对象程序设计-北京大学-郭炜【课程笔记(六)】

C面向对象程序设计-北京大学-郭炜【课程笔记&#xff08;六&#xff09;】 1、可变长数组类的实现2、流插入运算符和流提取运算符的重载2.1、对形如cout << 5 ; 单个"<<"进行重载2.2、对形如cout << 5 << “this” ;连续多个"<<&…

地埋电缆故障检测方法有哪些?地埋电缆故障检测费用是多少?

地埋电缆故障检测方法主要涵盖脉冲反射法、桥接法、高压闪络法和声波定位法等多种方法。选择适当的方法取决于故障类型、电缆类型和实际现场条件。至于地埋电缆故障检测费用则受到多个因素的影响&#xff0c;包括故障类型、检测方法的复杂性、检测设备的先进程度以及所处地区的…

【强化学习的数学原理-赵世钰】课程笔记(十)Actor-Critic 方法

目录 一.最简单的 actor-critic&#xff08;QAC&#xff09;&#xff1a;The simplest actor-critic (QAC) 二.Advantage actor-critic (A2C) 三.Off-policy actor-critic 方法 四. Deterministic actor critic(DPG) Actor-Critic 方法把基于 value 的方法&#xff0c;特别…

删除顺序表中所有值为X的元素(顺序表,单链表)

目录 时间复杂度为O(1)(顺序表)&#xff1a;代码实现&#xff1a; 运行结果&#xff1a; 时间复杂度为O(n)(顺序表)&#xff1a;代码实现&#xff1a; 运行结果&#xff1a; 单链表&#xff1a;时间复杂度o&#xff08;n&#xff09;:代码实现&#xff1a; 时间复杂度为O(1…

调研-转换zpl为png

文章目录 前言ZPLZPL相关转换的网站一、labelary常用功能 二、labelzoom三、https://www.htmltozpl.com/docs/demo/html-to-zpl四、 开源仓库&#xff1a;JSZPL五、 开源仓库&#xff1a;BinaryKits.Zpl六 redhawk其他相关概述Lodop 处理zpl 前言 为了解决ZPL指令转换为png&am…

软件需求开发和管理过程性指导文件

1. 目的 2. 适用范围 3. 参考文件 4. 术语和缩写 5. 需求获取的方式 5.1. 与用户交谈向用户提问题 5.1.1. 访谈重点注意事项 5.1.2. 访谈指南 5.2. 参观用户的工作流程 5.3. 向用户群体发调查问卷 5.4. 已有软件系统调研 5.5. 资料收集 5.6. 原型系统调研 5.6.1. …

Cesium中实现镜头光晕

镜头光晕 镜头光晕 (Lens Flares) 是模拟相机镜头内的折射光线的效果&#xff0c;主要作用就是让太阳光/其他光源更加真实&#xff0c;和为您的场景多增添一些气氛。 Cesium 中实现 其实 Cesium 里面也是有实现一个镜头光晕效果的&#xff0c;添加方式如下&#xff0c;只是效…