什么是操作系统:操作系统是应用程序和计算机硬件的中间层,确保资源的合理分配,方便用户更好的使用硬件。
操作系统的组成:
1.内核
2.接口:命令行或GUI
操作系统内核特征:
- 并发:多个应用程序交替运行,需要OS调度
- 共享:宏观上”同时访问“,微观上”互斥“
- 虚拟:通过交替运行,使每个用户感觉整个计算机只为自己所用
- 异步:用户输入一致的话,输出就一致
操作系统的启动:
1.加电后从BIOS里读加载程序:先读主引导扇区,转到活动引导扇区,再读加载程序。
2.加载程序读操作系统内核:读启动菜单,可以更改加载参数,然后再读操作系统内核
2.用加载程序加载操作系统。
为什么不能用BIOS直接加载操作系统?
因为操作系统的类型不确定,BIOS不需要认识每一种操作系统,它只需要认识加载程序,然后加载程序再去分辨操作系统。
系统启动规范:BIOS,UEFI(所有平台上一致的操作系统启动服务)
中断,异常和系统调用
为什么需要内核来执行中断、异常和系统调用?
因为在计算机系统中,内核是可以完全信任的第三方,所以交给它来处理各种突发事件。
中断:来自外部设备的请求,响应方式为异步,内核接收到中断后,会持续进行处理
异常:是应用程序的执行发生错误时的情况,响应方式为同步,内核处理方式为处理完异常再返回执行,中间的过程应用程序必须等待。
系统调用:是来自应用程序主动发起的请求,响应方式为同步或异步,处理方式为等待或持续。
系统调用与函数调用的区别:
1.通常用户不会直接进行系统调用,而是通过API函数调用进行间接调用
2.系统调用有堆栈转换和特权级的切换,函数调用没有。
物理内存管理
内存层次,从靠近CPU开始,依次是:
- 寄存器
- L1高速缓存
- L2高速缓存
- 主存
- 外存
由上到下存取速度依次减慢
存储管理单元要做的事:
- 抽象:把物理地址抽象成逻辑地址
- 保护:每个进程具有独立的地址空间
- 共享:共享某些相同的地址空间,比如操作系统内核
- 虚拟化:使进程具有超越物理内存的地址空间
内存管理方式:
- 重定位:把物理地址抽象成逻辑地址
- 分段:分为代码,地址,数据三段
- 分页:把内存分为最基本的单位
- 虚拟存储
物理地址:硬件支持的实际地址
逻辑地址:进程在运行时看到的地址
什么时候将物理地址转成逻辑地址?
- 编译时,这样程序的位置就被写死了,地址发生变化就得重新编译:例如老式手机,不能自己添加应用程序
- 加载时:加载到内存时,可通过重定位转为逻辑地址
- 执行时:执行时代码可以移动位置,十分灵活。前面两种在运行时没法改变,如果内存不够需要重新分配时就会出错。
地址生成过程中会有地址检查。
程序加载时按照连续内存分配的策略,每个进程分配合适大小的连续的内存,当某些内存结束之后,就会出现内存碎片,这时需要根据动态内存分配,从看看能不能从内存碎片中给这些进程分配内存。
内存碎片:
- 外碎片:分配单元之间的未利用的内存
- 内碎片:分配单元内部的未利用的内存,比如一个单元是512字节,你要510字节,这2字节就是内碎片
动态内存分配:
- 最先匹配:找最近的内存。内存碎片按地址排序,找到的第一个可以用的内存就行。
优点:找内存和合并内存比较方便,大地址区域空间很多
缺点:找地址越来越慢,因为近的都被找完了 - 最优匹配:找大于需要内存的最小内存。内存碎片按小到大排序,找到第一个就是。
优点:内存得到最优化的利用,当进程较小时适合用这种策略
缺点:合并的时候耗时间,用完得到的特别小的碎片几乎无法利用 - 最差匹配:找大于需要内存的最大内存。内存碎片按大到小排序,找到的第一个就是。
优点:利用过后的内存还比较大,还可以得到继续的利用,避免出现太多小块
缺点:合并慢,容易破坏大的分区
碎片整理:
当所有碎片都不够新的进程用时,需要通过碎片整理来挪出空间来,有两种方式:
1.紧凑:通过挪动碎片周围的进程,把碎片拼起来。需要考虑开销,通常在周围进程暂停时使用,而且进程需要能动态重定位,否则不能挪。
2.分区对换:把处于等待或暂停状态的进程移到外存去,让新进程先跑着。早期计算机就是通过分区兑换实现多进程。
伙伴系统:
伙伴系统是系统动态内存分配的一种实现,它综合了使用和释放合并内存的过程。
分配过程:
1.初始为2^n的空闲块
2.每次找最小的可用空闲块,如果太大,二等分直到合适为止。
释放合并过程:
把释放的内存放入空闲块数组,合并满足合并条件的空闲块。
合并条件为:
1.两个小块的大小必须一致
2.地址相邻
3.起始地址较小的地址必须是2*(i+1)的倍数
非连续内存分配:
连续内存分配会导致很多内存碎布,对内存的利用率比较低,这时需要非连续内存分配。
非连续内存分配需要解决逻辑地址和物理地址的转换:
- 软件解决:类似数据结构中的外排序,将需要执行的代码放到内存中,其他的在硬盘中
- 硬件解决:用硬件来解决
以多大为单位分配内存:
- 段式存储管理:
根据程序的功能和访问方式把程序分成几个段,例如数据段,代码段,堆栈段等,不同的段放在不同的地址空间里,通过段号和偏移量来访问具体的物理地址。 - 页式存储管理:把逻辑地址分成一页一页的,逻辑地址用一个二元组(页号,页内偏移)表示。物理地址对应的一帧一帧的,物理地址用一个二元组(帧号,帧内偏移)表示。业内偏移和帧内偏移是一样的,通过页表把页号转换为帧号。
页表:处了包含页号到帧号的转换,还有几个标志位,标志是否有对应的帧,是否最近访问过,或是否修改过。
用页式存储管理虽然能非连续的存储内存, 但是也带来了几个问题:
- 页表本身过大,解决方法:
采用多级页表,间接引用,一个进程通常不会用到所有的页表,通过存在位把不需要的省掉,就能节省很多空间。
采用反置页表:把物理地址和页表对应起来,而不是逻辑地址,查找时,把逻辑地址做Hash,找到索引,然后去索引对应的页表中找逻辑地址是否存在,如果存在,则该物理地址就是我们要找的,否则把遍历整个hash值得逻辑地址,找到对应的物理地址。 - 每次访问都需要先访问页表,再访问帧,访问次数过多,解决方法:用快表,把一部分页表缓存进CPU。
段页式存储管理:
将段式存储管理和页式存储管理结合起来,在段表里加入页表,逻辑地址表示为段号+页号+页内偏移,通过这种方式,还可以很方便的共享页,只要再段里加一个共享页号就行