【Linux】程序地址空间

初识程序地址空间

在这里插入图片描述
运行结果如下:
在这里插入图片描述
  我们发现父进程和子进程打印出来的值和地址都是一样的,我们暂时理解为子进程是以父进程为模板创建的,且父进程和子进程都没有对变量进行修改
在这里插入图片描述
运行结果如下:
在这里插入图片描述
  通过运行结果可以发现:即使是在子进程中对变量做了修改,但是打印出来的变量地址却是不一样的,可是变量的内容却又不一样。此时的变量仿佛就是随时可变的。此刻我们不妨大胆假设,这看似是一个变量,但是实际上已经是两个变量了,即变量的内容不一样,所以才会导致父子进程输出的变量绝对不会是同一个变量
  既然已经不是同一个变量了,虽然打印出来的地址是一样的,这就意味着这个地址不会是真实的物理地址
  在Linux中,这种地址叫做虚拟地址或者是线性地址
  其实,我们平时所用的语言级别的地址都是虚拟地址而非物理地址,物理地址是用户看不到的,而是由操作系统来统一管理,所以有得出一条很重要的结论:操作系统必须负责将虚拟地址转化为物理地址

进程地址空间

  其实,程序地址空间这个说法是不太准确的,准确的应该称作为进程地址空间
在这里插入图片描述
什么是进程地址空间?
  每个进程都为了自身需要一定的内存空间,由于进程很多,随之而来的空间的需求也逐渐变大,操作系统为了更好的管理进程及其对应的空间,就有了进程地址空间这样一个概念,地址空间实质上是以一个数据结构,具体到某个进程,就是特定的数据结构对象。

//可以将地址空间理解为这样
struct mm_struct//进程地址空间
{
	int code_start;
	int code_end;
	int heap_start;
	int heap_end;
	//...(其余相关属性,用整数描述起来的各个区域)
	struct mm_struct*next;
}

  地址空间是一个内核数据结构,该数据结构中存的是各个区域的划分。但是要注意的是地址空间并不具备对代码数据的保存能力,数据和代码都是被存放到物理内存中的。
  也就意味着,当我们拿到地址空间上的地址后(虚拟地址),还需要将这个地址经过一定的转化变成真实地址才能拿到具体的数据。于是,操作系统就会为进程提供一张用于映射的表——页表

一些补充

虚拟地址转物理地址的工作谁来做?

  CPU中存在一个寄存器叫做CR3,保存的是当前进程页表的起始地址(存的是物理地址),当要对某个数据进行修改的时候,就会通过CR3来找到页表,进而通过页表将虚拟地址找到该数据的真实物理地址,就可以访问到物理内存上的数据。而查找和转化工作是由CPU中的一个硬件叫做mmu(memory management unit可翻译成内存管理单元)完成

进程地址空间存在的意义

  1. 将物理内存从无序变为有序,让进程以统一的视角看待内存
  2. 将进程管理和内存管理进行解耦合(即进程管理和内存管理各管各的,相互不影响)
  3. 内存访问权限控制:页表可以记录每个页(页面)的访问权限,包括读、写、执行等权限。当程序试图访问不属于自己的内存页或越界访问时,硬件和操作系统可以根据页表的权限设置进行拦截和处理,从而防止非法的内存访问,确保内存的安全性。
  4. 隔离不同进程:通过使用地址空间和页表,操作系统可以将不同进程的内存空间隔离开来,确保它们互相不会干扰对方的内存数据。每个进程都有自己的地址空间和页表,这样即使多个进程运行在同一台计算机上,它们也不会相互影响。

回看new/malloc

#include<stdio.h>
#include<stdlib.h>

int main()
{
	int*p=(int*)malloc(sizeof(int));
	free(p);
	return 0;
}

