【Linux从入门到精通】进程地址空间(虚拟地址 vs 物理地址)

news2024/10/6 1:44:38

  

  本篇文章会围绕三个问题(什么是地址空间?地址空间是如何设计的?为什么要有地址空间?)进行展开讲述。其中主要是了解虚拟地址和物理地址的区别。希望本篇文章会对你有所帮助。

文章目录

一、什么是地址空间?

1、1 验证地址空间

1、2 地址空间是指的物理内存吗?

1、3 地址空间解释

二、进程访问地址

2、1 历史的程序寻址

2、2 进程地址空间映射到物理内存

2、3 解释相同地址打印出不同数据

三、为什么要有地址空间

3、1 保护物理内存

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

3、3 将无序的物理内存有序化

四、总结


🙋‍♂️ 作者:@Ggggggtm 🙋‍♂️

👀 专栏:Linux从入门到精通  👀

💥 标题:进程地址空间💥

 ❣️ 寄语:与其忙着诉苦,不如低头赶路,奋路前行,终将遇到一番好风景 ❣️  

一、什么是地址空间?

   什么是地址空间呢?我们在学C语言时,经常说到程序的变量存储在栈区、静态区

堆区等。这些综合起来就是地址空间。通俗来讲,地址空间就是表示计算机系统中内存的总体范围。它是可用于存储和访问数据的内存地址的集合。

1、1 验证地址空间

  我们了解地址空间后,不妨来验证一下我们之前所学的是否正确。我们之前学的地址空间如下图:

  

  我们通过下段代码来验证我们之前所学的是否正确:

#include <stdio.h>    
#include <unistd.h>    
#include <stdlib.h>    
    
int g_unval;    
int g_val = 100;    
    
    
int main(int argc, char *argv[], char *env[])    
{    
   // int a = 10;    
    //字面常量    
   const char *str = "helloworld";    
   // 10;    
   // 'a';    
    printf("code addr: %p\n", main);    
    printf("init global addr: %p\n", &g_val);    
    printf("uninit global addr: %p\n", &g_unval);    
    
    char *heap_mem = (char*)malloc(10);    
    char *heap_mem1 = (char*)malloc(10);    
    printf("heap addr: %p\n", heap_mem); //heap_mem(0), &heap_mem(1)    
    printf("heap addr: %p\n", heap_mem1); //heap_mem(0), &heap_mem(1)    
                                                                                                                                                             
    printf("stack addr: %p\n", &heap_mem); //heap_mem(0), &heap_mem(1)    
    printf("stack addr: %p\n", &heap_mem1); //heap_mem(0), &heap_mem(1)    
    
    printf("read only string addr: %p\n", str);    
    int i;    
    for(i = 0 ;i < argc; i++)
    {
        printf("argv[%d]: %p\n", i, argv[i]);
    }
    for(i = 0; env[i]; i++)
    {
        printf("env[%d]: %p\n", i, env[i]);
    }

    return 0;
}

  上述代码就有我们所熟知的不同存储区,我们再来看运行结果:

  上图正是在Linux下运行的结果。在windows下运行的结果所得出的结论也是相同的。我们看到上图的运行结果后是符合我们所学的地址空间的规律。

  栈和堆之间有大量空间是空着的其次堆和栈是相向而生的。细心的小伙伴可能发现,总共的内存空间是4G,而用户空间只占用3G,那剩下的1G呢?其实完整的地址空间如下:

1、2 地址空间是指的物理内存吗?

  我们之前在学C语言时,经常会提到 ‘‘地址’等词汇。例如,我们随查看的临时变量所存储的地址。那么我们经常所说的这些地址是指的物理内存(物理内存是指由于安装内存条而获得的临时储存空间。主要作用是在计算机运行时为操作系统和各种程序提供临时储存。)中的地址吗?

  答案不确定时,我们看看如下代码:

#include <stdio.h>    
#include <unistd.h>    
#include <stdlib.h>    
int g_val = 10;    
int main()    
{    
   pid_t id = fork();    
   if(id < 0){    
     perror("fork");    
     return 0;    
   }    
   else if(id == 0){ //child,子进程肯定先跑完,也就是子进程先修改,完成之后,父进程再读取    
     while(1)    
     {    
      printf("child[%d]: %d : %p\n", getpid(), g_val, &g_val);    
      sleep(1);    
     }    
    
   }else{ //parent    
     while(1)    
     {    
      printf("parent[%d]: %d : %p\n", getpid(), g_val, &g_val);    
      sleep(1);                                                                                                                                              
      g_val=100;    
     }    
   }    
   sleep(1);    
   return 0;    
}   

  上述代码就是区分父子进程,打印同一个变量的值。结果如下图:

  我们惊奇的发现,同一个变量(地址是相同的),他们的值竟然不一样!难道是一个变量可以存储两个不同的值的原因吗?答案是不是的。

  我们知道物理内存中的地址表示唯一一块空间,那上述的运行结果证明了,我们所说的地址空间并不是物理地址的!而是存储在虚拟地址(虚拟内存是计算机系统内存管理的一种技术。它使得应用程序认为它拥有连续可用的内存,它通常是被分隔成多个物理内存碎片,还有部分暂时存储在外部磁盘存储器上,在需要时进行数据交换)中。 

