Linux程序地址空间

引言

以下为示意草图 

下面以代码验证一下: 

  1 #include<stdio.h>                                                                                                                     
  2 #include<stdlib.h>
  3                                       
  4 int un_gval;                          
  5 int init_gval=666;                    
  6                                       
  7 int main()                            
  8 {                                     
  9   printf("code addr: %p\n", main);    
 10   const char *str = "hello Linux";    
 11                                       
 12    printf("read only char addr: %p\n", str);
 13    printf("init global value addr: %p\n", &init_gval);
 14    printf("uninit global value addr: %p\n", &un_gval);
 15                                                       
 16    char *heap1 = (char*)malloc(100);                  
 17    printf("heap1 addr : %p\n", heap1);
 18     printf("stack addr : %p\n", &str);
 19   return 0;                           
 20 }

其中堆区与栈区之间有一大块镂空,堆区向上增长,栈区向下增长,堆栈相对而生

代码验证堆区向上增长:

 16    char *heap1 = (char*)malloc(100);
 17    char *heap2 = (char*)malloc(100);
 18    char *heap3 = (char*)malloc(100);
 19    char *heap4 = (char*)malloc(100);
 20    printf("heap1 addr : %p\n", heap1);
 21    printf("heap2 addr : %p\n", heap2);
 22    printf("heap3 addr : %p\n", heap3);
 23    printf("heap4 addr : %p\n", heap4);

 

代码验证栈区向下增长:


 25    printf("stack addr : %p\n", &str);
 26    printf("stack addr : %p\n", &heap1);
 27    printf("stack addr : %p\n", &heap2);
 28    printf("stack addr : %p\n", &heap3);
 29    printf("stack addr : %p\n", &heap4); 

 

在栈区上开辟的变量,整体是向下增长的,但是每个对象的使用是局部向上使用

以该对象的的最低地址作为地址

  7 struct s  
  8 {  
  9   int a;  
 10   int b;  
 11   int c;  
 12 }obj;

 13   printf("%p\n",&obj.a);
 14   printf("%p\n",&obj.b);
 15   printf("%p\n",&obj.c);    

 

在栈区之上是命令行参数与环境变量存储区域

代码验证:

