文章目录
1.程序地址空间概念
- 程序:只是一段代码,保存在文件中。
- 编译器在编译程序生成可执行文件时,会对每一条指令和数据,进行地址排号。
- 程序运行时,就会将
指令和数据放到指定的内存当中
去。而程序只有在运行的时候才会占据内存,因此程序地址空间又被叫做进程地址空间
。
2.程序地址空间详谈
- 1.为什么要有程序地址空间?
- 2.程序地址空间有什么作用?
搞懂以上两个问题就可以清晰得了解到程序地址空间存在的意义
。
先来对比早期直接物理内存分配方式
。
2.1早期内存分配方式
- 最早的时候,
计算机还没有虚拟机制,程序指令所访问的内存地址就是物理内存地址,所以就要将所有程序都加载到内存中
,但是我们实际的物理内存只有4G。所以就会出现一些问题: 内存使用效率低
:内存空间不足,就需要将其他程序展示拷贝到硬盘当中,然后将新的程序装入内存。然而由于大量的数据装入装出,内存的使用效率会非常低进程地址空间不隔离
:由于程序是直接访问物理内存的,所以每一个进程都可以修改其他进程的内存数据,设置修改内核地址空间中的数据,所以有些恶意程序可以随意修改别的进程,就会造成一些破坏程序运行的地址不确定
:因为内存地址是随机分配的,所以程序运行的地址也是不正确的
2.1.1为什么要有程序地址空间?
正是因为早期这些问题得所在,所以诞生出程序地址空间来避免这些问题。
解决问题方法:
- 增加一个中间层,利用一种间接的地址访问方法访问物理内存。按照这种方法,程序中访问的内存地址不再是实际的物理内存地址,而是一个虚拟地址,然后由操作系统将这个虚拟地址映射到适当的物理内存地址上。这样,只要操作系统处理好虚拟地址到物理内存地址的映射,就可以保证不同的程序最终访问的内存地址位于不同的区域,彼此没有重叠,就可以达到内存地址空间隔离的效果。
2.2程序地址空间分布
程序地址空间分布图:
2.2.1程序地址空间有什么作用?
- 通过虚拟地址空间映射到物理内存上。而使用C语言/C++时,变量或函数的地址,都是虚拟空间地址,物理内存地址用户一概看不到,由OS统一管理。而OS负责
将虚拟地址映射到对应的物理地址
。 - 每运行一段程序,就会开辟连续的地址空间,若是每个程序占据的空间比较大,很多程序共同运行,就会导致有的程序在内存中无法运行。而连续开辟的内存地址空间的空间使用率是很低的。
而进程使用了虚拟内存之后,每个进程都拥有自己的虚拟地址空间,都会有一块连续的空间使用。
3.代码示例详谈进程地址空间
3.1代码一:
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<stdlib.h>
4
5 int g_val = 0;
6
7 int main()
8 {
9 pid_t id = fork();
10 if(id<0)
11 {
12 perror("fork");
13 return 1;
14 }
15 else if(id > 0)
16 {
17 printf("father[%d],PPID:%d,%d : %p\n",getpid(),getppid(),g_val,&g_val);
18 }
19 else
20 {
21 printf("child[%d],PPID:%d,%d : %p\n",getpid(),getppid(),g_val,&g_val);
22 }
23 sleep(1);
24 return 0;
25 }
输出结果:
结论:
父子进程里面的变量的值和地址是完全一样的,这很好理解,因为子进程是按照父进程为模板,子进程并没有对变量进行任何操作。
3.2代码二:
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<stdlib.h>
4
5 int g_val = 0;
6
7 int main()
8 {
9 pid_t id = fork();
10 if(id<0)
11 {
12 perror("fork");
13 return 1;
14 }
15 else if(id > 0)//father
16 {
17 printf("father[%d],PPID:%d,%d : %p\n",getpid(),getppid(),g_val,&g_val);
18 }
19 else//child
20 {
g_val=100;//修改了g_val的值
21 printf("child[%d],PPID:%d,%d : %p\n",getpid(),getppid(),g_val,&g_val);
22 }
23 sleep(1);
24 return 0;
25 }
输出结果:
结论:
- 变量内容不一样。所以父子进程输出的变量绝对不是同一个变量
- 但是地址是一样的,所以该地址一定不是物理地址
- 在Linux下,这种地址叫做虚拟地址
- 我们在c/c++语言所看到的地址,全都是虚拟地址,物理地址用户一概看不到,由OS统一管理
- OS负责将虚拟地址转化为物理地址
3.3代码一和代码二得结论
为什么子进程变量改变了,而父进程的变量没有改变?
- 子进程是父进程的一份拷贝,子进程拷贝了父进程所有的信息。在子进程中数据未发生改变的时候,子进程使用父进程的所有信息。
- 在第一份代码中,子进程中变量没有改变,父进程中变量没有改变。所以第一份代码中,地址相等,变量也相等。
- 第二份代码中,子进程中变量发生了改变,父进程中变量没有发生改变。相同的虚拟地址映射到了不同的物理地址。所以第二份代码,地址相同,变量不同。
虚拟地址空间带了什么好处?
- 提高物理内存的使用率。
- 保证进程之间的独立性
4.操作系统中管理内存的不同方法:
分段;分页;段页式
;
4.1分段
分段式:段号+段内偏移
- 段表:操作系统记录内存分了多少块。通过段号寻找对应的物理内存起始地址,再加上段内偏移量,就找到了物理地址。
4.2分页
分页式:页号+页内偏移
- 通过找到对应的页号,其物理地址和页内偏移就可找到变量的物理地址。
4.3段页式
- [ ]段页式:
内存通过分段式进行管理,而每个段内使用分页式。
首先取得段号,在段表中进行查找;在段表中,存放着对应段号的页表起始地址,再通过段内页表起始地址找到页表。 - 当前计算机使用的段页式管理。
5.结论
- 上面的图就足矣说名问题,同一个变量,地址相同,其实是虚拟地址相同,内容不同其实是被映射到了不同的物理地址!