当我们申请一段空间时,我们会直接使用吗?
  答案是不一定,就像上面的代码,我申请了一个整形的空间,但是却没有使用,此时操作系统在执行这段语句的时候,并不会立刻分配一个整形的空间,而是当我在真正写入数据的时候,操作系统才在物理内存中开辟空间并在页表中建立从虚拟地址到物理地址的映射关系(在操作系统进行这些操作的时候,会发生缺页中断),然后中断恢复,最后再进行写入操作。在缺页中断之前,也就是操作系统并没有在物理内存中申请空间之前,p中存的只是一个没有建立映射关系的虚拟地址

进程中的写时拷贝

  之前的有关进程的博客就提到过,子进程和父进程是共享数据的,当子进程或者是父进程要对中间的数据做一些写入的时候,就会发生写时拷贝,这里就针对于写时拷贝做一些更详细的介绍
为什么要存在写时拷贝,直接在内存中重新申请一块空间不可以吗
  如果说只是对原数据做一点修改的话(比如位操作),不拷贝怎么知道原来的数据是什么

怎么实现的写时拷贝?
  其实之前的页表并不是很完整,页表上不只是包括了从虚拟地址到物理地址的映射关系,还包括了对于读写权限的限制等,这里的话就只增加一个权限位
在这里插入图片描述
  数据段征程情况下是可以进行读写的,但是当父进程创建了子进程之后,数据段的权限就变成了只读(此时父子进程的页表中的权限位全部都是只读),后来当某个进程要修改数据段(假设该数据存在于栈区)的某个数据并尝试写入时,因为当前的映射条件是只读,要修改数据就会导致转化工作出问题,操作系统此时介入,并触发缺页中断。
  操作系统一看,这个数据段存在于栈区,确实是可以做写入,操作合理,但是此时页表上的写的权限为已读,操作系统就会进行写时拷贝,建立映射关系,并对相应的权限进行修改

一点延申

int main()
{
	const char*str="hello world";
	*str='H';
	return 0;
}

  对于以上代码的结果,我们不难看出当对于字符串进行修改的时候,时会出现报错的,因为字符串常量在常量区,不可以被修改
  但是现在,我们要对于所谓的“常量区”有更进一步的理解,什么叫做常量区?
  str保存的是这个常量字符串的起始地址,而这个地址是虚拟地址,当进行解引用并试图将字符H写入到字符串中时,注定要发生由虚拟地址到物理地址的转换,此时的权限为“只读”,页表根据权限判定写入操作不符合要求所以会失败
  正是因为存在页表,而页表中存在权限位,就有了语法中的“常量不可以被修改”的结论

相关推荐

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-03-20 23:22:01       98 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-03-20 23:22:01       106 阅读
  3. 在Django里面运行非项目文件

    2024-03-20 23:22:01       87 阅读
  4. Python语言-面向对象

    2024-03-20 23:22:01       96 阅读

热门阅读

  1. MongoDB聚合运算符:$floor

    2024-03-20 23:22:01       45 阅读
  2. 安卓面试题多线程 61-65

    2024-03-20 23:22:01       35 阅读
  3. Typescript泛型

    2024-03-20 23:22:01       42 阅读
  4. 5.1.1.1、【AI技术新纪元:Spring AI解码】功能调用

    2024-03-20 23:22:01       37 阅读
  5. SpringBoot 如何快速过滤出一次请求的所有日志?

    2024-03-20 23:22:01       42 阅读
  6. rtt自动初始化机制学习

    2024-03-20 23:22:01       45 阅读
  7. Linux 系统编程

    2024-03-20 23:22:01       37 阅读
  8. 在vue中使用海康web3.2插件连接云台摄像机

    2024-03-20 23:22:01       40 阅读
  9. C++: 多态实现原理解析

    2024-03-20 23:22:01       41 阅读
  10. 合成孔径雷达(SAR)中的雷达/信号相位

    2024-03-20 23:22:01       45 阅读
  11. 洛谷B3745 [语言月赛202304] 你的牌太多了

    2024-03-20 23:22:01       41 阅读
  12. 1.SQL获取列数和行数

    2024-03-20 23:22:01       43 阅读