本章主要讲解学习进程地址空间的知识
在学习C/C++中我们知道了程序内存的空间开辟以及内存分区的基本概念
#include<stdio.h>
#include<stdlib.h>
int g_unval;
int g_val=1;
int main(int argc,char* argv[],char* env[])//命令行参数以及环境变量
{
printf("code addr:%p\n",main);//代码区
char* str="hello world";
printf("read only addr:%p\n",str);//只读常量区:str是常量字符串的地址
printf("init addr:%p\n",&g_val);//初始化数据区
printf("uninit addr:%p\n",&g_unval);//未初始化数据区
int* p1=(int*)malloc(10);
int* p2=(int*)malloc(10);
printf("heap addr:%p\n",p1);//堆区
printf("heap addr:%p\n",p2);//堆区向上生长
printf("stack addr:%p\n",&str);//栈区
printf("stack addr:%p\n",&p1);//栈区向下生长
for(int i=0;argv[i];i++)
printf("args addr:%p\n",argv[i]);//命令行参数区
for(int i=0;i<2;i++)
printf("env addr:%p\n",env[i]);//环境变量区
return 0;
}
对于上述的程序地址空间,其实它的真实面貌为进程地址空间,对于进程地址空间本质上来说是一个虚拟地址空间,并非真实的物理空间
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
int main()
{
int i=10;
pid_t id=fork();//创建子进程
if(id<0)
{
perror("fork fail\n");
return 1;
}
else if(id==0)
{
//child
int cnt=0;
while(1)
{
printf("I am child: i:%3d &i:%p pid:%d\n",i,&i,getpid());
sleep(1);
if(cnt==3)//修改i的值
{
i=100;
}
cnt++;
}
}
else
{
//father
while(1)
{
printf("I am father: i:%3d &i:%p pid:%d\n",i,&i,getpid());
sleep(1);
}
}
return 0;
}
注:在结构体mm_struct中各个刻度之间的每一个刻度都代表一个虚拟地址,这些虚拟地址通过页表映射与物理内存建立联系
程序运行,进程被加载到CPU上,系统在内核为进程创建PCB记录进程属性,分配进程空间地址,由页表构建虚拟地址与物理地址的映射关系,程序查找或者修改数据会通过PCB找到对应的进程地址空间,再由进程地址空间上的虚拟地址由页表找到物理空间上分配的数据
父进程创建子进程时,子进程以父进程为模板构建进程,代码数据父子共享,当子进程进行修改数据时,由页表发现该数据是父子进程共享的,所以系统会找到另一个物理空间进行拷贝数据,拷贝数据后再修改数据,达到数据各有一份互不干扰的目的
注:这种在需要进行数据修改时再进行拷贝的技术称为写时拷贝
进程需要保证独立性,多进程运行,需要独享各种资源,多进程运行期间互不干扰,数据写实拷贝让子进程的修改不影响到父进程
90%的情况下是不会的,但这并不代表代码不能进行写时拷贝,例如在进行进程替换的时候,则需要进行代码的写时拷贝
如果可以直接访问,那么看到的地址就是物理地址,对于野指针,越界访问等问题则不能进行很好的控制,不能保证程序的独立性;当通过物理地址暴露,恶意程序通过物理地址进行读取或者修改数据,无法保证信息和数据安全;控制以及管理了访问的权限,以常量区不能的常属性来说,当常量定义出来的时候不就是修改数据了么,但是再次修改时,通过页表访问时,页表发现是常量区数据则拒绝修改的访问,以此保护了数据的常属性
如果直接使用物理地址,那么进程一创建就需要立即将数据写到物理内存中,当进程退出就需要将数据立即释放,也就是说内存的管理需要特别关注进程的状态,这之间具有强相关性(耦合度高);具有进程地址空间后,进程管理只需管理PCB以及进程地址空间,而内存管理只需管理物理地址空间,也就是内存管理只需要通过智能指针知道内存区域那些是有效的哪些是无效的就能管理好内存,实现了进程管理与内存管理的解耦
可执行程序实际上也被分为了各个区域,例如初始化区、未初始化区等(便于程序编译时进行查找和链接)。当该可执行程序运行起来时,操作系统则将对应的数据加载到对应内存当中即可,同时分区有利于执行的效率,大大提高了操作系统的工作效率。