【Linux】进程地址空间
创始人
2024-04-09 22:11:30
0次


目录

一、虚拟地址

二、对进程地址空间的理解

三、32位下的进程地址空间

那么进程如何找到内存中的数据呢?

四、为什么要通过虚拟地址映射的方式访问物理地址


一、虚拟地址

先看一段父子进程共存的程序,由子进程对全局变量grobal_val进行修改:

#include 
#include 
int grobal_val=10;
int main()
{pid_t id=fork();if(id==0){int cnt=0;while(1){printf("子进程:pid=%d,ppid=%d | grobal_val=%d,&grobal_val=%p\n",getpid(),getppid(),grobal_val,&grobal_val);sleep(1);++cnt;if(cnt==10){grobal_val=200;printf("子进程已更改全局变量grobal_val\n");}}}else if(id>0){while(1){printf("父进程:pid=%d,ppid=%d | grobal_val=%d,&grobal_val=%p\n",getpid(),getppid(),grobal_val,&grobal_val);sleep(1);}}else {printf("fork error\n");return 1;}return 0;
}

父子进程谁先执行不确定,由系统进行调度。

当子进程将全局变量grobal_val由10改为200,我们可以看到,父子进程的grobal_val的地址相同,但是父子进程从这个地址中获取的值却并不相同!

从同一块物理地址中取出的值是相同的,所以这个程序取出的地址(指针)并不是物理地址,而是虚拟地址(线性地址、逻辑地址)。注:逻辑地址指可执行程序编译完成后内部函数、变量的地址。逻辑地址有两种表示方法,一种是各个区域地址递增,另一种是每个区域的地址都从零偏移量开始(这种是比较老的表示方式)。

在Linux中的逻辑地址是第一种表示方式,所以Linux中逻辑地址就是虚拟地址。

之前学习的C/C++内存区域,是一块虚拟内存空间,每个进程有它自己的虚拟内存空间,即进程地址空间。所以上面的代码用fork创建子进程,因为子进程是父进程的拷贝,父子进程的grobal_val虽然虚拟地址一样,但会被映射到不同的物理地址上。

当grobal_val未被改变时,父子进程映射同一块grobal_val的物理地址,一旦父子进程的一方对共享数据进行修改,由于进程的独立性,操作系统会在物理内存中再开辟一块空间,并拷贝原数据,提出修改的进程的页表映射关系将会被改变,然后再让进程对数据进行修改,所以我们看到父子进程的数据并不一样。这种技术称为写时拷贝,对不同进程的数据进行分离。

二、对进程地址空间的理解

1、进程它自己会认为它独占CPU资源,但其实并不是。因为进程以时间片轮转的形式占用CPU资源,时间一到,马上从运行状态进入休眠状态,实质上是通过虚拟地址空间,让进程认为它独占CPU资源。

2、进程地址空间是操作系统给进程开辟的一块虚拟内存空间,这块空间用内核的一种数据结构来描述、组织。

操作系统给每个进程一块4GB的虚拟内存,进程每次想使用,按需申请即可,但不会全部给进程。(注意这里给的是虚拟内存,就像老板给员工画饼一样)

对Linux操作系统中进程的理解中提到过,进程使用进程控制块task_struct结构体进行管理,同样的,每个进程地址空间也需要被管理,管理进程地址空间的结构体叫mm_struct,task_struct中有一个指针指向自己的mm_struct。

mm_struct伪代码:

struct mm_struct
{uint32_t code_start,code_end;uint32_t data_start,data_end;uint32_t heap_start,heap_end;uint32_t stack_start,stack_end;······//存储进程地址空间各区域的起始位置
};

三、32位下的进程地址空间

地址空间中的最小单元是字节,所以在32位系统下共有2^(32)个地址空间,也就是4GB。

mm_struct结构体对象中存放各个区域的起始位置,栈区堆区的动态调整,本质上是修改各个区域的起始地址。

那么进程如何找到内存中的数据呢?

操作系统将进程中的虚拟地址通过页表映射到内存,找到对应的物理地址。

四、为什么要通过虚拟地址映射的方式访问物理地址

1、直接访问物理内存是非常不安全的,例如越界操作、恶意进程读取等。

2、页表会拦截不合理的请求,可以保护物理内存,防止恶意进程的访问 。

所以写代码出现野指针、内存越界等情况并不会造成操作系统的崩溃。

3、进程地址空间的存在,可以让进程和进程间的代码进行解耦(互不干扰),保证了进程独立性的特征。

4、进程和编译器均遵守进程地址空间这一套规则,编完即可使用。

编译器也遵守进程地址空间这一套规则:

我们的代码在磁盘时,程序的函数、变量等通过虚拟地址建立联系,满足程序间的互相跳转;

当程序由磁盘被加载到内存中时,就具备了物理地址。函数、变量等通过页表映射至虚拟地址。

根据可执行程序的虚拟地址初始化mm_struct结构体中每个虚拟内存中的边界。

当程序在CPU中跑起来时,CPU根据虚拟地址运行完程序后,通过页表映射至物理地址。

相关内容

热门资讯

前端-session、jwt 目录:   (1)session (2&#x...
linux入门---制作进度条 了解缓冲区 我们首先来看看下面的操作: 我们首先创建了一个文件并在这个文件里面添加了...
关于测试,我发现了哪些新大陆 关于测试 平常也只是听说过一些关于测试的术语,但并没有使用过测试工具。偶然看到编程老师...
前缀和与对数器与二分法 1. 前缀和 假设有一个数组,我们想大量频繁的去访问L到R这个区间的和,...
nodejs:本地安装nvm实... 一、背景-使用不同版本node的原因 vue3+ts、nuxt3版本,node...
JAVA集合知识整理 Java集合知识整理 HashMap相关 HashMap的底层数据结构:jdk1.8之...
无刷直流电机介绍及单片机控制实... 无刷直流电机介绍及单片机控制实例前言基本概念优势与劣势使用寿命基本结构使用单片机控制实例电子调速器&...
fwdiary(2) dp2 1.传纸条  AcWing 275. 传纸条 - AcWing 走两条路,走一条最大的...
常用的DOS命令 常用的DOS命令 DOS(Disk Operating System,磁...
<C++> 类和对象(下) 1.const成员函数将const修饰的“成员函数”称之为const成员函数,cons...