要是我之前就上了TSU向勇和陈渝老师的操作系统课,我的操作系统可能就不会学的这么渣了。。恶补一通。非常感谢该课程团队作出的努力与奉献,有兴趣的大家可以去B站上看向勇老师的授课视频,简直男神级别。
OS的内存管理
操作系统会对计算机的内存进行统一管理,注意一下,这里说的内存不要理解成真正物理内存空间,应该理解成一种可用的内存空间,也就是我们常说的逻辑内存
或者说虚拟内存
,这种逻辑内存有可能分布在外存(硬盘等)之上的,至于具体怎么分布和协调,就靠OS来完成。
OS的内存管理大致有以下4个特点:
- 抽象
可以为用户提供一个理论上无限大的逻辑地址空间,用户编程不需要去考虑实际的物理内存,具体开辟和释放内存空间交给OS来完成。
- 保护
OS确保执行的进程之间的地址空间是相互独立的,不会相互之前产生影响。
- 共享
有可能进程之间需要访问其他一段相同的内存,这个时候OS来完成内存共享,以及进程与进程之间的数据通信也由OS来负责协调
就像上图展示的一样,有4个进程,我们平时编程时所面向的一般也是逻辑内存,逻辑内存理论上来说可以是无限大的。
- 虚拟化
OS将内存和外存进行管理,可以虚拟化出更大的地址空间。
如上图所示,4个进程以及OS,OS及3个进程被加载进了内存,而P4进程被暂时放在外存上。对于用户来说,他认为这4个进程都被放到了“内存”之中。在实际执行到相应进程时,OS会采用一些进程调度算法来将暂时没用到的进程空间和在外存空间的进程进行交换。
地址空间 & 地址生成
逻辑地址生成
我们都知道,写一个程序,基本要面临编码、编译、链接、载入这几个过程,原来迷迷糊糊的不知道链接和载入的作用是什么,现在总算是明白了,先给下面这个图:
以一个C语言程序为例:
- 首先,我们需要coding,生成
XXX.c
文件,C语言里面的变量名,函数名这些其实都是地址的概念,不过是符号地址。 - 然后用gcc编译器(这其实是一个gcc的可选选项),将
XXX.c
文件编译成XXX.s
文件,这一般都是会生成汇编程序。这里的地址也还是符号地址。 - 再用gcc编译器,链接成
XXX.o
文件,在该文件中,会将汇编指令转为一条条的机器码中,其中,符号地址将会开始被转为逻辑地址,而且每一个XXX.o
文件的第一条语句默认从0地址开始编址的。 linker链接器
,链接的目的主要是将一些程序中的库函数链接起来,我的理解是,将程序中引用的各种文件包含起来,这里可能会涉及到多个XXX.O
文件,并将这些一段段都是从0开始的地址归总起来。loader装载器
,这一步主要是程序的重定位,你想,假如有多个程序被链接器链接好了之后,它们各自也从0逻辑地址开始编制,肯定是要有一个软件来负责将这些程序全部定位好,定位到哪呢?对,就是逻辑地址空间上。
以上五个步骤,循序渐进的将逻辑地址空间一步一步确定好,再次声明!上面说的地址全是逻辑地址,至于如何映射到物理地址,甚至说内存之间的空间交换,这就是OS的工作了。
最后,每次实际运行XXX.exe
或XXX.out
文件时,会执行逻辑地址到物理地址的映射。
连续内存分配:内存碎片
连续内存分配会带来两个问题:①内碎片 ②外碎片
- 内碎片
OS给进程分配了一段空间,但是空间没被完全利用,有一段区域始终没被利用,只有直到该进程被杀死或释放的时候,这段内存空间才有可能被重试拿到被其他进程使用。
所谓内碎片也比较好理解,就是OS给进程以及分配的空间里面不能被有效利用的那个fragment。
- 外碎片
外碎片则是相对于内碎片的一个概念,是指由于OS在给不同进程分配内存的过程中,可能两个相邻的内存之间产生了一部分空闲的内存,可是这部分内存又比较小,无法满足一个新来的进程所想要申请的内存空间,这样一来,这一部分空闲内存空间相当于被浪费了,成了外部碎片。
所谓外部随便,也好理解,就是进程外面不能被有效利用的那个fragment。
为了解决,更恰当的说是,为了缓解上述的内碎片和外碎片问题,就出