验证地址空间的基本排布
#include <stdio.h>
#include <stdlib.h>
//初始化变量
int g_val = 0;
//未初始化变量
int g_unval;
int main(int argc,char* argv[],char* env[])
{
// 以main函数为代表,打印代码区
printf("code addr : %p\n",main);
//初始化数据
printf("val addr : %p\n",&g_val);
//未初始化数据
printf("unval addr : %p\n",&g_unval);
char* mem = (char*) malloc(10);
//堆
printf("heap addr : %p\n",mem);
//栈:指针变量(函数内,局部变量)
printf("stack addr : %p\n ",&mem);
//第一个命令行参数
printf("opt addr : %p\n",argv[0]);
//最后一个命令行参数
printf("opt addr : %p\n",argv[argc - 1]);
//环境变量
printf("env addr : %p\n",env[0]);
return 0;
}
可以看到代码区的地址是最小的,依次向上递增,其中,堆栈相对而生。栈向地址减小方向生长,堆向地址增大方向生长。堆栈之间的区域称为共享区
代码段不是从0号地址开始的,它有一个确定的地址
共享区里一般放的是动态库,共享内存等
进程地址空间
虚拟地址
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
pid_t ret = fork();
int i = 0;
if(ret < 0)
{
perror("fork");
return 0;
}
else if(ret == 0)
{
//child
i = 100;
printf("child[%d] : %d : %p\n",getpid(),i ,&i);
}
else
{
//parent
printf("parent[%d] : %d : %p\n",getppid(),i,&i);
}
return 0;
}
我们发现,父子进程,输出地址是一致的,但是变量内容不一样
变量内容不一样,所以父子进程输出的变量绝对不是同一个变量
但地址值是一样的,说明,该地址绝对不是物理地址
在Linux地址下,这种地址叫做 虚拟地址
在创建子进程且子进程没有改写全局变量时,因为没有进行写入,OS本质不允许浪费一点空间的原则,父子进程共享这段空间,但是,一旦有任何一方进行写入,父子进程就要进行数据独立,单独给修改一方开辟一块空间,重新构建一种映射关系,整个操作过程中,父进程没有收到任何影响,而对子进程来讲,只是在物理内存中重新开辟了空间,本身的虚拟地址并没有发生变化,改变的只是虚拟地址与物理地址的映射关系
我们在用C/C++语言所看到的地址,全部都是虚拟地址!物理地址,用户一概看不到,由OS统一管理,OS必须负责将 虚拟地址 转化成 物理地址
灵魂三问理解地址空间
1.是什么?
地址空间的本质是所占有空间的一张表,,在操作系统内核当中,就是一个数据结构,将内存划分成了若干种区域
每个进程都有一个进程的地址空间,系统会存在多个进程,所以,也一定存在着多个地址空间,那么操作系统也需要将这些地址空间管理起来
只要是管理,就要遵循先描述,再组织
先描述
上面也说了地址空间本身就是一个数据结构
在Linux当中,叫做struct mm_struct(linux内核当中的地址空间结构体)
它包含了一些区域信息,能够实现区域划分
struct mm_struct
{
//区域划分
Unsigned long code_start;
Unsigned long code_end;
Unsigned long init_data_start;
Unsigned long init_data_end;
Unsigned long uninit_start;
Unsigned long uninit_end;
Unsigned long head_start;
Unsigned long head_end;
....
}
通过大量的起始地址和结束地址来划分区域
申请空间的本质:
向内存索要空间,得到物理地址。然后在特定区域申请没有被使用的虚拟地址,通过页表建立映射关系,返回虚拟地址
在向OS申请内存时,OS不会立马给你,在申请成功后,和在使用之前,就有一段小小的时间窗口,在这个时间段中,这个空间没有被正常使用,但是别人也用不了,这种转态叫做闲置状态!
再组织:
在进程的task_struct里有mm_struct的指针
mm_struct相当于task_struct的一张执行地图
我们知道一个进程 = PCB + 代码数据
因此,就把进程的PCB和mm_struct关联起来了
2.为什么
地址空间存在的意义是什么呢?
假设如果没有地址空间,那么进程访问的地址都是物理地址
若发生非法访问或异常时,其他空间的代码和数据就可能会遭到破坏,同时,也会存在一个进程的数据存放的位置是不连续的,访问起来特别不方便
增加了虚拟地址空间后
1.可以防止地址的随意访问,保护物理内存和其他地址
2.将进程管理和内存解耦合(解除两者或多着相互之间的影响,增强各自的独立性)
3.可以让进程以同一的视角,看待自己的代码和数据
3.怎么做
地址空间是如何工作的
地址空间上所呈现暴露给上层的所有的地址都叫做虚拟地址,而实际进程访问时,要通过页表映射转换到物理内存,然后拿到对应的代码和数据