【linux】程序地址空间

发布于:2025-06-28 ⋅ 阅读:(18) ⋅ 点赞:(0)

目录

一、前言

二、进程地址空间

三、扩展

四、总结


一、前言

        我们在学习C/C++的时候或多或少都会提到关于内存分布的相关知识,例如静态成员变量存放在静态区,局部变量存放在栈区等等,那么这些空间是怎么进行排布的呢?我们在运行程序时是直接根据数据在内存的地址来调用的吗?

二、进程地址空间

程序地址空间也可以叫做进程地址空间,因为程序运行起来就是一个进程。

先来看一张空间布局图

这是我们平时为了更好理解而虚构出来的空间布局,实际上这些数据和代码并不是按照这种排布在内存中的,下面会说明为什么。

我们先来看一段代码,并理解运行结果为什么是这样。

#include<stdio.h>    
#include<unistd.h>    
#include<sys/types.h>    
int val = 100;    
int main()    
{    
  pid_t id = fork();    
  if(id==0)    
  {  
    //子进程  
    sleep(1);    
    val+=5; //修改val                                                                                                                                                                     
    printf("child:pid %d val=%d addr=%p\n",getpid(),val,&val);    
  }    
  else    
  {    
    //父进程
    sleep(1);    
    printf("father:pid %d val=%d addr=%p\n",getpid(),val,&val);    
  }    
  return 0;    
}    

1.还可以发现他们之间val的地址是相同的,为什么会有这种现象呢?

首先这个地址肯定不是物理地址,如果是物理地址的话,指向相同空间,一方改变另一方也会发生改变。

在linux下这种地址被称为虚拟地址,并且我们写的C/C++程序中的地址也是虚拟地址,也就是说系统会根据虚拟地址进而转换位物理地址对数据进行访问,有一张对应虚拟地址与物理地址一一对应的表,在操作系统中这个表被称为页表。

为什么父进程与子进程的val的地址相同,因为创建的子进程会将父进程的代码和数据进行拷贝,当然也就把地址啥的都拷贝下来了,类似于浅拷贝,所以我们看到的地址是虚拟地址并且相同。

2.通过观察可以发现变量val在子进程中已经被改变了但是父进程中val的值未被改变。

每个进程都会有自己独立的虚拟地址空间,也有自己对应的页表。

我们上面讲的进程地址空间其实就是虚拟地址空间,是操作系统为了更好的管理并排列好每个区域的数据(例如常量在常量区等等)。使用虚拟地址就能够更好的进行区域划分,那么操作系统是怎么进行区域划分的呢?

在操作系统的task_struct中会维护着指向mm_struct的指针,而mm_struct就是进行虚拟地址空间区域划分(管理)的。

在mm_struct中定义的各个区域的范围,通过更改这些变量就可以对区域进行划分和修改。

    unsigned long total_vm, locked_vm, shared_vm, exec_vm;
	unsigned long stack_vm, reserved_vm, def_flags, nr_ptes;
	unsigned long start_code, end_code, start_data, end_data;
	unsigned long start_brk, brk, start_stack;
	unsigned long arg_start, arg_end, env_start, env_end;

在mm_struct中还存在着 

这个是用来管理每个区域的,例如管理栈区的地址范围,相当于是一个别墅的管家。

这个时候我们再来解释为什么子进程与父进程的的val互不影响。

我们知道进程之间是相互独立的,当某个进程对数据进行改变时就会发生写实拷贝,写时拷贝的触发条件就是当某个进程对数据进行修改时。

写实拷贝的过程:

所以我们看到的现象就是这样形成的。

三、扩展

系统怎么对页表进行管理:先描述,再组织

系统会维护一个关于页表信息的结构体,类似于对进程的管理一样的操作。

页表是用来做虚拟地址与物理地址的映射的。

我们可以不先加载代码和数据,只有task_struct,mm_struct等等,因为系统是通过查页表来进行访问数据和代码的,可以先有虚拟地址,然后再将物理地址一个一个填入页表中。

为什么我们对常量进行修改,程序缺崩溃了?因为在页表中还会维护着对每个数据的权限信息,如果我们对该数据进行的非权限访问或修改,系统就会做出相应的操作。

四、总结

为什么系统要使用虚拟地址空间?直接对物理内存的数据进行操作不行吗?

如果直接对内存进行操作,我们创建子进程时,就必须对父进程的数据和代码进行拷贝到内存,这样就会造成空间浪费。

使用虚拟地址空间的话,创建子进程只对会发生修改的数据进行拷贝到内存进行了。

可以让进程管理与内存管理,进行一定的解耦合(使两边的关联变浅)。

地址空间和页表是OS创建并维护的,凡是想使用地址空间和叶表进行映射, 也⼀定要在OS的监管之下来进行访问!!也顺便保护了物理内存中的所有的合法数据 ,包括各个 进程以及内核的相关有效数据!

因为有了虚拟地址空间,那么数据在内存中的存储位置也就没那么重要了。


网站公告

今日签到

点亮在社区的每一天
去签到