JavaGuide知识点整理——操作系统常见问题总结
因为我本人是非专科的,并且工作中对于操作系统仅限于用到什么百度什么,所以这里完全把guide中的文章作为正确答案来理解。如果有错误的地方欢迎指出。
简单来说,学习操作系统能够提高自己思考的深度以及对技术的理解力,并且操作系统方面的知识也是面试必备。
操作系统基础
什么是操作系统?
- 操作系统是管理计算机硬件与软件资源的程序,是计算机的基石。
- 操作系统本质上是一个运行在计算机上的软件程序。用于管理计算机硬件和软件资源。比如运行在电脑上的所有应用程序都通过操作系统来调用系统内存以及磁盘等等硬件。
- 操作系统存在屏蔽了硬件层的复杂性。操作系统就好像硬件使用的负责人,统筹着各种相关事项。
- 操作系统的内核是操作系统的核心部分。它负责系统的内存管理,硬件设备的管理,文件系统的管理以及应用程序的管理。内核是连接应用程序和硬件的桥梁,决定着系统的性能和稳定性。
系统调用
根据进程访问资源的特点,我们可以把进程在系统上的运行分为两个级别:
- 用户态:用户态运行的进程可以直接读取用户程序的数据。
- 系统态: 可以简单的理解系统态运行的进程或者程序几乎都可以访问到计算机的任何资源,不受限制。
我们运行的程序基本上都是运行在用户态,如果我们调用操作系统提供的系统态级别的子功能要怎么办呢?那就需要系统调用了。
也就是说我们在运行的用户程序中,凡是与系统态级别的资源有关的操作(如文件管理,进程控制,内存管理等)都必须通过系统调用的方式操作系统提出服务请求,并且由操作系统代为完成。
这些系统调用的功能大致可以分为如下几类:
- 设备管理。完成设备的请求或者释放,以及设备启动等功能。
- 文件管理。完成文件的读,写,创建,以及删除等功能。
- 进程控制。 完成进程的创建,撤销,阻塞以及唤醒等功能。
- 进程通信。 完成进程之间的消息传递或者信号传递等功能。
- 内存管理。 完成内存的分配,回收以及获取作业占用内存区大小以及地址等功能。
进程和线程
进程和线程的区别
进程和线程从上图可以看出,一个进程中可以有多个线程,多个线程共享进程的堆和方法区资源。但是每个线程都有自己的程序计数器,虚拟机栈和本地方法栈。
线程是进程划分成的更小的运行单位。一个进程在其执行的过程中可以产生多个线程。线程和进程最大的不同在于基本上各个进程的独立的,而各个线程则不一定。同一进程中的线程可能互相影响。线程执行开销小,但是不利于资源的管理和保护。而进程则正好相反。
进程有哪几种状态?
- 创建状态:进程正在被创建,尚未到就绪状态。
- 就绪状态:进程已处于准备运行状态,即进程获得了除了处理器之外的一切所需资源。一旦得到处理器资源(处理器分配的时间片)即可运行。
- 运行状态:进程正在处理器上运行(单核CPU下任意时刻只有一个进程处于运行状态)
- 阻塞状态:又称为等待状态,进程正在等待某一件事而暂停运行。如等待某资源为可用或者等到IO操作完成。即使处理器空闲,该进程也不能运行。
- 结束状态:进程正在从系统中消失,可能是进程正常结束或者其他原因中断退出运行。
进程间的通信
- 管道/匿名管道:用于具有亲缘关系的父子进程或者兄弟进程之间的通信。
- 有名管道:匿名管道由于没有名字,只能用于亲缘关系的进程间通信。为了克服这个缺点,提出了有名管道。有名管道严格遵循了先进先出。有名管道以磁盘文件的方式存在,可以实现本机任意两个进程通信。
- 信号:信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
- 消息队列:消息队列是消息的链表,具有特定的格式,存放在内存中并由消息队列标识符标识。管道和消息队列的通信数据都是先进先出的原则。与管道不同的是消息队列存放在内核中,只有在内核重启或者显式的删除一个消息队列时,该消息列队才会真正的删除。消息队列可以实现消息的随机查询。消息不一定要以先进先出的次序读取,也可以按消息的类型读取。比FIFO更有优势。消息队列克服了信号承载信息量少,管道只能承载无格式字,节流以及缓冲区大小受限等缺点。
- 信号量:信号量是一个计数器,用于多进程对共享数据的访问,信号量的意图在于进程间同步,这种通信方式主要用于解决与同步相关的问题,并避免竞争条件。
- 共享内存:使得多个进程可以访问到同一块内存空间,不同进程可以及时看到对方进程中对共享内存中数据的更新,这种方式需要依靠某种同步操作,如互斥锁和信号量等,可以说是最有用的进程间通信方式了。
- 套接字:此方法主要是用于在客户端和服务器间网络进行通信。套接字是支持TCP/IP的网络通信的基本操作单元,可以看作是不同主机之间进行双向通信的端点。简单的说就是通信的两方的一种约定,用套接字中的相关函数来完成通信工作。
进程的调度算法
为了确定首先执行哪个进程以及最后执行哪个进程以实现最大CPU利用率,计算机科学家定义了一些算法:
- 先到先服务调度算法:就绪队列中最先进入该队列的优先分配资源(阻塞会放弃CPU占用时间)。
- 短作业优先的调度算法:从就绪队列中选出一个估计运行时间最短的分配资源。
- 时间片轮转调度算法:这是最古老,最简单,最公平且使用最广的算法。又称RR调度。每个进程分配一个时间段,称为它的时间片,即该进程允许运行的时间。
- 多级反馈队列调度算法:前面的算法都有局限性,比如短作业优先仅照顾了短进程而忽略了长进程,多级反馈队列调度算法既能使优先级高的作业得到响应又能使短作业迅速完成,因而它是目前被公认的一种较好的进程调度算法。UNIX操作系统采用的就是这种调度算法。
- 优先级调度:为每个流程分配优先级,根据优先级从高到低执行。
什么是死锁
死锁是多个进程/线程被阻塞,它们中一个或者全部都在等某个资源被释放,由于进程/线程被无限期的阻塞,因此程序不能正常终止。
死锁的四个 必要条件
- 互斥:资源必须处于非共享模式。
- 占有并等待:一个进程至少应该占有一个资源,并且等待另一个资源,而该资源被其它进程占有。
- 非抢占:资源不能被抢占,不能等待主动释放。
- 循环等待:A等B线程持有的a资源,B等C线程持有的b资源,C等A线程持有的c资源这种。
解决死锁的方法
解决死锁可以从多个角度去分析,一般情况下有预防,避免,检测和解除四种。
- 预防是采用某种策略,限制并发进程对资源的请求。从而使得死锁的必要条件在系统执行的任何时间都不满足。
- 避免则是系统分配资源时,根据资源的使用情况提前做出预测。从而避免死锁的发生。
- 检测是指设有专门的机构,当死锁发生时该机构能检测到死锁的发生,并且精确定位与死锁有关的进程和资源。
- 解除是与检测相配套的措施,用于将进程从死锁状态下解脱出来。
死锁的预防互斥条件和非抢占很难打破,所以我们预防死锁一般都是破坏第二和第四个条件:
-
静态分配策略:该策略破坏了占有并等待。所谓静态分配策略是指一个进程必须执行前申请到所需要的全部资源才开始执行。进程要么占有资源执行,要不不占有资源不执行。
但是这种策略严重的降低了资源利用率。因为在每个进程占有的资源中,有些比较靠后使用,甚至有些额外情况下才使用,这样可能造成一个进程占有了一些几乎不用的资源二使其它需要该资源的进程产生等待的情况。 - 层次分配策略:该策略破坏了循环等待。所有的资源被分为多个层次,一个进程获得某个资源以后只能申请较高一层的资源。释放资源的时候也要先释放较高层的资源。这样就不会出现循环等待链路了。
上面提到的破坏死锁的四个条件之一就能预防系统发生死锁,但是会导致低效率的进程运行和资源使用率。而死锁的避免是允许存在死锁四要素,只要在并发进程中做好选择,仍然可以避免死锁。死锁四要素知识产生死锁的必要条件。
我们将系统的状态分为安全状态和不安全状态。每当为未申请者分配资源前先测试系统状态,若把资源分配给申请者会产生死锁,则拒绝分配,否则接受申请为他分配资源。
对资源的分配加以限制可以预防和避免死锁发生。但是都不利于各个进程对系统资源的充分共享。解决死锁问题的另一个途径就是死锁检测和解除(检测和解除类似乐观锁,而避免和预防类似悲观锁)。这种方式对资源的分配不加任何限制,但是系统定时运行一个死锁检测程序,判断系统内是否有死锁。如果检测有了再去解除。
死锁检测步骤
死锁检测的原理就是:
- 如果进程-资源分配图中无环路,则没有发生死锁。
- 如果进程-资源分配图中有环路,且每个资源类仅有一个资源,则系统中已经发生了死锁。
- 如果进程-资源分配图中有环路,但是涉及到的资源类中有多个资源,此时未必发生死锁。如果能在进程-资源分配图中找出一个既不阻塞又非独立的进程。该进程能够在有限的时间内归还占有的资源,也就是把边给消除掉了,重复此过程,直到能到有限的时间内消除所有的边,则不会发生死锁,否则会发生死锁。
死锁的解除
当死锁检测程序检测到存在死锁发生时,应设法让其解除。常用的解除死锁方法有以下四种:
- 立即结束所有进程的执行,重启操作系统。这种方法简单,但是以前所有的工作全部作废,损失很大。
- 撤销涉及死锁的所有进程,解除死锁后继续运行。这种方法能彻底打破死锁的循环等待条件,但是付出很大代价,例如有些进程可能已经计算了很长时间,由于被撤销而使产生的部分结果也被消除了,再重新执行时还要再次进行计算。
- 逐个撤销涉及死锁的进程,回收其资源直至死锁解除。
- 抢占资源:从涉及死锁的一个或者几个进程中抢占资源,把夺得的资源再分配给涉及死锁的进程直至死锁解除。
操作系统内存管理基础
内存管理介绍
操作系统的内存管理主要负责内存的分配和回收,另外地址转换也就是将逻辑地址转换成相应的物理地址等功能也是操作系统内存管理做的事。
常见的几种内存管理机制
简单分为连续分配管理方式和非连续分配管理方式这两种/连续分配管理方式是指为一个用户此程序分配一个连续的内存空间。常见的如块式管理。同样的非连续分配管理方式允许一个程序使用的内存分布在离散或者说不相邻的内存中,常见的如页式管理和段式管理。
- 块式管理:远古时代的计算机操作系统的内存管理方式。将内存分为几个固定大小的块,每个块中只包含一个进程。如果程序运行需要内存的话,操作系统就分配给它一块。如果程序运行只需要很小的空间的话,分配的这块内存很大一部分就被浪费了,这些在每个块中未被利用的空间我们称之为碎片。
- 页式管理:把主存分为大小相等且固定的一页一页 的形式。页比较小,相比于块式管理的划分力度更小,提高了内存利用率,减少了碎片。页式管理通过页表对应逻辑地址和物理地址。
-
段式管理:页式管理虽然提高了内存利用率。但是页式管理其中的页并无实际意义。段式管理把主存分为一段段的,段是有实际意义的。每个段定义了一组逻辑信息,例如有主程序段MAIN,子程序段X,数据段D等,段式管理通过段表对应逻辑地址和物理地址。
简单来说,页是物理单位,段是逻辑单位。分页可以有效提高内存利用率,分段可以更好的满足客户的需求。 - 段页式管理机制:段页式管理机制结合了段式管理和页式管理的优点。简单来说段页式管理机制就是把主存先分成若干段,每个段又分成若干页。也就是说段页式管理机制中段与段之间以及段的内部都是离散的。
块表和多级页表
页表管理机制中有两个很重要的概念:块表和多级页表。
在分页内存管理中,很重要的两点是:
- 虚拟地址到物理地址的转换要快。
- 解决虚拟地址空间大,页表也会很大的问题。
快表
为了提高虚拟地址到物理地址的转换速度,操作系统在页表方案基础上引入了快表来加速虚拟地址到物理地址的转换。我们可以把快表理解成一种特殊的高速缓存存储器。其中的内容是页表的一部分或者全部内容。作为页表的cache,它的作用和页表类似,但是提高了访问效率。由于采用页表做地址转换,读写内存数据时cpu要访问两次主存。有了快表,有时只要访问一次高速缓冲存储器,一次主存,这样可以加速查找并提高指令执行速度。
使用快表之后的地址转换流程是这样的:
- 根据虚拟地址中的页号查询快表
- 如果该页在快表中,直接从快表中读取相应的物理地址
- 如果该页不在快表中,就访问内存中的页表,再从页表中得到物理地址,同时将页表中的该映射表项添加到快表中。
- 当快表填满后,又要登记新页时,就按照一定的淘汰策略淘汰掉快表中的一个页。
其实快表和我们平时经常在开发的系统中使用的缓存很像。操作系统中的很多思想,很多经典算法,我们都可以在日常开发使用的各种工具或者框架中找到它们的影子。
多级页表
引入多级页表的主要目的是为了避免把全部页表一直都放在内存中占用过多空间,特别是哪些根本就不需要的页表就不需要保存在内存中。多级页表属于时间换空间的典型场景。
多级页表节省空间的原因主要有两个:
- 二级页表可以不存在
- 二级页表可以不在主存
总结
为了提高内存的空间性能,提出了多级页表的概念,但是提到了空间性能是以浪费时间性能为基础的,因此为了补充损失的时间性能,提出了快表的概念。不论是快表还是多级页面实际上都是利用到了程序的局部性原理。
分页机制和分段机制的共同点和区别
-
共同点:
- 分页机制和分段机制都是为了提高内存利用率,减少内存碎片
- 页和段都是离散存储的,所以两者都是离散分配内存的方式,但是每个页和段中的内存是连续的。
-
区别:
- 页的大小是固定的,由操作系统决定。而段的大小并不固定,取决于我们当前运行的程序。
- 分页仅仅是为了满足操作系统内存管理的需求,而段是逻辑信息的单位,在程序中可以体现为代码段,数据段。能够更好的满足用户的需求。
逻辑地址和物理地址
我们编程一般指可能和逻辑地址打交道,也可以理解成为内存里的一个地址。逻辑地址由操作系统决定。物理地址指的是真是物理内存中的地址,更具体一点来说就是内存地址寄存器中的地址,物理地址是内存单元真正的地址。
CPU寻址,为什么需要虚拟地址空间
现代处理器使用的是一种称为虚拟寻址的寻址方式。使用虚拟寻址,CPU需要将虚拟地址翻译成物理地址,这样才能访问到真实的物理内存。实际上完成虚拟地址转换为物理地址的硬件是CPU中含有一个被称为内存管理单元(Memory Management Unit, MMU)的硬件。
为什么要有虚拟地址空间呢?
如果没有虚拟地址空间,程序直接访问和操作的都是物理内存。但是这样会有几个问题:
- 用户可以访问任意内存,寻址内存的每个字节,这样就很容易有意或者无意的破坏操作系统,造成操作系统崩溃。
- 想要同时运行多个程序特别困难,比如你想同时运行一个微信和一个qq都不行,因为微信在运行的时候给内存地址1XX赋值,QQ同样给内存地址1XX赋值,那么QQ就会覆盖微信之前所赋的值,微信这个程序就会崩溃。
总结来说,如果直接把物理地址暴露出来的话会带来严重的问题,比如可能对操作系统造成伤害以及运行多个程序造成困难。
通过虚拟地址访问内存有以下优势:
- 程序可以使用一系列相邻的虚拟地址来访问物理内存中不相邻的大内存缓冲区。
- 程序可以使用一系列虚拟地址来访问大于可用物理内存的内存缓冲区。当物理内存的供应量变小时,内存管理器会将物理内存页保存到磁盘文件。数据或者代码页会根据需要在物理内存与磁盘之间移动。
- 不同进程使用的虚拟地址彼此隔离,一个进程中的代码无法更改正在由另一个进程或者操作系统使用的物理内存。
虚拟内存
什么是虚拟内存
这个在我们平时使用电脑特别是windows系统的时候很常见。很多时候我们使用了很多占用内存的软件。这些软件占用的内存已经超出了我们电脑本身具有的物理内存。能实现的原因正是因为虚拟内存的存在。
通过虚拟内存可以让程序拥有超过系统物理内存大小的可用内存空间。另外,虚拟内存为每个进程提供了一个一致的,私有的地址空间,它让每个进程产生了一种自己在独享主存的错觉(每个进程拥有一片连续完整的内存空间)。这样更加有效的管理内存减少出错。
虚拟内存是计算机系统内存管理的一种技术,我们可以手动设置自己电脑的虚拟内存。不要单纯的认为虚拟内存只是“使用硬盘空间来扩展内存”的技术。虚拟内存的重要意义是它定义了一个连续的虚拟地址空间,并且把内存扩展到硬盘空间。
局部性原理
局部性原理是虚拟内存技术的基础,正式因为程序运行具有局部性原理,才可以只装入部分程序到内存就开始运行。局部性原理既适用于程序结构,也适用于数据结构,是非常重要的一个概念。
局部性原理表现在以下两个方面:
- 时间局部性:如果程序中的某条指令一旦执行,不久以后该指令可能再次执行,如果某数据被访问过,不久以后该数据可能再次被访问。产生时间局部性的典型原因,是由于在程序中存在着大量的循环操作。
- 空间局部性:一旦程序访问了某个存储单元,在不久之后,其附近的存储单元也将被访问,即程序在一段时间内所访问的地址,可能集中在一定的范围之内,这是因为指令通常是顺序存放,顺序执行的,数据一般是以向量,数组,表等形式簇聚存储的。
时间局部性是通过将进来使用的指令和数据保存到高速缓存存储器中,并使用高速缓存的层次结构实现。空间局部性通常是使用较大地高速缓存,并将预取机制集成到高速缓存控制逻辑中实现。虚拟内存技术实际上就是建立了内存-外存的两级存储器结构,利用局部性原理实现高速缓存。
虚拟存储器(虚拟内存)
基于局部性原理,在程序装入时,可以将程序的一部分装入内存。而将其他部分留在外存,就可以启动程序执行。由于外存往往比内存大很多,所以我们运行的软件的内存大小实际上是可以比计算机系统实际的内存大的。在程序执行的过程中,当所访问的信息不在内存时,由操作系统将所需要的部分调用内存。然后继续执行程序。
另一方面,操作系统将内存中暂时不用的内容换到外存上,从而腾出空间存放要调入内存的信息。这样计算机好像为用户提供了一个比即使内存大很多的存储器:虚拟存储器。
实际上这也是一种时间换空间的策略。用CPU的计算时间,页的调用调出时间,换来一个虚拟的更大的空间来支持程序运行。程序世界几乎不是时间换空间就是空间换时间。
虚拟内存的技术实现
虚拟内存的实现需要建立在离散分配的内存管理方式的基础上。
- 请求分页存储管理:建立在分页管理之上,为了支持虚拟存储器功能而增加了请求调页功能和页面置换功能。请求分页是目前最常用的一种实现虚拟存储器的方法。在作业开始运行之前仅装入当前要执行的部分段即可运行。在作业运行时发现要访问的页面不在内存,则处理器通知操作系统按照对应的页面置换算法将相应的页面调入到主存。同时操作系统将暂时不用的页面置换到外存中。
- 请求分段存储管理:建立在分段存储管理之上,增加了请求调段功能,分段置换功能。同分页存储管理方式一样。
- 请求段页式存储管理
不管上面哪种实现方式,我们一般都需要:
- 一定容量的内存和外存,在载入程序的时候,只需要将程序的一部分装入内存,其它部分留在外存,然后程序就可以执行了。
- 缺页中断:如果需要执行的指令或者访问的数据尚未在内存(缺页或者缺段)则由处理器通知操作系统将相应的页面或者段调入到内存,然后继续执行程序。
- 虚拟地址空间:逻辑地址到物理地址的变换。
页面置换算法
地址映射过程中,若在页面中发现所要访问的页面不在内存中,则发生缺页中断。
当发生缺页中断时,如果当前内存没有空闲的页面,操作系统必须在内存中选择一个页面将其移除内存,以便为调入的页面让出空间。用来选择淘汰哪一页的规则叫页面置换算法。其实页面置换算法我们也可以看成淘汰页面规则。
- OPT页面置换算法(最佳页面置换算法):此算法所选择的被淘汰页面将是以后永不使用的。或者在最长时间内不再被访问的页面,这样可以保证获取最低的缺页率。但是由于人们目前无法预知进程在内存下的若干页面中哪个是最长时间不会被访问的,因此该算法无法实现。
- FIFO页面置换算法(先进先出页面置换算法):总是淘汰最先进入内存的页面,即在内存中驻留时间最久的页面进行淘汰。
- LRU页面置换算法(最近最久未使用页面置换算法)LRU算法赋予每个页面一个访问字段,用来记录上次被访问以来所经历的时间T,当需要淘汰一个页面时,选择T值最大的。
- LFU页面置换算法(最少使用页面置换算法):该置换算法选择在之前时期是用最少的页面作为淘汰页。
本篇笔记就记到这里,如果稍微帮到你了记得点个喜欢点个关注~因为这篇的知识偏底层,我这个非专科啃得很吃力,如果有写的不准确的欢迎指出。也希望大家都可以工作顺顺利利!