2000字范文,分享全网优秀范文,学习好帮手!
2000字范文 > Linux系统编程14:进程入门之Linux进程中非常重要的概念之进程地址空间-原来我们看到

Linux系统编程14:进程入门之Linux进程中非常重要的概念之进程地址空间-原来我们看到

时间:2018-12-09 21:36:26

相关推荐

Linux系统编程14:进程入门之Linux进程中非常重要的概念之进程地址空间-原来我们看到

文章目录

(1)旧知回顾(2)程序地址空间?A:同一个地址有两个数据?B:物理地址和虚拟地址C:进程地址空间及作用D:进程地址空间如何工作

(1)旧知回顾

学习C/C++总免不了这张图

这张图帮助我们解决了不少C/C++问题,但是我们对它的认识还是不够深刻。现在我我们用一端C程序,深刻的取感受一下这个过程的内存地址的变化

#include <stdio.h>#include <stdlib.h>int gobal_val=100;//全局变量已经初始化int gobal_unval;//全局变量未初始化int main(int argc,char* argv[],char* env[]){printf("main函数处于代码段,地址为:%p,十进制为:%d\n",main,main);printf("\n");printf("全局变量gobal_val,地址为:%p,十进制为:%d\n",&gobal_val,&gobal_val);printf("\n");printf("全局变量未初始化gobal_unval,地址为:%p,十进制为:%d\n",&gobal_unval,&gobal_unval);printf("\n");char* mem=(char*)malloc(10);printf("mem开辟的堆空间,mem是堆的起始地址,是%p,十进制为:%d\n",mem,mem);printf("\n");printf("mem是指针变量,指针变量在栈上开采,其地址为%p,十进制为:%d\n",&mem,&mem);printf("\n");printf("命令行参数起始地址:%p,十进制为:%d\n",argv[0],argv[0]);printf("\n");printf("命令行参数结束地址:%p,十进制为:%d\n",argv[argc-1],argv[argc-1]);printf("\n");printf("第一个环境变量的地址:%p,十进制为:%d\n",env[0],env[0]);printf("\n");}

(2)程序地址空间?

上面的图在学习C/C++时我们称之为程序的地址空间分布图,说白了就是一段程序在其运行过程中的数据的内存分布情况,但是接下来的叙述可能和你所认为的有所出入

A:同一个地址有两个数据?

如下C程序,有个全局变量val,初始值为0,使用fork创建一个子进程,然后让父进程先睡眠三秒,先让子进程运行,子进程运行时把val改为100,三秒后父子进程同时运行

#include <stdio.h>#include <stdlib.h>#include <unistd.h>int val=0;int main(){pid_t id=fork();if(id==0){val=100;while(1){printf("现在是子进程:val=%d,其地址为%p\n",val,&val);sleep(1);}}else if(id>0){sleep(3);while(1){printf("############################################\n")printf("现在是父进程:val=%d,其地址为%p\n",val,&val);sleep(1);}}else{exit(-1);}sleep(1);return 0;}

效果如下,问题就在于同一个变量怎么会同时具有两个不同的值?

B:物理地址和虚拟地址

我们能够很明确一点:同一片空间不可同时具有两个值,就像现实生活中,你不可能同时再学校又同时在家。那么只有一种解释:你所看到并不是真实的,也就是说你所看到的地址并不是真实的物理地址,而是表象上的相同,他们对应的真实的物理地址肯定是不相同的,我们称这种地址为虚拟地址

实际上:我们使用C/C++看到的地址,全部都是虚拟地址,真实的地址用户看不到,而操作系统就负责将虚拟地址转换为物理地址

可以这样描述:父进程启动(它的内存空间也是虚拟的不是真实的),然后遇见fork就创建了一个子进程,fork就把父进程的内存状态拷贝了给自己一份,如果val的值没有改变,那么操作系统本着不浪费一点空间原则,直接使用val这个虚拟地址所对应的物理地址就可以了。但是好景不长,子进程把val修改掉了,所以操作系统就把子进程val的虚拟地址所对应的物理地址进行了修改。从表面看起来好似地址没有变,但是真实情况早都变化了。

C:进程地址空间及作用

1:早期直接访问物理内存的缺陷所在

我们知道进程=进程控制块PCB+代码+数据,早期计算机,也就是没有进程地址空间的时候进程一旦启动,这些东西就会被全部装入内存,那么此时进程访问的就是真实的物理内存,如果画一张图,应该就是下面这样

但是这样防止有很大坏处:比如说野指针,放的这么近,一旦指针访问的别的进程的数据,这就出了大乱子了。还有进程在运行过程中,是会产生数据的,产生的数据一旦不能放在本进程后面,就要另外找内存去存放,这样就会导致不连续的现象,也增加了异常访问的情况

2:进程地址空间的发明

所以计算机设计者意识到了这种模式缺陷,想到了一种方法:增加一个中间层,利用中间层映射物理内存。程序访问内存时不直接访问物理内存,先访问中间层,如果中间层访问没有问题,那么操作系统就会将中间层映射到物理层,完成正常执行

一个进程创建之后,操作系统会为这个进程分配一个专属于它的大小为4GB的虚拟进程地址空间(4GB是因为32位系统中,指针是4个字节),与它相对的是一片真实的物理地址空间,操作系统在映射虚拟内存时只会映射到那一片物理空间,而且需要特别注意这个虚拟空间并不是真的有4GB,它只是虚拟的。由于每一个进程都有自己的虚拟的进程地址空间,所以它只能访问自己的进程的数据,这样做实现了隔离,也就是进程之间的相互独立。并且把虚拟地址空间划分为这样,那样的区,这样的话也能解决数据的连续存放

3:页表

不管怎样,程序运行一定要在物理内存上,所以如何映射成为了关键,,这种映射称为页表

映射时,让虚拟地址和物理地址一一对应,进程在虚拟地址上的地址就对应了物理内存上的某个地址。这样的话就不存在非法访问了,因为每个进程都有自己独立的进程地址空间,如果非法访问,页表上根本就找不到这样的地址,所以操作系统拒绝映射。“代码,字符串是只读的,数据是可读可写的”就是基于这个原因,虽然咋页表上能找到,但是对代码,字符串做了权限限制,一旦监测到这些数据类型要做一些写入操作,那么同样操作系统拒绝映射

D:进程地址空间如何工作

还是那个观点“先描述,再组织”,也就是说我们看见的那个经典的栈,堆分布图,其实本质也是一个结构体,这个结构体隶属于task_struct,叫做task mm_struct(文件在mm_types.h)

这张图可以说明task_stuct和task mm_struct的关系

申请空间的本质就是想内存索要空间,得到物理地址,然后在特定区域申请没有使用的虚拟地址,建立映射关系,再返回虚拟地址

Linux系统编程14:进程入门之Linux进程中非常重要的概念之进程地址空间-原来我们看到的地址全部是虚拟的

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。