【2022秋招面经】——操作系统

可以系统的学习一下小林coding写的面经,通俗易懂:
小林coding

操作系统引论

1. 操作系统的特性(并共虚异)

并发:同一段时间内多个程序执行
共享:系统中的资源可以被内存中多个并发执行的进/线程共同使用
虚拟:通过时分复用以及空分复用(如虚拟内存),把一个物理实体虚拟为多个
异步:系统中的进程是以走走停停的方式执行的,且以一种不可预知的速度推进

补充:

并发:

进程并发要做到互不干扰,需要使用信号量机制做到进程之间的同步与互斥

线程并发使用 volatile 和 synchronized 以及锁机制实现。

  1. 通过 volatile 关键字修饰变量,可以实现线程之间的可见性,避免脏读的出现,底层是通过限制 jvm 指令的重排序来实现的,适用于一个线程修改,多个线程读的场景。
  2. 通过 synchronized 来实现线程的同步,自动锁的思想,底层实现原理:当有进程进入同步代码块之后,利用 jvm 的计数器将锁的标记置为1,该线程就去锁池等待,当第一个线程出来之后,锁的标记会置为0,之后 cpu 会随机分配一个线程再次进入同步代码块。
  3. 通过 lock 锁的机制,进行手动 lock ,和 unlock ,但是很容易出现死锁。需要注意加锁以及解锁的顺序,就可以避免死锁。

虚拟:

时分复用(时间片轮转)、空分复用(虚拟内存)。

异步:

由于进程是并发执行的,所以进程可能会遇到挂起、阻塞、终端、死锁等情况,所以进程以一种不可预知的速度推进。

2. 操作系统的主要功能

进程管理:进程控制,进程同步,进程通信和进程调度
内存管理:内存分配,内存保护,地址映射,内存扩充
设备管理:管理所有外围设备,包括完成用户IO请求,为用户进程分配IO设备,提高IO设备利用率,提高IO速度,方便IO使用
文件管理:管理用户文件和系统文件,方便使用的同时保证安全性。包括磁盘存储空间管理,目录管理,文件读写管理以及文件共享及保护
提供用户接口:程序接口(如API)和用户接口(如GUI)

3. 各种操作系统的区别

批处理操作系统:成批处理、系统吞吐量高、资源利用率高、用户不能干预作业的执行;
分时操作系统:多路性、独立性、及时性、交互性;
实时操作系统:及时响应、快速处理、高可靠性和安全性、不要求系统资源利用率;
操作系统的主要组成部分:进程和线程的管理、存储管理、设备管理、文件管理。

4. 动态链接库与静态链接库的区别

静态链接:
在实际开发中,不可能将所有代码都放在一个源文件中,通常会有多个源文件,而且多个源文件之间不是互相独立的,会存在一定的依赖关系,如一个源文件可能需要调用另一个源文件的函数,但是每个源文件都是独立编译的。为了满足这种依赖关系,需要将这些源文件产生的目标文件进行链接,从而形成一个可执行的程序。这样的链接过程就是静态链接。

静态链接的有点是运行速度快,因为可执行程序中已经具备了所有执行程序需要的所有东西。

缺点是会造成空间上的浪费,因为每个可执行文件中对所有需要的目标文件都有一个副本。同时更新会造成困难,每次源文件更新之后,都需要重新编译链接成可执行文件。

多个源文件存在依赖关系,静态链接将这些源文件产生的目标文件进行链接,以副本的方式存在,优点是速度快,缺点是造成空间浪费和更新困难。

动态链接:
动态链接库是程序运行时动态装入内存的模块,格式为.dll,在程序运行是可以随意加载和移除,更新方便,节省内存空间。

5. 什么是编译、链接、运行

什么是编译

  • 如果程序没有错误,没有任何提示,但在 Debug 目录下会出现一个 Hello.obj 文件,该文件称为目标文件

