Linux: 进程地址空间(理解虚拟地址和页表)

news2024/11/25 9:43:23

目录

1. 虚拟地址

2. 进程地址空间分布

3. 描述进程地址空间

4. 内存管理——页表

5. 父子进程的虚拟地址关系

6. 页表标记位

6.1 读写权限

6.2 命中权限

7.为什么存在进程地址空间


1. 虚拟地址

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>

int gval = 100;

int main()
{
    printf("我是一个进程, pid: %d, ppid: %d\n", getpid(), getppid());
    pid_t id = fork();
    if (id == 0)
    {
        //child
        while(1)
        {
            printf("我是一个子进程, pid: %d, ppid: %d, gval: %d, &gval: %p\n", 
            getpid(), getppid(), gval, &gval);
            gval++;
            sleep(1);
        } 
    }
    else
    {
        while(1)
        {
            printf("我是一个父进程, pid: %d, ppid: %d, gval: %d, &gval: %p\n", 
            getpid(), getppid(), gval, &gval);
            sleep(1);
        }
    }
}

 我们写一份代码,先定义一个全局整型变量gval,赋值为100。再使用fork函数创建子进程,都使用while循环,不断打印pid,ppid,gval值和gval的地址,不一样的是子进程对gval不断加加。

运行结果如上,不管是子进程还是父进程对gval变量取地址,得到的地址都是相同的,但是子进程的gval变量在不断变大,而父进程的gval变量没有变。这在我们之前的学习过程中是不可能出现的情况。

因为子进程和父进程数据不是共享的,那么子进程和父进程打印出来的gval值应该是不同的,所以内存上的地址也应该不相同。如果内存上的地址相同,gval值就应该一样。这显然是互相矛盾。

这种情况表明什么呢?说明在语言层面上获得的地址不是物理内存地址,而是一个虚拟地址,也叫做线性地址。

2. 进程地址空间分布

 上面32为系统下虚拟地址分布情况中,先是由低地址往高地址增长,总共是4GB的内存。此外还有我们熟悉的代码段,栈区,堆区。我们可以写一段代码来验证上面区域的分布情况。

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

int unval;
int gval = 100;

int main()
{
    int a,b,c;
    printf("stack addr: %p\n", &a);
    printf("stack addr: %p\n", &b);
    printf("stack addr: %p\n", &c);

    int *heap = (int*)malloc(10*sizeof(int));
    printf("heap addr:  %p\n", heap);

    printf("unval addr: %p\n", &unval);
    printf("gval addr:  %p\n", &gval);
    printf("code addr:  %p\n", main);
    
    return 0;
}

运行结果如下,栈区地址最高,其次是堆区,并且栈区和堆区地址相差较大,再到数据区,最后是代码段。

3. 描述进程地址空间

那么我们该如何理解进程地址空间呢?我们通过一个的例子来说明这一概念。

假设一位公司老板希望评估其手下四位员工的能力,于是决定启动一个项目,并指示这四人分别组建团队来实施。尽管项目的实际启动资金仅为十万元,但老板对每位员工都声称项目拥有十万元的启动资金,任由他们自由支配。这样一来,老板实际上在概念上“创造”了四十万元的虚拟资金池,这种做法俗称为“画饼”。

在这个比喻中,老板的角色相当于操作系统,而他手下的四位员工则代表进程。那十万元的实际项目资金,可以看作是物理内存;而老板向每位员工承诺的十万元“虚拟”资金,则相当于每个进程的地址空间。

既然每个进程都拥有自己独立的地址空间,那么就需要对其进行有效的管理。这里,我们遵循“先描述,再组织”的原则。首先,将进程地址空间封装成一个结构体,该结构体内部还包含一些用于连接和管理的字段。这样一来,对进程地址空间的管理,就转化为了对这个特定数据结构的增删查改的操作。

