目录

一.进程地址空间

1.进程地址空间分布图

2.验证上述进程地址空间

3.Linux vs Windows

二.了解虚拟内存地址空间

0.通过代码引出虚拟内存地址空间概念

1.什么是虚拟内存地址空间

2.虚拟内存地址空间的设计方式

三.深入虚拟内存地址空间

1.虚拟内存地址空间与物理内存之间的映射

2.一个程序经过编译器到进程运行时的过程(帮助理解虚拟与物理之间映射关系)

3.虚拟内存地址空间存在的价值

四.重新理解fork函数

1.为什么会有两个返回值

2.为什么会有一个变量同时接收了两个返回值

3.写时拷贝

4.父子进程之间的关系

5.fork如何实现的调用fork之后父子进程代码共享


一.进程地址空间

我们先不谈虚拟内存与物理内存,当一个进程运行起来时,它的地址空间分布是什么样的呢?

1.进程地址空间分布图

2.验证上述进程地址空间

在Linux环境下验证!

代码:

#include<stdio.h>
#include<stdlib.h>int g_val1 = 10;
int g_val2 = 20;int g_val3;
int g_val4;int main(int argc, char* argv[], char* env[])
{//任务:验证进程地址空间//代码区printf("code address:%p\n", main);//只读常量区const char* p1 = "hello";printf("only read:%p\n", p1);const char* p2 = "hello world";printf("only read:%p\n", p2);//全局区printf("init global g_val1:%p\ninit global g_val2:%p\n", &g_val1, &g_val2);printf("uninit global g_val3:%p\nuninit global g_val4:%p\n", &g_val3, &g_val4);//堆区char* tmp1 = (char*)malloc(10);char* tmp2 = (char*)malloc(10);char* tmp3 = (char*)malloc(10);char* tmp4 = (char*)malloc(10);printf("heap:%p\n", tmp1);printf("heap:%p\n", tmp2);printf("heap:%p\n", tmp3);printf("heap:%p\n", tmp4);//栈区printf("stack:%p\n", &tmp1);printf("stack:%p\n", &tmp2);printf("stack:%p\n", &tmp3);printf("stack:%p\n", &tmp4);//命令行参数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;
}

3.Linux vs Windows

1).以上对于进程地址空间的验证,是在linux环境下.

在windows下会跑出不一样的结果,因为windows基于安全考虑,做了特殊处理

二.了解虚拟内存地址空间

0.通过代码引出虚拟内存地址空间概念

代码:

代码描述:

1.先创建一个全局变量g_val,并且初始化为100

2.创建子进程后,在子进程内部将g_val改为200

3.观察父子进程的g_val值以及g_val地址

