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

【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

相关文章:

  • 读文献先读图:GO弦图怎么看?
  • 基于vue框架的动漫网站noww0(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
  • DeepSeek+SpringAI实现流式对话
  • vue 多端适配之pxtorem
  • MySQL 事务深度解析:面试核心知识点与实战
  • Redis中的过期策略与内存淘汰策略
  • 组件库二次封装——透传问题
  • 项目练习:Vue2中el-button上的@click事件失效
  • spring4第6课-bean之间的关系+bean的作用范围
  • 解决IDE编译JAVA项目时出现的OOM异常问题
  • DeepSwiftSeek 开源软件 |用于 DeepSeek LLM 模型的 Swift 客户端 |轻量级和高效的 DeepSeek 核心功能通信
  • 《复制粘贴的奇迹:小明的原型工厂》
  • Python训练营打卡DAY44
  • JavaScript性能优化实战技术
  • 全志A40i android7.1 调试信息打印串口由uart0改为uart3
  • leetcode刷题日记——二叉树的锯齿形层序遍历
  • 为UE5的Actor添加能够读写姿态的功能
  • stm32——UART和USART
  • 开源模型应用落地-OpenAI Agents SDK-集成Qwen3-8B-function_tool(二)
  • AI在网络安全领域的应用现状和实践
  • 上传的网站打不开/宁波网络推广软件
  • 地方旅游网站开发/如何弄一个自己的网站
  • 中国铁路建设投资公司网站熊学军/百度竞价代运营外包
  • 安徽建设厅网站/口碑营销案例2022
  • 手机网站设计开发服务/新闻 近期大事件
  • 如何免费自做企业网站/百度品牌专区