我是靠谱客的博主 明亮水杯,最近开发中收集的这篇文章主要介绍详谈进程地址空间前言详解进程地址空间虚拟地址空间的建立写在最后,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

目录

前言

详解进程地址空间

进程地址空间的本质

为什么要有进程地址空间

简单了解页表 

虚拟地址空间存在的优点

虚拟地址空间的建立

写在最后


前言

        本篇介绍的是有关进程地址空间(线性地址)的一些详细概念,如什么是进程地址空间,为什么要有进程地址空间等。如果对进程还不是很了解的伙伴可以看看这篇文章:初始操作系统及进程

如果对进程还不是很了解的伙伴可以看看这篇文章:初识操作系统及进程


在详细介绍之前,我们先知道一个“结论”: 

几乎所有的编程语言只要涉及到“地址”的概念,都是属于虚拟内存地址(线性地址) 

详解进程地址空间

我们先来看一段简单的程序来感受一下进程地址空间

ps:fork()函数用于创建一个新的进程,如果不太了解的伙伴可以看看初识操作系统及进程

#include<iostream>
#include<cstdio>
#include<unistd.h>

using namespace std;

int g_val = 100;

int main()
{
    pid_t pid = fork();
    if(pid == 0){
        int cnt = 0;
        while(true){
            printf("this is child process,pid = %d,ppid = %d,g_val = %d,&g_val = %pn",getpid(),getppid(),g_val,&g_val);
            sleep(1);
            if(cnt == 5){
                g_val = 200;
                cout << "g_val was changed by child process" << endl;
            }
            cnt++;
        }
    }else{ 
        while(true){
            printf("this is parent process,pid = %d,ppid = %d,g_val = %d,&g_val = %pn",getpid(),getppid(),g_val,&g_val);
            sleep(1);
        }
    }
    return 0;
}

 

 我们先来简单分析一下这个程序的逻辑及运行结果

  1. 首先是存在一个全局变量:g_val;
  2. 随后用fork()创建一个子进程,子进程在休眠五秒后修改g_val这个全局变量的值;
  3. 我们可以看到五秒后,全局变量的值被修改之后,父子进程打印该变量的值时,变量的值不同,但是变量的地址却相同

这其实看似有“违背”我们之前的认知:一个变量对应一个地址,一个地址唯一标识一个变量

但其实编程的结果很明显看出来,这肯定不是同一个地址。这就引出了我们本篇要介绍的内容:虚拟地址空间


进程地址空间的本质

首先我们来认识一下之前所学过的“地址空间”,这即是虚拟地址空间的一个空间位置划分

这个虚拟地址空间本质上即操作系统内核为进程专门设计的一种数据结构,让每个进程都能“拥有”相同的视角去看待自己的内存空间。

 

我们可以通过简单的阅读一下Linux的源码即可以观察到该数据结构的存在
其本质就是通过一系列start和end的无符号整数进行区域划分来形成每个区域

当每个进程被创建时,操作系统则会对应为该进程创建一个PCB结构体,并为其分配虚拟地址空间,进程所有变量都在该虚拟地址空间的对应的某个地址上。


为什么要有进程地址空间

了解完什么是进程地址空间后,我们来看看简单了解一下为什么要有进程地址空间?

首先我们要知道,内存又被称为RAM(random-access memory),可随机读取存储

如果没有进程地址空间的存在,则每个进程则直接是访问我们计算机的内存,对于进程的直接读写行为其实是相对比较危险的,因为其行为无法很好的检测是否非法;

其次由进程直接进行内存读写,内存管理相对杂乱,因为糅合着进程管理,故会很容易产生内存碎片,降低计算机的性能等

故此虚拟地址空间便由此诞生,其类似作为进程与物理内存之间的一个访问“中介”存在


简单了解页表 

那么进程是如何通过虚拟地址空间访问物理内存的呢?

这里就要简单引入一个新的东西了:页表

页表其实也是一种特殊的数据结构,在进程的PCB结构体中便有指针指向页表
进程通过页表与实际物理内存建立映射关系,进程需要访问物理内存都需要通过页表来进行,如此只要保证了页表映射的区域彼此独立,则保证了进程的独立性

虚拟地址空间存在的优点

 那么我们来整体总结一下虚拟地址空间存在的优点吧

  • 由于地址空间的存在,使得操作系统能够有效保护物理内存的安全(即也同时保护了所有进程的合法数据) ps:地址空间和页表都是由OS创建并维护的
  • 由于地址空间和页表的存在,内存管理模块与进程管理模块完成了解耦合,进程管理时无需直接兼顾进程所对应的内存如何如何,都交由了进程对应的页表进行
    ps:进程与内存通过页表的映射联系起来,故此两模块在系统层面进行了解耦合
    • 补例:
    1. C/C++的内存申请(new,malloc)都是在虚拟地址上申请
    2. 申请时是在地址空间上申请,但物理内存可能并未真正的分配空间,当真正使用内存时OS才执行相关管理算法进行空间的分配,并构建页表的映射关系
    3. 由于OS采用了该种延迟分配的策略,故此内存的利用率几乎是百分之百的

      ps:用户和进程都是无法感知到的

  • 由于地址空间的存在,加载到物理内存的乱序的代码在进程的视角看来即是有序的 tips:理论上物理内存是可以任意进行读写的,加载的代码可以在任意位置
    • 每一个进程都认为自己是唯一的进程,让进程都以统一的视角看待内存
    • 每一个进程都是通过页表映射到不同区域,从而实现进程的独立性

虚拟地址空间的建立

简单了解完了虚拟地址空间大致的内容后,我们再来了解一下一个进程加载到内存后,虚拟地址空间是如何被建立的呢?

  • 虚拟地址空间深入理解
    • 程序在编译成.exe文件时,每一个字段都已经赋予了(虚拟)地址
      ps:编译器编址时同样遵循与Linux内核的虚拟内存相同的排序方式
    • 加载到物理内存中时,将编译好的地址也加载入了内存当中
      (与对应字段一起,即每一个字段都有一个虚拟内存地址)
      ps:加载到内存后每一个语句(字段)便存在了对应的物理内存地址
    • 此时对加载进内存的程序生成一个PCB结构体,结构体中有保存对应的虚拟地址的指针(mm_struct)
      ps:该指针指向的即是虚拟内存,其内容即是对虚拟内存的一个区域划分
    • 再通过早已编译好并已加载入内存的虚拟地址来对应初始化虚拟地址内存(一种数据结构),并建立“页表”,将虚拟内存地址和物理内存地址对应映射起来
    • CPU读到的指令内部即存在虚拟地址,通过页表找到该虚拟地址对应的物理地址上的指令,并读入CPU中
      ps:由于虚拟地址和物理地址的转换由页表执行,本质上CPU读到的都是虚拟地址,CPU甚至不知道物理地址的存在

MMU(Memory Management Unit)
内存管理单元 进行虚拟内存地址到物理内存地址的转换


到这儿我们可以更新一下进程的概念了:
之前为进程=数据和代码+PCB结构体

此时应为进程=数据和代码+内核数据结构(PCB、页表、虚拟地址空间等)


写在最后

        好了,本篇的内容到这里就结束了,如果对你有帮助的话,可以点赞收藏转发让更多的人看到~

最后

以上就是明亮水杯为你收集整理的详谈进程地址空间前言详解进程地址空间虚拟地址空间的建立写在最后的全部内容,希望文章能够帮你解决详谈进程地址空间前言详解进程地址空间虚拟地址空间的建立写在最后所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(38)

评论列表共有 0 条评论

立即
投稿
返回
顶部