脚本宝典收集整理的这篇文章主要介绍了【面试重点系列】操作系统常见面试重点题(万字图解),脚本宝典觉得挺不错的,现在分享给大家,也给大家做个参考。
📒博客首页:铁甲小宝同学 📒
🌞文章目的:
操作系统面试题分享
🌞🙏博主也在学习阶段,如若发现问题,请告知,非常感谢🙏
💗
同时也非常感谢各位小伙伴们的支持
💗🌈每日一语:承遇朝霞,年少正恰。整装戎马,刻印风华! 🌈
💗感谢: 我只是站在巨人们的肩膀上整理本篇文章,感谢走在前路的大佬们!💗
本文的目的是学习、记录和分享
一些常见的 操作系统 知识。同时会以面试场景的形式给大家带来详细的介绍。希望本篇文章能给屏幕前的你有所帮助。如果文章哪个地方有什么 错误,也恳请大佬们 斧正,博主将不胜感激!
学习长路漫漫,你我同行。共进步,同成长!
🌸
👨💼面试官:OK,我们开始了,首先来说一下进程和线程的一些区别吧!
🙍♂️小宝:好的面试官。
首先我们需要知道什么是 线程?什么又是 进程 ?
我们通过上述对两者的介绍我们能大概的知道了两者的 根本区别。
线程是进程当中的⼀条执⾏流程,也是是独立调度的基本单位。
进程是操作系统进行资源分配的基本单位。
我们在从其他方面对这两者进行对比:
进程:
进程包含独立的地址空间。线程:
线程没有自己独立的地址空间,它共享所属进程的空间。进程:
进程之间的切换会有较大的开销。线程:
线程之间的切换的开销比较小。进程:
系统在运行的时候会为每个进程分配资源,而不会为线程分配资源。线程:
线程所使用的资源来自其所属进程的资源。🌸
👨💼面试官:那么两者的分别适用于什么场景呢?
🙍♂️小宝:
进程适用的场景:需要安全稳定的场景。
线程适用的场景
🌸
👨💼面试官:进程切换为什么比线程更消耗资源?
🙍♂️小宝: 进程切换时需要刷新TLB并获取新的地址空间,然后切换硬件上下文和内核栈;线程切换时只需要切换硬件上下文和内核栈。
TLB补充: 英文:Translation Lookaside Buffer。通过翻译:旁路转换缓冲。TLB 就是负责将虚拟内存地址翻译成实际的物理内 存地址,而 CPU 寻址时会优先在 TLB 中进行寻址。处理器的性能就和寻址的命中率有很大的关系。
上下文补充: 在一个程序运行起来时
进程
会有很多中状态,状态包括存放在内存中的程序的代码和数据,它的栈、通用目的寄存器的内容、程序计数器、环境变量以及打开的文件描述符的集合,这个状态叫做上下文(Context)。上下文切换:
- 进程: 进程是由
内核
管理和调度的,所以说进程切换只能发生再内核态
。- 线程: 当两个
线程
不是属于同一个进程时,则和进程
的上下文切换是一样的;当两个线程
是属于同一个进程
时,因为虚拟机内存是共享的,所以再切换时,虚拟内存这些资源就会保持不变,只需要切换线程的私有数据,寄存器等不共享的数据。
线程存在于进程中,一个进程可以有一个或多个线程。线程是运行在进程上下文中的逻辑流,这个线程可以独立完成一项任务。同样线程有自己的上下文,包括唯一的整数线程ID, 栈、栈指针、程序计数器、通用目的寄存器和条件码。可以理解为线程上下文是进程上下文的子集。
由于保存线程的上下文明显比进程的上下文小,因此系统切换线程时,必然开销更小。
所以这个只是线程比进程消耗资源小的其中一个方面。
🌸
👨💼面试官:发生进程上下文切换有哪些场景?
🙍♂️小宝:进程上下文的切换场景有:
进程
时,当前进程
被挂起。🌸
👨💼面试官:有了解进程的一些状态吗?
🙍♂️小宝:知道一些的面试官。
首先 进程 有三个最基本的状态 —— 就绪状态、运行状态和阻塞状态
。当然除了这三个还有两个基本的状态——创建状态和结束状态
。
我们再来说一下基本状态的作用。
进程
的创建。进程
到 运行状态。简的来说:可运⾏,由于其他进程处于运⾏状态⽽暂时停⽌运⾏;
进程
占用 CPU进行一些操作。进程
暂停运行。这时CPU给他控制权,他也无法运行!进程
任务完成或出错时,需要从系统中消失。🌸下面是小宝画的流程图(有点手残希望大家别建议)
流程图:
🌸
👨💼面试官:回答挺不错的,那你知道挂起状态吗?
🙍♂️小宝:
挂起状态: 因为如果在上述中有很多的 阻塞状态 则进程
就会占用物理空间,这是非常浪费资源的。所以我们会把这些 阻塞状态 的进程 转移到外存(硬盘)中。当再次运行时则会把该进程
从外存转入到 物理内存。
挂起状态还分为:
就绪挂起状态
:进程在外盘中,进入内存立即运行。阻塞挂起状态
:进程在外盘中,并等待某个事件的出现。流程图:(手残党来啦)
补充:
导致挂起的不仅不仅只有阻塞进程过多消耗物理内存,还有下面一些情况:
Ctrl+Z
挂起进程;🌸
👨💼面试官:知道挂起和阻塞的区别吗?
🙍♂️小宝:通过上面对挂起和阻塞状态的分析我们先来说一下两者的 共同点 :
接下来是 不同点:
阻塞
是在物理内存中,而挂起
是在外存中。阻塞
一般在进程等待资源(IO资源、信号量等)时发生;而挂起
是由于用户和系统的需要,例如,终端用户需要暂停程序研究其执行情况或对其进行修改、OS为了提高内存利用率需要将暂时不能运行的进程(处于就绪或阻塞队列的进程)调出到磁盘 。阻塞
要在等待的资源得到满足(例如获得了锁)后,才会进入就绪状态,等待被调度而执行;被挂起
的进程由将其挂起
的对象(如用户、系统)在时机符合时(调试结束、被调度进程选中需要重新执行)将其主动激活。🌸
👨💼面试官:了解进程调度机制吗?
🙍♂️小宝:在进程
的生命周期中,当进程
从一个运行状态转变为另外一种状态时,就会触发一次调度
。
我们来看下图:
通过图片我们能看见一个 调度程序 包含有排队器、分派器和上下文切换器
。
先来看看它们的功能分别是什么?
进程
转换为就绪状态时,排队器就会将它插入到相对应的就绪队列中。进程
的上下文,即把当前进程
的处理机寄存器内存保存到该进程
的进程控制块内的相应单元,再装入分派到程序的上下文,以便分派程序的运行。进程
的CPU现场信息装入到处理机的各个相应的寄存器中,以便新选择进程
运行。上面是进程调度机制中三个基本部分。(为了更好的了解进程的调度机制先做个大概的了解)
调度时机:
以下状态的变化都会触发操作系统的调度:
调度原则:
🌸
👨💼面试官:你了解哪些进程调度的算法呢?
🙍♂️小宝:OK,接下来来说一下我们最常见的几种 调度算法 :
🌻
FCFS:从字面意思我们就能知道它的具体功能就是谁先来 CPU 就先处理谁,就和排队买饭一样,谁先来谁就先买饭。
图解:
缺点: 不利于短服务,可能会一直存在长任务一直无法调度的情况。
我们也能从字面意思来了解,这个算法就是一直会调用 就绪队列 中时间短的序列来进行处理。
图解:
缺点:
这样对运行时长比较长的任务是比较不利的,可能长作业会一直得不到调度发生 饥饿状态 。
在批处理系统中,前两种算法都没能很好的处理不同时机的作业。而 高响应比优先 则是既考虑作业的 等待时间 又考虑了作业 运行时间 ,因此既照顾了短作业,又不致使长作业的等待时间过长。
公式:
🌻
本算法是在 FCFS 的基础上,每一次调度到的进程
只能使用一个时间片的 CPU ,进程
执行完毕或时间片用尽后,将时间片交给队列中的下一个进程
,并返回到队列尾。
缺点:
通常时间片设为 20ms~50ms
通常是一个比较合理的折中值。
最高优先级的调度就是开始时给每一个进程分配一个优先级,然后优先级高的进程先调度。
我们这里来了解一下 静态优先级 和 动态优先级
该算法也有两种处理优先级高的方法,非抢占式和抢占式:
缺点:
可能会导致 低优先级 永远不会被运行。(解决: 可以每隔一段时间为队列中等待的 进程
增加优先级。)
🌻
本算法相当于结合了 时间片轮转 和 优先级调度 的结合,有多层 FCFS 队列,且为不同的队列分配不同的 优先级 。
过程:
优先级越高的时间片越短,当一个新的进程
就绪时,将其放入 优先级最高 的队列中,如果时间片用完后,将其放入到第二个队列,以次类推,知道该进程
运行完成。
注: 优先级低的队列必须等优先级高的队列中没有任务时才能分配时间片。
图解:
可以发现,对于短作业可能可以在第一级队列很快被处理完。对于长作业,如果在第一级队列处理不完,可以移入下次队列等待被执行,虽然等待的时间变长了,但是运行时间也会更长了,所以该算法很好的兼顾了长短作业,同时有较好的响应时间。
🌸
👨💼面试官:有了解死锁吗?来说一下它的概念。
🙍♂️小宝: 死锁 是两个或多个并发的进程占用了 对方 继续执行需要获取的资源,导致任意一个都无法继续执行。
为了更加了解 死锁 我们可以通过 哲学家的晚餐
这个故事来了解。当一群哲学家坐在餐桌上面准备吃饭时,他们左右各有一只筷子。当所有的哲学家都拿起自己右手边的筷子时,此时桌子上的筷子是都被拿起(每个哲学家手里一只),但是吃饭需要两只筷子,一只筷子没办法吃,所以在座的所有哲学家都没办法吃饭。这其实就是一个 死锁问题 。
🌸
👨💼面试官:那你知道怎么样形成死锁?
🙍♂️小宝:形成 死锁 有四个必要的条件:
互斥条件。
占有并保持条件。
非抢占条件。
循环等待条件。
互斥条件:
进程占用的资源必须是互斥的,即需要等待对方释放资源才能获取。
图解:
占用并保持:
进程尝试获取下一个资源时不会释放已经占用的资源。
图解:
非抢占条件:
只有进程执行完才能释放资源。
图解:
循环等待条件:
就和哲学家进餐
一样,若干个进程相互占用形成一个环。
图解:
🌸
👨💼面试官:既然形成了死锁,那有办法解决死锁这个问题吗?
🙍♂️小宝:死锁的解决策略有以下四种:
我们在预防 死锁 形成时,我们只需要让 死锁 四个条件其中一个不满足即可。
资源有序分配法补充:
线程 A 和 线程 B 获取资源的
顺序要一样
,当线程 A 是先尝试获取资源 A,然后尝试获取资源 B 的时候,线程 B 同样也是先尝试获取资源 A,然后尝试获取资源 B。也就是说,线程 A 和 线程 B 总是以相同的顺序申请自己想要的资源。
我们使用资源有序分配法的方式来修改前面发生死锁的代码,我们可以不改动线程 A 的代码。
我们先要清楚线程 A 获取资源的顺序,它是先获取互斥锁 A,然后获取互斥锁 B。
所以我们只需将线程 B 改成以相同顺序的获取资源,就可以打破死锁了。
图解:
当允许四个条件中的前三个条件存在,如果系统能按某个顺序给进程分配资源,则代表系统就是安全的。
则通过 银行家算法等死锁规避算法 ,计算每一次为进程分配资源后进程是否安全,如果不处于,则让进程 等待并恢复资源分配矩阵。
死锁检测算法补充:
通过计算资源分配图中是否存在环判断系统,进程间是否存在死锁。
通过终止进程的方式来释放资源,可终止所有进程,也可以终止一个进程,直到打破循环为止。
一般的鸵鸟策略就是大部分的操作系统都不会主动去处理死锁,而是忽略死锁。
🌸
👨💼面试官:既然说了死锁,那么你知道活锁吗?来说一下活锁的概念。
🙍♂️小宝:活锁就是当两个进程出现死锁时,都通过释放资源来解决。若释放资源保持相同步长,则会造成 活锁 ,即没有发送阻塞,但程序无法向下执行。意思就是想着相互谦让,A让B,B让A。这样就会形成谁也不去获取资源。
图解:
🌸
👨💼面试官:知道悲观锁和乐观锁的基本概念吗?
🙍♂️小宝:
悲观锁:
悲观锁 (Pessimistic Concurrency Control) 如其名,这个锁看起来就就很悲观。一般 悲观 是我们人类的一种情绪表现,它代表我们人类情感方面的消极一面,认为什么事情都是不好的。而这个悲观锁也是一样的,每次 悲观锁 去拿数据都会认为别人要去修改其数据,则就会相对应的加上 锁
,让其访问的进程
变为 阻塞状态 。一般的互斥锁、自旋锁、读写锁
都是 悲观锁 。
乐观锁:
乐观锁 (Optimistic Concurrency Control) 如其名,乐观代表的是一种积极向上的态度。即乐观锁也是表示 “数据变动不会太平凡” 同时允许多个事务对数据进行修改。那可能就会有小伙伴问 “这个没有加锁 乐观锁 有没有不都是一样的吗?那为什么还需要使用 乐观锁 ?“
为了回答上面的问题,我们先来了解 乐观锁 工作原理:先修改完共享资源,然后发现此段时间里有没有发生冲突,如果没有则表示此次修改完成。如果发现有线程对这个资源进行修改,那么则放弃本次修改。 在这个过程中其实 乐观锁 就是没有加锁,它也被称为 无锁编程 。
OK,我们再来看一看 悲观锁 的详细介绍。
上述中我们知道了一般悲观锁有 互斥锁、自旋锁、读写锁
。接下来我们在对这几个来详细的介绍一下。
互斥锁 是一种比较底层的锁,很多高级的锁都是基于 互斥锁 实现 。我们知道当我们给共享资源加锁时,就是为了保证只能有 一个 线程来进行访问,避免多线程同时操作导致数据出现问题。
而 互斥锁 的特点就是: 一次只能一个线程加锁,其余只能等待。且在一些线程
加锁失败后 线程
会释放 CPU 给其他 线程
来操作。 且会 内核 会让线程陷入 阻塞状态。
图解:
而我们知道一般任务从保存到再加载的过程就是一次上下文切换。 上下文切换 则就表示会有资源的开销。
则开销成本就是两次 上下文切换 :
线程
从运行态转化为睡眠状态,然后把 CPU 转化给其他线程
。线程
就会转化为就绪状态,然后 内核 会在合适的时间,把 CPU 切换给线程
运行!和 互斥锁 一样,自旋锁 也是一种底层的锁,很多高级的锁都是基于 自旋锁 。则其特点:
自旋锁 是一种特殊的互斥锁,当资源被枷锁后,其他线程想要再次加锁,此时该线程不会被阻塞睡眠而是陷入循环等待状态(CPU不能做其它事情),循环检查资源持有者是否已经释放了资源。
图解:
优缺点:
这样做的好处是减少了线程从睡眠到唤醒的资源消耗,但会一直占用CPU的资源。适用于资源的锁被持有的时间短,而又不希望在线程的唤醒上花费太多资源的情况。
总的来说两者:
从字面意思上我们能直到这个是分为 读锁和写锁 ,也就是说,当读取共享资源时用 读锁 。当修改共享资源时用 写锁 。
读写锁的工作原理:
所以说,写锁是独占锁,因为任何时刻只能有一个线程持有写锁,类似互斥锁和自旋锁,而读锁是共享锁,因为读锁可以被多个线程同时持有。
🌸
👨💼面试官:悲观锁和乐观锁分别适用于什么场景?
🙍♂️小宝:
在上述中我们已经知道了 悲观锁和乐观锁 各自的优缺点,两者不能说有好坏之分。而是在不同的时机运用不同的锁,让效率达到最高才是一种真正的方式。
🌸
👨💼面试官:乐观锁的两种实现方式?
🙍♂️小宝:知道的,乐观锁一般会使用版本号机制或CAS算法实现。
那我们再来详细的说一下这两种机制吧。
一般是在数据表中加上一个数据版本号version
字段,表示数据 被修改的次数 ,当数据被修改时,version
值会加一。当线程A要更新数据值时,在读取数据的同时也会读取version
值,在提交更新时,若刚才读取到的version
值为当前数据库中的version
值相等时才更新,否则重试更新操作,直到更新成功。
图解:
这样就避免了 B 修改后覆盖了 A 修改的值。
即compare and swap(比较与交换),是一种有名的无锁算法。无锁编程,即不使用锁的情况下实现多线程之间的变量同步,也就是在没有线程被阻塞的情况下实现变量的同步,所以也叫非阻塞同步(Non-blocking Synchronization)。CAS算法涉及到三个操作数:
当且仅当 V 的值等于 A时,CAS通过原子方式用新值B来更新V的值,否则不会执行任何操作(比较和替换是一个原子操作)。一般情况下是一个自旋操作,即不断的重试。
🌸
👨💼面试官:那你说说进程间的通信方式吧?
🙍♂️小宝:好的面试官。
首先进程都是有自己独立的用户地址空间,且不能相互直接访问,但 内核空间中每个进程都是共享的,所以进程
之间要通信必须是需要通过 内核 的。所以就有了接下来的几种通信方式。
进程通信有一下六种方式:
接下来我们就需要对上述的几个进行一个详细的介绍。
上述我们也说了进程
和进程
之间都是有自己的独立空间的,且不能直接访问
。那就是通过 内核 访问。那么大家有没有思考是怎么通过 内核 来进行一个通信的呢?
进程之间要交换数据必须通过内核,在内核中开辟一块缓冲区,进程A把数据从用户空间拷到内核缓冲区,进程B再从内核缓冲区把数据读走,内核提供的这种机制称为进程间通信。
图解:
基本定义:
所谓管道,就像生活中的煤气管道、下水管道等传输气体和液体的工具,而在进程通信意义上的管道就是传输信息或数据的工具。
管道实质上是一个内核缓冲区,进程间通过在缓冲区中读写(只能单向)来通信。管道有互斥的特点,同一时间只能有一个进程进入读写。
用于有 亲缘关系 的进程间的 字节流 通信,且是 半双工 一种特殊的 可读可写 的文件。位于 内存 中 通过 pipe
系统调用来创建。
特点:
在 匿名管道 的基础上可以用于任意两个进程
间的通信。且也是一种 半双工 的通信方式。
特点:
注意:
管道这种通信方式效率低,不适合进程间频繁地交换数据
。
消息队列是保存在内核中的消息链表,在发送数据时,会分成一个一个独立的数据单元,也就是消息体(数据块),消息体是用户自定义的数据类型,消息的发送方和接收方要约定好消息体的数据类型,所以每个消息体都是固定大小的存储块,不像管道是无格式的字节流数据。如果进程从消息队列中读取了消息体,内核就会把这个消息体删除。
图解:
此图是A用消息队列传输给B信息的一个大致流程,如果B传输给A消息则也是和上面一样的流程!!!
通过图我们也能看见消息队列
模型就像平时发邮件一样,你来一封,我回一封,可以频繁沟通了。
特点:
消息队列
生命周期随内核
,如果没有关闭操作系统则消息队列
会一直存在。FIFO
的方式进行数据的读取和存入。随机查询
,也可以按照消息的类型
读取。缺点:
进程
之间共享同一块内存区域,进程间可以通过对共享内存中变量的修改来实现进程
间的通信。
共享内存的机制,就是拿出一块虚拟地址空间来,映射到相同的物理内存中
图解:
共享内存 其实能够解决 消息队列 中过多的开销。它直接分配一个共享空间,每个进程都可以直接访问,就像访问进程自己的空间一样快捷方便,不需要陷入内核态或者系统调用,大大提高了通信的速度,享有最快的进程间通信方式之名。
缺点: 多进程竞争同个共享资源会造成数据的错乱。
信号量: 用计数器对访问共享资源的数量进行限制。
注: 信号量只是保持进程
间的互斥同步,并不存储数据。
信号量中有两种原子性
操作——P操作
和V操作
:
P操作
则是让信号量减 1,如果信号量 >= 0
则表示共享资源还有,可以继续执行。如果是 < 0
则表示该资源已经被占用,则该进程会进入 阻塞状态 。V操作
则是把信号量加 1 ,如果信号量 > 0
则表示当前没有阻塞中的进程
。如果信号量 <= 0
则表名当前有阻塞中的进程
,需要去唤醒阻塞中的 进程
。P 操作是用在进入共享资源之前,V 操作是用在离开共享资源之后,这两个操作是必须成对出现的。
图解:
通过上图我们就能知道如果 共享内存 被 进程A 占领,当 进程B 再次执行 P操作 的时候则信号量
是小于0的,所以 进程B
进入 阻塞状态 。当 进程A 结束了资源的使用后就会执行 V操作 。则此时就会唤醒 进程B 继续执行 P操作 。
我们在上述中介绍了 信号量 ,而其是在正常状态下的工作模式。而 信号 则就是在 异常 下的工作模式。
信号是进程间通信机制中唯一的异步通信机制,因为可以在任何时候发送信号给某一进程,一旦有信号产生,我们就有下面这几种,用户进程对信号的处理方式。
SIGKILL
和 SEGSTOP
,它们用于在任何时候中断或结束某一进程。Socket
实际上不仅用于不同的主机进程间通信,还可以用于本地主机进程间通信,可根据创建 Socket
的类型不同,分为三种常见的通信方式,一个是基于 TCP
协议的通信方式,一个是基于 UDP
协议的通信方式,一个是本地进程间通信方式。
附: 这个地方就对 Socket 做一个简单的了解,具体等后续的补充(因为博主也没弄明白5555),大家可以去看 小林coding
大佬的图解哦!!!
🌸
👨💼面试官:那你对线程间的通信又了解多少呢?
🙍♂️小宝:好的,面试官。由我慢慢道来。
首先我们需要知道为什么需要 线程通信 ?
答:线程是操作系统调度的最小单位,有自己的栈空间,可以按照既定的代码逐步的执行,但是如果每个线程间都孤立的运行,那就会造资源浪费。所以在现实中,我们需要这些线程间可以按照指定的规则共同完成一件任务,所以这些线程之间就需要互相协调,这个过程被称为线程的通信。
可以被定义为:
线程通信就是当多个线程共同操作共享的资源时,互相告知自己的状态以避免资源争夺。
进程的通信方式有一下几种:
Java
中的 synchronize
锁,任意时刻只允许一个 线程 对 共享资源 进行访问,阻塞其他 线程 。Java
中的 semophore
锁,限制任意时刻同时访问共享资源的最大数(和进程的一样)。Java
中的 CAS
,在 while
循环中不断查看是否满足 同步 条件,长时间自旋会浪费CPU
资源。PS:个人觉得线程通信这个地方写的不是很详细,因为博主对这方面了解过于浅。还是建议大家有时间可以去网上找一些资料来了解。
本篇文章是 【面试重点】—操作系统篇 的第一弹。本篇博主也是花了半个月整理出来的,不仅仅字数 1w+ ,而且画图也都是快30张了。真的是花费了很多的心血,更希望大家能够仔细看一看,而不是一眼而过。现在我觉得多看几眼比给我点赞还重要的哈!!!
接下来本系列还是会持续更新的,就是时间可能会久点(因为学业和一些项目压得喘不过来哈哈哈)。同时也希望大家能在本篇文章中收获到更多的知识哦!!!
面试的路上还有很长。你我同行,共进步,共成长!我们下期见。
文章名 |
---|
【面试重点】计算机网络第一弹 |
未完待续 |
首先博主是需要在这里感谢csdn、牛客、JavaGuide、小林和一众开源的大佬们
。本篇文章也是站在这些巨人的肩膀来完成的,再次感谢各位大佬们!!!
参考的博文连接:
以上是脚本宝典为你收集整理的【面试重点系列】操作系统常见面试重点题(万字图解)全部内容,希望文章能够帮你解决【面试重点系列】操作系统常见面试重点题(万字图解)所遇到的问题。
本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
如您有任何意见或建议可联系处理。小编QQ:384754419,请注明来意。