在进程地址空间的管理中,每个区域(如代码区、数据区、栈区、堆区等)都需要被精确地划分和界定。这可以通过在结构体中使用变量来记录每个区域的起始地址和结束地址来实现。这些变量定义了每个区域在进程地址空间中的位置和范围,从而确保了进程在运行过程中能够正确地访问和管理其内存资源。

接下来,让我们通过小明和小李的例子来进一步说明这一概念:

小明和小李作为同桌,他们共同使用了两张拼在一起的桌子,但为了避免相互干扰,他们设定了一条“三八线”。小明将桌子从1到100进行刻度划分,并规定了自己的活动范围为[1,50],而小李的活动范围为[51,100]。这样,一旦小李的活动超出了他规定的范围(即超过了51这条线),就被视为越界。

在32位操作系统中,进程地址空间也是从某个起始地址(通常是全0地址)到某个结束地址(如0xffffffff)进行排列的。与小明和小李的桌子划分类似,进程地址空间中的每个区域也可以通过两个变量(如起始地址和结束地址)来界定。例如,对于栈区,我们可以设定一个变量start来表示其起始地址,另一个变量end来表示其结束地址,那么栈区的空间范围就可以表示为[start, end]。

根据上面的讲解,我们大概了解进程地址空间。其中进程地址空间中有2^{32}个地址,每个地址占一个字节的空间,计算它们的乘积就相当于4GB的空间。并且每个地址就是一个无符号的整数,从最低位开始编址,刚好是全0地址到全f地址。

其中Linux的进程pcb是task_struct结构体,里面包含了一个指针变量,类型是名为mm_struct的结构体。该结构体就是描述进程地址空间的类,它的属性里就有对所有区域划分的start和end变量。因为地址是个无符号整数,所以使用的是unsigned long类型。

4. 内存管理——页表

前文讲解的都是虚拟地址,如果myproc可执行程序加载到物理内存中,它的代码和数据都存储在物理内存中,怎么做到从虚拟地址到物理地址的转换呢?

假设myproc进程中有个初始化的全局整型变量gval,物理地址为0x1234。它对应在进程地址空间的初始化数据区,也会有虚拟地址0x1010。操作系统会维护一个页表,对虚拟地址和物理地址建立映射关系。

因此,如果要修改gval变量,操作系统会通过页表,查找虚拟地址对应的物理地址,再进行写入操作。虚拟地址空间加上页表就是虚拟内存的管理方案。

5. 父子进程的虚拟地址关系

myproc进程通过fork函数创建一个子进程时,操作系统会为该子进程分配一个新的task_struct结构体对象。此时,子进程的虚拟地址空间属性会复制自父进程,包括页表。由于父子进程的代码区都映射到物理内存的同一块空间,因此可以认为它们的代码是共享的。

在父子进程都没有修改全局变量gval的值时,它们的虚拟地址空间中的数据区也会指向物理内存的同一块空间。然而,一旦子进程尝试修改gval的值,操作系统就会采用写时拷贝(Copy on Write)技术。这一技术会为子进程分配一块新的物理内存空间,并修改子进程的页表,以重新建立虚拟地址与物理地址之间的映射关系。这样,子进程就可以在其独立的内存空间中修改gval的值,而不会影响到父进程。

这也就是为什么fork函数看起来能返回两个返回值的原因,因为父子进程看似有相同的虚拟地址空间,实际上映射到物理内存上的空间是不同的。

通过了解进程地址空间,我们需要重新理解进程和进程的独立性。

  • 进程 = 内核数据结构(task_struct/mm_struct/页表) + 代码和数据

父进程创建子进程时,虽然子进程的内核数据结构一些属性会拷贝自父进程,但是内核数据结构各自一份。代码虽然在物理内存上共享,但是代码时只读的,也相当于各自一份。数据在父子任意进程中发生修改,会发生写时拷贝,也会各自私有一份。至于其他没有联系的进程,更不用说,所有的东西都是各自私有一份。因此,进程与进程之间是具有独立性的