#include<stdio.h>
#include<unistd.h>int g_val = 100;//全局变量int main()
{pid_t id = fork();if(id == 0){//子进程g_val = 200;printf("I am child, pid:%d, ppid:%d, g_val:%d, &g_val:%p\n", getpid(), getppid(), g_val, &g_val);}else{//父进程printf("I am father, pid:%d, ppid:%d, g_val:%d, &g_val:%p\n", getpid(), getppid(), g_val, &g_val);}return 0;
}

结果:

结论:

我们惊奇的发现,父子进程所打印的g_val的地址是一样的,但是!!里面却存了不同的值!!!

(注意一点:我们这里所打印的地址,是进程运行起来之后,由进程内部的printf系统调用所打印出来的,所以本质打印的这个地址是动态的)

为什么同一块空间,里面存放的不同的值呢?

经过进一步思考与猜测,那就是这个地址不是真实的物理地址! 而是虚拟地址!

在后面会给出对于这一现象的详细解释

1.什么是虚拟内存地址空间

现象:

每一个进程,都拥有自己的虚拟内存地址空间

虚拟内存地址空间的本质,就是我们上面验证的那个进程地址空间,这俩是一个东西

每一个进程都不知道外部其他进程的存在,都认为自己独享物理内存,并且拥有4G的空间

但实际上,并不是真正拥有4G内存,而是按需分配物理内存!

类比:

其实,这本质不就是老板给员工画的一张饼吗?

我们假设的认为:

操作系统 --- 老板

进程 --- 员工

虚拟内存地址空间 --- 老板给员工画的饼

物理内存 --- 真正的好处(例如:真的升职了,真的加薪了)

可以看出,虚拟内存地址空间只是操作系统给进程许诺,你拥有了全部的4G内存,但这仅仅只是是许诺而已,并没有执行!

而实际情况则是按需所取,如果进程过分索取内存的话,操作系统有权利拒绝!

这便可以很形象的表现出这四者之间的关系!

2.虚拟内存地址空间的设计方式

我们都知道,管理的本质是先描述,再组织

例如我们学过的进程,进程是如何被管理起来的呢?

答案是:先描述,再组织. 先通过进程控制块(PCB)(在linux中的具体表现形式:task_struct)将进程描述起来,然后将这些PCB以某种数据结构的形式组织起来,这样操作系统便将所有的进程管理了起来!

那么,虚拟内存地址空间是如何设计的呢?

以及它在linux中的具体表现形式是什么?

它是如何被管理的?

1.虚拟内存地址空间是如何设计的

实际上,虚拟内存地址空间的本质就是一种数据结构,并且将来要和特定的进程关联起来.

这种数据结构在linux中表现为mm_struct

与特定的进程关联起来是某个进程PCB中有一个指针指向mm_struct

2.mm_struct的本质是什么(大概了解)

其内部会有区域划分(这只是一部分)

3.对于虚拟内存地址空间的管理

先描述,再组织

先描述:通过mm_struct描述起来,mm_struct就是一个结构体,这个结构体内部包含各种数据结构,各种边界判定

再组织:进程PCB有一个指针指向mm_struct,便将进程与其虚拟地址空间一一对应起来,同时mm_struct内部有很多数据结构也能够自行组织

三.深入虚拟内存地址空间

1.虚拟内存地址空间与物理内存之间的映射

虚拟内存与物理内存映射图

2.一个程序经过编译器到进程运行时的过程(帮助理解虚拟与物理之间映射关系)

1).我们写的一个程序,在编译器编译成功之后,形成了一个.exe可执行文件(这是一个二进制文件)并且存储在磁盘上

提问:虚拟地址空间中的地址从何而来?磁盘中的.exe文件有没有地址呢?

答:虚拟地址空间中的地址,是编译器给的! 编译器在编译的时候,就已经为每一行代码都编好了地址(这个编好的地址是每一行代码的地址,是代码区的,并不是堆栈,堆栈在进程运行时才产生),并且在形成.exe文件的时候,代码/地址,此时已经全部存在于.exe文件中.

2).当我们准备执行这个.exe文件时,点击执行的那一刹那,程序就正式成为了一个进程,此时操作系统会为这个进程创建虚拟内存地址空间与页表,并且加载部分数据到物理内存,然后通过页表将虚拟内存与物理内存映射起来

2).0).创建虚拟内存地址空间时,会先将.exe文件中编译器给编好的地址放到代码区和全局区,然后在执行过程中,在生成对应的堆区和栈区

2).1).cpu读取/分析/执行指令时,会先去找虚拟地址,然后通过页表映射关系,找到物理地址

2).2).一些运行时的报错:比如说对只读变量做了修改,或者对野指针进行访问的非法操作

这些报错是谁报出来的?硬件,比如内存:其实是随时都仅仅只是简单的读写操作,并没有监管这么一说,那实际的报错,其实是

操作系统的介入,在页表映射的时候会记录有映射到这个物理内存空间中的权限,从而操作系统就可以根据权限来做出判断

3).当实际访问的时候,发现对应的物理地址没有数据或者代码,就直接发生缺页中断,此时操作系统执行页面调入与内存管理算法,完成之后,进行填充页表,完成映射

重点提取:

0.程序编译好生成.exe文件存在磁盘上,此时.exe内已经有地址了

1.编译器给程序生成了虚拟地址(代码区/全局区)

