Linux进程地址空间
阿里云国内75折 回扣 微信号:monov8 |
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6 |
目录
前言引入
一直以来我们学习C/C++离不开地址这个概念 地址空间排布划分了很多不同的区域今天我们来升深度探讨一下进程的地址空间下面是C/C++地址空间。
这里的C/C++地址空间是内存吗 答案不是下面我们用代码来证明一下
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
int global_val=100;
int main()
{
pid_t id=fork();
if(id<0)
{
printf("create child process error!\n");
}
else if(id==0)
{
int cnt=0;
while(1)
{
printf("我是子进程,我的id=%d | global_val=%d, &global_val=%p\n",getpid(),global_val,&global_val);
cnt++;
if(cnt==5)
{
global_val=300;
printf("我是子进程,global_val的值改变啦!!!\n");
}
sleep(1);
}
}
else
{
while(1)
{
printf("我是父进程,我的id=%d | global_val=%d, &global_val=%p\n",getpid(),global_val,&global_val);
sleep(1);
}
}
return 0;
}
我们可以发现之前全局变量都一样但是当我们在子进程中修改了全局变量子进程的全局变量的值确实变了但是父进程的变量没变但是global_val的地址却是一样的
这就说明父子进程的地址空间并不是内存内存空间是物理地址物理空间存储的变量值是唯一的
结论是这里打印出来的地址不是内存地址而是叫作虚拟地址下面我们来讲讲这个虚拟地址空间--进程地址空间
一、进程地址空间
OS给每个进程独立的进程地址空间每个进程自己认为自己独立占有整个内存
1.1 OS分配进程地址空间
1.1.1 感性理解进程地址空间
一个大富翁有十个亿个人资产内存它有三个儿子三个进程其中一个是名正言顺的儿子另外两个是私生子三个儿子都不知道对方的存在大富翁希望自己三个儿子都能努力他首先给每个儿子一部分自己的钱创建进程地址空间然后给每个儿子一个叮嘱如果X儿子你能...我就把自己的遗产全部给你(给进程画饼,让其认为独自占有内存) 三个儿子都认为自己独占未来父亲的资产每个儿子如果想要<亿级别的钱大富翁都会答应给(手动开辟空间 malloc/new)
1.1.2 OS管理进程地址空间
操作系统对每个进程的地址空间做了管理
OS给每一个进程创建进程地址空间的同时都会记录开辟给这个进程的内存空间大小这样OS就会知道内存还剩多少空间也能为后来的进程空间分配有效调节
1.2 进程地址空间区域
1.2.1 地址空间概念
1.地址空间的基本空间大小是字节
2.32位下地址空间最多形成的地址为2的32次方空间大小=4GB
3.每个地址具有唯一性我们用数据描述地址用32个bit位0/1来编址
4.地址范围为0x0000 ~ 0xFFFF
1.2.2 地址区域划分与调整
我们只要知道了起始地址就能划分一个地址区域
进程PCB内有一个结构体struct mm_struct描述区域数据区域的集合是连续的地址也就是线性地址的集合操作系统对每一个进程的mm_struct管理从而对地址空间管理
调整区域只要更改起始地址大小即可堆和栈的开辟空间相当于对区域的调整
下面我们来看看mm_struct结构体
二、内存与地址空间
2.1 内存的地址空间
通过上文我们知道地址空间不是内存下面我们浅谈一下内存
内存如何划分区域32位下他的存储是4GB连续空间称为page(页)。内存可以看作一个连续数组32位下数组元素个数为page mem[4GB/4KB];
2.2 页表映射
页表是一种特殊的数据结构放在系统空间的页表区存放逻辑页与物理页帧的对应关系。 每一个进程都拥有一个自己的页表PCB表中有指针指向页表地址空间的虚拟地址与内存的物理地址通过页表进行映射
有了页表映射我们就能理解父子进程的虚拟地址虽然一样但是它们的映射关系不同所以映射对应的物理内存不同
2.3 写时拷贝 (非常重要)
父进程创建子进程子进程会拷贝父进程的代码和数据所以父子进程会共享全局变量global_val父子进程全局变量的虚拟地址相同映射关系也相同所以父子进程全局变量的值相同
进程具有独立性当子进程想要修改共享数据OS会修改子进程页表的全局变量的映射关系然后在内存另开一块空间给子进程的全局变量这就是写时拷贝这就是为什么父子进程虽然地址(虚拟地址相同但它们页表映射不同全局变量物理地址不同所以两者值不同
所以 进程独立性 = 进程内核数据结构 + 进程对应的代码和数据的独立性
三、进程地址空间与代码执行
3.1 进程地址空间与编译器
当我们编译代码的时候地址空间已经存在了程序经过编译编译器会给代码进行编址(这里地址称之为逻辑地址也是虚拟地址)汇编指令中函数调用Call命令通过地址找到对应函数
3.2 逻辑地址贯穿代码执行
代码本身也是数据代码在进程地址空间的代码区代码编译链接形成可执行程序前编译器已经给代码编址也就是说代码指令内就存在了逻辑地址
当程序运行变成进程后程序代码被加载到内存中OS建立页表映射关系程序的代码区起始代码(main函数)的逻辑地址放入CPU寄存器CPU拿到了代码开始的逻辑地址然后通过页表映射找到物理地址然后将下一个代码指令(指令本身存在逻辑地址)放入CPU寄存器一直到程序代码结束就这样我们的CPU就执行了一条条代码
CPU整个过程中看到了物理地址了吗 没有CPU全部拿到的是逻辑地址逻辑地址贯穿了我们CPU处理代码的全过程
四、进程地址空间存在意义
①.进程地址空间保证了进程的独立性
②.进程地址空间保障了内存数据安全性
我们通过页表映射通过虚拟地址间接访问物理地址如果我们直接访问内存数据一步到位不是更好 如果进程直接访问内存假如一个恶意进程通过扫描的方式想要窃取或者篡改内存数据造成的后果很严重而进程地址空间的存在让进程不直接与内存打交道页表的存在会拦截虚拟地址的非法访问这样我们就能保障内存数据的安全性
③.进程地址空间的存在让所有进程以统一视角来看待进程代码和数据的各个区域
④.进程地址空间的存在让编译器以统一视角来进行编译代码