1、3 地址空间解释

  地址空间本质就是一种内核数据结构,在Linux当中,叫做struct mm_struct(linux内核当中的地址空间结构体)包含了一些区域信息(先描述),能够实现区域划分(本质就是在一定的范围内定义start和end)。  

struct mm_struct
{
    unsigned long code_start;
    unsigned long code_end;
    
    unsigned long init_start;
    unsigned long init_end;
    
    unsigned long uninit_start;
    unsigned long uninit_end;
    
    unsigned long heap_start;
    unsigned long heap_end;
    
    unsigned long stack_start;
    unsigned long stack_end;
    //...等不同的区域划分
}

   每个进程都会有自己的地址空间,同时进程控制块(PCB)中也包含了 *mm_struct 指针,可使我们直接找到自己所对应的进程地址空间(后组织)。

  上述讲述的这么多,我们可以理解为进程地址空间就是操作系统给进程花了一个大饼。

  这个大饼就是指的每个进程都会有4GB的连续的空间(0x00000000~0xFFFFFFFF)。实际上呢,这4GB的的空间是虚拟内存,虚拟内存对应的实际物理内存,可能只对应的分配了一点点的物理内存,实际使用了多少内存,就会对应多少物理内存。

  这4G虚拟内存是一个连续的地址空间(这也只是进程认为),而实际上,它的数据是存储在多个物理内存碎片的,还有一部分存储在外部磁盘存储器上,在需要时将数据交换进物理内存。

二、进程访问地址

2、1 历史的程序寻址

  在虚拟地址出现之前,程序的寻址都是直接寻找的物理地址。但是这样会有很多的不足:

  1. 直接访问物理内存不安全。例如我们假如使用了野指针,对内存中的数据进行了修改,那么这个时就会影响到其他的进程;
  2. 因为物理内存是有限的,当有多个进程要执行的时候,对每个进程都要分配4G内存,很显然你内存若小一点,这很快就分配完了,于是没有得到分配资源的进程就只能等待。当一个进程执行完后,再将等待的进程装入内存。这种频繁的装入内存的操作是很没效率的。
  3. 因为内存是随机分配的,所以程序运行的地址也是不正确的。

  由于上述的三个直接原因,后来就产生了虚拟地址。

2、2 进程地址空间映射到物理内存

  当有了虚拟内存的概念后,上述的问题就得到了很好的解决。当我们访问物理内存中的数据时,需要先访问进程地址空间上的地址。然后把虚拟地址空间上的地址通过页表映射到对应的物理内存上。具体如下图:

   地址空间和页表是每个进程都独有的一份,只要保证每一个进程的页表,能够映射到不同区域的物理内存,就能够做到进程之间互不干扰。这就是我们所说的进程所具有独立性。

  映射是由谁来完成的呢?答案是操作系统!操作系统通过地址转换机制将虚拟地址映射到物理地址,以实现对内存的访问。这种映射通常在页表或段表等数据结构上实现,其中存储了虚拟地址与物理地址之间的映射关系。 

2、3 解释相同地址打印出不同数据

  我们在上述的 1、2 中看到了相同的地址打印出不同的数据。注意,我们所访问到的地址都是虚拟地址。并不是物理地址。在上述的 1、2 中我们创建了一个子进程,子进程本身是继承了父进程的数据和代码。在没有对数据进行修改之前,子进程和父进程共享了一份数据。一但对子进程或者父进程的数据进行修改,就会发生写时拷贝。对修改的数据进行深拷贝,从而达到对彼此不产生干扰,实现进程独立性

  那就对相同地址打印出不同数据的现象不难理解了。当我们对父进程的数据进行修改时,父进程发生了写时拷贝,在内存中开辟了空间。但他们都有自己的地址空间(虚拟地址),所以地址相同也是正常现象(子进程继承父进程的代码和数据)。即使虚拟地址一样,但是可通过页表映射到不同的物理内存中。具体如下图:

三、为什么要有地址空间

