Linux:进程地址空间

1.程序地址空间(虚拟地址)结果图

  • 地址按照从正文代码到环境变量依次增大
  • 栈的增长相反
  • 程序地址空间不是内存
  • 程序地址空间(虚拟空间 / 进程地址空间)不是语言层的概念,是系统的概念

证明程序地址空间不是内存

cpp 复制代码
#include <stdio.h>

#include <unistd.h>

#include <stdlib.h>

int g_val = 0;

int main()
{
 pid_t id = fork();
 if(id < 0){
 perror("fork");
 return 0;
 }
 else if(id == 0){ //child

 printf("child[%d]: %d : %p\n", getpid(), g_val, &g_val);
 }else{ //parent

 printf("parent[%d]: %d : %p\n", getpid(), g_val, &g_val);
 }
 sleep(1);
 return 0;
}

我们发现,输出出来的变量值和地址是⼀模⼀样的,很好理解呀,因为⼦进程按照⽗进程为模版,⽗ ⼦并没有对变量进⾏进⾏任何修改。

cpp 复制代码
#include <stdio.h>

#include <unistd.h>

#include <stdlib.h>

int g_val = 0;

int main()
{
 pid_t id = fork();
 if(id < 0){
 perror("fork");
 return 0;
 }
 else if(id == 0){ //child,⼦进程肯定先跑完,也就是⼦进程先修改,完成之后,⽗进程
再读取 
 g_val=100;
 printf("child[%d]: %d : %p\n", getpid(), g_val, &g_val);
 }else{ //parent

 sleep(3);
 printf("parent[%d]: %d : %p\n", getpid(), g_val, &g_val);
 }
 sleep(1);
 return 0;
}

我们发现,⽗⼦进程,输出地址是⼀致的,但是变量内容不⼀样!能得出如下结论:

• 变量内容不⼀样,所以⽗⼦进程输出的变量绝对不是同⼀个变量

• 但地址值是⼀样的,说明,该地址绝对不是物理地址

• 在Linux地址下,这种地址叫做 虚拟地址

我们在⽤C/C++语⾔所看到的地址,全部都是虚拟地址!物理地址,⽤⼾⼀概看不到,由OS统⼀ 管理

OS必须负责将 虚拟地址 转化成 物理地址

2.引入新概念

2-1 一个进程对应一个虚拟地址空间

(1)虚拟地址空间 != 物理内存,它和物理内存之间的内在联系是页表,通过页表构成一个映射关系

(2)task_struct描述一个进程,对应一个虚拟地址空间。

(3)分为32位机器和64位机器:

例如:

32位机器-2^32个地址 = 4GB,一个地址表示一个字节。

2-2 一个进程对应一套页表

用户空间可以直接拿到地址直接访问带目标代码。

解释:

页表两边分别代表的是:虚拟地址+物理地址。

页表是用来做虚拟地址和物理地址映射的

2-3 子进程写时拷贝

父进程有自己的虚拟地址空间,子进程也要有自己的虚拟空间和页表。

子进程拷贝父进程PCB,地址空间,页表拷贝自父进程。

当子进程对变量进行修改时。

由于进程具有独立性!!!子进程g_val++对父进程g_val进行写时拷贝,新开辟地址空间物理空间改变,虚拟地址空间不变。

所以这就可以解释为什么输出地址一致,但是变量不同,因为那对应的是虚拟地址,而真实的数据存储在真实的物理内存中。

3.虚拟地址与进程地址空间

3-1 大富翁例子

一个富翁给他的每一个私生子都画一个大饼,跟他们说一旦他完成了一个什么什么目标,就把自己全部的财产都给他。

总体:让任意一个进程都以为自己独占整个物理内存。
如何管理饼(虚拟地址空间)?

先描述,再组织。

本质是通过一个数据结构来组织:struct mm_struct 。

区域划分:来规定虚拟地址空间各个区域的开始和结尾:

桌子:地址空间 小朋友:每个的空间规定开始和结束 刻度:就是地址。

4.具体是如何实现的?

虚拟地址空间范围:00000....~fffff.....

什么叫做区域划分?

标识确认区域的开始和结束即可,也可以添加区域的大小。

例如:

桌子上的每一个刻度叫做地址。

