地址空间排布

这段空间中自上而下,地址是增长的,栈是向地址减小方向增长,里面存放函数中的临时变量,而堆是向地址增长方向增长,malloc开辟的地址空间存放在堆区,堆栈之间的共享区域,主要用来加载动态库。

验证地址空间排布
#include<stdio.h>
#include<stdlib.h>
int g_val_1;//未初始化
int g_val_2 = 100;//初始化
////
int main(int argc, char *argv[], char *env[])
{printf("code addr: %p\n", main);//代码起始地址const char *str = "hello bit";printf("read only string addr: %p\n", str);//str是指针变量(栈区),str指向字符常量"h"(字符常量区)printf("init global value addr: %p\n", &g_val_2);//printf("uninit global value addr: %p\n", &g_val_1);char *mem = (char*)malloc(100);char *mem1 = (char*)malloc(100);char *mem2 = (char*)malloc(100);printf("heap addr: %p\n", mem);printf("heap addr: %p\n", mem1);printf("heap addr: %p\n", mem2);printf("stack addr: %p\n", &str);printf("stack addr: %p\n", &mem);static int a = 0;int b;int c;printf("a = stack addr: %p\n", &a);printf("stack addr: %p\n", &b);printf("stack addr: %p\n", &c);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]);
}

运行结果:

我们可以看到代码区的地址是最小的,

static修饰的全局变量,编译的时候已经被编译到全局数据区了。

