【Linux】进程虚拟地址空间

简介: 进程虚拟地址空间

@toc

1. 引入

在C/C++中,多次画过这幅程序地址空间布局图 ——

<img src=" title="">

那么这是内存吗?事实上它根本就不是内存!!!是不是颠覆了世界观?!那它是什么呢?

我们先来看一段程序。定义了一个全局变量,在3s时,父或子进程更改数据——

<img src=" title="">
我们惊奇的发现,同一个地址,居然打出了不同的变量 ——

<img src=" title="">

众所周知,在fork创建子进程时,父子默认情况共享数据,修改时,为了维护进程独立性,发生写时拷贝,这是能够理解的。但地址怎么能没有变化呢?

如果C/C++中打印出来的是物理内存的地址,这种现象绝对不可能存在!这说明,我们在语言中所使用的地址,绝对不是物理地址,而是虚拟地址

2. 进程地址空间

所以之前所说的“程序地址空间”是不准确的,准确的说是“进程地址空间”,那么什么是进程地址空间呢?

2.1 what?

每个进程都有一个地址空间,操作系统为每一个进程画了一个大饼,它们都认为自己在独占物理内存。 (至于为什么画大饼,暂时理解为便于统一规划,后文详谈)

系统中存在大量进程,需要管理地址空间,那么就需要先描述、再组织。

地址空间本质上在内核中是一个数据类型 ,可以定义具体的进程地址空间变量——

struct mm_struct
{
    //进程地址空间
}

<img src=" title="">

那么我们是如何用struct结构体进行区域划分的?各个区域又是如何与物理内存建立关联的?

2.2 how?

⛄️ 虚拟地址空间 & 分页

我们将实体物理内存抽象出一把尺子,上面的刻度相当于虚拟地址(地址空间进行区域划分时,对应的线性位置虚拟地址)

<img src=" title="">

每个进程都认为自己拥有4GB,都认为空间的划分是按照4GB来划分的。虽然这里只有start和end,但这是一个区间概念,每个进程都认为mm_struct代表的是从0x00000000到0xffffffff整个内存。

:heart: 那么如何将虚拟地址和物理地址建立映射关系呢?通过查页表(页表+MMU硬件设备)

<img src=" title="">

2.3 why?

:heart: 1. 通过添加一层软件层,完成有效的对进程操作内存的风险管理(权限管理),本质是为了保护物理内存各个进程的数据安全

类似于过年的压岁钱妈妈帮你收着,等你要用的时候,再来问我要,防止你乱花钱。对应到这里,中间层是有利于操作系统管理的,不是不给你,而是管控你的做法是否合适;如果没有中间层(OS),能直接访问物理地址,可能发生非法越界访问。

在语言层面上,我们知道,字符串存在字符常量区不能修改 ——

char* str = "more than words";
*str = 'b'; //不能修改

本质上是因为,这里str指针就是虚拟地址,*解引用进行写入时,访问虚拟地址,要进行虚拟地址和物理地址的转化,然而OS只给你读r权限,直接把你的进程崩溃掉。

<img src=" title="">

:heart: 2. 将内存申请和内存使用在时间上解耦。通过虚拟地址空间,来屏蔽底层申请内存的过程,达到进程读写内存操作和OS进行内存管理进行软件层面上的分离。

比如我们在堆上申请一大块空间,但是我们可能暂时不会全部使用甚至暂时不用(有了空间,从来没有读写),在OS角度,这部分空间本来是可以给别人立马用的,却被闲置着。于是,OS在当你真的要使用时,再把空间开辟出来,建立映射关系,这叫做基于缺页中断进行物理内存申请。

再比如假如物理内存已经100%占满了,而你还要,那么OS执行内存管理算法,把某些进程闲置的空间置换到磁盘上,这样进程照样可以申请到内存。而这些都是我们用户在应用层根本感受不到,换句话说OS做的内存操作是透明的。

:heart: 3. 站在CPU和应用层的角度,进程统一使用4GB的空间,且每个空间区域的相对位置是比较确定的。

比如CPU寻找不同进程代码的第一行,如果直接访问物理内存,CPU会比较凌乱。有了虚拟地址空间,CPU能以统一的视角看待物理内存,不同的进程再通过的各自的页表,映射到不同的物理内存。同时,程序的代码和数据可以加载到内存的任意位置,大大减少内存管理的负担。

进一步谈进程和程序有什么区别?进程要包括描述进程的PCB、进程虚拟地址空间、页表、代码和数据。

3. 再次理解

3.1 又回到最初的起点

:yellow_heart: 我们再次回到文章开头的问题,为什么相同的地址会打印出不同的值?

众所周知,子进程的创建是以父进程为模板的 ——
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-B6WUeykz-1648474228302)(C:\Users\13136\AppData\Roaming\Typora\typora-user-images\image-20220328194135912.png)]

为了维护进程的独立性,子进程在更改时发生写时拷贝,即为子进程重新开辟一段物理空间,把值拷贝过来,再重新建立虚拟地址到物理地址的映射关系 ——
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lapxZbAc-1648474228304)(C:\Users\13136\AppData\Roaming\Typora\typora-user-images\image-20220328195553972.png)]

