Linux程序的地址空间

Linux程序的地址空间

📟作者主页:慢热的陕西人

🌴专栏链接:Linux

📣欢迎各位大佬👍点赞🔥关注🚓收藏,🍉留言

本博客主要内容深刻理解了什么程序或者进程的地址空间,以及它存在的意义,和操作系统内部是如何实现进程地址空间的

文章目录

  • Linux程序的地址空间
    • 1.一个测试代码
      • 运行的结果:
      • 1.1可以得到的结论
    • 2.引入地址空间
      • 2.1小故事
      • 2.2 代码区,数据区,堆区,等等这些区域该如何理解
    • 3. 地址空间是什么?为什么?怎么办?
      • 3.1为什么要有地址空间
      • 3.2 malloc的本质
      • 3.3 重新理解地址空间

1.一个测试代码

我们先运行一个测试代码:

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



int g_val = 100; //全局变量

int main()
{

pid_t id = fork();

assert(id >= 0);

if(id == 0)
{
 //child
 while(1)
 {
   //此处反斜杠是为了让代码可以换行
   printf("我是子进程,我的PID是:%d,我的PPID是:%d,g_val:%d, &g_val:%p\n",\
       getpid(), getppid(), g_val, &g_val);                                                                      
   sleep(1);
   g_val++;
 }
}
else
{
 while(1)
 {
   //父进程
   printf("我是父进程,我的PID是:%d,我的PPID是:%d,g_val:%d, &g_val:%p\n",\
       getpid(), getppid(), g_val, &g_val);
   sleep(1);
 }
}
return 0;
}

运行的结果:

image-20231106170746600

1.1可以得到的结论

我们发现竟然指向相同地址的变量,却出现了不同值的情况,这其实是因为发生了写时拷贝,并且我们佐证了,子进程可以对全局变量进行修改并且不影响父进程全局变量的值,从而证明了进程具有独立性。

同时我们也可以看出这里的地址不是物理地址,因为假设是物理地址,我们不可能在同一块物理地址上,取到不同的值,所以我们认为这是一个虚拟地址or线性地址

2.引入地址空间

2.1小故事

我们用一个大富翁和私生子的故事来引入地址空间的概念:

大富翁有十亿美金,但是他有四个私生子(A , B, C , D);

image-20231106223351434

A:做生意的 | B:卖化妆品 | C:哈弗读书 | D:高中辍学的混混

并且ABCD并不知道彼此的存在,并且他们都坚信自己最终能够继承大富翁的遗产;

某一天大富翁对着A说:你好好做你的生意,将来等我去世的时候我的十亿家产全都是你的。

对B说:你的化妆品卖的挺好,你听老爹的话,等老爹到时候西去的时候,我的家产都是你的。

同样对C和D都是一样的画饼。

所以现在ABCD每个人脑子里都有一张饼,就是老爹的十亿。😆

有一天A来找到大富翁,告诉他说自己需要五万美金,做生意需要买一块手表,会见自己和合作伙伴,但是自己的生意目前周转不开,大富翁一想,才五万美金,大手一挥直接给A了。

C联系大富翁,并给他说:自己在学校念书,没有生活费了,想问老爹要1000美金的生活费。老爹一听,小钱大手一挥又给了。(以上两个是进程向操作系统申请较小的内存空间,操作系统当然允许)

有一天D找到大富翁说:我在社会上遇到一点事,需要一些钱摆平一下,你给我5000万美金吧。老爹一听,说:一边去,我这么多钱也不能这么花。D骂骂咧咧的走了。(进程向操作系统申请很大的内存空间,操作系统拒绝了)

…这是ABCD也都会认为自己最终如果想要那十亿美金**(饼)**,他们都还是会获得的。

但其实如果ABCD每个人向大富翁索要几千几万,大富翁都会给予。但是如果向大富翁索要很多,几千万几亿的情况下,大富翁是完全可以直接拒绝的。

这里我们引入第一个概念:

进程的地址空间:就是大富翁给每个私生子画的饼。

那么最后揭晓:十亿美金相当于内存,大富翁相当于操作系统


那么我们想想大富翁有没有必要将自己的“饼”管理起来呢?

答案是有必要的先描述在组织,因为如果大富翁的儿子多了,并且他画的饼多了之后,他也会忘记的。

2.2 代码区,数据区,堆区,等等这些区域该如何理解

我们用一个小故事引入:

时间来到小学时代,有一对同桌,小花和小胖,两个人**共用一张大桌子。(内存空间)**然而小胖,不爱干净,男孩子很爱运动,所以身上老有一股汗味。小花就不满意了,二话不说先把小胖揍了一顿,告诉他:回家之后好好洗一洗,回去告诉你爸爸妈妈,我们上幼儿园要讲卫生。第二点,你从今天开始不能再欺负我了,要不然我再揍你一顿。