6. 页表标记位

6.1 读写权限

页表除了会建立虚拟地址和物理地址的映射关系,还会存储一些标记位。因为一个字节有八个比特位,每个比特位可以写入0和1,表示两种不同的状态。这样一个字节就可以表示八种不同权限的有无。

其中就有几个比特位确定能否进行读写操作。我们以前谈程序崩溃,就是进程崩溃,进程又跟操作系统相关。假如我们写char *buff = “helloworld”,字符“helloworld”保存在代码区里面,而代码区是只读的,当我们写下*buff = “byte”代码时,编译器是检查不出来的,只有操作系统查看页表,发现你准备对只读代码区进行写操作,操作系统才会阻拦你,杀掉进程。

这就是为什么C语言会有const关键字的原因。因为编译器和操作系统耦合度不能过高,所以编译器无法获取进程页表信息,只能通过const关键字知道该变量无法修改,进而报错。

6.2 命中权限

页表中还有一个标记位,isexist表示该虚拟地址上的数据是否在物理内存中。因为有些可执行程序十分庞大,可能有几GB,操作系统不会一次性加载进来,会分批记载进来。或者是某些进程正在等待硬件资源的响应,进程占用内存空间但不被CPU调度,操作系统会将该进程占用的物理空间换出去,给其他进程使用。这些操作都会用到该标记位,该标记就可以支撑分批加载和进程挂起等待的操作。

其实代码在编译的过程中,就会按照代码区,初始化数据区和为初始化数据区的格式编码,形成elf格式的可执行程序。当可执行程序导入内存中,操作系统才会动态创建栈区、共享区和堆区。

我们通过malloc或者new函数申请堆区空间,实际上先向虚拟地址空间上申请一段空间,等要使用时,操作系统发现你的isexist标记位为0,会在物理内存上开辟一段空间给你使用。

7.为什么存在进程地址空间

  • 虚拟地址空间加上页表管理模式是为了保护内存。

比如我们常说的野指针问题,在语言层拿到的地址都是虚拟地址,通过页表转换指向其他位置的内存空间,而野指针崩溃是因为转换过程中的权限问题,或者根本不存该映射关系。

  • 进程管理和内存管理在系统层面上可以解耦合。

因为进程pcb和物理内存通过虚拟地址空间和页表隔开,物理内存根本不用关心进程申请的空间要做什么,而进程通过虚拟地址空间认为自己可以支配整个物理内存,他在虚拟地址开辟的空间,只有进程被调度执行时,才会在物理空间上申请。这就做到了进程管理和内存管理在系统层面上解耦了。

  • 让进程以统一的视觉看待物理内存。

如果没有虚拟地址,进程直接在物理内存上开辟空间,可能会有许多不连续的内存空间,产生内存碎片,造成内存空间的浪费。当虚拟地址空间和页表出现,进程认为自己可以支配整个物理内存,可以为许多区域进行扩展,再通过页表都映射到同一块连续的内存空间,这样就不会出现内存空间的浪费。


创作充满挑战,但若我的文章能为你带来一丝启发或帮助,那便是我最大的荣幸。如果你喜欢这篇文章,请不吝点赞、评论和分享,你的支持是我继续创作的最大动力!

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

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

相关文章

C语言:深入理解指针

一.内存和地址 我们知道计算机上CPU&#xff08;中央处理器&#xff09;在处理数据的时候&#xff0c;需要的数据是在内存中读取的&#xff0c;处理后的数据也会放回内存中&#xff0c;那我们买电脑的时候&#xff0c;电脑上内存是 8GB/16GB/32GB 等&#xff0c;那这些内存空间…

transformer.js(一):这个前端大模型运行框架的可运行环境、使用方式、代码示例以及适合与不适合的场景

