浅谈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

相关文章

linux-基础知识3

打包和压缩 zip 安装zip软件包 yum -y install zip unzip 压缩打包命令: zip -q -r -d -u 压缩包文件名 目录和文件名列表 -q:不显示命令执行过程-r:递归处理,打包各级子目录和文件-u:把文件增加/替换到压缩包中-d:从压缩包中删除指定的文件 解压:unzip 压缩包名 打包文件 把压缩包从服务器下载到本地 把压缩包上传到服务器(zip

常用的jdk下载地址

jdk下载地址 安装方式可以看之前的博客: mac安装jdk oracle 版本:https://www.oracle.com/java/technologies/downloads/ Eclipse Temurin版本:https://adoptium.net/zh-CN/temurin/releases/ 阿里版本: github:https://github.com/

Linux 网络编程 --- 应用层

一、自定义协议和序列化反序列化 代码: 序列化反序列化实现网络版本计算器 二、HTTP协议 1、谈两个简单的预备知识 https://www.baidu.com/ --- 域名 --- 域名解析 --- IP地址 http的端口号为80端口,https的端口号为443 url为统一资源定位符。CSDNhttps://mp.csdn.net/mp_blog/creation/editor

【Python编程】Linux创建虚拟环境并配置与notebook相连接

1.创建 使用 venv 创建虚拟环境。例如,在当前目录下创建一个名为 myenv 的虚拟环境: python3 -m venv myenv 2.激活 激活虚拟环境使其成为当前终端会话的活动环境。运行: source myenv/bin/activate 3.与notebook连接 在虚拟环境中,使用 pip 安装 Jupyter 和 ipykernel: pip instal

浅谈主机加固,六种有效的主机加固方法

在数字化时代,数据的价值不言而喻,但随之而来的安全威胁也日益严峻。从勒索病毒到内部泄露,企业的数据安全面临着前所未有的挑战。为了应对这些挑战,一种全新的主机加固解决方案应运而生。 MCK主机加固解决方案,采用先进的安全容器中间件技术,构建起一套内核级的纵深立体防护体系。这一体系突破了传统安全防护的局限,即使在管理员权限被恶意利用的情况下,也能确保服务器的安全稳定运行。 普适主机加固措施:

Linux_kernel驱动开发11

一、改回nfs方式挂载根文件系统         在产品将要上线之前,需要制作不同类型格式的根文件系统         在产品研发阶段,我们还是需要使用nfs的方式挂载根文件系统         优点:可以直接在上位机中修改文件系统内容,延长EMMC的寿命         【1】重启上位机nfs服务         sudo service nfs-kernel-server resta

【Linux 从基础到进阶】Ansible自动化运维工具使用

Ansible自动化运维工具使用 Ansible 是一款开源的自动化运维工具,采用无代理架构(agentless),基于 SSH 连接进行管理,具有简单易用、灵活强大、可扩展性高等特点。它广泛用于服务器管理、应用部署、配置管理等任务。本文将介绍 Ansible 的安装、基本使用方法及一些实际运维场景中的应用,旨在帮助运维人员快速上手并熟练运用 Ansible。 1. Ansible的核心概念

Linux服务器Java启动脚本

Linux服务器Java启动脚本 1、初版2、优化版本3、常用脚本仓库 本文章介绍了如何在Linux服务器上执行Java并启动jar包, 通常我们会使用nohup直接启动,但是还是需要手动停止然后再次启动, 那如何更优雅的在服务器上启动jar包呢,让我们一起探讨一下吧。 1、初版 第一个版本是常用的做法,直接使用nohup后台启动jar包, 并将日志输出到当前文件夹n

[Linux]:进程(下)

✨✨ 欢迎大家来到贝蒂大讲堂✨✨ 🎈🎈养成好习惯,先赞后看哦~🎈🎈 所属专栏:Linux学习 贝蒂的主页:Betty’s blog 1. 进程终止 1.1 进程退出的场景 进程退出只有以下三种情况: 代码运行完毕,结果正确。代码运行完毕,结果不正确。代码异常终止(进程崩溃)。 1.2 进程退出码 在编程中,我们通常认为main函数是代码的入口,但实际上它只是用户级