2.cpu执行的是虚拟地址

3.物理内存只需要按需载入

4.物理内存很傻,只是随时随地可以读取,而是在页表映射中给相应的物理内存空间赋予权限

3.虚拟内存地址空间存在的价值

从安全的角度来看: 有效保护物理内存

如果直接使用物理内存,必然会发生进程A可以访问或修改进程B的数据,甚至可以影响内核程序

使用虚拟内存然后映射到物理内存这一机制,使得每个进程彼此互不干扰甚至不知道对方的存在

同时由于中间层(虚拟内存与页表)的介入,使得可以直接拒绝(通过OS)一些危险操作

从而OS可以杀掉一些危险进程,起到保护其他进程与OS内核的作用

从管理模式的角度来看: 完成了对于进程管理模块与内存管理模块的解耦合

什么是解耦合:

如果我将所有代码都写到main函数内,这就是高耦合

如果我将代码分功能分模块的写到不同函数内,然后在main函数中只是调用这些函数,这就是低耦合

所谓的耦合,就是关联性的意思

  虚拟内存地址空间 --- 只负责进程管理

        物理内存 --- 只负责内存管理

两者互不干扰

也就是说,代码和数据被加载到物理内存上时,并不用考虑区域的问题,而是随意加载物理内存的任意位置

无论加载到哪里,都可以让虚拟地址通过映射关系,找到所对应的所在物理内存上的位置

虚拟内存地址空间+页表 可以将在物理内存中乱序的指令有序化(因为虚拟地址空间负责进程管理,它是有序的)

从效率的角度来看: 避免了不必要的空间浪费

对于空间浪费的认识:

如果我说申请了多余的空间,毫无疑问这肯定是浪费了空间

而如果说,我申请了一块空间,但这块空间没有很及时的被使用,这依旧也造成了空间浪费

有了虚拟内存地址空间与页表的介入之后,如果我们申请了一块空间,这样的申请只能说是一种约定,相当于告诉物理内存

我之后要使用这块空间,物理内存知道了,但没有立刻开辟出来,而是当我们在使用这块空间时,发现实际并没有这块空间,此时

就会发生缺页中断,然后分配物理内存,建立映射关系,再继续执行

这种延时分配的策略,提高了整机的效率,使内存的利用率几乎是100%

以上这些逻辑是由操作系统自动完成,用户不会感知到,并且对于用户来说也无需担心!

从进程独立性的角度来看: 真正意义上的实现了进程之间互不干扰,互相独立

由于每个进程都拥有自己的虚拟内存地址空间与页表,并且当cpu在执行时,总是执行虚拟地址再找到物理地址,所以对于这个          进程来说,它是不知道外部世界的,因为cpu根本不会访问外部地址(物理地址),总是通过进程内的虚拟地址找到对应的物理地

从进程挂起的角度来看: 实现了对进程分批加载分批换入换出,进而提高硬件的上限

既然可以实现延时分配内存的策略,自然也就同样可以支持分批换入换出操作

(本质都是当实际映射到的物理内存空间不存在时,通过缺页中断,来执行页面调入和内存管理算法,填充页表,完成映射)

现实生活中的例子: 当前时代的游戏,随便就有几十G大小,而我们的内存只有4G/8G/16/32G...(内存的成本非常高)

如何让高达几十甚至几百G的游戏运行起来?

很显然将几十/几百G全部加载到内存,成本消耗巨大,普通人是承受不起的,这时发现我们8G的电脑都是可以运行并且

很流畅的,这就应用到了OS对进程分批加载,并且将需要的部分换入,不需要的部分换出.让8G的内存达到了几十G的效果

如果这个进程短时间内不会被执行,就叫做阻塞

        进程的数据和代码被换出,就叫做挂起

        所以进程阻塞之后,进程的代码和数据就会被挂起!!!!!!

四.重新理解fork函数

1.为什么会有两个返回值