随着大模型的广泛应用&#xff0c;越来越多的开发者希望在前端直接运行机器学习模型&#xff0c;从而减少对后端的依赖&#xff0c;并提升用户体验。Transformer.js 是一个专为前端环境设计的框架&#xff0c;它支持运行基于 Transformer 架构的深度学习模型&#xff0c;尤其是…

uni-app 发布媒介功能(自由选择媒介类型的内容) 设计

1.首先明确需求 我想做一个可以选择媒介的内容&#xff0c;来进行发布媒介的功能 &#xff08;媒介包含&#xff1a;图片、文本、视频&#xff09; 2.原型设计 发布-编辑界面 通过点击下方的加号&#xff0c;可以自由选择添加的媒介类型 但是因为预览中无法看到视频的效果&…

行业分析---2024年小鹏汽车AI Day及三季度财报

1 背景 在之前的博客中&#xff0c;笔者撰写了多篇行业类分析的文章&#xff08;科技新能源&#xff09;&#xff1a; 《行业分析---我眼中的Apple Inc.》 《行业分析---马斯克的Tesla》 《行业分析---造车新势力之蔚来汽车》 《行业分析---造车新势力之小鹏汽车》 《行业分析-…

数据可视化复习1-Matplotlib简介属性和创建子图

1.Matplotlib简介 Matplotlib是一个Python的2D绘图库&#xff0c;它可以在各种平台上以各种硬拷贝格式和交互环境生成具有出版品质的图形。通过Matplotlib&#xff0c;开发者可以仅需要几行代码&#xff0c;便可以生成绘图、直方图、功率谱、条形图、错误图、散点图等。 以下…

WebStorm 2024.3/IntelliJ IDEA 2024.3出现elementUI提示未知 HTML 标记、组件引用爆红等问题处理

WebStorm 2024.3/IntelliJ IDEA 2024.3出现elementUI提示未知 HTML 标记、组件引用爆红等问题处理 1. 标题识别elementUI组件爆红 这个原因是&#xff1a; 在官网说明里&#xff0c;才版本2024.1开始&#xff0c;默认启用的 Vue Language Server&#xff0c;但是在 Vue 2 项目…

如何安全删除 Linux 用户帐户和主目录 ?

Linux 以其健壮性和灵活性而闻名&#xff0c;是全球服务器和桌面的首选。管理用户帐户是系统管理的一个基本方面&#xff0c;包括创建、修改和删除用户帐户及其相关数据。本指南全面概述了如何在 Linux 中安全地删除用户帐户及其主目录&#xff0c;以确保系统的安全性和完整性。…

如何利用ros搭建虚拟场景通过仿真机器人完成一次简单的SLAM建图、导航规划(超简单)?——学习来源:机器人工匠阿杰

一&#xff1a;什么是SLAM&#xff0c;SLAM和导航规划又有什么关系&#xff1f; SLAM&#xff08;Simultaneous Localization and Mapping&#xff0c;即同时定位与建图&#xff09;是一种在未知或动态环境中自行驶的重要技术。主要通过设备上的传感器&#xff08;如激光雷达、…

shell脚本(完结)

声明&#xff1a;学习视频来自b站up主 泷羽sec&#xff0c;如涉及侵权马上删除文章 感谢泷羽sec 团队的教学 视频地址&#xff1a;shell编程&#xff08;完结&#xff09;_哔哩哔哩_bilibili 本文主要讲解不同shell脚本中的相互调用以及输入输出重定向操作。 一、不同脚本之间…

禁用达梦DEM的agent

agent占用内存较多&#xff0c;实际没什么使用&#xff0c;考虑停止agent 应该切换到root执行停止 cd /dm/dmdbms/tool/dmagent/service/ ./DmAgentService stop禁用

使用ChatGPT生成和优化电子商务用户需求规格说明书

在电子商务项目开发中&#xff0c;用户需求规格说明书&#xff08;User Requirement Specification, URS&#xff09;是团队沟通与项目成功的基石。然而&#xff0c;面对复杂多变的需求&#xff0c;如何快速生成清晰、完整且具备说服力的文档&#xff1f;这正是AI工具的用武之地…