什么是链接

  • 有了目标文件(.obj 文件),通过链接程序将其和运行需要的 c 库文件链接 成 exe 文件(可执行文件)。
  • 为什么需要链接库文件呢? 因为我们的 C 程序中会使用 C 程序库的内容,比如 <stdio.h> <stdlib.h> 中的函数printf() system()等等, 这些函数不是程序员自己写的,而是 C 程序库中提供的,因此需要链接

什么是运行

  • 有了可执行的 exe 文件, 也称为可执行程序 (二进制文件),在控制台下可以直接运行 exe 文件
6. 用户态和核心态

运行在用户态下的程序,只能受限地访问内存,不允许访问外围设备。占用CPU的能力被剥夺,CPU资源可以被其他程序获取。

运行在内核态下的程序,可以访问内存所有数据,包括外围设备。

用户态切换到内核态的三种方式:
1)系统调用:用户态进程主动要求切换到内核态的一种方式。用户态进程通过系统调用,申请使用操作系统提供的服务程序,以完成工作。
2)异常:当CPU在执行运行在用户态下的程序时,发生了某些事先不可知的异常,会触发由当前运行进程切换到处理此异常的内核相关程序中。因此也就转到了内核态,比如缺页异常。
3)外围设备终端:外围设备完成用户请求的操作后,会像CPU发送相应的中断信号。此时,CPU会暂停执行下一条即将要执行的指令,转而执行与中断信号对应的处理程序。如果先前执行的指令是用户态的程序,那么转换的过程自然就发生了由用户态到内核态的转换。

进程

1. 什么是进程、线程、协程? 有何区别?
  • 进程是系统进行资源分配的基本单位。占据独立的内存,上下文进程的切换开销(栈、寄存器、虚拟内存、文件句柄等)较大,但是相对比较稳定安全,
  • 线程是进程的一部分,系统任务调度和执行的基本单位。线程只拥有一点在运行中必不可少的资源(如程序计数器,一组栈和寄存器),但是它可与同属一个进程的其他线程共享进程所拥有的全部资源。
  • 协程是一种用户态的轻量级线程,协程的调度完全由用户所控制(也就是在用户态执行),没有内核切换的开销,所以上下文切换非常快。

一个进程可以有多个线程,一个线程可以有多个协程。

2. 进程、线程、协程的通信方式

进程通信方式:
1)管道:半双工,字节流,速度慢,容量有限,只有父子进程能通讯
2)命名管道:任何进程间都能通讯,但速度慢
3)消息队列:链表结构,容量受到系统限制,且要注意第一次读的时候,要考虑上一次没有读完数据的问题
4)共享内存:能够很容易控制容量,速度快,但要保持同步,比如一个进程在写的时候,另一个进程要注意读写的问题,相当于5)线程中的线程安全问题。
6)信号量:不能传递复杂消息,只能用来同步。
7)信号:用于通知接收进程某个事件已经发生。
8)套接字:可用于不同机器之间的进程间通信。

线程的通信方式:
1)互斥锁提供了以排他方式防止数据结构被并发修改的方法。
2)读写锁允许多个线程同时读共享数据,而对写操作是互斥的。
3)条件变量可以以原子的方式阻塞进程,直到某个特定条件为真为止。对条件的测试是在互斥锁的保护下进行的。条件变量始终与互斥锁一起使用。

协程的通信方式:
与线程不同,协程使用程序自定义的调度器进行调度,因此更容易控制协程之间的执行顺序,要想充分利用协程的调度模型,有一个完备的通信机制是很重要的。它主要应该有以下的功能:

  1. 能从一个协程发送消息到另一个协程,通知另一个协程特定的事件已经发生。
  2. 能够让协程在事件未发生之前挂起,等待事件发生后被调度并处理,从而有效让出CPU时间。
  3. 能够在消息中附带相应的数据。
3. 进程的状态

基本状态
请添加图片描述

进程的状态有下面五种,其中前三种是进程的基本状态:

  1. 运行状态:进程正在处理机上运行。在单处理机的环境下,每一时刻最多只有一个进程处于运行状态。
  2. 就绪状态:进程获得了除处理机之外的一切所需资源。
  3. 阻塞状态:进程等待某一事件而暂停运行。
  4. 创建状态:进程正在被创建,尚未转到就绪态。创建完成后,系统会将进程设置为就绪态。
  5. 终止状态:进行正在从系统中消失,然后进行资源释放与回收工作。

