进程地址空间
语言中的程序地址空间
在c语言中的学习中 我们是这样子描述内存空间的
内存包括栈区 堆区 静态区
栈区是高地址向低地址增长的
堆区是低地址向高地址增长的
其中静态区又分为三部分 分别是初始化数据 未初始化数据 数据段和代码段
可以通过下面的代码来验证上面的这张图
#include <stdio.h>
#include <stdlib.h>
int g_unval;
int g_val = 100;
int main()
{
const char *s ="hello world";
printf("code addr: %p\n", main);
printf("string rdonly addr: %p\n", s);
printf("uninit addr: %p\n", &g_unval);
printf("init addr: %p\n", &g_val);
char *heap = (char*)malloc(10);
char *heap1 = (char*)malloc(10);
char *heap2 = (char*)malloc(10);
char *heap3 = (char*)malloc(10);
char *heap4 = (char*)malloc(10);
printf("heap addr: %p\n", heap1);
printf("heap addr: %p\n", heap2);
printf("heap addr: %p\n", heap3);
printf("heap addr: %p\n", heap4);
printf("stack addr: %p\n", &s);
printf("stack addr: %p\n", &heap);
int a = 10;
int b = 30;
printf("stack addr: %p\n", &a);
printf("stack addr: %p\n", &b);
return 0;
}
我们发现运行的结果和结论是吻合的
问题
我们首先写出下面的一段代码
int g_val = 100;
int main()
{
//数据是各自私有一份(写时拷贝)
if(fork() == 0)
{
//child
int cnt = 5;
while(cnt){
printf("I am child, times: %d, g_val = %d, &g_val = %p\n", cnt, g_val, &g_val);
cnt--;
sleep(1);
if(cnt == 3)
{
printf("##################child更改数据#########################\n");
g_val = 200;
printf("##################child更改数据done#########################\n");
}
}
}
else
{
//parent
while(1)
{
printf("I am father, g_val = %d, &g_val = %p\n", g_val, &g_val);
sleep(1);
}
}
return 0;
}
一个物理地址中对应的值只能有一个 所以说这里的地址肯定不是真正的物理地址
系统中的进程地址空间
我们在语言层面上打出来的地址都不是真实的物理地址 而是由操作系统分配的虚拟地址
所以说尽管我们看上去父子进程的虚拟地址是一样的 但是它们实际的物理地址却是不一样的 这也就造成一个地址中会出现两个值的情况
进程地址空间是操作系统对于开辟空间的描述 Linux中它就是一个结构体 叫做mm_struct
进程地址空间就类似于一把尺子 尺子的刻度由0x00000000到0xffffffff 尺子按照刻度被划分为各个区域 例如代码区、堆区、栈区等
而在结构体mm_struct当中 便记录了各个边界刻度 例如代码区的开始刻度与结束刻度
在这个结构体中 每一个刻度都代表着一个虚拟地址 这些虚拟地址通过页表 和物理地址建立联系
而由于这些地址是线性增长的 所以说我们也可以将虚拟地址叫做线性地址
操作系统在创建进程的时候会创建PCB和程序地址空间
在进程运行的时候程序地址空间会经过页表映射到真实的物理内存中开辟新的空间
父子进程是共享代码和大部分数据的
但是当修改子进程的数据的时候 就会发生 缺页中断 由于要保持进程的独立性 所以这里会用到一种叫做写时拷贝的技术
将父进程的数据拷贝一份到物理内存中 再修改
为什么要有进程地址空间
- 有了进程地址空间之后可以保护内存 防止恶意程序修改真实的内存数据和破坏计算机
因为加了一个中间件之后程序就失去了访问真实物理内存的权力 而是由操作系统进行管理 - 将内存申请和使用概念划分清楚
因为使用物理内存的权力在操作系统 它可以通过“欺骗”程序 让他误以为自己申请成功了真正的物理内存
操作系统可以在程序使用内存的时候再分配给程序 - 每个进程都认为自己在独占内存 这样能更好的完成进程的独立性以及合理使用内存空间
每个进程都认为自己能独占内存 所以说它们会按照操作系统给的内存分布区规划使用 更加方便管理并且进程之间解耦
我们现在对于进程创建的理解可以认为是
创建 PCB + 代码和数据 + mm_sturct + 页表