虚拟地址空间 -- 虚拟地址,虚拟内存管理

news2024/12/23 9:28:28

1. C/C++语言的内存空间分布

        用下列代码来观察各种区域的地址:

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

int g_unval;
int g_val = 100;

int main(int argc, char *argv[], char *env[])
{
        const char *str = "helloworld";
        printf("code addr: %p\n", main);
        printf("init global addr: %p\n", &g_val);
        printf("uninit global addr: %p\n", &g_unval);

        static int test = 10;
        char *heap_mem = (char*)malloc(10);
        char *heap_mem1 = (char*)malloc(10);
        char *heap_mem2 = (char*)malloc(10);
        char *heap_mem3 = (char*)malloc(10);

        printf("heap addr: %p\n", heap_mem);
        printf("heap addr: %p\n", heap_mem1);
        printf("heap addr: %p\n", heap_mem2);
        printf("heap addr: %p\n", heap_mem3);

        printf("test static addr: %p\n", &test);
        printf("stack addr: %p\n", &heap_mem);
        printf("stack addr: %p\n", &heap_mem1);
        printf("stack addr: %p\n", &heap_mem2);
        printf("stack addr: %p\n", &heap_mem3);

        printf("read only string add: %p\n", str);
        for (int i = 0; i < argc; 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;
}

        上述打印的地址,从正文代码区到命令行参数环境变量的地址,依次增大。 

        上述内存空间分布并不是实际的物理内存,而是进程地址空间,也叫做虚拟地址空间。

2. 虚拟地址

        用下列一段代码证明上述所说的地址为虚拟地址。

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

int gval = 100;

int main(int argc, char *argv[], char *env[])
{
        pid_t id = fork();
        if (id == 0)
        {
                while(1)
                {
                        printf("子进程: gval: %d, &gval: %p, pid: %d, ppid: %d\n", gval, &gval, getpid(), getppid());
                        sleep(1);
                        gval++;
                }
        }
        else
        {
                while(1)
                {
                        printf("父进程: gval: %d, &gval: %p, pid: %d, ppid: %d\n", gval, &gval, getpid(), getppid());
                        sleep(1);
                }
        }
        return 0;
}

知识点1:        

        C/C++等语言中输出的地址,全都是虚拟地址。虚拟地址是提供给上层用户使用的。 

知识点2:

        一个进程对应一个虚拟地址空间。一个虚拟地址对应一个字节,32位机器下有2的32次方(4GB)个地址,64位机器下有2的64次方个地址。

 3. 进程地址空间

        一个进程启动之后,就会有一套页表,该页表存储的是进程中各变量的进程地址空间中的虚拟地址和物理内存的地址的映射关系。

        下图中是上述例子中父进程和子进程gval变量的虚拟地址空间和物理内存的关系。子进程的代码和数据以及页表都是拷贝父进程的。所以子进程中虚拟地址和物理地址的映射关系和父进程一样,当gval在子进程中被修改时,发生写时拷贝,在物理内存中就会开辟一块新的物理地址来存储子进程的gval变量,然后修改子进程中页表的映射关系,但是gval变量在子进程中的虚拟地址没有改变,所以出现了上述子进程和父进程gval变量虚拟地址一样而变量的值不一样的情况。

        上述打印的子进程和父进程同一个变量地址相同,是虚拟地址相同,内容不同其实是虚拟地址和物理地址的映射关系被修改了。

知识点1:
        在32位机器下,每个进程的虚拟地址空间都是4GB,每个进程都认为自己独占全部的物理内存。 

4. 虚拟内存管理

        虚拟地址空间本质就是一个结构体对象,名为mm_struct(内存描述符),描述Linux下进程地址空间的所有信息。每个进程只有一个mm_struct结构,在每个进程的task_struct结构中,有一个指向该进程mm_struct的指针。mm_struct结构体中存储的是对进程地址空间中代码区、堆区、栈区等每个区域进行区域划分的信息,存储的是每个区域的开始位置和结束位置。

struct task_struct
{
    /*...*/

    struct mm_struc *mm; 
    //对于普通的⽤⼾进程来说该字段指向他的虚拟地址空间的⽤⼾空间部分
    //对于内核线程来说这部分为NULL。

    struct mm_struct *active_mm; 
    //该字段是内核线程使⽤的。当该进程是内核线程时
    //它的mm字段为NULL,表⽰没有内存地址空间,可也并不是真正的没有
    //这是因为所有进程关于内核的映射都是⼀样的,内核线程可以使⽤任意进程的地址空间。

    /*...*/
}

知识点1:

        为什么要有虚拟地址空间?

                (1) 可以使一个程序的代码和数据以及变量随机存储在物理内存的不同地方,通过页表的映射关系进行查找,并且使上层用户(进程视角)看到这些数据时,地址(虚拟地址)顺序是有序的。 

                (2) 将虚拟地址通过页表转化为物理地址的时候,每个区域在映射的过程中还有rwx等权限,可以使程序在访问虚拟内存的时候就对一些操作进行判定拦截,保护物理内存。

              例如字符常量区定义的字符串常量是不允许被修改的,如果在程序中对其进行修改,在地址映射的时候没有w权限,直接进行了拦截,所以在字符常量区进行写入时程序会发生崩溃。

                (3) 通过页表来建立虚拟地址和物理地址的映射关系,让进程管理和内存管理进行一定程度的解耦合。

知识点2:
        创建一个进程的时候先有task_struct这样的内核数据结构,再加载进程对应的代码和数据。所以一个进程可以不用加载程序的代码和数据,只先创建进程的task_struct,mm_struct,页表。

4.1 mm_struct结构体

        下图在逻辑上表示mm_struct中存储的信息,存储的信息为各个区域在虚拟内存空间中的起始位置和结束位置。

         每一个进程都会有自己独立的mm_struct,操作系统要将全部进程的虚拟地址分区组织起来。所以在每个进程的task_struct中有mm_struct,mm_struct中有以下两种方式组织该进程对应的虚拟内存分区:

        (1)当虚拟分区较少时采取单链表进行管理,由mmap指针指向这个链表。

        (2)当虚拟分区较多时采用红黑树进行管理,由mm_rb指向这棵树。

        Linux内核使用 vm_area_struct 结构体表示一个独立的虚拟内存区域(VMA),由于每个不同的虚拟内存区域功能和内部机制不同,因此一个进程使用多个vm_area_struct来分别表示不同类型的虚拟内存区域。上述两种组织方式使用的就是vm_area_struct来连接各个VMA,方便程序快速访问。

        上图表示mm_struct中有一张链表,用于维护该进程中所以的虚拟内存区域。 

struct vm_area_struct {
    unsigned long vm_start;     //虚存区起始
    unsigned long vm_end;    //虚存区结束
    struct vm_area_struct *vm_next, *vm_prev;    //前后指针
    struct rb_node vm_rb;    //红⿊树中的位置
    unsigned long rb_subtree_gap;
    struct mm_struct *vm_mm;    //所属的 mm_struct
    pgprot_t vm_page_prot;
    unsigned long vm_flags;    //标志位
    struct {
        struct rb_node rb;
        unsigned long rb_subtree_last;
    } shared;
    struct list_head anon_vma_chain;
    struct anon_vma *anon_vma;
    const struct vm_operations_struct *vm_ops; //vma对应的实际操作
    unsigned long vm_pgoff;    //⽂件映射偏移量
    struct file * vm_file;    //映射的⽂件
    void * vm_private_data;    //私有数据
    atomic_long_t swap_readahead_info;

#ifndef CONFIG_MMU
    struct vm_region *vm_region;
    /* NOMMU mapping region */
#endif
#ifdef CONFIG_NUMA
    struct mempolicy *vm_policy;
    /* NUMA policy for the VMA */
#endif
    struct vm_userfaultfd_ctx vm_userfaultfd_ctx;
} __randomize_layout;

        每个虚拟内存区域中也存有自己分区的起始位置和结束位置,还有一个mm_struct类型的指针,指向自己所属的mm_struct结构体。上图表示用双链表连接每个VMA。 

知识点1:

        堆区在虚拟内存中会存在多个,并且是离散的,上图中只画了一个,一个进程中实际会存在多个堆区,也和其他VMA一样被这样管理起来。

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

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

相关文章

【数字化】华为数字化转型架构蓝图-2

目录 1、客户联结的架构思路 1.1 ROADS体验设计 1.2 具体应用场景 1.3 统一的数据底座 1.4 案例与成效 2、一线作战平台的架构思路 2.1 核心要素 2.2 关键功能 2.3 实施路径 2.4 案例与成效 3、能力数字化的架构思路 3.1 能力数字化的核心目标 3.2 能力数字化的实…

【优选算法】—移动零(双指针算法)

云边有个稻草人-CSDN博客 想当一名牛的程序员怎么能少的了练习算法呢&#xff1f;&#xff01; 今天就立即开启一个新专栏&#xff0c;专干算法&#xff0c;提高算法能力&#xff08;废柴的我也在准备蓝桥杯哈哈&#xff09;—— 目录 1.【 283. 移动零 - 力扣&#xff08;Lee…

AI的进阶之路:从机器学习到深度学习的演变(三)

&#xff08;承接上集&#xff1a;AI的进阶之路&#xff1a;从机器学习到深度学习的演变&#xff08;二&#xff09;&#xff09; 四、深度学习&#xff08;DL&#xff09;&#xff1a;机器学习的革命性突破 深度学习&#xff08;DL&#xff09;作为机器学习的一个重要分支&am…

Python自动化测试:线上流量回放

&#x1f345; 点击文末小卡片&#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 在自动化测试中&#xff0c;线上流量回放是一项关键技术&#xff0c;可以模拟真实用户的请求并重现线上场景&#xff0c;验证系统的性能和稳定性。本文将介绍Pytho…

初始C语言3

目录 9. 操作符 9.1 算术操作符 9.2 移位操作符 9.3 位操作符 9.4 赋值操作符 9.5 单目操作符 9.6 关系操作符 9.7 逻辑操作符 9.8 条件操作符 9.9 逗号表达式 下标引用、函数调用和结构成员 10. 常见关键字 10.1 typedef 10.2 static 10.2.1 修饰局部变量 10.…

【Rust自学】4.5. 切片(Slice)

4.5.0. 写在正文之前 这是第四章的最后一篇文章了&#xff0c;在这里也顺便对这章做一个总结&#xff1a; 所有权、借用和切片的概念确保 Rust 程序在编译时的内存安全。 Rust语言让程序员能够以与其他系统编程语言相同的方式控制内存使用情况&#xff0c;但是当数据所有者超…

VPN技术-GRE隧道的配置

GRE隧道的配置 1&#xff0c; 在AR1上配置DHCP接口地址池&#xff0c;AR3上配置DHCP全局地址池 2&#xff0c; PC1获取的IP地址为10.10.10.253&#xff0c;PC2获取的IP地址为10.10.30.253 3&#xff0c;通过ip route-static将目的地址为10.10.30.253的流量引入到Tunnel #配…

碰撞检测算法之闵可夫斯基差集法(Minkowski Difference)

在游戏开发和机器人路径规划乃至于现在比较火的自动驾驶中&#xff0c;我们常常需要确定两个物体是否发生碰撞&#xff0c;有一种通过闵可夫斯基差集法求是否相交的算法&#xff0c;下面将介绍一下 闵可夫斯基差集法的优势 闵可夫斯基差集法优势&#xff1a; 可以处理复杂的…

Python OCR 文字识别

一.引言 文字识别&#xff0c;也称为光学字符识别&#xff08;Optical Character Recognition, OCR&#xff09;&#xff0c;是一种将不同形式的文档&#xff08;如扫描的纸质文档、PDF文件或数字相机拍摄的图片&#xff09;中的文字转换成可编辑和可搜索的数据的技术。随着技…

【系统】Windows11更新解决办法,一键暂停

最近的windows更新整的我是措不及防&#xff0c;干啥都要关注一下更新的问题&#xff0c;有的时候还关不掉&#xff0c;我的强迫症就来了&#xff0c;非得关了你不可&#xff01; 经过了九九八十一难的研究之后&#xff0c;终于找到了一个算是比较靠谱的暂停更新的方法&#x…

复合翼与倾转旋翼飞行器:设计与控制算法对比

一、引言 复合翼&#xff08;Compound Wing&#xff09;和倾转旋翼&#xff08;Tilt - Rotor&#xff09;飞行器在现代航空领域均占据独特地位&#xff0c;二者在设计和控制算法方面展现出显著差异。这些差异在飞行模式切换、推进系统设计、控制算法复杂度以及飞行器稳定性等多…

空闲中断配合DMA

1.传统串口接收数据&#xff1a;来一个字节接受一个。 2.一次中断将一包数据存到缓冲区 3.DMA原理

三格电子——新品IE103转ModbusTCP网关

型号&#xff1a;SG-TCP-IEC103 产品概述 IE103转ModbusTCP网关型号SG-TCP-IEC103&#xff0c;是三格电子推出的工业级网关&#xff08;以下简称网关&#xff09;&#xff0c;主要用于IEC103数据采集、DLT645-1997/2007数据采集&#xff0c;IEC103支持遥测和遥信&#xff0c;可…

HDLBits训练3

时间&#xff1a;2024.12.22 Hadd 代码 法一&#xff1a; module top_module( input a, b,output cout, sum );assign {cout,sum}ab; endmodule法二&#xff1a; 运行结果 Fadd 代码 法一&#xff1a; module top_module( input a, b, cin,output cout, sum );assign…

Qt之串口设计-线程实现(十二)

Qt开发 系列文章 - Serial-port&#xff08;十二&#xff09; 目录 前言 一、SerialPort 二、实现方式 1.创建类 2.相关功能函数 3.用户使用 4.效果演示 5.拓展应用-实时刷新 总结 前言 Qt作为一个跨平台的应用程序开发框架&#xff0c;在串口编程方面提供了方便易用…

STM32F407ZGT6-UCOSIII笔记12: 事件标志组

有时一个任务需要与多个事件同步&#xff0c;这就要用到事件标志组 本文学习与程序编写基于 正点原子的 STM32F1 UCOS开发手册 文章提供测试代码讲解、完整工程下载、测试效果图 目录 事件标志组&#xff1a; 定义与初始化事件标志组&#xff1a; #include "Public.h&quo…

聊一聊 C#前台线程 如何阻塞程序退出

一&#xff1a;背景 1. 讲故事 这篇文章起源于我的 C#内功修炼训练营里的一位朋友提的问题&#xff1a;后台线程的内部是如何运转的 ? &#xff0c;犹记得C# Via CLR这本书中 Jeffery 就聊到了他曾经给别人解决一个程序无法退出的bug&#xff0c;最后发现是有一个 Backgrond…

JVM性能优化一:初识内存泄露-内存溢出-垃圾回收

本文主要是让你充分的认识到什么叫做内存泄露&#xff0c;什么叫做内存溢出&#xff0c;别再傻傻分不清了&#xff0c;别再动不动的升级服务器的内存了。 文章目录 1.基本概念1.1.内存泄露1.2.内存溢出1.3.垃圾回收1.4.内存泄露-垃圾回收-内存溢出三者的关系关系 2.代码示例2.…

为什么使用环形队列

1.看以下两种情况。第一种不会出现问题&#xff0c;当主流程读取次数比较慢时&#xff0c;数据会被覆盖。 2.扩大空间。不可取。 3.什么是队列

【WRF教程第3.6期】预处理系统 WPS 详解:以4.5版本为例

预处理系统 WPS 详解&#xff1a;以4.5版本为例 Geogrid/Metgrid 插值选项详解1. 插值方法的工作机制2. 插值方法的详细说明2.1 四点双线性插值&#xff08;four_pt&#xff09;2.2 十六点重叠抛物线插值&#xff08;sixteen_pt&#xff09;2.3 简单四点平均插值&#xff08;av…