状态之间的切换

  1. 就绪 -> 运行:获得了处理机的资源
  2. 运行 -> 就绪:时间片用完或者是有更高等级的进程执行,抢占了处理机。
  3. 阻塞 -> 就绪:进程等待的事件到来。
  4. 运行 -> 阻塞:当进程请求某一资源(外设)没有得到满足。
4. 进程的同步与互斥

进程同步的四种方法

  1. 临界区(Critical Section)
    通过对多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问。
    优点:保证在某一时刻只有一个线程能访问数据的简便办法
    缺点:虽然临界区同步速度很快,但却只能用来同步本进程内的线程,而不可用来同步多个进程中的线程。
  2. 互斥量(Mutex):为协调共同对一个共享资源的单独访问而设计的。
    互斥量跟临界区很相似,比临界区复杂,互斥对象只有一个,只有拥有互斥对象的线程才具有访问资源的权限。
    优点:使用互斥不仅仅能够在同一应用程序不同线程中实现资源的安全共享,而且可以在不同应用程序的线程之间实现对资源的安全共享。
    缺点:①互斥量是可以命名的,也就是说它可以跨越进程使用,所以创建互斥量需要的资源更多,所以如果只为了在进程内部使用的话使用临界区会带来速度上的优势并能够减少资源占用量。因为互斥量是跨进程的互斥量一旦被创建,就可以通过名字打开它。
    ②通过互斥量可以指定资源被独占的方式使用,但如果有下面一种情况通过互斥量就无法处理,比如现在一位用户购买了一份三个并发访问许可的数据库系统,可以根据用户购买的访问许可数量来决定有多少个线程/进程能同时进行数据库操作,这时候如果利用互斥量就没有办法完成这个要求,信号量对象可以说是一种资源计数器。
  3. 信号量(Semaphore):为控制一个具有有限数量用户资源而设计。
    它允许多个线程在同一时刻访问同一资源,但是需要限制在同一时刻访问此资源的最大线程数目。互斥量是信号量的一种特殊情况,当信号量的最大资源数=1就是互斥量了。
    优点:适用于对Socket(套接字)程序中线程的同步。(例如,网络上的HTTP服务器要对同一时间内访问同一页面的用户数加以限制,只有不大于设定的最大用户数目的线程能够进行访问,而其他的访问企图则被挂起,只有在有用户退出对此页面的访问后才有可能进入。)
    缺点:① 信号量机制必须有公共内存,不能用于分布式操作系统,这是它最大的弱点;
    ② 信号量机制功能强大,但使用时对信号量的操作分散, 而且难以控制,读写和维护都很困难,加重了程序员的编码负担;
    ③ 核心操作P-V分散在各用户程序的代码中,不易控制和管理,一旦错误,后果严重,且不易发现和纠正。
  4. 事件(Event): 用来通知线程有一些事件已发生,从而启动后继任务的开始。
    优点:事件对象通过通知操作的方式来保持线程的同步,并且可以实现不同进程中的线程同步操作。
    总结:
    ①临界区不是内核对象,只能用于进程内部的线程同步,是用户方式的同步。互斥、信号量是内核对象可以用于不同进程之间的线程同步(跨进程同步)。
    ②互斥其实是信号量的一种特殊形式。互斥可以保证在某一时刻只有一个线程可以拥有临界资源。信号量可以保证在某一时刻有指定数目的线程可以拥有临界资源。
5. 进程的调度

(1)先来先服务(FCFS,First-Come-First-Served): 此算法的原则是按照作业到达后备作业队列(或进程进入就绪队列)的先后次序来选择作业(或进程)。

(2)短作业优先(SJF,Shortest Process Next):这种调度算法主要用于作业调度,它从作业后备队列中挑选所需运行时间(估计值)最短的作业进入主存运行。

