【八股】操作系统
操作系统常见面试题总结(上) | JavaGuide
操作系统
基础
什么是操作系统?
操作系统是一个管理软硬件资源的一个软件程序,它屏蔽了复杂的底层硬件。
@ANKI
ID: 1759533789173
什么是操作系统的内核
操作系统内核是软硬件连接的桥梁,它负责内存管理、文件系统、硬件设备和应用程序的管理。
@ANKI
ID: 1759533789178
什么是操作系统用户态和系统态
根据进程访问资源的特点,进程在操作系统上运行分为用户态和内核态。用户态只可以读取用户程序的数据,当应用程序需要执行某些需要特殊权限的操作,例如读写磁盘、网络通信等,就需要向操作系统发起系统调用请求,进入内核态。
- 内核态(Kernel Mode):内核态运行的进程几乎可以访问计算机的任何资源包括系统的内存空间、设备、驱动程序等,不受限制,拥有非常高的权限。当操作系统接收到进程的系统调用请求时,就会从用户态切换到内核态,执行相应的系统调用,并将结果返回给进程,最后再从内核态切换回用户态。
@ANKI
ID: 1759533789182
什么是用户态和内核态/什么是系统调用(中)
根据进程访问资源的特点,我们可以把进程在系统上的运行分为两个级别:
- 用户态:用户态运行的进程可以直接读取用户程序的数据。
- 内核态:可以简单的理解内核态运行的进程或程序几乎可以访问计算机的任何资源,不受限制
我们运行的程序基本都是运行在用户态,如果我们调用操作系统提供的系统态级别的子功能咋办呢?那就需要系统调用了!
也就是说在我们运行的用户程序中,凡是与系统态级别的资源有关的操作(如文件管理、进程控制、内存管理等),都必须通过系统调用方式向操作系统提出服务请求,并由操作系统代为完成
如 java 的 io 操作,需要操作系统从用户态陷入到内核态,然后读取文件,放入系统内存,然后放入 java 的缓冲区,然后操作系统再从内核态切换到用户态,此时 java 就可以操作文件了。为了避免复制,所以出现了 nio 和直接内存。
进程和线程篇
@ANKI
ID: 1759533789187
什么是进程和线程?
- 进程(Process) 是指计算机中正在运行的一个程序实例。举例:你打开的微信就是一个进程。
- 线程(Thread) 线程是进程划分的更小运行单元,一个进程可以有多个线程。多个线程共享进程的资源比如内存空间、文件句柄、网络连接等。举例:你打开的微信里就有一个线程专门用来拉取别人发你的最新的消息。
- 多个线程共享进程的堆和方法区(元空间)资源,每个线程有自己的程序计数器、虚拟机栈和本地方法栈。各个进程之间是独立的,而各个线程间可能会相互影响。线程执行开销小,但是不利于资源的管理保护,而进程则相反。
@ANKI
进程和线程的区别
- 线程是进程划分成的更小的运行单位,一个进程在其执行的过程中可以产生多个线程。
- 线程和进程最大的不同在于基本上各进程是独立的,而各线程则不一定,因为同一进程中的线程极有可能会相互影响。
- 线程执行开销小,但不利于资源的管理和保护;而进程正相反。
@ANKI
ID: 1759533789191
有了进程为什么还需要线程?
- 进程切换是一个开销很大的操作,线程切换的成本较低。
- 线程更轻量,一个进程可以创建多个线程。
- 多个线程可以并发处理不同的任务,更有效地利用了多处理器和多核计算机。而进程只能在一个时间干一件事,如果在执行过程中遇到阻塞问题比如 IO 阻塞就会挂起直到结果返回。
- 同一进程内的线程共享内存和文件,因此它们之间相互通信无须调用内核。
@ANKI
ID: 1759533789195
为什么要使用多线程?
提高 CPU 利用率
- 单核下,一个线程阻塞时可以切换到另一个线程,提高利用率
- 多核下利用多核,并行执行,再次提高 CPU 利用率
@ANKI
ID: 1759533789199
进程的几种状态(中)
创建状态(new):进程正在被创建,尚未到就绪状态。
就绪状态(ready):进程已处于准备运行状态,即进程获得了除了处理器之外的一切所需资源,一旦得到处理器资源(处理器分配的时间片)即可运行。
运行状态(running):进程正在处理器上上运行(单核 CPU 下任意时刻只有一个进程处于运行状态)。
阻塞状态(waiting):又称为等待状态,进程正在等待某一事件而暂停运行如等待某资源为可用或等待 IO 操作完成。即使处理器空闲,该进程也不能运行。
结束状态(terminated):进程正在从系统中消失。可能是进程正常结束或其他原因中断退出运行。
线程的生命周期
@ANKI
ID: 1759533789203
进程间的通信方式(中)
进程是分配系统资源的单位(包括内存地址空间),因此各进程拥有的内存地址空间相互独立。一个进程不能访问另一个进程的内存地址空间,所以就需要进程通信。
-
共享内存(Shared memory):在内存中划分出一块共享存储区,使得多个进程可以访问同一块内存空间,不同进程可以及时看到对方进程中对共享内存中数据的更新。各个进程要互斥地访问共享内存,可以用互斥锁和信号量等实现互斥操作。这个的典型使用就是剪贴板。
-
管道通信(字符流):操作系统建立管道连接两个软件,再传输字符流。这个管道是一种特殊的 pipe 文件,管道大小固定,采用半双工方式通信,各进程互斥访问管道。数据以字符流的形式写入管道,当管道写满时,写进程的 write()系统调用将被阻塞,等待另一个进程将数据取走。当读进程将数据全部取走后,管道变空,此时读进程的 read()系统调用将被阻塞,第一个进程就可以开始写了。(写满了才能读,读完了才能写)
-
消息传递:
-
直接通信方式:每个进程会有一个消息缓冲队列,其他进程想要通信,就把要发送的数据封装为一个消息,然后放入目标进程的消息缓冲队列中,目标进程就能读取消息缓冲队列,获得数据
-
间接通信方式:进程发送消息时,会发送到一个中间实体中,称为信箱。消息的消息头中存放了发送进程 ID 和接收进程 ID,所以目标进程想知道消息,直接从信箱取即可。
-
socket 套接字:此方法主要用于在客户端和服务器之间通过网络进行通信
@ANKI
ID: 1759533789207
进程的调度算法有哪些 (中)
@ANKI
1. 先来先服务 first-come first-serverd (FCFS)
把需要调度的进程放到就绪队列中,先进先出,从就绪队列中选择一个最先进入该队列的进程为之分配资源,使它立即执行并一直执行到完成或发生某事件而被阻塞放弃占用 CPU 时再重新调度
有利于长作业,但不利于短作业,因为短作业必须一直等待前面的长作业执行完毕才能执行,而长作业又需要执行很长时间,造成了短作业等待时间过长。
@ANKI
2. 短作业优先 shortest job first (SJF)
按估计运行时间最短的顺序进行调度
会导致饥饿,长作业有可能会饿死,处于一直等待短作业执行完毕的状态。因为如果一直有短作业到来,那么长作业永远得不到调度。
@ANKI
3. 优先级调度
为每个进程分配一个优先级,按优先级进行调度。
为了防止低优先级的进程永远等不到调度,可以随着时间的推移增加等待进程的优先级。
满足紧急作业的要求,特别适合用在实时系统中
@ANKI
4. 时间片轮转
将所有就绪进程按 FCFS 的原则排成一个队列,每次调度时,把 CPU 时间分配给队首进程,该进程可以执行一个时间片。
当时间片用完时,由计时器发出时钟中断,调度程序便停止该进程的执行,并将它送往就绪队列的末尾,同时继续把 CPU 时间分配给队首的进程。
时间片轮转算法的效率和时间片的大小有很大关系:
- 因为进程切换都要保存进程的信息并且载入新进程的信息,如果时间片太小,会导致进程切换得太频繁,在进程切换上就会花过多时间。
- 而如果时间片过长,那么实时性就不能得到保证。
@ANKI
5. 多级反馈队列调度算法
- 设置多级就绪队列,各级队列优先级从高到低,时间片从小到大
- 新进程到达时先进入第 1 级队列,按 FCFS 原则排队等待被分配时间片,若用完时间片进程还未结束,则进程进入下一级队列队尾。
- 当第一级队列的进程被服务完,开始运行第二级队列的进程,类推多级反馈队列调度算法综合了 FCFS,短作业优先,时间片轮转,优先级调度的优点,是当前认为比较好的一种调度算法
@ANKI
ID: 1759533789211
线程是不是越多越好(中)
多线程不一定越多越好,过多的线程可能会导致一些问题。
-
切换开销:线程的创建和切换会消耗系统资源,包括内存和 CPU。如果创建太多线程,会占用大量的系统资源,导致系统负载过高,某个线程崩溃后,可能会导致进程崩溃。
-
死锁的问题:过多的线程可能会导致竞争条件和死锁。竞争条件指的是多个线程同时访问和修改共享资源,如果没有合适的同步机制,可能会导致数据不一致或错误的结果
@ANKI
ID: 1759533789216
线程切换为什么比进程切换快,节省了什么资源?(中)
- 线程切换比进程切换快是因为线程共享同一进程的地址空间和资源,线程切换时只需切换堆栈和程序计数器等少量信息,而不需要切换地址空间
- 避免了进程切换时需要切换内存映射表等大量资源的开销,从而节省了时间和系统资源
@ANKI
ID: 1759533789220
死锁的四个必要条件 (中)
-
互斥条件:只有对临界资源(需要互斥访问的资源)的争夺才会产生死锁
-
不可剥夺条件:进程在所获得的资源未释放前,不能被其他进程强行夺走,只能自己释放。
-
请求保持条件: 两个进程各占有一部分资源, 保持占有一部分资源的同时都请求对方让出另一部分资源
-
循环等待条件: 双方都等待对面让出资源, 产生僵持
@ANKI
ID: 1759533789225
死锁检测, 预防, 避免 (中)
检测死锁
- jps 可以查看定位进程号, jstack 进程号 可以查看栈信息, 来排查死锁
- jconsole 可以用来检测死锁
- arthas 这种工具也可以用来检测排查死锁
预防死锁
-
破坏互斥条件: Java 的 ThreadLocal, 每个线程都拥有自己数据副本, 自己访问自己的, 不需要互斥访问.
-
破坏请求与保持条件: 一次性申请所有的资源
-
破坏不可剥夺条件: 占用部分资源的线程进一步申请其他资源时, 如果申请不到, 可以主动释放它占有的资源
-
超时放弃 (破坏不可剥夺条件)
-
Lock 接口提供了 boolean tryLock(long time, TimeUnit unit) 方法, 如果一定时间没获取到锁就放弃.
-
破坏循环等待条件: 靠按序申请资源来预防。按某一顺序申请资源, 释放资源则反序释放。破坏循环等待条件
-
指定获取锁的顺序 (破坏循环等待条件)
-
比如某个线程只有获得 A 锁和 B 锁才能对某资源进行操作.
-
规定获取锁的顺序, 比如只有获得 A 锁的线程才有资格获取 B 锁, 按顺序获取锁就可以避免死锁
@ANKI
ID: 1759533789229
内存管理
内存管理机制/内存分配方式(低)
简单分为连续分配管理方式和非连续分配管理方式这两种。连续分配管理方式是指为一个用户程序分配一个连续的内存空间,常见的如块式管理。同样地,非连续分配管理方式允许一个程序使用的内存分布在离散或者说不相邻的内存中,常见的如页式管理和段式管理
-
块式管理:远古时代的计算机操作系统的内存管理方式。将内存分为几个固定大小的块,每个块中只包含一个进程。如果程序运行需要内存的话,操作系统就分配给它一块,如果程序运行只需要很小的空间的话,分配的这块内存很大一部分几乎被浪费了。这些在每个块中未被利用的空间,我们称之为碎片
-
页式管理:把主存分为大小相等且固定的一页一页的形式,页非常小,相比于块式管理的划分粒度更小。页式管理提高了内存利用率,减少了碎片。页式管理通过页表对应逻辑地址和物理地址
-
段式管理:页式管理虽然提高了内存利用率,但是页式管理其中的页并无任何实际意义。段式管理把主存分为一段段的,段是有实际意义的,每个段定义了一组逻辑信息,例如,有主程序段MAIN、子程序段X、数据段D及栈段S等。段式管理通过段表对应逻辑地址和物理地址
简单来说:页是物理单位,段是逻辑单位。分页可以有效提高内存利用率,分段可以更好满足程序员需求。
- 段页式管理机制。段页式管理机制结合了段式管理和页式管理的优点。简单来说段页式管理机制就是把主存先分成若干段,每个段又分成若干页,也就是说段页式管理机制中段与段之间以及段的内部的都是离散的。
分页机制和分段机制的共同点和区别(低)
- 共同点
- 分页机制和分段机制都是为了提高内存利用率,减少内存碎片。
- 页和段都是离散存储的,所以两者都是离散分配内存的方式。但是,每个页和段中的内存是连续的。
- 区别
- 页的大小是固定的,由操作系统决定;而段的大小不固定,取决于我们当前运行的程序。
- 分页仅仅是为了满足操作系统内存管理的需求,而段是逻辑信息的单位,在程序中可以体现为代码段,数据段,能够更好满足用户的需要
快表和多级页表(低)
- 快表
为了提高虚拟地址到物理地址的转换速度,操作系统在页表方案基础之上引入了快表来加速虚拟地址到物理地址的转换。我们可以把快表理解为一种特殊的高速缓冲存储器(Cache),其中的内容是页表的一部分或者全部内容。
每次查询时,先查快表,如果快表命中,就能直接拿到物理地址。如果快表未命中,则去查页表,同时将页表中的该映射表项添加到快表中。
快表本质上就是一个缓存,和redis或高速缓冲存储器(Cache)的作用类似。
快表实际上用了程序的局部性原理,一旦程序访问了某个存储单元,在不久之后,其附近的存储单元也将被访问。某条指令一旦执行,不久以后该指令可能再次执行;如果某数据被访问过,不久以后该数据可能再次被访问。
- 多级页表
引入多级页表的主要目的是为了避免把全部页表一直放在内存中占用过多空间,特别是那些根本就不需要的页表就不需要保留在内存中。
- 总结
为了提高内存的空间性能,提出了多级页表的概念;但是提到空间性能是以浪费时间性能为基础的,因此为了补充损失的时间性能,提出了快表(即TLB)的概念。不论是快表还是多级页表实际上都利用到了程序的局部性原理
逻辑(虚拟)地址和物理地址(低)
编程一般只有可能和逻辑地址打交道,比如在C语言中,指针里面存储的数值就可以理解成为内存里的一个地址,这个地址也就是我们说的逻辑地址,逻辑地址由操作系统决定。
物理地址指的是真实物理内存中地址,更具体一点来说就是内存地址寄存器中的地址。物理地址是内存单元真正的地址。
CPU寻址了解吗?为什么需要虚拟(逻辑地址)地址空间?(低)
现代处理器使用的是一种称为虚拟寻址(Virtual Addressing)的寻址方式。使用虚拟寻址,CPU需要将虚拟地址翻译成物理地址,这样才能访问到真实的物理内存
没有虚拟地址空间的时候,程序直接访问和操作的都是物理内存
- 直接使用物理地址让用户程序可以访问任意内存,寻址内存的每个字节,这样就很容易(有意或者无意)破坏操作系统,造成操作系统崩溃,或者破坏其他程序。
- 直接使用物理地址会导致程序在装入的时候会出问题,想要同时运行多个程序特别困难,比如你想同时运行一个微信和一个QQ音乐都不行。为什么呢?举个简单的例子:微信在运行的时候给内存地址1xxx赋值后,QQ音乐也同样给内存地址1xxx赋值,那么QQ音乐对内存的赋值就会覆盖微信之前所赋的值,这就造成了微信这个程序就会崩溃
所以每个程序都使用虚拟地址,然后由操作系统将虚拟地址转化为物理地址来装入内存.避免用户程序破坏系统,方便多个程序装入内存.
虚拟内存
局部性原理(低)
局部性原理是虚拟内存技术的基础,正是因为程序运行具有局部性原理,才可以只装入部分程序到内存就开始运行
- 时间局部性:如果程序中的某条指令一旦执行,不久以后该指令可能再次执行;如果某数据被访问过,不久以后该数据可能再次被访问。产生时间局部性的典型原因,是由于在程序中存在着大量的循环操作。
- 空间局部性:一旦程序访问了某个存储单元,在不久之后,其附近的存储单元也将被访问。这是因为指令通常是顺序存放、顺序执行的,很多数据也一般是以数组的形式存储的。
传统存储管理的缺点(低)
- 一次性:程序必须一次性全部装入内存后才能开始运行。这会造成两个问题:
程序很大时,不能全部装入内存,导致大程序无法运行;- 当大量程序要求运行时,由于内存无法容纳所有程序,因此有少量程序能运行,导致并发度下降
- 驻留性:一旦作业被装入内存,就会一直驻留在内存中,直至作业运行结束。
事实上,在一个时间段内,只需要访问程序的一小部分代码即可正常运行
- 驻留性就导致了内存中会驻留大量的、暂时用不到的数据和代码,浪费了宝贵的内存资源
什么是虚拟内存/虚拟存储技术/虚拟存储器(低)
我们一个游戏,像3A大作,下载需要50G,甚至100G。但是我们电脑的内存一般是8G,16G,32G,远远小于游戏所需空间,但是游戏却可以顺利运行在我们的电脑上。这就用到了虚拟存储技术。
因为程序有局部性原理,所以就可以用虚拟存储技术:
-
基于局部性原理,在程序装入时,可以将程序中很快会用到的部分装入内存,暂时用不到的部分留在外存,就可以让程序开始执行。
-
在程序执行过程中,当所访问的信息不在内存时,就会产生缺页中断,由操作系统负责将所需信息从外存调入内存,然后继续执行程序。
-
若内存空间不够,由操作系统负责将内存中暂时用不到的信息换出到外存。
这样,计算机好像为用户提供了一个比实际内存大得多的存储器——虚拟存储器
虚拟存储技术的实现 (低)
虚拟内存的实现需要建立在离散分配的内存管理方式的基础上。虚拟内存的实现有以下三种方式:
- 请求分页存储管理:
- 建立在分页管理之上,为了支持虚拟存储器功能而增加了请求调页功能和页面置换功能。请求分页是目前最常用的一种实现虚拟存储器的方法。
- 请求分页存储管理系统中,在程序开始运行之前,仅装入当前要执行的部分页面即可运行。
- 假如在程序运行的过程中发现要访问的页面不在内存,则由处理器通知操作系统按照对应的页面置换算法将相应的页面调入到主存,同时操作系统也可以将暂时不用的页面置换到外存中。
- 请求分段存储管理:
- 建立在分段存储管理之上,增加了请求调段功能、分段置换功能。
- 请求分段储存管理方式就如同请求分页储存管理方式一样,在作业开始运行之前,仅装入当前要执行的部分段即可运行;
- 在执行过程中,可使用请求调入中断动态装入要访问但又不在内存的程序段;当内存空间已满,而又需要装入新的段时,根据置换功能适当调出某个段,以便腾出空间而装入新的段
- 请求段页式存储管理
结合请求分页和请求分段
请求分页与分页存储管理有何不同呢(低)
请求分页存储管理建立在基本分页管理之上。他们的根本区别是是否将程序全部所需的全部地址空间都装入主存,这也是请求分页存储管理可以实现虚拟内存的原因。
页面置换算法(低)
页面置换算法(低)虚拟内存需要用到请求段页式内存管理,缺页时会发生缺页中断,就需要页面置换算法将页面换入或者换出内存。
最佳置换算法:(OPT页面置换算法)
算法思想:淘汰以后永不访问或将来最长时间内不再访问的页面特点:不能预测未来,所以该算法不能实现
先进先出置换算法:(FIFO页面置换算法)
算法思想:将最早进入内存的页面淘汰
特点:有可能调出主程序,所以性能最差
最近最久未使用置换算法:(LRU)
算法思想:每次淘汰的页面是最近最久未使用的页面特点:性能优异,接近最佳置换算法,但是需要硬件栈支持,开销大
最近最少使用算法(LFU)
最近最少使用算法。它是基于“如果一个数据在最近一段时间内使用次数很少,那么在将来一段时间内被使用的可能性也很小”的思路。
LRU是淘汰最长时间没有被使用的页面。LFU是淘汰一段时间内,使用次数最少的页面。
磁盘和IO
磁盘和IO(操作系统中,磁盘和IO并不是常考点,所以这里不多做赘述。