fork是系统调用,fork函数内部会创建子进程,当fork函数准备return时,子进程便已经被创建出来了,子进程被创建出来之后的代码父子共享(原理在第5点会介绍),此时父进程需要return,而刚刚被创建出来的子进程也要执行return,给子进程返回0,给父进程返回子进程的pid

2.为什么会有一个变量同时接收了两个返回值

我们知道了为什么表面上同一个函数为什么会有两个返回值之后

那么同一个变量,是如何接收两个不同的返回值,并且还能同时作出判断的呢???

这在我们不知道虚拟内存地址空间之前,是无法理解的

现在我们便可以给出合理并且正确的解释:

1).当我们创建出子进程,子进程会拷贝(或继承)父进程的虚拟地址空间

(为什么会继承父进程的虚拟地址空间呢? 因为在编译器编译的时候,只编译了父进程,给父进程生成了.exe文件,并且给每一行代码都进行了编址,那么在父进程运行时,子进程凭空出现,自己成的虚拟地址又怎么样让它生成呢?直接简单粗暴,拷贝父进程的!)

2).假设接收返回值的变量命名为id,这时子进程和父进程对于id这个变量拥有相同的虚拟地址(因为子继承的父),并且通过映射指向的是同一块物理内存空间

3).当第二次进行对id的写入时,会通过虚拟地址然后通过映射机制找到物理内存,并且知道这一次写入的(第二次写入)是父还是子,发现第一次已经写入过一次了,所以第二次就会重新为其开辟新的空间并且建立新的映射关系,此时父子表面上对于id的虚拟地址是相同的,但是实际指向的物理内存却是两块不同的空间

画图解释:

3.写时拷贝

创建子进程时只需要将未来会写入的数据再重新拷贝一份

但是操作系统并不知道哪些数据将来会被写入,即便是知道了提前就开辟好一块新的空间并且拷贝,这样长时间占用内存却不使用的情况也会造成空间浪费

所以操作系统所采用的方法是写时拷贝,只有当数据被写入时,才会发生拷贝

这一理念在C++的深浅拷贝中也有所体现!

4.父子进程之间的关系

1).子进程被创建出来时,会拷贝父进程的虚拟地址空间

2).共享子进程被创建完之后的代码(fork之后),也就是父子都从那一刻同时执行之后的代码

3).父与子是两个独立的进程,具有独立性,不相互影响不相互依赖

4).代码是只读的,所以代码层面上的共享,对于他俩的独立性而言没有影响

5).数据是可以被修改的,所以必须分离

5.fork如何实现的调用fork之后父子进程代码共享

由于进程具有并发性,所以随时有可能中断,所以必须要有多个硬件(寄存器)来存放当前进程执行到的数据与状态(例如进程执行到的位置),以便将来用于继续执行还未执行完的进程,这种寄存器内,存储的数据称为:进程的上下文数据

其中EIP寄存器,就是用来存放cpu将要读取的下一条指令的地址

创建好子进程之后,子进程认为从EIP开始执行,所以就会从父进程的EIP记录处为起始,开始继续向下执行指令