#include<stdio.h>
#include<unistd.h>
int g_val = 0;
int main()
{printf("begin.....%d\n",g_val);pid_t id = fork();if(id==0){//childint count = 0;while(1){printf("child: pid: %d,ppid: %d, g_val:%d, &g_val: %p\n",getpid(),getppid(),g_val,&g_val);sleep(1);count++;if(count == 5){g_val = 100;}}}else if(id>0){//fatherwhile(1){printf("father: pod: %d,ppid: %d, g_val:%d, &g_val: %p\n",getpid(),getppid(),g_val,&g_val);sleep(1);}}else{//todo}return 0;
}

在五秒后,子进程的g_val变成了100,但父进程的g_val没有改变,但神奇的是,他们俩的地址竟然还完全相同。

怎么可能同一个变量,同一个地址,同时读取,读到了不同的内容。

结论:如果变量的地址,是物理地址,不可能存在上面的现象。绝对不是物理地址,是线性地址或者是虚拟地址。平时写的c++/c用的指针,指针里面的地址全部都不是物理地址

进程地址空间的概念

每一个进程在创建时不仅要创建内核pcb还要创建进程地址空间

父进程pcb中会有指针指向这块地址空间,

页表是一种key-value式的表格,左侧对应虚拟地址,右侧对应实际物理地址

当父进程创建子进程时,先创建子进程pcb结构,子进程有自己独立的页表结构,此时子进程数据和代码都和父进程相同。子进程一开始页表为空,一个全局变量在父子进程具有相同的虚拟地址,当子进程往页表中写入时,发现父子进程指向相同的物理地址,系统会为子进程重新开辟一段空间来存储这个全局变量,此时父子进程相同的虚拟地址会映射到不同的物理地址。

先经过写时拷贝--是由操作系统自动完成的。本质重新开辟空间,但是在这个过程中,左侧的虚拟地址是0感知的,不会影响他。所在上上面例子中,将g_val改成100后进行打印,得到的地址相同,都是虚拟地址(子进程的虚拟地址继承自父进程),但内容不同是因为父进程通过虚拟地址查页表映射到一块物理地址,而子进程通过页表映射映射到物理内存的另一个地址。

地址空间究竟是什么?

什么叫做地址空间?

前提是一个进程一定是正在运行,在32位计算机中,有32位的地址和数据总线,cpu和内存之间的线叫做系统总线,内存和外设之间的线叫做io总线。拷贝的本质是磁盘向内存充放电的过程。,每一根地址总线只有0,1,32跟会有2的32次方中组合,2^32*1bit=4GB.

所以地址空间就是你的地址总线排列组合形成地址范围[0,2^32)

所谓的进程地址空间,本质是一个描述进程可视范围的大小

如何理解地址空间上的区域划分?-

地址空间一定要存在各种区域划分,要对线性地址进行start和end即可。地址空间本质是内核的一个数据结构对象,类似pcb一样,地址空间也是要被操作系统管理的,先描述后组织。

struct mm_struct
{unsigned long code_start;//代码区unsigned long code_end;unsigned long init_start;//初始化区unsigned long init_end;unsigned long uninit_start;//未初始化区unsigned long uninit_end;unsigned long heap_start;//堆区unsigned long heap_end;unsigned long stack_start;//栈区unsigned long stack_end;//...等等
}

所以在创建一个进程时,先要创建对应pcb,再创建对应结构体mm_struct,并划分区域,32位默认大小为4GB。在范围内,连续的空间中,每一个最小单位都可以有地址,每个地址都可以被小胖(进程)直接使用。

为什么要有进程地址空间

我们再来思考什么叫做进程?以及为什么要有进程地址空间?

如果进程直接访问物理内存,那么看到的地址就是物理地址,而语言中有指针,如果指针越界了,一个进程的指针指向了另一个进程的代码和数据,那么进程的独立性,便无法保证,因为物理内存暴露,其中就有可能有恶意程序直接通过物理地址,进行内存数据的篡改,如果里面的数据有账号密码就可以改密码,即使操作系统不让改,也可以读取。

增加进程虚拟地址空间可以让我们访问内存的时候,增加一个转换的过程,在这个转换的过程中,可以对我们的寻址请求进行审查,所以一旦异常访问,直接拦截,该请求不会到达物理内存,保护物理内存。

我们在写代码的时候肯定了解过指针越界,我们知道地址空间有各个区域,那么指针越界一定会出现错误吗?

不一定,越界可能他还是在自己的合法区域。比如他本来指向的是栈区,越界后它依然指向栈区,编译器的检查机制认为这是合法的,当你指针本来指向数据区,结果指针后来指向了字符常量区,编译器就会根据mm_struct里面的start,end区间来判断你有没有越界,此时发现你越界了就会报错了,这是其中的一种检查,第二种检查为:页表因为将每个虚拟地址的区域映射到了物理内存,其实页表也有一种权限管理,当你对数据区进行映射时,数据区是可以读写的,相应的在页表中的映射关系中的权限就是可读可写,但是当你对代码区和字符常量区进行映射时,因为这两个区域是只读的,相应的在页表中的映射关系中的权限就是只读,如果你对这段区域进行了写,通过页表当中的权限管理,操作系统就直接就将这个进程干掉。

页表

cr3寄存器

在cpu中会有一个cr3寄存器,这个寄存器会保留当前页表的起始地址,本质上属于进程的硬件上下文,所以当进程切换时,会带走寄存器的数据。(这个cr3是物理地址)

页表会给我们提供很好的权限管理

我们来思考一个问题,我们如何知道某个区域是只读还是可写入的呢?

页表中会有一个标志位来说明该区域是只读还是可写入。若一个位置权限是可读,当我们尝试对这个位置进行写入时,此时页表会直接进行拦截,相当于进行了一次非法操作,操作系统会直接终止这个进程。

此时我们来看一段代码

char *str ="hello bit";
*str='H';
return 0;

按照我们之前的理解,“hello bit”存在于字符常量区,是不可以被修改的。

但我们今天深入思考一下,为什么代码是只读?字符常量区是只读的?他们是如何加载到只读区域的?物理内存没有只读的概念,可以随意进行读写,没有权限管理。

这是因为在页表中的地址映射关系中,页表中的标志位是只读的,所以操作系统才会拦截你,跨权限时进程才会被操作系统终止。

我们再来思考一个问题

我们知道,当内存空间不足的时候,处于阻塞态的进程是可以被挂起的,代码和数据被换出内存,那我们如何知道这个进程已经被挂起了呢?你怎么知道你的进程的代码和数据在不在内存中?

如果进程在内存,并且状态时R,说明处于运行态,如果代码和数据在内存中,并且状态是S,说明进程正在被阻塞,可是Linux内核状态没有挂起转换态,我们如何知道进程此时是被挂起呢?

操作系统对大文件可以实现分批加载(打游戏时游戏大小十几个G,而你的内存只有八个G),现在操作系统用多少给你加载多少。页表中还有一个标志位来表示对应的代码和数据是否已经被加载到内存中。若这个标志位为1.表示已经被加载,我们直接读取对应的物理地址,找到对应的物理内存进行访问。当标志位为0表示当前代码和数据并未加载到内存中,操作系统会触发缺页中断。我们会找到这个可执行程序的代码和数据,在内存中申请一块内存,然后把可执行程序剩余的代码和数据加载到内存中,然后把这段内存的地址填写到对应的页表当中。我们再访问可以访问对应的代码和数据了。

这个时候我们就知道为什么某个区域是只读还是可读可写了,因为通过页表对他进行管理。

我们再思考一下

当进程在被创建的时候,是先创建内核数据结构还是先加载对应的可执行程序呢?

答案是先创建内核数据结构,即先要为该进程创建对应的pcb,进程地址空间,页表对应关系,然后才慢慢加载可执行程序,可能你的程序已经跑起来了,但还没加载完

因为有地址空间和页表的存在,我们可以将进程管理模块和内存管理模块进行解耦合!

我们再来谈进程。进程=内核数据结构(tast_struct&&mm_struct地址空间空间&&页表)+程序的代码和数据。所以进程在切换的时候不仅要切换pcb还要切换进程地址空间和页表。

我们经常说进程具有独立性,是怎么做到的?每一个进程都有独立的内核数据结构,在物理内存中加载的代码和数据都是独立的。在物理内存中加载的数据可以随便加载,是无序的,但经过页表的映射,可以在虚拟地址中有序的排布,这就有了各种区域(正文代码区,初始化区,未初始化区等等)

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

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Go语言实战案例-判断二叉树是否对称

给定一棵二叉树&#xff0c;判断这棵树是否是对称的。对称的含义是&#xff1a;这棵树的左子树和右子树在结构上是镜像对称的&#xff0c;且对应节点的值相等。示例 1&#xff1a;1/ \2 2/ \ / \ 3 4 4 3输出&#xff1a;true示例 2&#xff1a;1/ \2 2\ \3 3输出&a…

【机器学习深度学习】为什么需要分布式训练?

目录 前言 一、模型规模爆炸&#xff1a;单卡GPU已难以承载 1.1 问题描述 1.2 面临挑战 1.3 解决方案&#xff1a;模型并行 (Model Parallelism) 1.4 类比理解&#xff1a;模型并行 1.5 模型并行的关键点 1.6 模型并行&#xff08;Model Parallelism&#xff09;的流程…

二十八、【Linux系统域名解析】DNS安装、子域授权、缓存DNS、分离解析、多域名解析

DNS服务深度解析&#xff1a;缓存、分离与多域名管理一、DNS服务架构全景DNS核心组件关系DNS服务器类型对比二、基础DNS服务配置1. Bind9核心配置文件2. 区域文件结构解析区域文件记录类型表三、子域授权与分层解析子域授权原理子域配置流程1. 父域配置2. 子域配置递归与迭代查…

【LeetCode】前缀表相关算法

目录1、介绍2、核心概念【1】前缀和后缀【2】最长公共前后缀&#xff08;LPS&#xff09;3、相关算法题【1】找出字符串中第一个匹配项的下标【2】重复的子字符串1、介绍 前缀表是一种在字符串匹配算法&#xff08;特别是KMP算法&#xff09;中使用的数据结构&#xff0c;用于…

(六) Spring AI 1.0版本 + 千问大模型+RAG

上篇文章我们大概讲了一下向量模型的知识&#xff0c;本篇文章&#xff0c;我们将会通过RAG实战的形式&#xff0c;来感受一下RAG。 项目准备 pom.xml 这里我们需要引入向量库和pdf相关的包<dependency><groupId>org.springframework.ai</groupId><artifa…

Spring Boot与Mybatis-Plus集成SQLServer的完整指南

本文还有配套的精品资源&#xff0c;点击获取 简介&#xff1a;本项目旨在演示如何将SQLServer与Spring Boot以及Mybatis-Plus框架进行整合&#xff0c;打造一个高效稳定的后端服务。详细介绍涉及了数据库连接、实体类定义、Mapper接口创建、Service层业务逻辑编写、Control…

【工作笔记】判断一条方法需不需要事务/AOP

① 看注解方法/类上有 Transactional → 需要事务&#xff0c;必须走代理方法/类上有自定义 AOP 注解&#xff08;如 Log、Retry、Cacheable 等&#xff09;→ 需要代理什么都没有 → 几乎肯定不需要示例需求Transactional public void generateDailyTask(...)✅ 需要事务publi…

Unity 的UI动画调节

在游戏开发中&#xff0c;精美的UI动画能极大提升用户体验。Unity提供了强大的动画系统&#xff0c;让开发者可以轻松创建流畅的界面动效。本文将介绍UI动画的核心概念、制作流程和实用技巧。一、核心动画组件Animation窗口 - 可视化创建关键帧动画Animator组件 - 控制动画状态…

26考研11408数据结构

数据结构 1.绪论1.1.1数据结构的基本概念 数据数据元素&#xff1a;数据的基本单位&#xff0c;一个数据元素由多个数据项组成&#xff0c;数据项是组成数据元素不可分割的最小单位数据对象&#xff1a;具有相同性质的数据元素的集合&#xff0c;是数据的一个子集数据类型&…

Solar月赛(应急响应)——攻击者使用什么漏洞获取了服务器的配置文件?

某某文化有限公司的运维小王刚刚搭建服务器发现cpu莫名的异常的升高请你帮助小王排查一下服务器。 文章目录事件介绍事件1&#xff1a;帮助小王找到是什么漏洞?事件2&#xff1a;系统每天晚上系统都会卡卡的帮小明找到问题出在了那&#xff1f;事件3&#xff1a;恶意域名是什么…

高频面试题

1.HashMap的底层原理JDK1.7版本之前&#xff0c;HashMap的底层数据结构是数组链表&#xff0c;HashMap通过哈希算法会将元素的key映射待数组的的槽位(Bucket)。如果多个键映射到同一个槽位&#xff0c;就会以链表的形式存储在同一个槽位上。但是链表的查询的复杂度O(n),所有冲突…

鱼皮项目简易版 RPC 框架开发(四)

本文为笔者阅读鱼皮的项目 《简易版 RPC 框架开发》的笔记&#xff0c;如果有时间可以直接去看原文&#xff0c; 1. 简易版 RPC 框架开发 前面的内容可以笔者的前面几篇笔记 鱼皮项目简易版 RPC 框架开发&#xff08;一&#xff09; 鱼皮项目简易版 RPC 框架开发&#xff08;二…

力扣-79.单词搜索

题目链接 79.单词搜索 class Solution {int m, n;public boolean exist(char[][] board, String word) {m board.length;n board[0].length;boolean[][] visited new boolean[m][n];// 遍历网格中的每个单元格作为搜索起点for (int i 0; i < m; i) {for (int j 0; j …

LabVIEW的To More Specific Class功能说明

​To More Specific Class 是 LabVIEW 中用于控件引用类型转换的关键函数。可将通用 GObject 引用&#xff0c;精准转为 Listbox、TreeControl 等特定控件类引用&#xff0c;让开发者能调用专属属性&#xff08;如获取列表行数&#xff09;&#xff0c;实现对不同控件类的差异化…

Ubuntu20.04安装和配置Samba实现Win11下共享文件夹

Samba是在Linux和UNIX系统上实现 SMB / CIFS 协议的开源软件&#xff0c;主要用于局域网内的文件共享和打印服务。Samba通过SMB/CIFS协议实现跨平台资源共享&#xff0c;支持匿名用户和本地用户访问共享目录&#xff0c;客户端主要为Windows系统。其核心进程包括&#xff1a; ‌…

设计模式(八)结构型:桥接模式详解

设计模式&#xff08;八&#xff09;结构型&#xff1a;桥接模式详解桥接模式&#xff08;Bridge Pattern&#xff09;是 GoF 23 种设计模式中的结构型模式之一&#xff0c;其核心价值在于将抽象部分与实现部分分离&#xff0c;使它们可以独立变化。它通过“组合”而非“继承”…

【边缘填充】——图像预处理(OpenCV)

目录 1 边界复制&#xff08;BORDER_REPLICATE&#xff09; 2 边界反射&#xff08;BOEDER_REFLECT&#xff09; 3 边界反射101&#xff08;BORDER_REFLECT101&#xff09; 4 边界常数&#xff08;BORDER_CONSTANT&#xff09; 5 边界包裹&#xff08;BORDER_WRAP&#xf…

git同步到github出错-几个问题-一天晚上(2025.7.29)

访问不了github 代理和加速器都正常&#xff0c;但是就是访问不了这个网站尝试过几种方法都不行&#xff0c;后面突然可以了。 之后发现一种情况会不行&#xff1a;同时开启 同步不了 http连接 https://blog.csdn.net/m0_73972962/article/details/146198392 一堆问题 ssh连接才…

Redis未授权访问的利用的几种方法原理以及条件

一、redis通过定时任务反弹shell1.利用条件&#xff1a;需要能够登录redis数据库&#xff0c;并且redis以root用户运行。同时/var/spool/cron目录要具有写和执行权限。二、Redis主从getshell1.原理&#xff1a;在Redis 4.x之后&#xff0c;Redis新增了模块功能&#xff0c;通过…

DNF 与 YUM 的区别详解:从 CentOS 7 到 CentOS 9 的演进

&#x1f365; DNF 与 YUM 的区别详解&#xff1a;从 CentOS 7 到 CentOS 9 的演进标签&#xff1a;CentOS、YUM、DNF、Linux 包管理、系统升级、兼容性 适用版本&#xff1a;CentOS 7、CentOS 8、CentOS 9&#x1f9e9; 一、背景介绍 CentOS 中使用的包管理工具是 RedHat 系列…