吃透进程地址空间,理清OS内存管理机制-2

简介: 吃透进程地址空间,理清OS内存管理机制

三、分页 & 虚拟地址空间

经过上面的学习,我们明白了【区域划分】的意义所在,但是光划分出来区域是不够的,还需要有数据在里面存在,那我们必须明白一点:数据和代码真正只能在内存中!

1、页表的概念

  • 上面我们有谈到【虚拟地址】和【物理地址】,但是对它们之间的联系还是不太清楚,现在我们再通过引入 ==页表== 这个概念来进一步理解一下:
  • 可以看到对于task_struct来说它是指向一个内存中的地址空间,我们的有一块在这个地址空间中的 虚拟地址,但是呢我们的目的不是找到这个地址,而是要拿到这块地址中的内容,那么在Linux中呢,就会使用到 ==页表== 这个东西来做一个【映射】的操作,经过页表的转化变为物理内存之后,继而得到这个内存中的地址,确定清楚地址后内容也就读出来了,放到CPU里就可以被操作执行了

image.png💬 那对于上面这个呢,就是 ==页表== 的基本模型了,它所呈现出来的是一个KV的结构,类似于我们在C++中所学习过的《map》,一个 key 值对应一个 value

2、疑难解答:为何父子进程没有发生同步修改?

那有了页面这个概念之后呢,我们就可以通过其来解释一下我们在前面所提出的问题了:父子进程访问的是同一个地址,那为何子进程在修改了g_val后父进程并没有发生同步改变呢?

  • 这里的话我们就需要通过父子两个进程来进一同观察了。通过下图我们可以看出,子进程按照着父进程拷贝了一份代码和数据,并且连虚拟地址也是一样的,那么通过页表的映射之后,它们便指向了物理内存中的同一块空间

image.png💬 那为何子进程在修改完数据之后父进程也跟着一同修改了呢?

  • 还记得我们在 进程基本概念 的时候所说到过的【写时拷贝】这个 概念吗,因为进程在运行的时候是具有独立性的,所以为了不引起 ==并发修改异常==,当子进程需要对父进程中的数据做修改时,就会在系统的某一个位置开辟一段空间,将父进程中的数据拷贝到此处,在此处去做一个修改,然后再去改变一下页表的映射值,此时就不会对原先父进程中的数据产生影响
  • 在物理内容中申请完空间之后,并且修改的是当前进程所对应的 value值【物理地址】,不会影响到 key值【虚拟地址】,所以即使两个进程所访问的地址是同一块,所看到的也是不同的两个值

image.png

💬 解答:为何在fork之后父子进程得到的值不一样?

  • fork在返回的时候,父子都有了,return两次。id是不是pid_t类型的变量呢?返回的本质就是写入!谁先返回,谁就让OS发生写时拷贝
  • 所以父进程读到的就是子进程的pid,而给子进程返回的就是0

3、无进程地址空间的危害

上面我们谈及的是【页表】,但其实【进程地址空间】也同样得重要,我们再来看看这块

💬 如果没有地址空间,我们的OS是如何工作的

  • 如果没有虚拟地址空间的话,我们平常写代码用到的地址全都是 物理地址,那么若有些进程在访问地址的时候发生了越界,便可通过指针的方式修改这个地址中的数据,那就影响了其他进程的正常运行
  • 例如这里有个用于【客户端登录】的进程,就需要让用户输账号和密码,所以有的人在通过某种手段故意越界访问别的地址,就可以获取到别的用户的账号和密码,这就导致了安全问题

image.png

⭐ 但是当我们有了【页表】和【虚拟地址空间】之后,任何一个被CPU读进来的数据进行访问的时候,不是直接去访问这个物理内存了,而是需要通过 虚拟地址结合页表 进行映射才能访问到物理内存 ⭐ 只要我们需要映射的话,就可以决定你是否能成功进行映射,比方说当前的这个进程通过页表映射去访问了一个错误的位置,那么就会被操作系统给检测出来

4、页表的意义所在💡

明白了进程地址空间的重要性之后呢,页表也同样得重要,接下去就让我们来学习一下 页表的意义所在