(3)时间片轮转调度算法(RR,Round-Robin):当某个进程执行的时间片用完时,调度程序便停止该进程的执行,并将它送就绪队列的末尾,等待分配下一时间片再执行。然后把处理机分配给就绪队列中新的队首进程,同时也让它执行一个时间片。这样就可以保证就绪队列中的所有进程,在一给定的时间内,均能获得一时间片处理机执行时间。

(4)高响应比优先(HRRN,Highest Response Ratio Next): 按照高响应(RR = (已等待时间+要求运行时间)/(要求运行时间))比优先的原则,在每次选择作业投入运行时,先计算此时后备作业队列中每个作业的响应比RP然后选择其值最大的作业投入运行。

(5)优先权(Priority)调度算法: 按照进程的优先权大小来调度,使高优先权进程得到优先处理的调度策略称为优先权调度算法。

(6)多级队列调度算法:多队列调度是根据作业的性质和类型的不同,将就绪队列再分为若干个子队列,所有的作业(或进程)按其性质排入相应的队列中,而不同的就绪队列采用不同的调度算法。

6. 程序与进程的区别
  1. 程序是一个静态概念,进程是一个动态概念
  2. 程序没有并行特征,而进程有并行特征
  3. 程序不会竞争计算机系统资源,而进程是竞争计算机系统资源的基本单位
  4. 不同的进程可以包含同一程序,只要该程序所对应的数据集不同
7. 多线程共享什么数据
  • 进程代码段
  • 进程的公有数据
  • 进程打开的文件描述符
  • 信号的处理器
  • 进程的当前目录
  • 进程用户ID与进程组ID
8. 什么是死锁?

死锁的概念:多个进程之间因竞争资源而造成的一种僵局(互相等待),若无外力作用,这些进程都将无法向前推进。
死锁产生的原因主要是:1、 系统资源不足;2、进程推进顺序非法。

9. 死锁产生的条件

1)互斥,一个资源每次只能被一个进程使用;

2)不可剥夺,进程已获得的资源,在未使用完之前,不能强行剥夺;

3)占有并等待,一个进程因请求资源而阻塞时,对已获得的资源保持不放;

4)循环等待,若干进程之间形成一种首尾相接的循环等待资源关系。

这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之一不满足,就不会发生死锁。

10. 怎么处理死锁

1)预防死锁
破坏互斥条件,破坏不可剥夺条件,破坏请求与保持条件,破坏循环等待条件

2)避免死锁

  1. 安全状态:找到一个分配资源的序列能让所有的进程都能顺利完成。
  2. 银行家算法:采用预分配策略检查分配完成时系统是否处在安全状态。

3)检查死锁
利用死锁定理化简资源分配图以检测死锁的产生。

4)解除死锁

  1. 资源剥夺法:挂起某些死锁进程并抢占它的资源,以便让其他进程继续推进。
  2. 撤销进程法:强制撤销部分,甚至全部死锁进程并剥夺这些进程的资源
  3. 进程回退法:让进程回退到足以回避死锁的地步。
11. 什么是临界区

每个进程中,访问临界资源的那段程序块被称为临界区。每次只准许一个进程进入临界区,进入后不允许其他进程进入。
如何解决冲突?
a. 若有若干进程要求进入空闲临界区,一次仅允许一个进程进入
b. 若已有进程进入临界区,其他进程必须等待
c. 进入临界区的进程必须在有限时间内退出
d. 如果进程不能进入临界区,则必须让出CPU

12. sleep 和 wait 方法的区别
  1. 所属类不同:sleep 是 Thread 类的静态方法,wait 则是 Object 类的本地方法。
  2. 释放锁不同:wait 可以释放当前线程对 lock 对象锁的持有,而 sleep 不会。
  3. 作用不同:sleep 一般用于当前线程的休眠,或者轮询暂停操作,wait 则多用于多线程之间的进程通信。
  4. 线程切换不同:sleep 会让出 CPU 执行之间且强制上下文切换,而 wait 则不一定,wait 后可能还是有机会争取到锁继续执行。
  5. 使用限制:sleep 方法可以在任何地方使用,wait 必须放在 synchronized 块里面。