小花告诉小胖:我们以中间这根线为界谁都不许越界,不能把你的东西放到我这边来,也不能把你的胳膊放到我这边来。

小花画这条线的本质:叫做区域划分!如下这样:

struct area
{
    int start;
    int end;
}
struct area xiaohua_area = {1, 50};
struct area xiaopang_area = {50, 100};

地址空间本质就是线性结构的

image-20231107145145203

不管是32位还是64位机器,我们并不需要记录地址空间是多少GB的,原因是:不管是多少位的机器,在操作系统刚刚开始加载的时候就确定好了。

那么有了之前的铺垫,我们就可以地址空间是怎么在mm_struct中划分的;

struct mm_struct ---- 4GB
{
    long code_start; //代码区的起始
    long code_end; //代码区的结束
    long init_start;
    long init_end;
    ....
    long brk_start; //堆区的起始
    long brk_end; //堆区的结束
    long stack_start; //栈区的起始
    long stack_end; //栈区的结束
}

那么如果限定了区域,那么区域之间的数据是什么?

例如[1000,2000],1000和2000被称作我们的地址,那么他们之间的区域就被称作虚拟地址or线性地址

那么我们故事在继续…

小花和小胖画好了三八线之后,一段时间,两个人都很受规矩,但是小胖却很调皮,小胖总是将自己的胳膊放到三八线左侧,或者将自己的书包和都扔到小花的桌子上,这强烈的引起了小花的不满,小花就将小胖揍了一顿,然后更改了三八线的位置…

那么对于小花来说,叫做区域扩大。对于小胖来说叫做区域缩小;

体现在mm_struct中呢就是:

xiaohua_area.end = 75;
xiaopang_area.start = 75;

那么对应到我们的地址空间上,对于我们的栈区,堆区等的区域扩大和缩小也是一样的,都是更改我们的startend.

3. 地址空间是什么?为什么?怎么办?

数据和代码真正是存储在内存中。

是什么?

虚拟地址空间是一种在操作系统内部为进程创建出来的具体的数据结构对象,让进程以同意的视角来查看内存。

image-20231107153416685

虚拟地址会经过也变转化为物理地址,我们平时使用的地址都是虚拟地址,当我们有了虚拟地址,我们就可以通过页表找到物理地址中对应的位置,从而将物理地址中存储的内容放到我们的CPU中。

接下来我们用一幅图来解释最开始的父进程和子进程,查看相同地址的内存,却看到不同值的问题。

首先操作系统为每一个进程维护一张页表,用来存储虚拟地址和物理地址的映射关系。

image-20231107155544493

最开始的时候父子进程的虚拟地址都是指向最开始的g_val的位置,但是当子进程尝试修改g_val的时候,这时候操作系统会在物理地址的另一个位置拷贝一份g_val的值,然后修改,并且将这个物理地址存储到子进程页表刚刚指向原物理地址的位置。

并且这里也是体现了进程的独立性。

同时我们这里也可以解释最开始的之前的代码fork()函数貌似有两个返回值的问题:

首先fork在返回的时候,父子进程都有了,return两次,id是不是也是pid_t类型定义的变量呢?那么返回的本质就是写入,父子进程谁先写入,就发生了写时拷贝。造成了一个变量,有两个值的情况。

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
int main()
{     
	pid_t ret = fork();
	if(ret == 0)
	{ 
		//子进程
		printf("我是子进程,我的pid是:%d,我的父进程是:%d",getpid(),getppid()); 
		sleep(1);        
	}
	else if(ret > 0)
	{    
		//父进程
		while(1)
		{ 
			printf("我是父进程,我的pid是:%d,我的父进程是:%d"getpid(),getppid());
			sleep(2);   
		}                          
	}                                                                                            
	return 0;                                                                                  
} 

3.1为什么要有地址空间

如果没有地址空间,我们的进程的pcb只能直接指向物理内存中的该进程相关的数据和代码。

这样就会产生很多不安全的问题,以及效率问题。

①防止地址随意访问,保护物理内存和其他进程

②将进程管理和内存管理解耦合

③可以让进程以统一的视角,看待自己的代码和数据

3.2 malloc的本质

当我们向操作系统申请内存,操作系统立马给你,还是需要的时候再给你呢?

那么毫无疑问肯定是我们需要的时候才会给我们。

①操作一同不会允许任何的浪费和不高效

②申请内存 != 立马使用

③在你申请成功之后,和你使用之前,有很小一段的时间窗口,----这个空间没有被正常使用,但是别人也用不了,—闲置状态。

那么malloc的时候申请空间,我们操作系统只会再地址空间上对应的堆区进行申请空间,那么这里就是我们之前提到的,操作系统会为我们的堆区进行区域扩大,也就是去操作我们的mm_struct中的brk_endbrk_start,然后去页表中存储,但是不会再物理地址上去帮我们申请。相当于做了一半的映射,这时候直到当我们的进程需要用到这片地址的时候,操作系统才会帮我们去完成页表和物理地址的映射。

