[Linux]进程地址空间
创始人
2024-05-16 21:40:33
0

🥁作者华丞臧.
📕​​​​专栏:【LINUX】
各位读者老爷如果觉得博主写的不错,请诸位多多支持(点赞+收藏+关注)。如果有错误的地方,欢迎在评论区指出。
推荐一款刷题网站 👉 LeetCode刷题网站


文章目录

  • 前言
  • 一、进程地址空间
    • 1.1 程序地址空间回顾
    • 1.2 地址空间的存在
    • 1.3 进程地址空间
    • 1.4 程序如何变成进程?
    • 1.5 如何理解进程的独立性
    • 1.6 为什么要有虚拟进程地址空间?


前言

在之前的学习中,我们通常知道代码和数据存放在程序地址空间不同的几个区域,如:代码区、常量区、静态区、堆区、栈区等;那么我们说的这个地址空间是内存吗?

一、进程地址空间

1.1 程序地址空间回顾

在这里插入图片描述
前面粗略地学习过程序地址空间,更准确地应该称为进程地址空间;进程地址空间不是C/C++是操作系统上概念,并且进程地址空间不是内存。

//进程地址空间的验证
#include 
#include 
#include int un_g_val;
int g_val = 100;int main(int argc, char *argv[], char *env[])
{char *m1 = (char*)malloc(sizeof(char));char *m2 = (char*)malloc(sizeof(char));char *m3 = (char*)malloc(sizeof(char));char *m4 = (char*)malloc(sizeof(char));printf("code addr           : %p\n", main);printf("init global addr    : %p\n", &g_val);printf("uninit global  addr : %p\n", &un_g_val);printf("heap addr           : %p\n", m1);printf("heap addr           : %p\n", m2);printf("heap addr           : %p\n", m3);printf("heap addr           : %p\n", m4);printf("stack addr          : %p\n", &m1);printf("stack addr          : %p\n", &m2);printf("stack addr          : %p\n", &m3);printf("stack addr          : %p\n", &m4);printf("argv addr           : %p\n", &argv);int i = 0;for(;env[i]; ++i){printf("env addr           : %p\n", &env[i]);}return 0;
}

堆区向地址增大的方向增长,栈区向地址减小的方向增长;一般在C函数中定义的变量,通常在栈上保存,那么先定义的一定是地址较高的。
在这里插入图片描述

如何理解static变量?

函数内定义的变量用static修饰,本质是编译器会把该变量编译进全局数据区。

1.2 地址空间的存在

首先来看下面这一段代码:

//code1.c
#include 
#include int g_val = 100;int main()
{pid_t id = fork();if(id == 0){int i = 0;//childwhile(1){printf("我是子进程:%d,ppid:%d,g_val:%d,&g_val:%p\n", getpid(), getppid(), g_val, &g_val);sleep(1);++i;if(i == 5){printf("我是子进程,全局变量已被修改\n");g_val = 200;}}}else if(id > 0){//parentwhile(1){printf("我是父进程:%d,ppid:%d,g_val:%d,&g_val:%p\n", getpid(), getppid(), g_val, &g_val);sleep(3);}}return 0;
}

当父子进程没有修改全局变量时,如下图:我们发现父子进程的全局变量数据一模一样并且地址相同,那么可以确定父子进程是共享该数据的。
在这里插入图片描述

当子进程修改全局数据时,如下图所示:我们发现子进程的全局数据修改并没有影响父进程的全局数据,但是父子进程全局数据的地址是相同的,显然同一块空间不可能存放两个不同的值,所以我们可以肯定在C/C++中使用的地址都不是物理地址
在这里插入图片描述

通过上述代码实现,我们可以感觉到实际上物理内存和我们的操作系统之间隔了一层地址,这个地址是虚拟地址,是一种线性地址逻辑地址。

为什么操作系统不让用户直接访问物理内存呢?

内存是一个硬件,不能拦截用户访问,只能被动的进行读取和写入,让用户直接访问存在风险。

1.3 进程地址空间

进程地址空间不是物理内存,它是一种虚拟地址,介于操作系统和物理内存之间;那么该如何理解呢?

首先我们需要知道虚拟地址并不是真正存在的物理空间,而是逻辑地址是操作系统给进程画的蓝图;简单来说就是操作系统给进程画饼说你可以独占所有资源,进程可以随意使用,而且进程之间并不知道对方存在。
32位的机器上进程地址空间大小为4G,而实际上我们自己编写出的进程不会使用4G的进程地址空间,而是每次都申请进程需要的空间大小。

操作系统如何画饼呢?画饼本质是在你的大脑中描绘蓝图,而在操作系统中描述某种对象通常使用数据结构。
结论:进程地址空间本质是内核的一种数据结构。
每个进程都有一个自己的进程地址空间,操作系统通过其数据结构管理这些进程地址空间,管理的本质是先描述再组织,因此进程地址空间被操作系统中的struct mm_struct描述再被链表组织,此时进程地址空间就能被操作系统管理。实际上,进程PCB中有指向进程地址空间的指针即PCB中有特定的指针指向进程的mm_struct。

//mm_struct实际上是描述进程地址空间是对其空间各个区域大小的描述
//类似下面这种方式
struct mm_struct
{	//代码区long code_start;long code_end;//初始化数据区long init_start;long init_end;//未初始化数据区long uninit_start;long uninit_end;//堆区long heap_start;long heap_end;//栈区long stack_start;long stack_end;
}

进程地址空间的好处:

  1. 虚拟地址空间会让进程认为自己独占操作系统的资源,进程之间并不知道对方的存在;
  2. 所有进程都可以用统一的方式进程描述,因为虚拟地址空间是一样的,方便操作系统管理进程。

1.4 程序如何变成进程?

所谓进程地址空间其实就是操作系统通过软件的方式,给进程提供一个软件视角,认为自己会独占系统所有资源(主要指内存)。
那么程序如何变成进程的呢?操作系统又是如何通过进程地址空间运行进程的呢?

首先,程序本质是磁盘上的文件,即编译出来的可执行文件;程序内部肯定有地址,也肯定划分了区域,理由是一个代码经过编译形成可执行文件需要四个阶段:预编译、编译、汇编、链接,其中汇编会将程序中的全局符号及其地址汇总成一个一个的符号表,各个符号表在链接时合并。
其次在LInux中,通过指令可以查看可执行文件的内容,如下图:

在这里插入图片描述

虚拟地址空间不仅仅是操作系统会考虑,编译器也会考虑,编译程序的时候就认为程序是按照0000~FFFF进行编址的。程序内部的地址可以看做是虚拟地址空间,和内存的地址没有关系。

  1. 内存地址是物理地址;
  2. 进程地址空间是虚拟地址;
  3. 代码和数据是加载到内存中的;
  4. 操作系统管理进程通过PCB来管理;
  5. 操作系统管理进程地址空间通过mm_struct来管理;

因此操作系统要执行进程代码获得进程的数据,可以通进程PCB找到进程mm_struct,通过mm_struct操作系统可以执行进程的代码也可读取进程的数据,进程地址空间通过页表和实际内存地址相关联,通过页表可以找到进程在内存中的代码和数据。

在Linux当中,映射进程地址空间和内存地址的表称为页表。

在这里插入图片描述

1.5 如何理解进程的独立性

在前面的测试当中,fork创建子进程,父子进程同时运行,同样地址的全局变量,子进程改变全局变量,父进程不会改变。
在这里插入图片描述
这体现了进程的独立性,即多进程运行,独享个中资源,运行期间进程互不干扰。

当我们使用fork创建子进程时,子进程会继承父进程的一切包括进程控制块PCB(task_struct),也包括mm_struct和页表,因此子进程也会继承mm_struct和页表;那么子进程就有了自己的PCB并且该PCB与父进程相同。
这也就是为什么我们看到开始时,父子进程全局变量的值和地址都相同。
在这里插入图片描述

当有进程试图改变g_val时,该进程就进行写时拷贝,即:任何一方尝试写入,操作系统先进行数据拷贝,更改该进程的页表映射,然后再让进程进行修改。(这就是写时拷贝

在这里插入图片描述

1.6 为什么要有虚拟进程地址空间?

1. 保护内存,访问内存添加了一层软硬件层,可以对转化过程进行审核,非法访问,就可以直接拦截了。
2. 进程和进程代码数据的解耦,通过地址空间进行功能模块的解耦,即进程管理和内存管理解耦。
3. 让进程或者程序可以以一种统一的视角看待内存,方便以统一的方式来编译和加载所有的可执行程序,简化进程本身的设计与实现。

相关内容

热门资讯

安卓系统应用数据目录,揭秘系统... 你有没有想过,你的安卓手机里那些应用,它们的数据都藏在哪个角落呢?今天,就让我带你一探究竟,揭开安卓...
kindle 安卓 系统 刷机... 亲爱的读者们,你是不是也和我一样,对电子阅读设备情有独钟?尤其是那款小巧便携的Kindle,简直是阅...
平板 win 安卓 双系统,... 你有没有想过,拥有一台既能运行Windows系统,又能流畅使用安卓应用的多功能平板电脑,是不是能让你...
电脑安卓和苹果系统,电脑操作系... 你有没有发现,现在无论是工作还是娱乐,电脑已经成了我们生活中不可或缺的好伙伴呢!而在这众多电脑中,安...
手机安卓系统下载5.0,引领智... 你有没有发现,最近手机界又掀起了一股热潮?没错,就是安卓系统5.0的下载。这可是安卓家族里的一大亮点...
小森生活系统安卓,打造绿色生态... 你知道吗?最近在手机应用市场上,有个叫做“小森生活系统安卓”的新玩意儿火得一塌糊涂。它就像一个神奇的...
王者荣耀安卓系统更换,畅享全新... 最近是不是发现你的王者荣耀游戏体验有点不对劲?别急,让我来给你揭秘一下安卓系统更换背后的那些事儿!一...
ios系统数据如何导入安卓系统... 你是不是也有过这样的经历:手机里存满了珍贵的照片、视频和联系人,突然有一天,你决定换一台安卓手机,却...
王者荣耀启动安卓系统,畅享指尖... 你知道吗?最近王者荣耀可是大动作连连,竟然宣布要启动安卓系统了!这消息一出,瞬间在游戏圈引起了不小的...
安卓始终显示系统栏,安卓系统栏... 你是不是也遇到了这个问题?手机屏幕上那个讨厌的系统栏,有时候它就像一个顽皮的小鬼,总是赖在你的屏幕上...
苹果系统可以装在安卓,探索跨平... 你知道吗?最近在科技圈里可是掀起了一股热潮呢!那就是——苹果系统竟然可以装在安卓设备上!是不是听起来...
安卓系统双清目的,安卓系统双清... 你知道吗?最近在安卓系统圈子里,有个话题可是热得不得了,那就是“双清”。别小看这个“双清”,它可是关...
安卓系统台电平板,畅享智能生活... 你有没有发现,最近身边的朋友都开始讨论起一款叫做台电的安卓系统平板电脑呢?这可不是随便说说,这款平板...
三菱安卓系统,智能科技与驾驶体... 亲爱的读者,你是否曾好奇过,那些在我们生活中默默无闻的汽车品牌,它们是如何将科技与驾驶体验完美结合的...
安卓系统为什么好,引领智能生活... 你有没有发现,身边的朋友、同事,甚至家人,几乎人手一台安卓手机?这可不是偶然现象哦!安卓系统,这个来...
安卓如何改键盘系统,Andro... 你是不是也和我一样,对安卓手机的键盘系统有点儿不满意?想要换一个更顺手的键盘,但又不知道怎么操作?别...
怎么升级安卓14系统,解锁安卓... 你有没有发现,你的安卓手机最近是不是有点儿慢吞吞的?别急,别急,升级到安卓14系统,让你的手机焕发新...
安卓手机如何系统内录,轻松生成... 你有没有想过,有时候想要记录下手机里的精彩瞬间,却发现没有合适的工具?别急,今天就来教你怎么在安卓手...
最绚丽的安卓系统,最绚丽版本全... 哇,你知道吗?在安卓的世界里,有一款系统,它就像是一颗璀璨的明珠,闪耀着最绚丽的色彩。它就是——最绚...
小米系统安卓通知权限,深度解析... 亲爱的手机控们,你是否曾为手机通知栏里乱糟糟的信息而烦恼?又或者,你是否好奇过,为什么有些应用总是能...