浅谈linux下的进程地址空间(虚拟地址/线性地址)

2024-03-26 01:36

本文主要是介绍浅谈linux下的进程地址空间(虚拟地址/线性地址),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

什么是地址空间 - 虚拟地址空间

地址空间是如何设计的

为什么要有地址空间


什么是地址空间?

示例:

运行之后发现:同一个变量,同一个地址,在运行一段时间后,竟然会在同一时间出现两个不同的值?这是完全违背常理的,按道理来说一个 int类型 ,只能存储一个整数,为什么这里会出现两个完全不同的值呢???

要想了解这个问题,我们需要先了解一些东西。

linux下的地址空间:注意,这里指的是虚拟地址空间

【说明】
1.栈区(stack):在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返回地址等,栈是向下增长的。

⒉.共享区:内存映射段是高效的I/O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口创建共享共享内存,做进程间通信。

3.堆区:堆用于程序运行时动态内存分配,堆是可以上增长的。一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收。分配方式类似于链表。

4.数据段:--存储全局数据和静态数据。(未初始化/初始化数据)

5.代码段:--可执行的代码/只读常量。(正文代码)

上述的地址空间,也可以通过代码验证:示例:

打印结果如下:可以发现的确是符合进程空间地址排布规律的,地址也是由低到高进行增长的。(注:这里栈区之所以很大,是因为使用的是云服务器)

        上述的进程地址空间被称为 --- 虚拟地址空间 / 线性地址空间,它并不是实际上存储数据的物理内存空间,而是一种Linux下的内核数据结构,并且每一个进程都有独立私有的虚拟地址空间。

这里在Linux内核的源码中也有体现:进程的虚拟地址空间

那么如何管理各个区域呢?

在一个范围内定义start,end本质上就是在进行区域划分

所谓的范围变化,本质其实就是对start or end标记值 + -  特定的范围即可

地址空间是Linux内核数据结构,它里面一定有区域划分
 


地址空间是如何设计的

我们在虚拟地址和物理内存之间会设计一个页表,来维系二者的关联。

我们可以把各个区域及其对应的地址通过页表映射到不同的物理内存中,也就是说页表是维护映射关系的

这种映射关系是来维护虚拟地址和物理地址之间的的一种映射关系

换句话说,每个进程即使它的虚拟地址空间是完全一样的,但是只要它的页表映射关系是不同的,映射到物理内存的不同区域,那么它们就可以做到每个进程是具有独立性的

注:这里的页表是底层是哈希表,存储的是key_value结构,左边存储的的虚拟地址,右边存储的是物理内存地址


回答最开始的现象,为什么同一个地址,在同一个时刻,同时读取的时候,出现不同的值?

是因为这里的地址,绝对不是物理内存的地址,而是虚拟地址(线性地址)

所以,几乎所有的语言,如果它有”地址“的概念,那么这个地址一定不是物理地址,而是虚拟地址

        子进程继承了父进程大部分属性,页表和地址空间都相同,刚开始父进程对应的全局变量g_val的虚拟地址被映射到了物理内存上

        创建子进程的时候,因为页表的映射关系相同,此时父进程与子进程指向的是同一个变量g _val,所以地址相同,甚至值也相同

        但是当子进程尝试去修改g_val,因为要保障进程的独立性,当操作系统识别到当前的子进程想去通过页表去访问g_val,想要写入的时候

        那么操作系统会重新开辟一块空间,然后有必要的情况下,拷贝原来的值,然后再更改子进程的映射关系,防止子进程修改到原来地址的g_val

        完成更改之后,因为虚拟地址并不受影响,所以虚拟地址的值是相同的,但是物理数据经过页表的映射,被映射到了不同的区域,所以看到的g _val的值是不同的

补:最开始创建指向同一个位置,当子进程尝试去做修改的时候,才发现地址是不同的,这种策略叫做写时拷贝


深入了解虚拟地址空间:

当我们的程序,在编译的时候,形成可执行程序的时候,没有被加载到内存中的时候,其实已经有地址了! !

可执行程序其实编译的时候,内部已经有地址了!

地址空间不要仅仅是系统内部要遵守的,其实编译器也要遵守!

即编译器编译代码的时候,就已经给我们形成了各个区域代码区,数据区,...并且,采用和Linux内核中一样的编址方式,给每一个变量,每一行代码都进行了编址,故,程序在编译的时候,每一个字段早已经具有了一个虚拟地址!

物理内存中,可执行程序内部的地址,依旧用的是编译器编译好的虚拟地址

当程序加载到内存的时候,每行代码,每个变量边具有了一个物理地址,外部的
 

1.进程访问数据时,地址空间和页表(虚拟地址的一部分)会被编译时生成的虚拟地址填充,页表(物理地址的一部分)会被加载时产生的物理地址填充。

2例子:假设函数A调用函数B
此时,进程开始运行,cpu拿到的是编译时函数A产生的虚拟地址,通过函数A的虚拟地址映射到了物理内存
此时,访问内存空间,通过函数A中存储B函数的虚拟地址,获现到了的B函数的虚拟地址,再次通过映射关系访问到B函数(其中,A函数调用B函数,编译时A函数中一定会存储B函数的虚拟地址)
 


为什么要有地址空间

1.保障安全

历史原因:

        在当初,可执行程序被加载到内存中,进程是可以直接访问物理内存的,但是我们需要明白,内存本身是随时都可以被修改的,假如出现野指针的问题,进程之间是会被相互干扰,此时安全是很难保障的。

于是,现代计算机在此基础之上,提出了以下方式,虽然通过虚拟地址最终还是会访问到了物理内存,但是需要映射,而页表会对指针进行监测,如果出现非法访问,是可以禁止映射的。

凡是非法的访问或者映射,系统都会识别到,并终止你这个进程,此时有效的保护了物理内存

因为地址空间和页表是系统创建并维护的,也就意味着凡是想使用地址空间和页表进行映射,也一定要在OS的监管之下来进行访问!!

也就保护了物理内存中的所有的合法数据,包括各个进程,以及内核的相关有效数据!
 

2.方便管理

a.解耦合

因为有地址空间的存在,因为有页表映射的存在,我们的物理内存中,可以对未来的数据进行任意位置的加载

物理内存的分配就可以和进程的管理,做到没有任何关系!!

内存管理模块 vs 进程管理模块就完成了
 

注:所以,我们在C、C++语言上,new,malloc空间的时候,本质是在虚拟地址空间上申请的

因为如果我申请了物理空间,但是如果我不立马使用? 是不是空间的浪费呢?  是的!!

本质上,(因为有地址空间的存在,所以上层申请空间,其实是在虚拟地址空间上申请的,物理内存可以甚至一个字节都不给你! !而当你真正进行对物理地址空间访问的时候,才执行内存的相关管理算法,帮你申请内存,构建页表映射关系),然后,再让你进行内存的访问。这都是由操作系统,自动完成,用户,包括进程,完全0感知 ---缺页中断!
 

b.有序性

因为在物理内存中理论上可以任意位置加载,那么物理内存中的几乎所有的数据和代码在内存中是乱序的!

但是,因为页表的存在,它可以将地址空间上的虚拟地址和物理地址进行映射,那么在进程视角所有的内存分布,都可以是有序的!!

地址空间+页表的存在可以将内存分布,有序化!
 

c.独立性

通过进程映射到不同的物理内存,很容易做到让不同的进程具有独立性

地址空间+页表的存在可以实现进程的独立性


其实这也符合linux下,一切皆文件的概念

上帝视角下,内存可以任意读写,那么我们的非法访问也就是权限不够,也就是Linux下,一切皆文件的概念

注:以上仅代表个人观点,仅供参考

这篇关于浅谈linux下的进程地址空间(虚拟地址/线性地址)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/846900

相关文章