所以打印的是一样的虚拟地址,而不同的值,是因为在物理内存上本来就是不同的变量。

之前说的,父子进程的代码一般是共享的,也就是通过映射到同一段物理空间实现的。

之前说的,所有的只读数据一般可以只有一份,本质不是在语言上,而是在系统上,这样操作系统的维护成本是最低的,不同的虚拟地址映射到相同的物理地址上 ——

// 打印str和p的地址相同 
char* str = "more than words";
char* p = "more than words";

3.2 验证进程地址空间

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

int g_unval;
int g_val = 100;
  
int main(int argc,char* argv[],char* env[])
{
    const char* str = "more than words";
    printf("code addr:%p\n",main);
    printf("string rdonly addr:%p\n",str);
    printf("init addr:%p\n",&g_val);
    printf("uninit addr:%p\n",&g_unval);

    char* heap1 = (char*)malloc(10);
    char* heap2 = (char*)malloc(10);                                                                                                               
    char* heap3 = (char*)malloc(10);    
    char* heap4 = (char*)malloc(10);    
    printf("heap addr:%p\n",heap1);    
    printf("heap addr:%p\n",heap2);    
    printf("heap addr:%p\n",heap3);    
    printf("heap addr:%p\n",heap4);    

    int a = 10;                    
    int b = 20;                    
    printf("stack addr:%p\n",&a);    
    printf("stack addr:%p\n",&b);    

    for(int i = 0; argv[i]; i++)    
    {                              
      printf("argv[%d]:%p\n", i, argv[i]);    
    }
    
      for(int i = 0; env[i]; i++)
    {
      printf("env[%d]:%p\n", i, env[i]);
    }
    return 0;
  }

可以看到,地址空间变化完全吻合。其中堆向上生长,栈向下生长,堆栈相对而生,且空间很大 ——
<img src=" title="">
相同的程序,每次运行,main函数的地址都一样 ——

<img src=" title="">

系统传的参数,在栈的上面,再完善一下 ——
<img src=" title="">
持续更新@边通书

相关文章
|
8天前
|
安全 Linux 虚拟化
网络名称空间在Linux虚拟化技术中的位置
网络名称空间(Network Namespaces)是Linux内核特性之一,提供了隔离网络环境的能力,使得每个网络名称空间都拥有独立的网络设备、IP地址、路由表、端口号范围以及iptables规则等。这一特性在Linux虚拟化技术中占据了核心位置🌟,它不仅为构建轻量级虚拟化解决方案(如容器📦)提供了基础支持,也在传统的虚拟机技术中发挥作用,实现资源隔离和网络虚拟化。
网络名称空间在Linux虚拟化技术中的位置
|
8天前
|
网络协议 安全 Linux
Linux网络名称空间之独立网络资源管理
Linux网络名称空间是一种强大的虚拟化技术🛠️,它允许用户创建隔离的网络环境🌐,每个环境拥有独立的网络资源和配置。这项技术对于云计算☁️、容器化应用📦和网络安全🔒等领域至关重要。本文将详细介绍在Linux网络名称空间中可以拥有的独立网络资源,并指出应用开发人员在使用时应注意的重点。
|
8天前
|
安全 网络协议 Linux
Linux网络名称空间概述
Linux网络名称空间是操作系统级别的一种虚拟化技术🔄,它允许创建隔离的网络环境🌐,使得每个环境拥有自己独立的网络资源,如IP地址📍、路由表🗺️、防火墙规则🔥等。这种技术是Linux内核功能的一部分,为不同的用户空间进程提供了一种创建和使用独立网络协议栈的方式。本文旨在全方面、多维度解释Linux网络名称空间的概念、必要性和作用。
Linux网络名称空间概述
|
6天前
|
算法 Linux 调度
深度解析:Linux内核的进程调度机制
【4月更文挑战第12天】 在多任务操作系统如Linux中,进程调度机制是系统的核心组成部分之一,它决定了处理器资源如何分配给多个竞争的进程。本文深入探讨了Linux内核中的进程调度策略和相关算法,包括其设计哲学、实现原理及对系统性能的影响。通过分析进程调度器的工作原理,我们能够理解操作系统如何平衡效率、公平性和响应性,进而优化系统表现和用户体验。
15 3
|
10天前
|
监控 Linux Shell
初识Linux下进程2
初识Linux下进程2
|
10天前
|
Linux 编译器 Windows
【Linux】10. 进程地址空间
【Linux】10. 进程地址空间
19 4
|
Linux Shell C语言
介绍 Linux 的命名空间
介绍 Linux 的命名空间 背景 从Linux 2.6.24版的内核开始,Linux 就支持6种不同类型的命名空间。它们的出现,使用户创建的进程能够与系统分离得更加彻底,从而不需要使用更多的底层虚拟化技术。
1715 0
|
9天前
|
Web App开发 Linux 网络安全
工作中常用到的Linux命令
工作中常用到的Linux命令
|
9天前
|
Web App开发 Java Linux
Linux之Shell基本命令篇
Linux之Shell基本命令篇
Linux之Shell基本命令篇
|
6天前
|
NoSQL Linux Shell
常用的 Linux 命令
常用的 Linux 命令
27 9