3、1 保护物理内存

  可能还有一些疑惑:即使有了虚拟地址,那我要是对野指针进行了访问修改,页表对野指针映射后,还不是对物理内存进行了非法的访问修改吗?地址空间的设置不就多此一举了吗?

  事实并非上述一样。凡是非法的访问或者映射,操作系统都会识别到的。一但你进行了非法的访问或者映射,操作系统就会终止掉你的程序。举个例子,当我们对野指针进行访问修改时,你的程序就会崩溃,这不就是程序终止退出吗!!!

  地址空间有效的保护了物理内存。因为地址空间和页表是操作系统创建并且维护的。这也就意味着地址空间和页表进行映射时需要操作系统进行监管!

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

  因为有了地址空间和页表,所以我们的数据可以在物理内存中的任何合法位置加载。因为他们之间有映射。物理内存的分配和进程的管理可以做到没有关系!

  所以进程模块和内存模块只需要各自完成各自的事情,最后通过页表的映射将他们连接起来,产生关系。降低了他们之间互相的影响度。

3、3 将无序的物理内存有序化

  由于数据可以在物理内存中理论上可以加载任何位置,那么是不是物理内存中几乎所有的数据和代码在内存中都是乱序的。

  但是,也为页表的存在,它可以将地址空间上的虚拟地址和物理地址进行映射,那么在进程的视角所有内存分布就是有序的!

四、总结

  我们平常所访问到的地址均为虚拟地址。地址空间并不是物理地址,而是虚拟地址。通过页表映射访问物理地址。 每个进程都有自己的地址空间和页表。

  页表是一种数据结构,它存储了虚拟地址与物理地址之间的映射关系。在进行地址转换时,操作系统根据进程的页表查找对应的物理地址,然后将虚拟地址转换为物理地址,以便进行实际的内存访问。

  通过使用虚拟地址,操作系统可以为每个进程提供独立的地址空间,使得多个进程可以并发运行,彼此之间相互隔离,互不干扰。虚拟地址还提供了更高的灵活性和保护性,使得操作系统可以有效地管理和分配内存资源,提高系统的性能和安全性。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/680272.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

《机器学习公式推导与代码实现》chapter6-k近邻算法

《机器学习公式推导与代码实现》学习笔记&#xff0c;记录一下自己的学习过程&#xff0c;详细的内容请大家购买作者的书籍查阅。 k近邻算法 k近邻(k-nearest neighbor, k-NN)算法是一种经典的分类算法。k近邻算法根据新的输入实例的k个最近邻实例的类别来决定其分类。所以k近…

rust abc(1): 最小环境搭建

文章目录 1. 目的2. 命令集合3. 安装或更新 rust3.1 命令3.2 运行结果 4. 包管理工具 Cargo5. 创建 Rust 的 Hello World 程序: 单个文件6. 创建 Rust 的 Hello World 工程&#xff1a; 基于 Cargo6.1 cargo new 创建工程6.2 cargo run6.3 完整输出6.4 解释 7. IDE/编辑器8. Re…

Jetson安装Anaconda(miniforge3)

1 miniforge3 miniforge集成了Anaconda的核心工具&#xff1a;conda。conda是一个包和环境管理工具。因此&#xff0c; miniforge里面的conda和Anaconda里面的conda完全一样&#xff1b;你能用Anaconda做的安装、升级、删除包等功能&#xff0c;miniforge都能做&#xff1b;你…

angular实现自定义模块路由懒加载;配置自定义模块路由及子路由

图片中绿色表示新建的文件;黄色表示被更改的文件; 1、创建一个新的项目 ng new angularlazyload2、创建一个用户模块,并配置路由 ng g module module/user --routing如图: 3 、在module/模块下创建user组件 ng g component module/user如图: 4、实现路由懒加载 依次…

java00——类和对象

在Java中一切皆对象 什么是对象&#xff1f; 一个人、一只猫、一条狗…这些就是一个对象&#xff1b; 每个对象都有属性和行为。 什么是类&#xff1f; 类即同类别&#xff0c;例如不论男人、女人、黑人、白人…&#xff0c;都是人类&#xff0c;即同一类事务的统称。 类的…

HTB-Sandworm

HTB-Sandworm 立足altas -> silentobserversilentobserver -> 完整的atalsatlas -> rootexploit 扫描最常用的1000个端口。 80会重定向到443。 去看看443有什么吧。 目录扫描可能不会起作用。在concat上面找到了一个有趣的东西。 “如果不知道怎么PGP&#xff1…

Axure教程—折叠面板

本文介绍利用Axure中的动态面板制作折叠面板 一、效果 预览地址&#xff1a;https://3k8az1.axshare.com 二、功能 1、点击标题展开面板内容 2、点击标题折叠面板 三、制作 从默认元件库拖入一个动态面板&#xff0c;设置两个状态&#xff0c;一个状态面板标题&#xff0c;一…

【MySQL】不允许你还不了解创建计算字段