int main(int argc, char *argv[], char *env[])
{ 
      int i= 0;
      for(; argv[i]; 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;
}

 

以下图所示真的是我们认为的内存吗? 

 来看一段代码:

int g_val = 555;

int main()
{
    pid_t id = fork();
    if(id == 0)
    {
        //child
        int cnt = 5;
        while(1)
        {
            printf("child, Pid: %d, Ppid: %d, g_val: %d, &g_val=%p\n", getpid(), getppid(), g_val, &g_val);
            sleep(1);
            if(cnt == 0)
            {
                g_val=888;
                printf("child change g_val: 555->888\n");
            }
            cnt--;
        }
    }
    else
    {
        //father
        while(1)
        {
            printf("father, Pid: %d, Ppid: %d, g_val: %d, &g_val=%p\n", getpid(), getppid(), g_val, &g_val);
            sleep(1);
        }
    }

    sleep(100);
    return 0;
}

这里有一个问题:对同一个地址进行读取,竟然读取到了不同的内容

变量内容不一样,所以父子进程输出的变量绝对不是同一个变量
但地址值是一样的,说明,该地址绝对不是物理地址
在Linux地址下,这种地址叫做 虚拟地址

结论:C/C++看到的地址,绝对不是物理地址,物理地址,用户一概看不到,由OS统一管理(OS必须负责将 虚拟地址 转化成 物理地址

           我们平时用到的地址,都是虚拟地址/线性地址 

什么是进程地址空间

每一个进程运行之后,都会有一个进程地址空间的存在,也都要在系统层面有自己的页表映射结构 ,进程地址空间不存储任何数据,所有数据都存储在物理内存中

每个进程都有自己的task_struct结构体

 

子进程尾修改数据前与父进程共享代码和数据,它们的虚拟地址是一样的,根据虚拟地址在页表映射出的物理地址也是一样的,所以前期打印出来的g_val的值相同

当子进程要修改g_val,那么发生写时拷贝,子进程页表中的g_val的物理地址改变,虚拟地址不变,所以子进程打印g_val=888 父进程打印g_val=555 ,&g_val父子进程打印出来都相同 

因为我们打印出来的不是真正的物理地址,而是虚拟地址

结论:1 写时拷贝发生在物理内存中  2 这份工作由操作系统来完成 3 知道与否不会影响上层语言

每个进程要被OS管理(先描述再组织),所以每个进程都有各自的task_struct, 每个进程都有的地址空间,所以地址空间也要被OS管理 (先描述再组织)-->地址空间最终一定是一个内核的数据结构对象,就是一个内核结构体

在linux中,这个进程/虚拟地址空间,叫做:mm_struct 

其中每个进程的地址空间都有区域划分,有每个区域的start与end

 挑重点:

每个进程被创建时,既有自己的PCB即task_struct 也有自己的地址空间即mm_struct 

 为什么要有地址空间

1 让进程以统一的视角看待内存,所以任意一个进程,可以通过地址空间+页表将乱序的内存数据,变成有序,分门别类地规划好

2  存在虚拟地址空间,可以有效地进行进程访问内存的安全检查

页表结构中除却虚拟地址与物理地址外,还存在访问权限字段

举个例子:我们常说字符常量区不可被修改,所以以下代码是不被通过的:

char *str = "hello Linux";
*str = 'H';

为什么呢? 那曾经又是怎么被加载的?内存不是可以被读写的吗?

原因:字符串常量区的数据在页表映射时,它的访问权限字段被设置为"r"(只读),所以试图修改字符串常量区数据的写入操作在页表映射阶段就被拦截,所以无法修改。

每个进程有各自的地址空间,各自的页表,那么在众多页表中,每个进程怎么找到属于自己的那一张呢?

原因:进程进行各种虚拟地址到物理地址的转换,各种访问内存,一定是这个进行正在CPU上运行

CPU内有一个叫做CR3的寄存器,会存储该进程页表的物理地址,当该进程在CPU上加载时,会把自己的上下文数据中存有的页表地址加载到CR3中,当该进程要切换走时,会把CR3寄存器中存储的页表地址一并带走,所以每个进程都有自己的页表

3 将进程管理与内存管理进行解耦 

页表结构中还存在一个字段用于:内存是否分配以及是否有内容

此外,通过页表让进程的代码和数据映射到不同的物理内存处,从而实现进程的独立性质

进程=内核数据结构+进程的代码和数据

最后,起始完整的地址空间如下图:

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C++数据结构与算法——双指针法

C第二阶段——数据结构和算法&#xff0c;之前学过一点点数据结构&#xff0c;当时是基于Python来学习的&#xff0c;现在基于C查漏补缺&#xff0c;尤其是树的部分。这一部分计划一个月&#xff0c;主要利用代码随想录来学习&#xff0c;刷题使用力扣网站&#xff0c;不定时更…

Flutter 动画(显式动画、隐式动画、Hero动画、页面转场动画、交错动画)

前言 当前案例 Flutter SDK版本&#xff1a;3.13.2 显式动画 Tween({this.begin,this.end}) 两个构造参数&#xff0c;分别是 开始值 和 结束值&#xff0c;根据这两个值&#xff0c;提供了控制动画的方法&#xff0c;以下是常用的&#xff1b; controller.forward() : 向前…

【开源】在线办公系统 JAVA+Vue.js+SpringBoot+MySQL

目录 1 功能模块1.1 员工管理模块1.2 邮件管理模块1.3 人事档案模块1.4 公告管理模块 2 系统展示3 核心代码3.1 查询用户3.2 导入用户3.3 新增公告 4 免责声明 本文项目编号&#xff1a; T 001 。 \color{red}{本文项目编号&#xff1a;T001。} 本文项目编号&#xff1a;T001。…

前端vue3实现本地及在线文件预览(含pdf/txt/mp3/mp4/docx/xlsx/pptx)

一、仅需实现在线预览&#xff0c;且文件地址公网可访问 &#xff08;一&#xff09;微软office免费预览&#xff08;推荐&#xff09; 支持doc/docx/xls/xlsx/ppt/pptx等多种office文件格式的免费预览 //示例代码//​在https://view.officeapps.live.com/op/view.aspx?src…

骨传导耳机是什么?如何选择骨传导耳机不会踩雷?

骨传导耳机是利用骨传导技术研发而成一种新型蓝牙耳机&#xff0c;其传声方式很独特&#xff0c;不通过空气传导&#xff0c;而是通过人体骨骼来传递声音。 详细传声原理请看下图&#xff1a; 随着骨传导耳机逐渐热门&#xff0c;如何选购耳机成为了问题&#xff0c;下面跟大家…

计算机设计大赛 深度学习YOLO图像视频足球和人体检测 - python opencv

文章目录 0 前言1 课题背景2 实现效果3 卷积神经网络4 Yolov5算法5 数据集6 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 深度学习YOLO图像视频足球和人体检测 该项目较为新颖&#xff0c;适合作为竞赛课题方向&#xff0c;学长非…

1_opencv3环境搭建与测试

之前2020年5月写过一次&#xff0c;时隔3年多&#xff0c;有机会再重新写一次。相比之前&#xff0c;应该是有一点儿进步的。之前是使用默认安装路径&#xff0c;所以无需配置共享库的搜索路径。这次是自定义安装路径&#xff0c;略有区别。随着写程序的时间增长&#xff0c;编…

Dockerfile 常用指令

1、FROM 指定base镜像。 2、Docker history 显示镜像的构建历史&#xff0c;也就是Dockerfile的执行过程。 Missing 表示无法获取IMAGE ID&#xff0c;通常从Docker Hub下载的镜像会有这个问题。 3、调试Dockerfile&#xff0c;使用sudo docker run -it XXXX&#xff0c;XXXX…

036-安全开发-JavaEE应用第三方组件Log4j日志FastJson序列化JNDI注入

036-安全开发-JavaEE应用&第三方组件&Log4j日志&FastJson序列化&JNDI注入 #知识点&#xff1a; 1、JavaEE-组件安全-Log4j 2、JavaEE-组件安全-Fastjson 3、JavaEE-基本了解-JNDI-API 演示案例&#xff1a; ➢Java-三方组件-Log4J&JNDI ➢Java-三方组件-Fa…

【开源】SpringBoot框架开发大学生相亲网站

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块三、系统展示四、核心代码4.1 查询会员4.2 查询相亲大会4.3 新增留言4.4 查询新闻4.5 新增新闻 五、免责说明 一、摘要 1.1 项目介绍 基于JAVAVueSpringBootMySQL的大学生相亲网站&#xff0c;包含了会员管理模块、新闻管…

炬芯ATS2819 soundbar音响系统开发完全手册

加我微信hezkz17,可申请加入数字音频系统研究开发交流答疑群,赠送音频项目核心开发资料 ATS2819音响系统开发完全手册 1 硬件原理实现 图1 硬件原理框图 2 SOC ATS2819介绍 3 E800 板子项目实物…

黑群晖一键修复:root、AME、DTS、转码、CPU型号等

食用方法&#xff1a;SSH连接群晖使用临时root权限执行 AME3.x激活补丁 只适用于x86_64的&#xff1a;DSM7.x Advanced Media Extensions (AME)版本3.0.1-2004、3.1.0-3005 激活过程需要下载官方的解码包&#xff0c;过程较慢&#xff0c;耐心等待。。。 DSM7.1和7.2的AME版…

【前端设计】炫酷导航栏

欢迎来到前端设计专栏&#xff0c;本专栏收藏了一些好看且实用的前端作品&#xff0c;使用简单的html、css语法打造创意有趣的作品&#xff0c;为网站加入更多高级创意的元素。 html <!DOCTYPE html> <html lang"en"> <head><meta charset&quo…

Java 和 JavaScript 的奇妙协同:语法结构的对比与探索(中)

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

家人们,比赛打完了

啊&#xff0c;终于打完一场比赛了&#xff0c;但还有三场…… 先看看我的战绩&#xff1a; 共八题&#xff0c;AC6题&#xff0c;总共3902分&#xff0c;3.7k人参加&#xff0c;第980名 来看看第一&#xff1a; A8题&#xff0c;我只有2题没做出&#xff0c;相差4000多分&am…

wsl连接USB设备

参考教程&#xff1a;连接 USB 设备 | Microsoft Learn 1.安装usbipd-win WSL 本身并不支持连接 USB 设备&#xff0c;因此你需要安装开源usbipd-win项目【下载Releases dorssel/usbipd-win (github.com)】 2.共享USB设备 通过以管理员模式打开PowerShell或者CMD并输入以下…

吐血整理!操作系统【处理机调度】

&#x1f308;个人主页&#xff1a;godspeed_lucip &#x1f525; 系列专栏&#xff1a;OS从基础到进阶 1 基本概念1.1 总览1.2 什么是调度1.2.1 调度1.2.2 处理机调度 1.3 调度的三个层次1.3.1 高级调度1.3.2 中级调度&#xff08;内存调度&#xff09;1.3.3 低级调度&#xf…

学生成绩管理系统|基于Springboot的学生成绩管理系统设计与实现(源码+数据库+文档)

学生成绩管理系统目录 目录 基于Springboot的学生成绩管理系统设计与实现 一、前言 二、系统功能设计 三、系统实现 1、管理员功能模块 2、学生功能模块 3、教师功能模块 四、数据库设计 1、实体ER图 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源…

Medical Boundary Diffusion Modelfor Skin Lesion Segmentation

皮肤病灶分割的医学边界扩散模型 摘要 由于多尺度边界关注和特征增强模块的进步&#xff0c;皮肤镜图像中的皮肤病变分割最近取得了成功。然而&#xff0c;现有的方法依赖于端到端学习范式&#xff0c;直接输入图像和输出分割图&#xff0c;经常与极其困难的边界作斗争&#…

009集——vba实现内存中大小端序的转换(附不同进制转换代码)

小端序为很多系统默认的数据存储方式&#xff0c;但有些数据格式为大端序模式解读文件&#xff0c;因此我们需将小端序字节颠倒排序&#xff0c;这样用大端序模式解读此文件&#xff0c;最后即可读取我们想要的内容。方法如下&#xff1a; Function SwapEndian(ByVal value As…