怎样通过分析GC日志来定位Java进程的内存问题

《怎样通过分析GC日志来定位Java进程的内存问题》:本文主要介绍怎样通过分析GC日志来定位Java进程的内存问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、GC 日志基础配置1. 启用详细 GC 日志2. 不同收集器的日志格式二、关键指标与分析维度1.

Java进程异常故障定位及排查过程

《Java进程异常故障定位及排查过程》:本文主要介绍Java进程异常故障定位及排查过程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、故障发现与初步判断1. 监控系统告警2. 日志初步分析二、核心排查工具与步骤1. 进程状态检查2. CPU 飙升问题3. 内存

Linux中压缩、网络传输与系统监控工具的使用完整指南

《Linux中压缩、网络传输与系统监控工具的使用完整指南》在Linux系统管理中,压缩与传输工具是数据备份和远程协作的桥梁,而系统监控工具则是保障服务器稳定运行的眼睛,下面小编就来和大家详细介绍一下它... 目录引言一、压缩与解压:数据存储与传输的优化核心1. zip/unzip:通用压缩格式的便捷操作2.

Linux中SSH服务配置的全面指南

《Linux中SSH服务配置的全面指南》作为网络安全工程师,SSH(SecureShell)服务的安全配置是我们日常工作中不可忽视的重要环节,本文将从基础配置到高级安全加固,全面解析SSH服务的各项参... 目录概述基础配置详解端口与监听设置主机密钥配置认证机制强化禁用密码认证禁止root直接登录实现双因素

MySQL之InnoDB存储页的独立表空间解读

《MySQL之InnoDB存储页的独立表空间解读》:本文主要介绍MySQL之InnoDB存储页的独立表空间,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1、背景2、独立表空间【1】表空间大小【2】区【3】组【4】段【5】区的类型【6】XDES Entry区结构【

在Linux终端中统计非二进制文件行数的实现方法

《在Linux终端中统计非二进制文件行数的实现方法》在Linux系统中,有时需要统计非二进制文件(如CSV、TXT文件)的行数,而不希望手动打开文件进行查看,例如,在处理大型日志文件、数据文件时,了解... 目录在linux终端中统计非二进制文件的行数技术背景实现步骤1. 使用wc命令2. 使用grep命令

Linux如何快速检查服务器的硬件配置和性能指标

《Linux如何快速检查服务器的硬件配置和性能指标》在运维和开发工作中,我们经常需要快速检查Linux服务器的硬件配置和性能指标,本文将以CentOS为例,介绍如何通过命令行快速获取这些关键信息,... 目录引言一、查询CPU核心数编程(几C?)1. 使用 nproc(最简单)2. 使用 lscpu(详细信

linux重启命令有哪些? 7个实用的Linux系统重启命令汇总

《linux重启命令有哪些?7个实用的Linux系统重启命令汇总》Linux系统提供了多种重启命令,常用的包括shutdown-r、reboot、init6等,不同命令适用于不同场景,本文将详细... 在管理和维护 linux 服务器时,完成系统更新、故障排查或日常维护后,重启系统往往是必不可少的步骤。本文

基于Linux的ffmpeg python的关键帧抽取

《基于Linux的ffmpegpython的关键帧抽取》本文主要介绍了基于Linux的ffmpegpython的关键帧抽取,实现以按帧或时间间隔抽取关键帧,文中通过示例代码介绍的非常详细,对大家的学... 目录1.FFmpeg的环境配置1) 创建一个虚拟环境envjavascript2) ffmpeg-py

CSS实现元素撑满剩余空间的五种方法

《CSS实现元素撑满剩余空间的五种方法》在日常开发中,我们经常需要让某个元素占据容器的剩余空间,本文将介绍5种不同的方法来实现这个需求,并分析各种方法的优缺点,感兴趣的朋友一起看看吧... css实现元素撑满剩余空间的5种方法 在日常开发中,我们经常需要让某个元素占据容器的剩余空间。这是一个常见的布局需求