当操作系统检测到我们需要内存的时候才给我们申请,或者检测到我们的代码和数据不在内存,当我们需要的时候给我们换入,这样的操作叫做缺页中断

总结一句就是malloc在申请空间的时候,我们的操作系统不会立马分配空间,而是在我们需要使用的时候才给我们分配,这就是malloc的本质。

并且正是因为有了地址空间的存在,我们的进程也不关心,它的数据和代码存储在物理地址的任意位置。

**进程管理:**操作系统管理进程和地址空间的过程,叫做进程管理。

内存管理: 操作系统管理页表和物理内存之间的映射的过程,叫做内存管理。

并且操作系统将进程和物理内存之间分成进程管理内存管理的过程,叫做解耦合。

3.3 重新理解地址空间

首先我们想一想我们的程序在被编译的时候,没有被加载到内存,请问,我们的程序内部有没有地址呢?

答案肯定是有的,源代码被编译的时候,就是按照虚拟地址空间的方式进行对代码和数据早就已经编好了对应的编址。

我们不能认为虚拟地址这样的策略只会影响操作系统,还要让我们的编译器也要遵守这样的规则。(Linux系统中的可执行文件的格式是ELF格式)

进程的代码和数据需要一直在内存中吗?

答案是不一定的,就算是我们要运行一个进程,我们操作系统也不会一次将这个进程所对应的代码和数据加载到内存中。

操作系统一般是边加载边执行,这也是地址空间的意义。

到这本篇博客的内容就到此结束了。
如果觉得本篇博客内容对你有所帮助的话,可以点赞,收藏,顺便关注一下!
如果文章内容有错误,欢迎在评论区指正

在这里插入图片描述

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

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

相关文章

【性能测试】非GUI模式Jemter压测+TPS性能拐点详细,一篇带你打通...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 非GUI模式执行Jem…

YOLOv8 Ultralytics:使用Ultralytics框架训练RT-DETR实时目标检测模型

YOLOv8 Ultralytics&#xff1a;使用Ultralytics框架训练RT-DETR实时目标检测模型 前言相关介绍前提条件实验环境安装环境项目地址LinuxWindows 制作自己的数据集训练自己的数据集创建自己数据集的yaml文件football.yaml文件内容 进行训练进行验证进行预测 数据集获取参考文献 …

python 对全局变量的修改,需要使用global关键字

is_debug Falsedef get_is_debug():return is_debugdef set_is_debug(dbg):global is_debugis_debug dbg代码review的时候有个同事&#xff08;我们主要都是开发c代码的&#xff0c;python也会写&#xff0c;但是用的少&#xff09;说&#xff0c;set_is_debug函数中 is_debu…

Leetcode-94 二叉树的中序遍历