① 谨防滥操,保护物理内存

  • 还是一样,我会通过一个案例来进行引出:到了春节我们都要走亲访友,去给长辈拜年的时候难免会收到一些压岁钱,现在可不像从前只是一、两百得给,而是几千几千得给了,那么此时我们的手上就会有一大笔钱了💴,于是就可能会拿着这笔钱去买一些自己喜欢的东西。
  • 那这时呢店家看你是小孩子就会狠狠地敲诈你一笔,说:小朋友,你看你现在已经XX年级了,比你高一年级的同学都要买这个书籍的,看看你是不是也有必要买一本呢😁

image.png

  • 那你的这种行为被你妈妈知道之后呢,就开始对你进行『制裁』,开始帮你去 “保管” 手上的这笔前,那妈妈这么做也是有道理的:核心工作是为了拦住你,不让你乱花钱

image.png

💬 那这对应到我们所说的【页表映射机制】其实也是同样的道理

  • 进程地址空间想要访问物理内存需要先经过 ==页表映射==,但页表映射的时候不合理时就会拦截你的映射,操作系统识别到就会不让你访问物理内存。

光是感知层面的事,我们再来看看在代码层面该如何去进行理解:

  • 还记得我们在C语言中所学习过的常量字符串吗,那学习了 C/C++内存分布 之后我们明白了这些字符串都是存放在进程地址空间中【常量区】,且都是不可修改的,因此*str = 'H'就属于 非法操作 了
  • 于是此刻 ==页表== 的功能就显现出来了,当我们通过代码去做这样非法的操作时,就会在页表映射的时候发生被OS检测到,从而告知用户此操作是非法的,


char* str = "hello world";
*str = 'H';
  • 所以在CPU操控进程访问内存的时候,中间相当于加了一层转化的过程,这转化的过程就是由OS帮我们去转的,它相当于在进程和内存之间呢加了一层 软件层,你想做转换的话如果合法的话我就load把你加载进去,但如果你并不合法的话我就就会拦截你,这就叫做 保护物理内存与其他进程

所以在学习了【进程地址空间】之后,我们之前在C语言中所学习的一些知识就可以进一步作加深和理解了,这些边界性的知识在我们学习了《操作系统》这门课后就可以更加地融汇贯通了

⇒ 因此我们可以得出页表的第一个意义所在:防止地址随意访问,保护物理内存与其他进程


当然除此之外页表还存在着其他的意义,不知大家对C语言中malloc是否还有印象?

💬 那首先我想问一个问题:当我们使用malloc去申请内存的时候,操作系统立马给你,还是 需要的时候 给你呢?

  • 可能我们平常不会去关注这个点,因此在调用了malloc()之后执行了程序后,感觉操作系统立马就将内存空间分配给我们了,但其实呢并不是这样的
  • 我们在上面通过这幅图已经理清了内部的访问机制,那我们在使用malloc申请内存的过程其实也类似:当一个申请内存的进程访问时,就在物理内存中为其申请一块空间,然后通过也页表映射,建立起【物理地址】和【虚拟地址】之间的关系,然后从虚拟地址映射出来堆空间的起始地址返回给到这个进程,此时我们就通过代码申请到了这个空间的地址

image.png

理清了内部的这个流程之后呢,我们更要清楚的一点是:这块内存虽然会给到你,但并不是立即给你的,因为在操作系统内部一般有着这么几点共识:

  1. OS一般不允许任何的浪费或者不高效
  2. 申请内存不一定立马使用
  3. 在你申请成功之后,和你使用之前,就会存在一段小小的时间窗口,这个空间就没有被正常使用,但是别人用不了,因此我们将其称作为【闲置状态】
  • 于是呢操作系统内部就产生了这么一个机制,当有进程想要在物理内存中申请一块空间的时候,页表会先为虚拟地址先建立映射,相当于是 做个标记🔰,代表你需要申请内存空间,但是呢先不设置value值即相对应的不在内存中申请空间
  • 说得再通俗一点:就是当OS在识别到有下面这段代码的时候,知道了你将会有申请内存空间的这个需求,但是呢先不给你申请,知道你将所有的代码写完之后,将程序给运行起来了,此时当进程执行到这句代码的时候,就会通过一开始在页表中建立的映射关系,对应地在物理内存中也申请一块空间并返回起始地址,将两个地址通过页表再关联起来✌


