进程地址空间(虚拟地址 | 物理内存)
文章目录
- 前言
- 一、简单理解地址空间
- 二、虚拟地址
- 现象解释
- 三、三个问题搞懂地址空间
- 1. 什么是地址空间?
- 2. 为什么要有地址空间?
- 3. 地址空间是如何工作的?
- 四、一些补充
前言
在之前的学习中,我们只学习了图中的下半部分(用户空间),当时还并未涉及到操作系统。
我们写一段代码来验证一下这张图。
#include<stdio.h>
#include<stdlib.h>int g_val = 100; //初始化
int g_unval; //未初始化
int main(int argc, char* argv[], char* env[])
{ // 以main函数为代表,打印代码区printf("code addr : %p\n", main); // 初始化数据printf("val addr : %p\n", &g_val);// 未初始化数据printf("unval addr : %p\n", &g_unval);char* mem = (char*)malloc(10);// 堆printf("heap addr : %p\n", mem);// 栈:指针变量(函数内,局部变量)printf("stack addr : %p\n", &mem);// 第一个命令行参数printf("opt addr : %p\n", argv[0]);// 最后一个命令行参数printf("opt addr : %p\n", argv[argc - 1]);// 环境变量printf("env addr : %p\n", env[0]);
}
运行结果 ↓
按照之前的说法,在C程序地址空间当中,地址的增长方向是自底向上。分别是代码段、已初始化全局数据区、未初始化全局数据区、堆区、栈区、命令行参数、环境变量。
其中,堆栈相对而生。栈向地址减小方向生长,堆向地址增大方向生长。堆栈之间的区域称为共享区。
再往上,叫做内核空间。
有两个小细节:
1、代码段不是从0号地址开始的,它有一个确定的地址。
2、共享区里一般放的是动态库,共享内存等。
这是以前的说法,是不准确的。
本文将详细介绍对于地址空间的认知。
首先要纠正的是,之前的程序地址空间应该叫做,进程地址空间。
一、简单理解地址空间
我们知道猫咪是具有领地意识的,那么,是不是这片领地,猫咪随时都在使用呢? 不是的。
它只是限定了一片区域,属于它自身的活动范围。
所谓进程(猫咪)的地址空间(活动范围),进程不是随时随地使用这个地址空间内所有的资源。
【第一层理解】地址空间的作用:仅仅限定了一块内存空间,属于当前进程的一段活动范围,并不代表这个进程把所有资源都占有了。
二、虚拟地址
我们先来看一段代码
#include<stdio.h>
#include<unistd.h>
int g_val = 100;int main()
{pid_t id = fork();if(id == 0){//parentg_val = 1000;while (1){printf("g_val:%d, addr : %p child \n", g_val, &g_val);sleep(1);}}else if (id > 0){//childwhile(1){sleep(3);printf("g_val:%d, addr : %p father \n", g_val, &g_val);sleep(1);}}else{//error}
}
fork之后,父子进程谁先运行是由调度器决定的(随机的)。
让子进程先运行,修改全局变量g_val的值,观察现象。
在Linux中的运行结果是这样的。
我们发现,父进程打印的g_val依然是100。同时,父子进程打印的地址是一样的!
内存中的同一个地址,被不同的进程读取,打出来的值却完全不一样。
这是不可能发生的。
【第二层理解】所以,我们可以知道——地址空间绝对不是物理内存。
进程和物理内存之间还存在一个虚拟层,叫做进程地址空间。
它其实是一种可被父进程指向的数据结构,父进程能看到的所有的内存的现象(或者叫布局情况)是被地址空间所解释的。而这部分区域可以在物理内存的任意区域被映射,此时就可以通过进程地址空间的内容访问物理内存中被映射的部分。
我们运用语言在屏幕上打印出来的地址其实都叫做“虚拟地址”。
从上面的例子,可以得出结论:
这里的地址,绝对不是物理地址,而是叫做虚拟地址。父子进程访问的数据,绝对被保存到了不同的物理内存中。
(只有物理内存有保存数据的能力,所谓的地址空间它仅仅是一种区域的划分。)
现象解释
为什么同样的地址,呈现的却是不一样的内容?
在创建子进程且子进程没有改写全局变量时,因为没有写入,只是读取,操作系统本着不浪费空间的目的,父子进程默认共享这段空间。
进程的运行是具有独立性的。体现在数据层面上先进行独立!
一旦!有任意一方,对数据进行了修改,父子进程就要进行数据的独立。单独给修改数据的这一方重新开了一块空间,重新构建一种映射关系。
在整个操作过程中,父进程是没有受任何影响的,且对子进程来说,只影响了子进程物理内存单独开辟的空间。它本身的虚拟地址是没有发生变化的,变的只是虚拟地址到物理地址的映射关系。
简单来说就是,
二者虚拟地址相同,但被映射到了不同的物理内存处。所以打出来的值也是不同的!
【第三层理解】地址空间是对物理内存的一种虚拟化表示。
进程只能看到虚拟地址,操作系统会帮我们把虚拟地址转化为物理地址。
进程的地址空间是内存吗?不是,给进程看到的永远都是虚拟地址,把物理内存保护起来。
三、三个问题搞懂地址空间
1. 什么是地址空间?
- 本质是描述进程所占有空间的一张表,而它在操作系统内核当中,就是一个数据结构,这个数据结构,将我们的内存划分成了若干种区域,分别对应我们学过的那些区域。
每个进程都有一个进程的地址空间,系统中可能存在多个进程,每个进程透过地址空间访问内存,所以,系统中一定存在多个地址空间。所以,地址空间也要被管理起来。
只要是管理,就要先描述,再组织。
- 先描述:
地址空间本质就是一个数据结构,
在Linux当中,叫做struct mm_struct(linux内核当中的地址空间结构体)
它包含了一些区域信息,能够实现区域划分。
struct mm_struct
{//划分区域Unsigned long code_start;Unsigned long code_end;Unsigned long init_data_start;Unsigned long init_data_end;Unsigned long uninit_start;Unsigned long uninit_end;Unsigned long head_start;Unsigned long head_end;....
}
申请空间的本质是:向内存索要空间,得到物理地址。然后在特定区域申请没有被使用的虚拟地址,建立映射关系,返回虚拟地址即可。
- 再组织:
进程的PCB(task_struct)里有指向mm_struct的指针。
mm_struct相当于一张进程的执行地图。
我们知道一个进程 = PCB + 代码数据
因此,就把进程的PCB和mm_struct关联起来了。
2. 为什么要有地址空间?
假设如果没有地址空间,那么进程访问的地址都是物理地址。
当发生非法操作或异常的情况时,有可能就破坏其他空间的代码或数据。同时,也大概率会存在一个进程的数据存放的内存位置,是不连续的(代码和数据不连续)。
会导致
- 访问特别不方便
- 增加了异常越界的概率
为修正这些问题,计算机设计者想到了给每个进程创建一个虚拟地址空间。
解决了问题
- 保护物理内存:一个进程只会映射自己的数据,如果越界访问别人的空间,那么在页表当中是查找不到这种映射关系的,操作系统直接进行终止。也就是,只要进入内存访问了,一定是经过页表合法转化过来的。
- 页表中有相关的权限管理。可以通过页表的结构来限制一个进程对物理内存空间的访问。
- 转换的过程是操作系统做的。从最初的由用户去直接访问物理内存,改成了由操作帮我们访问,那么进程做的任何非法操作,操作系统可识别到。
- 同时做了空间连续化处理,方便使用。让我们的地址信息以连续的形式暴露给用户。
3. 地址空间是如何工作的?
地址空间上所呈现暴露给上层的所有的地址都叫做虚拟地址,而实际进程访问时,要通过页表映射转换到物理内存,然后拿到对应的代码和数据。
四、一些补充
通过认识地址空间,我们可以再次清晰进程!
- 进程从硬盘加载到内存,操作系统为其创建task_struct 和 mm_struct 并且用指针把二者关联起来。
然后,虚拟地址(mm_struct)通过页表和物理内存建立一种关系。
补充PCB中的内容分类说明:
内存指针:进程的pcb中包含了可以帮我们找到它代码和数据的指针信息。
程序计数器:保存当前正在执行指令的下一条指令的地址。
上下文数据:一个进程在CPU上运行会产生各种临时数据(就叫做当前进程的上下文),CPU内部保存数据的区域只有一份。
当进程切换时:时间片到了,或者有更高优先级的进程过来时,当前进程要发生切换,切换时要进行上下文保护;重新恢复回来时,要进行上下文恢复。保证这个进程能够接着上次被中断的位置继续运行。
进程地址空间(虚拟地址 | 物理内存)相关推荐
- [入门篇]用史上最生动的方式让你一篇博客搞懂Linux进程地址空间,包看包懂!
目录 0.前言 1.初始程序的地址空间划分 1.1程序地址空间图解 1.2程序地址空间区域划分验证 1.3 程序地址空间小补充 1.4 引入进程地址空间 *2. 两个生动的例子理解进程地址空间 2.1 ...
- 两个不同的进程 虚拟地址相同_Linux的进程地址空间[一]
所谓进程地址空间(process address space),就是从进程的视角看到的地址空间,是进程运行时所用到的虚拟地址的集合. 32位系统的进程地址空间 以IA-32处理器为例,其虚拟地址为32 ...
- Linux操作系统~什么是虚拟地址?深度剖析进程地址空间
目录 1.所以进程的地址空间是什么呢? 2.mm_struct内部有什么? 3.虚拟地址空间与物理内存如何关联 页表 4.为什么设计这样一个进程地址空间,不让程序直接访问内存 Q:为什么子进程修改值以 ...
- Linux 内存管理 详解(虚拟内存、物理内存,进程地址空间)
Linux -操作系统内存管理 存储系统 存储器的层次结构 Linux的内存管理 物理内存 物理内存管理 虚拟内存 虚拟地址空间 (写时拷贝) 和物理地址映射关系 页表 虚拟内存优缺点 「在 4GB ...
- 【Linux进程概念——下】验证进程地址空间的基本排布 | 理解进程地址空间 | 进程地址空间如何映射至物理内存(页表的引出) | 为什么要存在进程地址空间 | Linux2.6内核进程调度队列
文章目录 [写在前面] 一.回顾与纠正 二. 验证进程地址空间的基本排布 三. 什么是进程地址空间
- linux 进程 地址空间 内存分布 简介
目录 一 进程空间分布概述 二 内核空间和用户空间 三 进程内存布局 栈 内存映射段 堆 BBS和数据段 C语言程序实例 栈与堆的区别 一 进程空间分布概述 对于一个进程,其空间分布如下图所示: 程序 ...
- Linux进程地址空间学习总结
Linux内核--内核地址空间分布和进程地址空间 http://www.cnblogs.com/bizhu/archive/2012/10/09/2717303.html 内核地址空间分布 直接映射区 ...
- Linux进程地址空间与进程内存布局详解,内核空间与用户空间
Linux进程地址空间与进程内存布局详解 程序段(Text):程序代码在内存中的映射,存放函数体的二进制代码. 初始化过的数据(Data):在程序运行初已经对变量进行初始化的数据. 未初始化过的数据( ...
- linux 进程地址空间的一步步探究
我们知道,在32位机器上linux操作系统中的进程的地址空间大小是4G,其中0-3G是用户空间,3G-4G是内核空间.其实,这个4G的地址空间是不存在的,也就是我们所说的虚拟内存空间. 那虚拟内存空间 ...
最新文章
- 转:ECharts图表组件之简单关系图:如何轻松实现另类站点地图且扩展节点属性实现点击节点页面跳转...
- Python time 100 天以后的日期
- jQuery获取元素内容
- LCD驱动移植(二)
- anaconda管理环境
- springboot自动配置_揭秘SpringBoot自动化配置
- HDU 3507 Print Article(单调队列)
- Netty源码分析第3章(客户端接入流程)----第1节: 初始化NioSockectChannelConfig
- Apache HBase 最新发布2.0.4 ,分布式数据库
- x64 盗版PCHunter
- usb转232串口线驱动android,prolific usb转串口驱动下载
- 计算机更新bios,GIGABYTE How to Reflash VGA BIOS
- java一个字符几个字节_Java 语言中一个字符占几个字节?
- vue实现浏览器桌面通知
- 前端 pdf 预览功能
- 又一批大学生毕业了,献上天津大学校园里的励志标语
- 运营——线上引流9大方法
- 数据治理:数据治理框架和标准
- js加密变量_Android上的Cloudbuild —使用加密的环境变量
- bWAPP中利用反射型xss获取cookie