七、Linux入门| 进程地址空间

 一、进程地址空间

1、引入

我们发现,父子进程,输出地址是一致的,但是变量内容不一样!能得出如下结论:

  • 变量内容不一样,所以父子进程输出的变量绝对不是同一个变量。
  • 但地址值是一样的,说明,该地址绝对不是物理地址!
  • 在Linux地址下,这种地址叫做虚拟地址。
  • 我们在用C/C++语言所看到的地址,全部都是虚拟地址!物理地址,用户一概看不到,由OS统一管理。

OS必须负责将虚拟地址转化成物理地址 !

2、什么是进程地址空间

定义:就是从进程的视角看到的地址空间,是进程运行时所用到的虚拟地址的集合,地址最大的作用是唯一性。

 

我们在语言层面遇到的地址都是虚拟地址!每个进程都有一个地址空间,都认为自己独占物理内存。

3、进程地址空间怎么管理 

进程地址空间跟进程一样都得被数据化,也是先描述再组织,所以要描述地址空间,就要有一个结构体数据结构类型。(struct mm_struct)。

每个进程都认为地址空间划分是按照4GB空间划分的,都认为自己独有这4GB。

 所有虚拟地址是地址空间上进行区域划分时,对应的线性位置。

4、虚拟地址到物理地址 

为什么不让PCB直接去访问物理地址:

  • task_struct如果可以直接访问物理地址,但是物理地址有很多进程,如果你不小心寻址错误,访问到了其他进程,而这个进程是转账类似的,那是不是很危险?而如果我们有一层中间层,不允许你直接访问物理地址,而是在这之间对你的请求进行检查,如果合法给你映射过去,不合法就中止你的请求。
  • 例子:就像const char* s=“hello world”
    *s=‘H’,这样是不允许的,当页表识别出你是字符常量区的,它映射时就不会给你w的权限,本质上就是OS给你的权限只有r权限。
  • 每一个进程只隐射到合法内存,不会恶意进程访问,保护物理内存,可以更方便进程与进程之间的解耦,保证了独立性这样的特性。

为什么要有地址空间:

  1. 首先,通过添加一层软件层,完成有效的对进程操作内存进行风险管理(权限管理),本质目的是为了保护物理内存以及各个进程的数据安全。其次,如果我想申请1000字节空间,我们立马就可以得到吗?不一定,可能我们只是单纯告诉OS我要申请空间,以后我要用,但是现在不一定用。那在OS角度,如果空间给你,你又不立马用,或者用不完,那岂不是这部分空间就闲置着?别人想用也用不了。所以地址空间的第二个作用就来了!这叫基于缺页中断进行物理内存申请!如果进程直接访问物理内存,那么看到的地址就是物理地址。 例子:而语言中有指针,如果指针越界了,一个进程的指针指向了另一个进程的代码和数据,那么进程的独立性,便无法保证,因为物理内存暴露,其中就有可能有恶意程序直接通过物理地址,进行内存数据的篡改,如果里面的数据有账号密码就可以改密码,即使操作系统不让改,也可以读取。
  2. 将内存申请和内存使用的概念在时间上划分清楚,通过虚拟地址空间来屏蔽底层申请内存的过程,达到进程读写内存和OS进行内存管理操作进行软件上面的分离。

    你别管我最后咋给你在物理内存上开辟,反正给你开了就行,如果当你要的时候物理内存不够了,我就进行内存管理算法给你腾出地方。(你没权利管我,我能给就行)。

  3. cpu:我想知道我该运行哪个进程了,这个进程的开始地址是啥。
    不同进程的main()地址也不同,cpu还得自己找,所以cpu不乐意了,我就0x00000000位置拿进程地址,我不找了,你想让我运行哪个,你给我送到这。所以进程地址空间就一直在0x00000000处存进程地址,在页表建立映射关系。

    站在cpu和应用层角度,进程统一可以看作,它们各自独有4GB虚拟空间,而且这个空间区域相对位置是比较确定的。因为地址空间的存在,程序的代码和数据可以被加载到物理内存的任意位置,通过映射到地址空间,而地址空间的地址是连续的,大大减少了内存管理的负担。

    OS最终目的就是:让每个进程都独享4G空间,这样每个进程都统一起来,方便管理。

    具体解释:进程将自己的代码和数据首先放在虚拟地址空间的对应的区域,在这其中会有一种表结构,叫做页表,页表的核心工作就是完成虚拟地址到物理地址之间的映射,最终我们的可执行程序的代码和数据可以加载到物理内存的任意位置,因为最终只需要建立代码和数据与物理内存之间的映射关系,就可以通过虚拟地址找到物理内存的对应地址不同进程的虚拟地址可以完全一样吗?答案是可以完全一样,因为每个进程都有各自的页表,每个进程都是独立的进行通过各自页表中虚拟地址和物理内存的映射关系去找代码和数据。

我们在写代码的时候肯定了解过指针越界,我们知道地址空间有各个区域,那么指针越界一定会出现错误吗? 

不一定,越界可能他还是在自己的合法区域。比如他本来指向的是栈区,越界后它依然指向栈区,编译器的检查机制认为这是合法的,当你指针本来指向数据区,结果指针后来指向了字符常量区,编译器就会根据mm_struct里面的start,end区间来判断你有没有越界,此时发现你越界了就会报错了,这是其中的一种检查,第二种检查为:页表因为将每个虚拟地址的区域映射到了物理内存,其实页表也有一种权限管理,当你对数据区进行映射时,数据区是可以读写的,相应的在页表中的映射关系中的权限就是可读可写,但是当你对代码区和字符常量区进行映射时,因为这两个区域是只读的,相应的在页表中的映射关系中的权限就是只读,如果你对这段区域进行了写,通过页表当中的权限管理,操作系统就直接就将这个进程干掉。

所以进程地址空间的存在也使得可以通过start和end以及页表的权限管理来判断指针是否合法访问。

 

父子进程本来是共享代码和数据的。子进程的创建是以父进程为模板的,而进程也是独立的。

当有人要修改数据,那么为了保证进程的独立性,发生写时拷贝,在物理内存新开辟一片空间,其实在物理内存,子进程的g_val地址已经改变,但是映射到虚拟地址上,虚拟地址没有改变,只是页表到物理内存的映射关系变一下。

 

 

你可能感兴趣的:(Linux,linux,运维,服务器)