int* a = (int*)malloc(sizeof(int) * 10);

image.png💬 那有同学此时就要问了:这样子确实蛮好的,在执行的再去申请具体的空间,但感觉意义也不是特别得大╮(╯▽╰)╭

  • 同学,这你就不懂了吧,其实它内部还蕴含着很重大的意义呢,再往深层次一面去想:因为有着 ==页表映射== 的存在,所以进程在执行的时候完全不需要关系操作系统为其在物理内存中所分配的是哪一块空间,只需要去执行当前进程中的那个代码块即可
  • 那由这个页表为中心,我们可以很好地将【地址空间】和【物理内存】分为两块来进行看待,左侧为进程管理,右侧呢即为内存管理,如果有学习过《操作系统》这门课的同学就可以清楚这两种管理
  • 因为有了 ==页表映射机制== 所以作为一个进程来讲,它永远知道自己虚拟地址的这个范围,但具体在哪个物理内存当中它并不关心,对于进程的代码和数据可以放在任意的位置,只要最终能找到就可以。因此这就使得进程管理内存管理进行了一个 解耦合 操作,二者既有关联、又互不影响

image.png

举个很简单易懂的例子:

🎯 比方说你这个月向你爸要生活费,然后你爸给你大了1000块,但是呢你并不需要关心这笔钱是从哪里来的,是你爸工作赚来的、或者是打零工赚来的,这是你爸应该关心的事,要怎么给你去弄到这一千块钱的生活费,而你要关心的则是如何拿到这笔钱并且怎么合理地使用它

⇒ 因此我们可以得出页表的第二个意义所在:将进程管理和内存管理进行解耦合,使二者既有关联、又互不影响

② 解耦『进程管理』和『内存管理』

接下去我们再来继续挖挖【页表】这个东西,它的存在还有这什么更加深层次的意义

💬 首先还是先以问题引入:我们的程序在编译完成之后,没有加载到内存,那么此时程序的内部还有没有地址呢?

  • 答案是:有的! 那有些同学就会很疑惑?程序都还没有装入到内存,何来地址呢?
  • 其实对于我们的程序而言,它在编译完之后并不是混乱地放在磁盘内部的,而是也会像进程地址空间那样去做一一的区域划分,类似于:已初始化全局数据段、未初始化全局数据段等等,内部的代码和数据在加载到内存的时候是以分批的形式进行加载的

③ 统一视角,进程循环

所以读者在看了虚拟地址空间之后不要认为这样的策略只会影响OS,==对于我们的编译器而言,其实也遵守着这样的规则==

  • 当源代码被编译的时候,就是按照虚拟地址空间的方式对代码和数据早就编号了对应的编址,不过具体是怎么去编译的读者如果有兴趣的话可以去学习一下 《编译原理》 这门课,里面会有相关的涉及image.png
  • 可以带读者真实地来到Linux下看一看,对于可执行程序为何会存在编址


objdump -S 可执行程序

image.png

  • 我们透过一个具体的案例再来理解一下,现在我们的磁盘中有一个可执行程序,它是一个函数调用,然后当这个call 0x1122ff80加载到内存中的时候,就会产生一个物理地址。此时当外界的进程开始通过进程地址空间进行访问的时候,就可以由 ==页表映射== 找到这块物理地址,从而找到里面的这个函数调用

image.png

  • 接下去呢,便可以通过将读取到的这个数据返回,因为它是个地址调用,所以CPU会将其有不一样的看法,在这里就会将其看作为是虚拟地址

image.png

  • 那么既然它是一个虚拟地址的话,就可以通过【进程地址空间】去转换到 页表 进行映射,然后再去取到下一个物理地址中的内容,发现地址中还是一个虚拟地址,读取到CPU内再度调用,这也就开启了一个[进程读取循环]
  • 以进程地址空间【正文代码】中的main()函数作为起始地址开始执行,一句句执行下去,若是碰到有函数调用的话就进行跳转执行。这就使得每一个进程都遵循一个统一的规则去进行运转。

image.png⇒ 因此我们可以得出页表的第三个意义所在:可以让进程以统一的视角,看待自己的代码和数据!