【Linux】进程概念 —— 虚拟内存地址空间相关推荐

  1. 深入理解Linux进程概念

    Linux进程概念 冯·诺依曼体系结构: 冯诺依曼体系结构作为现代计算机硬件体系结构,规定了现代计算机应该具有哪些硬件单元 输入设备  :键盘,鼠标,图像采集设备  ,声音采集设备 输出设备:显示器 ...

  2. 实验五 显示进程的虚拟内存地址空间分布信息

    实验五 显示进程的虚拟内存地址空间分布信息 目录 实验五 显示进程的虚拟内存地址空间分布信息 实验环境 一.实验目的 二.实验内容 三.实验步骤 四.实验总结 实验环境 操作系统版本:ubuntu-1 ...

  3. 【Linux从青铜到王者】第五篇:Linux进程概念第一篇

    系列文章目录 文章目录 系列文章目录 前言 一.冯诺依曼体系结构 二.操作系统 1.操作系统的概念 2.操作系统的目的 3.操作系统的定位 4.如何理解管理 5.操作系统总结 6.系统调用和库函数概念 ...

  4. Linux打怪通关攻略(16)Linux 进程概念

    文章目录 Linux 进程概念 概念理解 进程的属性 进程的分类 进程的衍生 进程组与 Sessions 工作管理 Linux 进程概念 概念理解 首先程序与进程是什么?程序与进程又有什么区别? 程序 ...

  5. 【Linux进程概念】 (4)进程地址空间

    我们在编写C代码时往往出现一个错误就是变量地址的非法访问和使用,如果此时的地址对应内存中真实的物理地址,那么我们造成的多数错误可能会导致系统崩溃.所以关于地址空间的分布不是我们想象的那么简单,接下来我 ...

  6. Linux进程概念4 程序地址空间

    一. 程序地址空间 此时我们的研究是在kernel 2.6.32和32位平台进行说明 1. 程序地址空间图 我们在学习C/C++时候,应该学习过内存布局,以及了解各种变量的存储位置,例如局部变量存储在 ...

  7. 【Linux进程概念】冯 诺依曼体系结构 操作系统 进程 fork 进程状态 优先级

    文章目录 [写在前面] 一.冯 ? 诺依曼体系结构 ?? 体系结构 ?? 数据流向 ?? 实例 二.操作系统 (Operator System) ?? 概念 ?? 计算机体系及操作系统定位 ?? 管理 ...

  8. 【Linux】四、Linux 进程概念(上篇)

    目录 前言 一.冯诺依曼体系结构 1.1 冯诺依曼体系结构是什么 1.2 冯诺依曼体系结构为什么这么设计 1.2.1 思考 1.2.2 了解一下计算机的存储分级 1.2.3 解释 1.3 往下要明确几 ...

  9. Linux ——进程的虚拟地址空间,逻辑地址和物理地址,进程管理命令

    进程的虚拟地址空间 进程有自己的独立地址空间,每启动一个进程,系统就会为它分配地址空间,虚拟地址空间的大小由计算机的硬件平台决定,比如32位的平台决定了虚拟地址空间为4G(因为32位系统上指针能够寻址 ...

最新文章

  1. 深度学习——数据预处理篇
  2. Vim 键盘指令高清图
  3. python打地鼠游戏代码_妈妈和宝宝在家,自己做了个打地鼠游戏,网友:宝宝笑得好开心...
  4. ubuntu14.04下通过.frm, .MYD,.MYI文件恢复建立mysql数据库
  5. 虚函数实现多态---C++
  6. Microsoft Edge 浏览器开始支持webkit私有样式
  7. UI设计干货模板|输入框设计临摹素材
  8. ZooKeeper学习第一期---Zookeeper简单介绍
  9. .net remoting 使用事件
  10. 微软在线实验室启用谷歌的reCAPTCHA,我们又丢失了一个好东东
  11. pmp 第六版 模拟卷1疑难问题
  12. 支付:在线支付功能的概述
  13. PAT乙级-1028人口普查
  14. 【190302】VC+ 视频捕捉与录像+实例源码源代码
  15. 学习Python的第七天
  16. Objective-C学习笔记(二)——OC基本语法概述
  17. 超详细面试准备(10分钟打遍所有初级后端开发面试)
  18. 从前慢-Mysql高级及实战
  19. 英语钻石法则(一)-----句子中心论
  20. 小米Redis的K8s容器化部署实践

热门文章

  1. 质因数分解求给定正整数的因数个数
  2. 泉州水产大佬苏德义没落调查 亿万富翁变负翁
  3. WIN7及WIN环境下APEX(Oracle Application Express )安装(最简方式)
  4. 07 情商上来了-自卑也能出奇迹
  5. Mininet系列实验(四):基于Mininet测量路径的损耗率
  6. Ubuntu 20.04.5安装NVIDIA显卡驱动
  7. 最重要的技术深入学习
  8. 软考高级-系统架构师-案例分析-案例题2
  9. java record用法_Java14不得不知的5个新功能
  10. 阿里云架构师梁旭:MES on 云盒,助力客户快速构建数字工厂