当前位置: 首页 > ops >正文

【Linux】Linux程序地址基础

参考博客:https://blog.csdn.net/sjsjnsjnn/article/details/125533127

一、地址空间的阐述

1.1 程序地址空间

下面的图片展示了程序地址空间的组成结构

在这里插入图片描述

我们通过代码来验证一下

int g_unval;
int g_val = 100;int main(int argc, char *argv[]);void test1()
{int a = 10;int b = 20;const char *s = "hello world";printf("code addr:%p\n", main);       // 代码区printf("string rdonly addr:%p\n", s); // 字符常量区printf("init addr:%p\n", &g_val);     // 已初始化printf("uninit addr:%p\n", &g_unval); // 未初始化char *heap = (char *)malloc(10);printf("heap addr:%p\n", heap); // 堆区printf("stack addr:%p\n", &a);printf("stack addr:%p\n", &b);printf("stack addr:%p\n", &s);  // 栈区printf("stack addr:%p\n", &heap);
}

运行结果如下:

  1. 代码区的地址位于最低处,0x63e2eb7be37e
  2. 接着是字符串常量区0x63e2eb7bf004
  3. 然后是已初始化区0x63e2eb7c1010和未初始化区0x63e2eb7c1154
  4. 后面是堆区内存地址0x63e31e4b6ec0,堆区内存地址不是连续的
  5. 最后是栈区0x7fff3e007150~0x7fff3e007160,栈区地址是连续的
code addr:0x63e2eb7be37e
string rdonly addr:0x63e2eb7bf004
init addr:0x63e2eb7c1010
uninit addr:0x63e2eb7c1154
heap addr:0x63e31e4b6ec0
stack addr:0x7fff3e007150
stack addr:0x7fff3e007154
stack addr:0x7fff3e007158
stack addr:0x7fff3e007160

可以发现,打印的结果符合对应的地址结构

二、进程地址空间

2.1 程序虚拟地址打印

下面的代码演示创建一个子进程后,打印子进程和父进程程序变量的地址

void test2(){int ret = fork();if(ret == 0){std::cout << "I am child, g_val = " << g_val << ", &g_val = " << &g_val << std::endl;for(int i = 0 ; i< 5;++i){g_val--;std::cout << "==========change g_val=========" << std::endl;std::cout << "I am child, g_val = " << g_val << ", &g_val = " << &g_val << std::endl;sleep(1);}}else if(ret > 0){while(1){std::cout << "I am father, g_val = " << g_val << ", &g_val = " << &g_val << std::endl;sleep(1);}}else{std::cout << "error:" << strerror(ret) << std::endl;}}

运行结果如下:

  • 在没有修改变量的情况下,父进程和子进程中g_val的地址一样,指向同一块内存
  • 但是修改了子进程中值的大小后,g_val的值不同,打印出来的地址确是同一段
  • 这里涉及了虚拟地址,我们打印出来的地址实际上是虚拟机制,而不是物理地址

在这里插入图片描述

2.2 进程地址空间结构

  • 之前说‘程序的地址空间’是不准确的,准确的应该说成进程虚拟地址空间 ,每个进程都会有自己的地址空间,认为自己独占物理内存。
  • 操作系统在描述进程地址空间时,是以结构体的形式描述的,在linux中这种结构体是 struct mm_struct 。它在内核中是一个数据结构类型,具体进程的地址空间变量。

这些变量就是每个空间的起始位置与结束位置。如下图所示

在这里插入图片描述

进程地址空间就类似于一把尺子,每个空间都有对应的起始位置和结束位置。通过这个虚拟地址去间接访问内存;

为什么不能直接去访问物理内存?

如果没有进程地址空间的加持,那么程序就会直接访问物理内存,没有区间可言,会存在恶意程序可以>随意修改别的进程的内存数据,以达到破坏的目的。有些非恶意的,但是有bug的程序也可能不小心修改了其它程序的内存数据,就会导致其它程序的运行出现异常。这种情况对用户来说是无法容忍的,因为用户希望使用计算机的时候,其中一个任务失败了,至少不能影响其它的任务。

2.3 如何通过虚拟地址访问物理地址

  • 每个进程都是独立的虚拟地址空间,两个独立进程的相同地址互不干扰,但是在物理上对每个进程可能也就分了一部分空间给了某个进程。

  • 每个进程被创建时,其对应的进程控制块和进程虚拟地址空间也会随之被创建。而操作系统可以通过进程的控制块找到其进程地址空间,通过页表对将虚拟地址转换为物理地址,达到访问物理地址的目的。

  • 这种方式称之为映射,调度某个进程执行时,就要把它的地址空间映射到一个物理空间上。
    在这里插入图片描述

因此,最终得到的虚拟地址通过下述转换得到物理地址,虽然虚拟地址一致,但是通过不同进程中的映射关系,会得到不同的物理地址,这就是为什么虚拟地址一致,但是得到的值是不同的原因

在这里插入图片描述

写时拷贝:就是等到修改数据时才真正分配内存空间,这是对程序性能的优化,可以延迟甚至是避免内存拷贝,当然目的就是避免不必要的内存拷贝

更多资料:https://github.com/0voice

http://www.xdnf.cn/news/11905.html

相关文章:

  • ‘utf-8‘ codec can‘t decode byte 0xc9 in position 18:
  • css-塞贝尔曲线
  • Ubuntu 25.10 将默认使用 sudo-rs
  • Python IP可达性检测脚本解析
  • Redis初入门
  • python爬虫:Newspaper3k 的详细使用(好用的新闻网站文章抓取和解析的Python库)
  • MySQL 8.0 窗口函数全面解析与实例
  • 力提示(force prompting)的新方法
  • leetcode1443. 收集树上所有苹果的最少时间-medium
  • Oracle数据库笔记
  • Windows下运行Redis并设置为开机自启的服务
  • @Prometheus动态配置管理-ConsulConfd
  • ArcGIS Pro 3.4 二次开发 - 地图探索
  • unix/linux,sudo,其基本概念、定义、性质、定理
  • 705SJBH超市库存管理系统文献综述
  • 关于面试找工作的总结(四)
  • 【无标题】图着色问题的革命性解决方案:拓扑膨胀-收缩对偶理论
  • 【黄金评论】美元走强压制金价:基于NLP政策因子与ARIMA-GARCH的联动效应解析
  • react+taro 开发第五个小程序,解决拼音的学习
  • 如果安装并使用RustDesk
  • TDengine 在电力行业如何使用 AI ?
  • win32com.client模块 —— Python实现COM自动化控制与数据交互
  • Linux系统iptables防火墙实验拓补
  • 77、完全植入式脑机接口神经数据编码解码数据处理等问题答疑[嘿!快看,馒头老师在这里蹲着!]
  • 详解Jenkins Pipeline 中git 命令的使用方法
  • Kubernetes 网络方案:Flannel 插件全解析
  • 常用的录音芯片型号有哪些?
  • 高并发区块链系统实战:从架构设计到性能优化
  • NFS的基本配置
  • Java中的多态