🎁小彩蛋:手机为何会发烫呢?

看那么久文章脑瓜子一定嗡嗡的吧😵 马上进入我们的彩蛋时刻

💬 那么再问一个小问题:进程的代码和数据必须一直在内存中吗?

  • 答案是:不一定。因为有【进程地址空间】的存在,你用多少我给你加载多少,其中执行完的代码就直接扔掉了,再重新去加载。这样就可以边加载边执行,需要的时候就把数据提前加载到内存里,不需要就把它扔掉,这样就可以使操作系统中的内存在很低的使用量的同时还可以把大软件给跑起来

  • 现在市面上有很多的游戏,像:LOL、王者荣耀、刀塔传奇、原神、炉石传说等等,这些都是大型的网游,下载下来需要很大内容空间10/20G。不知大家日常在打游戏的时候会不会越玩越卡,然后手机就渐渐发烫然后掉帧呢(处理器好的同学请忽略)
  • 这是因为我们的手机在运行起来的时候就会加载一些系统自带的内容,大部分情况下我们的时候正在进行网络IO,还有一种情况呢我们的手机可能把当前进程正在加载的进程中的数据换出到固态硬盘中,也就是我们之前所学习过的【进程的换入换出】操作 ,不断地在做数据交换,说白了就是在做 二进制写入,所以我们手机的CPU在工作久了之后压力就比较大,就导致了发热的情况

四、总结与提炼

那么经过上面的这么一番分析之后呢,大家应该对页表在操作系统管理的时候所呈现的重要性有了一定的认识,最后来总结以下本文所学习的内容:book:

本文我们主要是在讲Linux下的『进程地址空间』,我们可以通过三个方面来回顾一下:

💬 进程地址空间是什么?

  • 在操作系统内部为进程创建出来的一种具体的数据结构对象,用来让进程以统一的视角去看待物理内存。因为有进程地址空间的存在,便可以让进程管理和内存管理独立开来
  • 并且有进程地址空间的存在,我们可以在磁盘当中编译程序时就把编译好的程序以地址空间的形式把它排布好,这样加载到内存CPU在进行读取识别时,它读到的就都是虚拟地址。根据这个虚拟地址经过页表映射之后再进行相应的读取,内部的就开始转起来了

💬 为什么要有进程地址空间?

  1. 防止地址随意访问,保护物理内存与其他进程
  2. 将进程管理和内存管理进行解耦合
  3. 可以让进程以统一的视角,看待自己的代码和数据!

💬 怎么去用这个进程地址空间?

  • 在内核当中定义mm_struct这个数据结构,这个数据结构里充满了大量的区域,所谓的区域就是startend这样的指针,用来限定各种区域的起始和结束。然后通过页表再经过映射到物理内存

以上就是本文要介绍的所有内容,感谢您的阅读:rose::rose::rose:

相关文章
|
4天前
|
算法 调度 UED
深入理解操作系统内存管理:原理与实践
【4月更文挑战第23天】 在现代计算机系统中,操作系统的内存管理是保证系统高效、稳定运行的关键组成部分。本文旨在深入探讨操作系统中内存管理的理论基础、关键技术以及实际操作过程,通过对内存分配策略、虚拟内存技术、分页与分段机制等核心概念的详细解析,为读者提供一个清晰、全面的内存管理视角。此外,文章还将通过案例分析,展示内存管理在解决实际问题中的应用,以期加深读者对操作系统内存管理复杂性的认识和理解。
|
20天前
|
算法 程序员
深入理解操作系统内存管理:分页系统的优势与挑战
【4月更文挑战第7天】 在现代操作系统中,内存管理是一项至关重要的任务,它确保了计算机能够高效、安全地运行各种程序。分页系统作为内存管理的一种技术,通过将物理内存分割成固定大小的单元——页面,为每个运行的程序提供了一种独立且连续的内存地址空间。该技术不仅简化了内存分配,还允许更高效的内存使用和保护。本文探讨了分页系统的核心原理,优势以及面临的挑战,旨在为读者揭示其在操作系统设计中的重要性。
|
19小时前
|
存储 算法 安全
深入理解操作系统的内存管理机制
【4月更文挑战第27天】 本文将探讨操作系统中一个至关重要的组成部分——内存管理。我们将深入分析内存管理的基本原理,包括分页、分段和虚拟内存的概念,以及它们如何共同作用以支持现代多任务操作系统。文章还将讨论内存管理的关键性能指标,如页面置换算法的效率对系统响应时间的影响,以及内存碎片问题的解决方案。通过对这些高级概念的剖析,读者将获得操作系统内存管理机制深层次的认识。
|
23小时前
|
缓存 算法 调度
深入理解操作系统的内存管理机制
【4月更文挑战第27天】 在现代计算机系统中,操作系统扮演着至关重要的角色,尤其在资源管理和调度方面。内存管理是操作系统的核心功能之一,它负责分配、跟踪和回收应用程序使用的物理内存。本文将探讨操作系统如何通过不同的内存管理技术来优化内存使用效率,包括分页、分段以及虚拟内存等概念。通过对这些技术的深入分析,读者将获得对操作系统内部工作原理的更深刻理解,并了解它们如何影响应用程序性能和系统稳定性。
|
1天前
|
算法
深入理解操作系统的内存管理
【4月更文挑战第26天】 在现代计算机系统中,操作系统的内存管理是确保系统高效、稳定运行的关键组成部分。本文将深入探讨操作系统内存管理的核心技术,包括虚拟内存、物理内存分配策略、分页和分段机制以及内存交换技术。通过分析这些技术的工作原理及其优缺点,读者将获得对操作系统如何优化内存使用和管理的深刻理解。
|
2天前
|
算法 Linux 调度
深入理解操作系统中的进程调度策略
【4月更文挑战第25天】 在多任务操作系统中,进程调度策略是核心组件之一,它负责决定哪个可运行的进程将获得CPU时间。本文将探讨不同的进程调度算法,包括它们的原理、优缺点以及适用场景。我们将重点分析先到先服务(FCFS)、短作业优先(SJF)、轮转调度(RR)和多级反馈队列(MLFQ)等经典算法,并讨论现代操作系统如Linux和Windows中的实际调度策略。文章的目的是为读者提供对操作系统进程调度机制深度了解,并展示其在系统性能和用户体验中的关键作用。
|
3天前
|
算法
探索现代操作系统的虚拟内存管理
【4月更文挑战第25天】 操作系统的心脏——虚拟内存管理,是确保多任务并发执行和系统稳定性的关键。本文将深入剖析虚拟内存的核心机制,包括分页、分段、请求调页以及交换技术。我们将探讨虚拟内存如何允许操作系统使用有限的物理内存来模拟更大的地址空间,以及这一过程对性能的影响。此外,文章还将介绍一些高级话题,比如内存分配策略、页面置换算法以及虚拟内存的优化方法。
|
3天前
|
负载均衡 算法 调度
深入理解操作系统中的进程调度策略
【4月更文挑战第25天】 在现代操作系统的核心功能中,进程调度策略扮演着至关重要的角色。本文将详细解析进程调度的基本概念、调度算法的种类及其背后的原理,并探讨它们对系统性能的影响。通过比较不同的调度策略,我们可以更深入地理解操作系统如何管理资源,确保多任务环境下的效率和公平性。
|
4天前
|
存储 算法
深入理解操作系统的内存管理机制
【4月更文挑战第24天】 在现代计算机系统中,操作系统扮演着资源管理者的角色,其中内存管理是其核心职责之一。本文将探讨操作系统如何通过内存管理提升系统性能和稳定性,包括物理内存与虚拟内存的概念、分页机制、内存分配策略以及内存交换技术。我们将透过理论与实践的结合,分析内存管理的关键技术及其对系统运行效率的影响。
|
11天前
|
存储 算法 数据安全/隐私保护
深入理解操作系统的内存管理机制
【4月更文挑战第17天】 在现代计算机系统中,操作系统扮演着资源管理者的角色,其中内存管理是其核心职能之一。本文探讨了操作系统内存管理的关键技术,包括虚拟内存、物理内存分配与回收、分页和分段机制,以及内存交换技术。通过分析这些机制的原理和实现,我们旨在加深读者对操作系统如何有效管理和保护内存资源的理解。