&#x1f3ac; 博客主页&#xff1a;博主链接 &#x1f3a5; 本文由 M malloc 原创&#xff0c;首发于 CSDN&#x1f649; &#x1f384; 学习专栏推荐&#xff1a;LeetCode刷题集&#xff01; &#x1f3c5; 欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指…

【开源与项目实战:开源实战】82 | 开源实战三(中):剖析Google Guava中用到的几种设计模式

上一节课&#xff0c;我们通过 Google Guava 这样一个优秀的开源类库&#xff0c;讲解了如何在业务开发中&#xff0c;发现跟业务无关、可以复用的通用功能模块&#xff0c;并将它们从业务代码中抽离出来&#xff0c;设计开发成独立的类库、框架或功能组件。 今天&#xff0c;…

【后端】使用TS编写任务管理系统----Express

文章目录 常见的后端框架安装并且声明文件库项目基本配置编写任务管理后端API添加任务查看任务设置任务完成状态删除任务 总结 node -v v16.13.0https://github.com/dL-hx/server-side 常见的后端框架 expresskoa… 安装并且声明文件库 $ npm i express $ npm i types/exp…

前端vue入门(纯代码)14

内容创作不易&#xff0c;各位帅哥美女&#xff0c;求个小小的赞&#xff01;&#xff01;&#xff01; 【15.给todoList案例添加编辑按钮】 本篇内容在TodoList案例的基础上添加个编辑按钮&#xff0c;要求&#xff1a; &#xff08;1&#xff09;点击编辑按钮后&#xff0c…

轻松学会研华屏幕下载和上传

&#x1f525;一个人走得远了&#xff0c;就会忘记自己为了什么而出发&#xff0c;希望你可以不忘初心&#xff0c;不要随波逐流&#xff0c;一直走下去&#x1f3b6; &#x1f98b; 欢迎关注&#x1f5b1;点赞&#x1f44d;收藏&#x1f31f;留言&#x1f43e; ✅ 如果觉得博主…

HashMap和HashSet的知识点总结

前言 在之前我们介绍过TreeMap和TreeSet&#xff1a; TreeMapTreeSet 知识点梳理总结_Crystal_bit的博客-CSDN博客 也知道Key-Value和Key模型&#xff0c;但是我们可能还对Hash不太了解&#xff0c;这里我们对Hash了解之后再对HashMap和HashSet的基本使用了解一下。 目录 1…

LabVIEW 图像处理功能

设置成像系统并采集图像后&#xff0c;您可以分析和处理图像&#xff0c;以提取有关被检测对象的有价值信息。 内容 图像分析图像处理斑点分析机器视觉 图像分析 影像分析结合了基于影像像素的灰度强度计算统计数据和测量的技术。您可以使用影像分析功能来确定影像质量是否足以…

scratch lenet(10): C语言计算log

scratch lenet(10): C语言计算log 文章目录 scratch lenet(10): C语言计算log1. 目的2. 原理: x m ∗ 2 p x m * 2^p xm∗2p3. 公式展开3.1 公式分解3.2 获取 m m m3.3 获取 p p p3.4 Remez 算法Remez 算法用于 sin(x) 的快速计算Remez 算法用于 exp 的近似Remez 用于自然…

【MySQL数据库】存储过程

目录 一、存储过程1.1概述1.2优点 二、存储过程实战2.1创建存储过程2.2存储过程的参数2.3条件语句 if-then-else end if2.4循环语句while end while 一、存储过程 1.1概述 存储过程是一组为了完成特定功能的SQL语句集合。存储过程在使用过程中是将常用或者复杂的工作预先使…

CSS基础学习--25 border(边框)进阶

一、边框常见属性 border-radiusbox-shadowborder-image 属性说明CSSborder-image设置所有边框图像的速记属性。3border-radius一个用于设置所有四个边框- *-半径属性的速记属性3box-shadow附加一个或多个下拉框的阴影3 二、border-radius 圆角 在 CSS2 中添加圆角棘手。我…

网络协议TCP/IP 协议学习笔记一

T C P / I P通常被认 为是一个四层协议系统&#xff0c;每一层负责不同的功能&#xff1a; 1) 链路层&#xff0c;有时也称作数据链路层或网络接口层&#xff0c; 通常包括操作系统中的设备驱动程序和计算机 中对应的网络接口卡。它们一起处理与电缆&#xff08;或其他任何传输…

CSS基础学习--26 渐变(Gradients)

CSS3 渐变&#xff08;gradients&#xff09;可以让你在两个或多个指定的颜色之间显示平稳的过渡。以前&#xff0c;你必须使用图像来实现这些效果。但是&#xff0c;通过使用 CSS3 渐变&#xff08;gradients&#xff09;&#xff0c;你可以减少下载的时间和宽带的使用。此外&…