知道桌子的大小,那么就对桌子进行统一编址。

内核管理下的区域划分

总地址空间,开始,结束, 开始, 结束

综上所述

物理地址转化成虚拟地址的过程:

(1)通过mm_struct申请开辟空间和初始化数据:

1.开辟空间

2.初始化从哪来?

程序加载的过程中,进行初始化。

(2) 一个程序加载到内存后:
1.虚拟地址空间中申请指定大小的空间 --- 调整区域划分

2.加载程序,申请物理空间

3.页表进行虚拟地址空间和物理地址空间的映射,完毕

5.为什么我们需要地址空间

(1)代码的地址一定是连续的,地排布是规律的。

页表的映射->使得无序变得有序

(2)在地址转换过程中,对地址的操作进行合法性判定 -- 保护物理内存

访问某个代码或者变量的时候,需要把虚拟地址转化成为物理地址。页表还有读写权限,如果要对代码区进行写入,但只有读权限,那么就会实现对物理内存的保护"转化过程中,也可以对你的地址和操作进行合法性判定",实现对物理内存的保护

a.什么是野指针?

对一个已经释放的空间进行访问,就会造成野指针

但不一定会造成程序崩溃。
b.char *str = "helloworld";

*str = 'H';

能编译过,但运行时不行

字符串是字符常量区,只读,不可以写或者修改。强行进程写入会导致崩溃。

为什么在字符常量区写入会崩溃?

答:查找页表的时候没权限被拦截了

(3).解耦合, 不让虚拟地址空间直接指向物理内存

(4).缺页中断:

虚拟地址找不到对应的物理地址,那么就把数据代码重新加载,新生成一个物理内存,再次形成对应。

澄清一些问题:

1.我们可以不加载对应的代码和数据,只有task_struct,mm_struct,页表即可, 因为缺页保护会自动把数据和代码进行加载

2.创建进程,先有task_stcurt,mm_struct等,还是先加载代码和数据?

现有内核数据结构 --- task_stcurt,mm_struct等。

3.如何理解进程挂起?

只保留内核数据结构(task_stcurt,mm_struct),页表清空, 代码数据等换出磁盘,省出空间。

堆区,不止一个吧?brk,不止一个起始虚拟地址吧?

栈有栈顶和栈底,移动指针即可,堆的时候可能new malloc10几次,说不通。

vm_area_struct(list链表链接起来的),每一个子区域的有start和end,一个区域一个vm_area_struct

形成链表把多个堆区管理起来

mm_struct对整个堆区的整体描述,指向vm_struct的链表,方便对vm_area_struct进行管理

vm_area_struct包含起始地址和结束地址,已经前后区域的指针

vm_area_struct:局部代码区域

mm_area_struct:整体代码区域
进程具有独立性
1.内核数据结构独立
2.加载进的内存的代码和数据独立

每个区域都有vm

为什么需要地址空间?

1.保护物理内存的所有合法数据

2.进程管理和内存管理完成解耦合

3.进程视角下内存分布都可以是有序的

相关推荐
Xの哲學4 小时前
Linux MAC层实现机制深度剖析
linux·服务器·算法·架构·边缘计算
奇点爆破XC4 小时前
centos进阶命令.Linux系统介绍(运维版)
linux·运维·centos
南棱笑笑生4 小时前
20251215给飞凌OK3588-C开发板适配Rockchip原厂的Buildroot【linux-5.10】后调通typeC1接口
linux·c语言·开发语言·rockchip
tianyuanwo4 小时前
深度解析:Linux ISO引导配置与安装模式设计
linux·uefi·iso·isolinux.cfg·grub.cfg
starvapour5 小时前
Ubuntu触发硬件级系统重启
linux·运维·ubuntu
咋吃都不胖lyh5 小时前
CUDA、Ubuntu、显卡驱动:零基础讲清(附三者关联)
linux·运维·ubuntu
羊村懒哥5 小时前
ubuntu24.04系统安装VNC
linux·运维·服务器
A7bert7775 小时前
【YOLOv5seg部署RK3588】模型训练→转换RKNN→开发板部署
linux·c++·人工智能·深度学习·yolo·目标检测
BullSmall6 小时前
linux 根据端口查看进程和对应的应用
linux·运维