递归实现 /*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* TreeNode right;* TreeNode() {}* TreeNode(int val) { this.val val; }* TreeNode(int val, TreeNode left, TreeNode right) {* …

网络安全基础之php开发文件下载的实现

前言 php是网络安全学习里必不可少的一环&#xff0c;简单理解php的开发环节能更好的帮助我们去学习php以及其他语言的web漏洞原理 正文 在正常的开发中&#xff0c;文件下载的功能是必不可少&#xff0c;比如我们在论坛看到好看图片好听的歌时&#xff0c;将其下载下来时就…

蓝桥杯算法竞赛第一周题型总结

本专栏内容为&#xff1a;蓝桥杯学习专栏&#xff0c;用于记录蓝桥杯的学习经验分享与总结。 &#x1f493;博主csdn个人主页&#xff1a;小小unicorn ⏩专栏分类&#xff1a;C &#x1f69a;代码仓库&#xff1a;小小unicorn的代码仓库&#x1f69a; &#x1f339;&#x1f33…

为什么单片机内不继承晶振

一、晶振是什么&#xff1f;有什么作用&#xff1f; 晶振&#xff08;Crystal Oscillator&#xff09;是一种基于晶体材料的振荡器&#xff0c;用于提供稳定的振荡信号。晶体是一种可通过机械振动在电场作用下产生相应电压的物质。晶振由晶体与电路共同组成&#xff0c;晶体负责…

KT6368A蓝牙芯片的距离天线周围的匹配元器件LC,能增加距离吗

一、简介 KT6368A蓝牙芯片的距离&#xff0c;以及天线周围的元器件&#xff0c;电感和电容&#xff0c;添加上去是否可以增加距离&#xff1f; 详细描述 关于蓝牙芯片的rf性能&#xff0c;也就是距离&#xff0c;其实中规中矩吧&#xff0c;但是达到20米还很简单的&#xff…

2023年10 种用于最佳稳定扩散最佳方案

在过去的一年里&#xff0c;您可能已经看到了很多关于 ChatGPT 和其他 AI 自动纹理的新闻。但是&#xff0c;页面&#xff08;或屏幕&#xff09;上的文字远非现代组织和设计师使用人工智能的唯一方式。Stable Diffusion 等工具可帮助您创建令人惊叹的 AI 图像&#xff0c;供个…

【CodeTop】TOP 100 刷题 1-10

文章目录 1. 无重复字符的最长子串题目描述代码与思路 2. 反转链表题目描述代码与解题思路 3. LRU 缓存题目描述代码与解题思路 4. 数组中的第K个最大元素题目描述代码与解题思路 5. K 个一组翻转链表题目描述代码与解题思路 6. 三数之和题目描述代码与解题思路 7. 最大子数组和…

稳定扩散AI 纹理生成器

推荐基于稳定扩散(stable diffusion) AI 模型开发的自动纹理工具&#xff1a; DreamTexture.js自动纹理化开发包 - NSDT 什么是稳定扩散&#xff1f; 从技术上讲&#xff0c;Stable Diffusion 是一种用于机器学习的潜在扩散模型 &#xff08;LDM&#xff09;。这种类型的专用深…

2023-11笔记

1.switch空指针异常 Exception in thread "main" java.lang.NullPointerException:Cannot invoke "String.hashCode()" because "<local2>" is nullat Study5.Test03.main(Test03.java:6)我们由此可以知道&#xff0c;switch语句部分情况下…

@ConfigurationProperties使用

一直有个疑问,在使用ConfigurationProperties注解作用一个配置类时,如果该配置类继承了一个父类,那么父类的那些配置字段是否可以读取配置信息。 答案是可以的&#xff0c;前提是父类对应字段的set方法是public。 BaseProperties.java Getter Setter public class BasePropert…

51单片机PCF8591数字电压表LCD1602液晶显示设计( proteus仿真+程序+设计报告+讲解视频)

51单片机PCF8591数字电压表LCD1602液晶设计 ( proteus仿真程序设计报告讲解视频&#xff09; 仿真图proteus7.8及以上 程序编译器&#xff1a;keil 4/keil 5 编程语言&#xff1a;C语言 设计编号&#xff1a;S0060 51单片机PCF8591数字电压表LCD1602液晶设计 1.主要功能&a…

目标检测算法改进系列之Backbone替换为VanillaNet

VanillaNet简介 简介&#xff1a;VanillaNet是一种在设计中融入优雅的神经网络架构&#xff0c;通过避免高深度&#xff0c;shortcut和自注意力等复杂操作&#xff0c;VanillaNet简单而强大。每一层都经过精心制作&#xff0c;紧凑而直接&#xff0c;在训练后对非线性激活函数…

【赠书活动】嵌入式虚拟化技术与应用

文章目录 前言 1 背景概述 2 专家推荐 3 本书适合谁&#xff1f; 4 内容简介 5 书籍目录 6 权威作者团队 7 粉丝福利 前言 随着物联网设备急剧增长和万物互联应用迅速发展&#xff0c;虚拟化技术成为嵌入式系统焦点。这反映了信息技术迫切需求更高效、灵活和可靠系统。…

漫谈广告机制设计 | 万剑归宗:聊聊广告机制设计与收入提升的秘密(1)

小时候看武侠电视剧《风云》的时候&#xff0c;其中无名有一招叫“万剑归宗”&#xff0c;乃是剑术最高境界。修炼的口诀是“万气自生&#xff0c;剑冲废穴&#xff1b;归元武学&#xff0c;宗远功长”&#xff0c;也就是说欲练此功&#xff0c;先自废武功&#xff0c;然后回归…

it is missing from your system. Install or enable PHP‘s sodium extension.

Composer with –ignore-platform-reqext-sodium it is missing from your system. Install or enable PHP’s sodium extension. 出现如上的报错是的 开启php.ini中的 sodium的扩展

华为防火墙二层透明模式下双机热备主备备份配置(两端为交换机)

这种模式只能是主备备份模式&#xff0c;不能是负载分担&#xff0c;因为会有环路。 故障切换是&#xff0c;如果主故障&#xff0c;主设备所有接口全都会down状态&#xff0c;然后再up一次&#xff0c;用于改变mac转发表。 FW1 hrp enable hrp interface GigabitEthernet1/0…

数据结构 树和二叉树

敬请期待 1. 术语详解 树&#xff1a; 二叉树&#xff1a; 森林&#xff1a; 完全二叉树&#xff1a; 满二叉树&#xff1a; 二叉排序树&#xff1a; 二叉搜索树&#xff1a; 哈夫曼树&#xff1a;分为左小右大和左先右后两种构造方法。 平衡二叉树&#xff1a; 线索…