13. 什么是线程安全?如何保证线程安全?

当多线程访问一个对象时,调用这个对象的行为都可以获得正确的结果,那么这个对象就是线程安全的。

那么如何保证线程安全呢?

  1. 使用线程安全的类,如JDK里面提供的一些原子类。
  2. 使用 synchronized 同步代码块,或者用Lock锁;

内存管理

1. 内存管理分类

Windows操纵内存可以分两个层面:物理内存和虚拟内存。

  • 物理内存的管理方式分为连续分配管理方式(单一连续分配,固定分区分配,动态分区分配和非连续分配内存管理(分页,分段)。
  • 虚拟内存管理,则需要使用页面置换算法来实现。
2. 内存连续分配管理方式有哪些?

1)首次适应算法:空闲分区以地址递增次序链接,分配内存时顺序查找,找到大小能满足要求的第一个空闲分区。
2)最佳适应算法:空闲分区按容量递增的次序链接,找到第一个能满足要求的空闲分区,也就是挑选尽量小的分区,使碎片尽量小。
3)最坏适应算法:空闲分区以容量递减的次序链接,找到第一个能满足要求的空闲分区,也就是挑选最大的分区,使剩余分区大小趋于均匀。

3. 内存非连续分配方式有哪些?

1)分页存储
用户程序的地址空间被划分成若干固定大小的区域,称为“页”,相应地,内存空间分为若干个物理块,页和块的大小相等。可将用户程序的任一页放在内存的任一块中,实现了离散分配。

页表用于记录逻辑地址和实际存储地址之间的映射关系,以实现从页号到物理块号的映射。

访问分页系统中内存数据需要两次的内存访问。
① 从内存中访问页表,找到指定的物理块号,加上页内偏移,得到实际物理地址
② 根据第一次访问得到的物理地址,访问内存,存取数据
请添加图片描述

页号与块号的关系:页号就是逻辑地址,而块号是物理地址。

2)分段存储
分段内存管理中,地址是二维的,其中一维为段号,另一维是段内地址。每个段的长度不一样,每个段内部都从0开始编址。

段内部为连续内存分配,但段与段之间离散分配,因此有了段表机制,以从一个逻辑地址映射到一个物理地址。

请添加图片描述

3)分页与分段的区别
段式存储管理是一种符合用户视角的内存分配管理方案。在段式存储管理中,将程序的地址空间划分为若干段,如代码段、数据段、堆栈段。每个进程有一个二维地址空间,相互独立,互不打扰。

段式存储管理的优点是:没有内碎片,因为段大小可变,可以通过改变段的大小而消除内碎片。但会产生外碎片,比如4K的段换5K的段,会产生1K的外碎片

页式存储管理是一种用户视角内存与物理内存相分离的内存分配管理方案。在页式存储管理中,将程序的逻辑地址划分为固定大小的页,而物理内存划分为同样大小的帧。程序加载时,可将任意一页放入内存中任意一个帧,这些帧不必连续,从而实现了离散分离。

页式存储管理的优点是:没有外碎片,因为页的大小固定,但会产生内碎片,因为一个页可能填充不满。

两者的不同点:目大地信内
1)目的不同:分页是由于系统管理的需要,是信息的物理单位。分段是由于用户的需要,是信息的逻辑单位。
2)大小不同:页的大小固定,由系统决定。段的大小不固定,由其完成的功能决定
3)地址空间不同:页向用户提供一维地址空间,段向用户提供二维地址空间
4)信息共享:页的保护和共享收到限制,段利于存储保护和信息共享
5)内存碎片:分页没有外碎片,但有内碎片;分段没有内碎片,但有外碎片

4. 为什么需要虚拟内存

若同时有很多进程在运行,则需要很多的内存来供应。当一个程序没有内存空间可以用的时候,那么他就无法运行。所以在物理上扩展内存相对有限的条件下,需要尝试一些其他可行的方式在逻辑上扩充内存。

5. 什么是虚拟内存

