【Linux杂货铺】3.程序地址空间

1.程序地址空间的引入

fork()函数在调用的时候子如果是子进程则返回0,如果是父进程则返回子进程的pid,在代码中我们分别在子进程和父进程读取全局变量g_val的时候居然出现了俩个不同的值。如下:

cpp 复制代码
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int g_val = 0;
int main()
{
  pid_t id = fork();
  if(id == 0)
  {
    g_val = 100
    int i = 5;
    while(i--)
    {
      printf("我是子进程,g_val值为:%d,g_val的地址:%p\n",g_val,&g_val);
      sleep(1);
    }
  }
  else
  {
    int i=5;
    while(i--)
    {
      printf("我是父进程,g_val值为:%d,g_val的地址为:%p\n",g_val,&g_val);
      sleep(1);
    }
  }
  return 0;                                                                                                                        }

执行结果:

xvjiyi@hecs-399428 20240712_pragramAddressSpace\]$ ./mytest 我是父进程,g_val值为:0,g_val的地址为:0x7ffd52f7b964 我是子进程,g_val值为:100,g_val的地址为:0x7ffd52f7b964 我是父进程,g_val值为:0,g_val的地址为:0x7ffd52f7b964 我是子进程,g_val值为:100,g_val的地址为:0x7ffd52f7b964 我是父进程,g_val值为:0,g_val的地址为:0x7ffd52f7b964 我是子进程,g_val值为:100,g_val的地址为:0x7ffd52f7b964 我是父进程,g_val值为:0,g_val的地址为:0x7ffd52f7b964 我是子进程,g_val值为:100,g_val的地址为:0x7ffd52f7b964 我是父进程,g_val值为:0,g_val的地址为:0x7ffd52f7b964 我是子进程,g_val值为:100,g_val的地址为:0x7ffd52f7b964

这个时候我们发现明明读取的地址相同为什么会读出俩个不同的值呢?既然读出了俩个值,说明这俩个值存在不同空间里,定然不可能地址相同,唯一的解释就是这个地址不是真实的地址。事实也正是这样,这里读取到的地址是**虚拟地址,**虚拟地址空间也就是程序地址空间的产物,接下来我们来看程序地址空间,以及虚拟地址的实现机制。

2.程序地址空间

2.1虚拟地址空间介绍

程序地址空间本质是一个结构体(我们认为是 struct mm_struct),这个结构体记录着不同内存区间的在这个程序地址空间的相对地址(不是真正的物理地址),映射关系如下:

2.2页表

当进程访问地址的时候访问的是虚拟地址,那我们最终是怎样访问到物理地址的呢?当进程地址空间创立时,操作系统会建立一张页表完成虚拟地址到物理地址的映射,如下:

2.3 子进程父进程访问全局变量出现俩个不同值

接下来我们回到开头的问题,为什么我们会在同一个地址上访问到俩个不同的值。这是因为子进程继承了父进程的地址空间,父子进程在访问这个全局变量时访问的虚拟地址是一致的,而这俩个虚拟地址映射的物理地址不同,而我们的数据是存放在物理内存中的,也就造成了同一个地址出现了俩个不同值的假象。(至于为什么会存放在不同的物理内存中在下一节进程控制中给大家讲解,为了确保进程的独立性相互之间不影响,发生了写时拷贝)。

3.扩展

3.1为什么要有地址空间

如果没有进程地址空间,物理内存就可以被随意访问,可以随意改动不属于自己的空间,系统安全性大大降低。我们有了地址空间有以下优点:

  1. 1.当进程访问到不属于该进程的空间时,操作系统可在页表映射时拒绝进程的访问,有效杜绝了物理地址被随意访问,大大保证了物理内存和其他进程的安全。
  2. 2.将进程管理与内存管理解耦合。
  3. 3.让进程以统一的视角看待自己的代码和数据。

3.2 malloc申请空间的实质

当进程使用malloc申请空间时,在访问页表的时候操作系统不会给你立马分配物理地址,而会发生缺页中断,在进程需要访问这块空间时才给你分配空间。

3.3程序的加载

程序编译在加载的时候并不是一股脑全部加载到物理内存中,而是执行多少加载多少,一边执行一边加载。(源代码在被编译时早就将源代码和数据按照程序地址空间的方式完成了编址)

相关推荐
绵羊20232 小时前
单细胞转录组测序上游——cellranger
linux
chinesegf3 小时前
Docker篇4-本地项目app.py与docker加载项目镜像的开发顺序
运维·docker·容器
CyreneSimon3 小时前
Docker 拉取配置教程:解决镜像拉取连接超时问题
运维·docker·容器
李子红了时4 小时前
【墨铺网教程】一台电脑加入多个局域网,让电脑做上传下载主力又当存储盘
运维·服务器·电脑
Awkwardx5 小时前
Linux系统编程—线程同步与互斥
linux·服务器
snpgroupcn6 小时前
Adobe SAP S/4HANA 升级实践:企业规模化转型关键要素
运维·adobe·云计算
赖small强6 小时前
[Linux]内核队列实现详解
linux·kfifo·请求队列·工作队列(workqueue)·等待队列·kfifo_init
www.028 小时前
linux服务器升级显卡驱动(笔记)
linux·运维·服务器·笔记·ubuntu·服务器环境
Linux-palpitate8 小时前
基于Prometheus和Grafana的MySQL监控,服务器监控
服务器·grafana·prometheus
Yeats_Liao8 小时前
Go Web 编程快速入门 13 - 部署与运维:Docker容器化、Kubernetes编排与CI/CD
运维·前端·后端·golang