产品研发管理和研发项目管理的区别是什么

产品研发管理与研发项目管理有显著的区别&#xff0c;主要体现在管理范围、目标导向和执行方法上。产品研发管理侧重于产品生命周期的规划与执行&#xff0c;强调产品的创新性和市场需求对接&#xff0c;而研发项目管理则更注重具体项目的执行过程&#xff0c;聚焦项目时间、成…

摆烂仙君传——深度学习秘境奇缘

第一章&#xff1a;深度学习秘境 在修仙界与科技交织的边缘&#xff0c;八荒六合九天无上摆烂仙君在其高科技修炼室中感应到一股神秘的召唤。这股力量似乎与他的灵魂产生了共鸣&#xff0c;引导他前往传说中的深度学习秘境。在那里&#xff0c;古老的仙法与前沿的算法交织&…

【FPGA开发】Vivado自定义封装IP核,绑定总线

支持单个文件的封装、整个工程的封装&#xff0c;这里用单个文件举例。 在文件工程目录下&#xff0c;自建一个文件夹&#xff0c;里面放上需要封装的verilog文件。 选择第三个&#xff0c;指定路径封装&#xff0c;找到文件所在目录 取个名&#xff0c;选择封装IP的路径 会…

【CS61A 2024秋】Python入门课,全过程记录P2(Week3开始,更新中2024/11/24)

文章目录 关于基本介绍&#x1f44b;Week 3Mon Environments阅读材料Lab 02: Higher-Order Functions, Lambda ExpressionsQ1: WWPD: The Truth Will PrevailQ2: WWPD: Higher-Order FunctionsQ3: WWPD: Lambda 关于 个人博客&#xff0c;里面偶尔更新&#xff0c;最近比较忙。…

在Linux下配置gitee与Github的远程仓库

目录 前言 云服务器下载git 检测是否下载成功git Linux下配置gitee远程仓库 代码提交演示 git三板斧 Linux下配置Github远程仓库 最后的提醒 前言 那么本篇文章将是在&#xff0c;你已经创建了本地仓库的基础上&#xff0c;在Linux下配置gitee的远程仓库的步骤&#xff…

Mac配置maven环境及在IDEA中配置Maven

Mac配置maven环境及在IDEA中配置Maven 1. 介绍 Maven是一款广泛用于Java等JVM语言项目的工具&#xff0c;它以项目对象模型&#xff08;POM&#xff09;为基础进行项目管理&#xff0c;通过POM文件来定义项目信息和依赖关系。同时&#xff0c;它也是构建自动化工具&#xff0…

硬中断关闭后的堆栈抓取方法

一、背景 性能和稳定性是一个计算机工程里的一个永恒的主题。其中尤其稳定性这块的问题发现和问题分析及问题解决就依赖合适的对系统的观测的手段&#xff0c;帮助我们发现问题&#xff0c;识别问题原因最后才能解决问题。稳定性问题里尤其底层问题里&#xff0c;除了panic问题…

STL关联式容器之hashtable

hashtable的桶子与节点 下图为开链法(separate chaining)完成hashtable的图形表述。为了剖析SGI STL源码&#xff0c;我们遵循SGI的命名&#xff0c;称hash table表格内的元素为桶(bucket),此名称的大约意义是&#xff0c;表格内的每个单元&#xff0c;涵盖的不只是个节点(元素…

基于python的长津湖评论数据分析与可视化,使用是svm情感分析建模

引言 研究背景及意义 上世纪初开始&#xff0c;中国电影就以自己独有的姿态登上了世界电影史的舞台。中国电影作为国家文化和思想观念的反映与延伸&#xff0c;能够增强文化自信&#xff0c;在文化输出方面有着极其重要的作用1[1]。 改革开放以来&#xff0c;随着生产力的提高…