基于局部性原理,将程序的一部分装入内存,而将其余部分留在外存,就可启动程序执行。在程序执行过程中,当所访问的信息不在内存中时,由操作系统将所需要的部分调入内存,然后继续执行程序。另一方面,操作系统将内存中暂时不使用的内容换出到外存上,从而腾出空间存放将要调入内存的信息。这样,系统就好像为用户提供了一个比实际大得多的存储器。

之所以将其称为虚拟的,是因为这种存储器实际上并不存在,只是由于系统提供了部分装入,请求调入和置换功能后,给用户的感觉就好像存在一个比实际物理内存大得多的存储器。虚拟存储器的大小是由计算机的地址结构决定,并不是内存与外存的简单相加。

虚拟存储器的三大特性:
1)多次性:无须在作业运行时,一次性的全部装入内存,而允许被分成多次装入内存运行。
2)对换性:无须作业一直在内存中,允许在作业运行的过程中,进行换入与换出操作。
3)虚拟性:从逻辑上扩充内存容量,使用户看到的内存容量远大于实际的内存容量。

6. 虚拟内存页面置换算法

FIFO,先进先出
LRU,last recently used,最近最少使用,根据使用时间到现在的长短来判断
LFU,last frequently used,最近使用次数算法,根据使用次数来判断
OPT,optimal replacement,最优置换算法,保证置换出去的是不再被使用的页,或在实际内存中最晚使用

补充

1. 进程切换发生了什么?做了什么操作?
  1. 发生中断时,保存现场,将发生中断时的所有通用寄存器保存到进程控制块(PCB,堆栈结构)中;
  2. 将要运行的就绪状态任务的当前状态,从任务栈中重新载入CPU寄存器,开始下一任务的运行。
2. 局部变量、全局变量放在什么地方?
  • 局部变量放在栈区
  • 全局变量放在静态存储区,占用静态存储单元
  • 动态变量放在堆区
  • 函数和代码放在代码区
3. 父子进程关系

子进程结束之后,父进程会收到exit消息。但是父进程结束的时候,子进程并不知道父进程结束,因此会成为孤儿进程,被 init 进程收留。

4. new 和 malloc 的区别
  1. 申请的内存所在地址
    new操作符从自由存储区(free store,动态内存区域之一)上为对象动态分配内存空间,而malloc函数从堆上动态分配内存。

  2. 返回类型安全性

new 类型匹配,安全;
malloc void * 类型,需要强制类型转换,不安全。

new操作符内存分配成功时,返回的是对象类型的指针,类型严格与对象匹配,无须进行类型转换,故new是符合类型安全性的操作符。而malloc内存分配成功则是返回void * ,需要通过强制类型转换将void*指针转换成我们需要的类型。

  1. 内存分配失败的返回值
    new内存分配失败时,会抛出bac_alloc异常,它不会返回NULL;malloc分配内存失败时返回NULL。

  2. 是否需要指定内存大小
    使用new操作符申请内存分配时无须指定内存块的大小,编译器会根据类型信息自行计算,而malloc则需要显式地指出所需内存的尺寸。

参考链接:
new与malloc的10点区别

5. 线程的隔离方式

主要有两种策略:

  1. 线程池
  • 该策略下,用户请求会被提交到各自的线程中执行,把执行每个下游服务的线程分离,从而达到资源隔离的作用。
  • 线程池满,则将请求放入请求队列中;请求队列满,则使用拒绝策略。
  1. 信号量
  • 通过信号量来限制线程访问临界区/争夺临界资源,达到线程隔离的目的。
  • 在该策略下,接收请求和执行下游依赖在同一个线程内完成,不存在线程上下文切换带来的性能开销,所以大部分场景都是信号量模式、

两者的主要区别:

  1. 实际工作的线程是谁创建的?
    使用线程池,实际工作线程由线程池创建;使用信号量,实际工作由程序员创建。

  2. 是否自动限流?
    线程池自动,信号量手动。

补充:进程隔离通常是由操作系统来完成的,做到进程隔离需要用到内存管理(内存分配),进程调度(调度策略)等功能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值