【面试重点系列】操作系统常见面试重点题(万字图解)(1)

先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Python全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img



既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Python知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以添加V获取:vip1024c (备注Python)
img

正文

🌻

🌻先来先服务

FCFS:从字面意思我们就能知道它的具体功能就是谁先来 CPU 就先处理谁,就和排队买饭一样,谁先来谁就先买饭。

图解:

在这里插入图片描述

缺点: 不利于短服务,可能会一直存在长任务一直无法调度的情况。


🌻最短优先作业

我们也能从字面意思来了解,这个算法就是一直会调用 就绪队列 中时间短的序列来进行处理。

图解:

在这里插入图片描述

缺点:

这样对运行时长比较长的任务是比较不利的,可能长作业会一直得不到调度发生 饥饿状态


🌻高响应比优先

在批处理系统中,前两种算法都没能很好的处理不同时机的作业。而 高响应比优先 则是既考虑作业的 等待时间 又考虑了作业 运行时间 ,因此既照顾了短作业,又不致使长作业的等待时间过长。

公式:

在这里插入图片描述

🌻

🌻时间片轮转

本算法是在 FCFS 的基础上,每一次调度到的进程只能使用一个时间片的 CPU进程 执行完毕或时间片用尽后,将时间片交给队列中的下一个进程,并返回到队列尾。

在这里插入图片描述

缺点:

  • 如果时间片设得太短会导致过多的进程上下文切换,降低了 CPU 效率

  • 如果设得太长又可能引起对短作业进程的响应时间变长。

通常时间片设为 20ms~50ms 通常是一个比较合理的折中值。

🌻最高优先级

最高优先级的调度就是开始时给每一个进程分配一个优先级,然后优先级高的进程先调度。

我们这里来了解一下 静态优先级动态优先级

  • 静态优先级:创建进程时候,就已经确定了优先级了,然后整个运行时间优先级都不会变化;

  • 动态优先级:根据进程的动态变化调整优先级,比如如果进程运行时间增加,则降低其优先级,如果进程等待时间(就绪队列的等待时间)增加,则升高其优先级,也就是随着时间的推移增加等待进程的优先级

该算法也有两种处理优先级高的方法,非抢占式和抢占式:

  • 非抢占式调度算法: 挑选一个进程,然后让该进程运行直到被阻塞,或者直到该进程退出,才会调用另外一个进程也就是说,不会理时钟中断这个事情。

  • 抢断式调度算法: 让进程只运行某段时间,如果运行时间结束,则该进程就会被挂起,再从就绪队列中挑选另外一个进程。

缺点:

可能会导致 低优先级 永远不会被运行。(解决: 可以每隔一段时间为队列中等待的 进程 增加优先级。)

🌻

🌻多级反馈队列

本算法相当于结合了 时间片轮转优先级调度 的结合,有多层 FCFS 队列,且为不同的队列分配不同的 优先级

过程:

优先级越高的时间片越短,当一个新的进程就绪时,将其放入 优先级最高 的队列中,如果时间片用完后,将其放入到第二个队列,以次类推,知道该进程运行完成。

注: 优先级低的队列必须等优先级高的队列中没有任务时才能分配时间片。

图解:

在这里插入图片描述

可以发现,对于短作业可能可以在第一级队列很快被处理完。对于长作业,如果在第一级队列处理不完,可以移入下次队列等待被执行,虽然等待的时间变长了,但是运行时间也会更长了,所以该算法很好的兼顾了长短作业,同时有较好的响应时间。


四、死锁


🌸

1、死锁的概念

👨‍💼面试官:有了解死锁吗?来说一下它的概念。

🙍‍♂️小宝: 死锁 是两个或多个并发的进程占用了 对方 继续执行需要获取的资源,导致任意一个都无法继续执行。

为了更加了解 死锁 我们可以通过 哲学家的晚餐这个故事来了解。当一群哲学家坐在餐桌上面准备吃饭时,他们左右各有一只筷子。当所有的哲学家都拿起自己右手边的筷子时,此时桌子上的筷子是都被拿起(每个哲学家手里一只),但是吃饭需要两只筷子,一只筷子没办法吃,所以在座的所有哲学家都没办法吃饭。这其实就是一个 死锁问题

在这里插入图片描述

🌸

2、形成死锁的必要条件

👨‍💼面试官:那你知道怎么样形成死锁?

🙍‍♂️小宝:形成 死锁 有四个必要的条件:

  • 1、互斥条件。

  • 2、占有并保持条件。

  • 3、非抢占条件。

  • 4、循环等待条件。

互斥条件:

进程占用的资源必须是互斥的,即需要等待对方释放资源才能获取。

图解:

在这里插入图片描述

占用并保持:

进程尝试获取下一个资源时不会释放已经占用的资源。

图解:

在这里插入图片描述

非抢占条件:

只有进程执行完才能释放资源。

图解:

在这里插入图片描述

循环等待条件:

就和哲学家进餐一样,若干个进程相互占用形成一个环。

图解:

在这里插入图片描述

🌸

3、死锁的解决策略

👨‍💼面试官:既然形成了死锁,那有办法解决死锁这个问题吗?

🙍‍♂️小宝:死锁的解决策略有以下四种:

  • 1、死锁预防

  • 2、死锁避免

  • 3、死锁解除

  • 4、鸵鸟策略

🌻一、死锁预防

我们在预防 死锁 形成时,我们只需要让 死锁 四个条件其中一个不满足即可。

  • 破环互斥条件

  • 出现的问题: 无法实现对共享资源的操作同步,无实用价值。

  • 破快占有并保持条件: 可以提前对资源进行分配,即在程序开始前,就分配好所有资源。

  • 出现问题: 部分情况下,程序是在运行过程中动态获取资源的、预分配会给进程分配一些极少使用的资源。降低了资源的利用率。

  • 破坏非抢占条件: 进程无法获取到 继续执行 所需要的资源时,释放自己已有的资源,并重新尝试执行。

  • 出现问题: 性能开销大,效率低。

  • 破坏循环等待: 使用资源有序分配法,来破环环路等待条件。

资源有序分配法补充:

线程 A 和 线程 B 获取资源的顺序要一样,当线程 A 是先尝试获取资源 A,然后尝试获取资源 B 的时候,线程 B 同样也是先尝试获取资源 A,然后尝试获取资源 B。也就是说,线程 A 和 线程 B 总是以相同的顺序申请自己想要的资源。

我们使用资源有序分配法的方式来修改前面发生死锁的代码,我们可以不改动线程 A 的代码。

我们先要清楚线程 A 获取资源的顺序,它是先获取互斥锁 A,然后获取互斥锁 B。

所以我们只需将线程 B 改成以相同顺序的获取资源,就可以打破死锁了。

图解:

在这里插入图片描述


🌻二、避免死锁

当允许四个条件中的前三个条件存在,如果系统能按某个顺序给进程分配资源,则代表系统就是安全的。

则通过 银行家算法等死锁规避算法 ,计算每一次为进程分配资源后进程是否安全,如果不处于,则让进程 等待并恢复资源分配矩阵。

死锁检测算法补充:

通过计算资源分配图中是否存在环判断系统,进程间是否存在死锁。

🌻三、死锁解除

通过终止进程的方式来释放资源,可终止所有进程,也可以终止一个进程,直到打破循环为止。

🌻四、鸵鸟策略

一般的鸵鸟策略就是大部分的操作系统都不会主动去处理死锁,而是忽略死锁。


🌸

4、活锁

👨‍💼面试官:既然说了死锁,那么你知道活锁吗?来说一下活锁的概念。

🙍‍♂️小宝:活锁就是当两个进程出现死锁时,都通过释放资源来解决。若释放资源保持相同步长,则会造成 活锁 ,即没有发送阻塞,但程序无法向下执行。意思就是想着相互谦让,A让B,B让A。这样就会形成谁也不去获取资源。

图解:

在这里插入图片描述


五、悲观锁和乐观锁


🌸

1、知道悲观锁和乐观锁的基本概念吗?

👨‍💼面试官:知道悲观锁和乐观锁的基本概念吗?

🙍‍♂️小宝:

悲观锁:

悲观锁 (Pessimistic Concurrency Control) 如其名,这个锁看起来就就很悲观。一般 悲观 是我们人类的一种情绪表现,它代表我们人类情感方面的消极一面,认为什么事情都是不好的。而这个悲观锁也是一样的,每次 悲观锁 去拿数据都会认为别人要去修改其数据,则就会相对应的加上 ,让其访问的进程变为 阻塞状态 。一般的互斥锁、自旋锁、读写锁都是 悲观锁

乐观锁:

乐观锁 (Optimistic Concurrency Control) 如其名,乐观代表的是一种积极向上的态度。即乐观锁也是表示 “数据变动不会太平凡” 同时允许多个事务对数据进行修改。那可能就会有小伙伴问 “这个没有加锁 乐观锁 有没有不都是一样的吗?那为什么还需要使用 乐观锁 ?“

为了回答上面的问题,我们先来了解 乐观锁 工作原理:先修改完共享资源,然后发现此段时间里有没有发生冲突,如果没有则表示此次修改完成。如果发现有线程对这个资源进行修改,那么则放弃本次修改。 在这个过程中其实 乐观锁 就是没有加锁,它也被称为 无锁编程

OK,我们再来看一看 悲观锁 的详细介绍。

上述中我们知道了一般悲观锁有 互斥锁、自旋锁、读写锁。接下来我们在对这几个来详细的介绍一下。


🌻互斥锁

互斥锁 是一种比较底层的锁,很多高级的锁都是基于 互斥锁 实现 。我们知道当我们给共享资源加锁时,就是为了保证只能有 一个 线程来进行访问,避免多线程同时操作导致数据出现问题。

互斥锁 的特点就是: 一次只能一个线程加锁,其余只能等待。且在一些线程加锁失败后 线程会释放 CPU 给其他 线程来操作。 且会 内核 会让线程陷入 阻塞状态

图解:

在这里插入图片描述

而我们知道一般任务从保存到再加载的过程就是一次上下文切换。 上下文切换 则就表示会有资源的开销。

则开销成本就是两次 上下文切换

  • 当加锁失败时, 内核 会把线程从运行态转化为睡眠状态,然后把 CPU 转化给其他线程

  • 当锁被释放时,之前的睡眠状态的线程就会转化为就绪状态,然后 内核 会在合适的时间,把 CPU 切换给线程运行!

🌻自旋锁

互斥锁 一样,自旋锁 也是一种底层的锁,很多高级的锁都是基于 自旋锁则其特点:

自旋锁 是一种特殊的互斥锁,当资源被枷锁后,其他线程想要再次加锁,此时该线程不会被阻塞睡眠而是陷入循环等待状态(CPU不能做其它事情),循环检查资源持有者是否已经释放了资源。

图解:

在这里插入图片描述

优缺点:

这样做的好处是减少了线程从睡眠到唤醒的资源消耗,但会一直占用CPU的资源。适用于资源的锁被持有的时间短,而又不希望在线程的唤醒上花费太多资源的情况。

总的来说两者:

  • 互斥锁加锁失败后,线程会释放 CPU ,给其他线程;

  • 自旋锁加锁失败后,线程会忙等待,直到它拿到锁;


🌻读写锁

从字面意思上我们能直到这个是分为 读锁和写锁 ,也就是说,当读取共享资源时用 读锁 。当修改共享资源时用 写锁

读写锁的工作原理:

  • 当「写锁」没有被线程持有时,多个线程能够并发地持有读锁,这大大提高了共享资源的访问效率,因为「读锁」是用于读取共享资源的场景,所以多个线程同时持有读锁也不会破坏共享资源的数据。

  • 但是,一旦「写锁」被线程持有后,读线程的获取读锁的操作会被阻塞,而且其他写线程的获取写锁的操作也会被阻塞。

所以说,写锁是独占锁,因为任何时刻只能有一个线程持有写锁,类似互斥锁和自旋锁,而读锁是共享锁,因为读锁可以被多个线程同时持有。


🌸

2.悲观锁和乐观锁分别适用于什么场景?

👨‍💼面试官:悲观锁和乐观锁分别适用于什么场景?

🙍‍♂️小宝:

在上述中我们已经知道了 悲观锁和乐观锁 各自的优缺点,两者不能说有好坏之分。而是在不同的时机运用不同的锁,让效率达到最高才是一种真正的方式。

  • 乐观锁: 并发访问共享资源时如果 冲突率 比较低可以使用 乐观锁

  • 悲观锁: 反之,如果并发访问共享资源 冲突率 比较高的情况下可以使用 悲观锁


🌸

2.乐观锁的两种实现方式?

👨‍💼面试官:乐观锁的两种实现方式?

🙍‍♂️小宝:知道的,乐观锁一般会使用版本号机制或CAS算法实现。

那我们再来详细的说一下这两种机制吧。

🌻一、版本号机制

一般是在数据表中加上一个数据版本号version字段,表示数据 被修改的次数 ,当数据被修改时,version值会加一。当线程A要更新数据值时,在读取数据的同时也会读取version值,在提交更新时,若刚才读取到的version值为当前数据库中的version值相等时才更新,否则重试更新操作,直到更新成功。

图解:

在这里插入图片描述

这样就避免了 B 修改后覆盖了 A 修改的值。


🌻二、CAS算法

compare and swap(比较与交换),是一种有名的无锁算法。无锁编程,即不使用锁的情况下实现多线程之间的变量同步,也就是在没有线程被阻塞的情况下实现变量的同步,所以也叫非阻塞同步(Non-blocking Synchronization)。CAS算法涉及到三个操作数:

  • 需要读写的内存值 V

  • 进行比较的值 A

  • 拟写入的新值 B

当且仅当 V 的值等于 A时,CAS通过原子方式用新值B来更新V的值,否则不会执行任何操作(比较和替换是一个原子操作)。一般情况下是一个自旋操作,即不断的重试


六、进程和线程的通信方式


🌸

1.那你说说进程间的通信方式吧?

👨‍💼面试官:那你说说进程间的通信方式吧?

🙍‍♂️小宝:好的面试官。

首先进程都是有自己独立的用户地址空间,且不能相互直接访问,但 内核空间中每个进程都是共享的,所以进程之间要通信必须是需要通过 内核 的。所以就有了接下来的几种通信方式。

进程通信有一下六种方式:

  • 管道通信(使用最简单)

  • 消息队列

  • 共享内存

  • 信号量

  • 信号(开销最小)

  • Socket

接下来我们就需要对上述的几个进行一个详细的介绍。


🌻进程间通信的工作原理

上述我们也说了进程进程之间都是有自己的独立空间的,且不能直接访问。那就是通过 内核 访问。那么大家有没有思考是怎么通过 内核 来进行一个通信的呢?

进程之间要交换数据必须通过内核,在内核中开辟一块缓冲区,进程A把数据从用户空间拷到内核缓冲区,进程B再从内核缓冲区把数据读走,内核提供的这种机制称为进程间通信。

图解:

在这里插入图片描述

🌻管道

基本定义:

所谓管道,就像生活中的煤气管道、下水管道等传输气体和液体的工具,而在进程通信意义上的管道就是传输信息或数据的工具。

管道实质上是一个内核缓冲区,进程间通过在缓冲区中读写(只能单向)来通信。管道有互斥的特点,同一时间只能有一个进程进入读写。

🌼匿名管道

用于有 亲缘关系 的进程间的 字节流 通信,且是 半双工 一种特殊的 可读可写 的文件。位于 内存 中 通过 pipe 系统调用来创建。

特点:

  • 1、只能进行单向通信。

  • 2、用于有亲缘关系的通信。

  • 3、管道内部自带同步机制:子进程写一条,父进程读一条。

  • 4、当进程退出时,管道也随之释放。

  • 5、管道的生命周期随着进程的结束而消失。

  • 6、先进先出


🌼命名管道FIFO

匿名管道 的基础上可以用于任意两个进程间的通信。且也是一种 半双工 的通信方式。

特点:

  • 1、它可以在不相关的进程间也能相互通信。

  • 2、先进先出

  • 3、只支持单向的数据流。

  • 4、管道的生命周期随着进程的结束而消失。

注意: 管道这种通信方式效率低,不适合进程间频繁地交换数据


🌻消息队列

消息队列是保存在内核中的消息链表,在发送数据时,会分成一个一个独立的数据单元,也就是消息体(数据块),消息体是用户自定义的数据类型,消息的发送方和接收方要约定好消息体的数据类型,所以每个消息体都是固定大小的存储块,不像管道是无格式的字节流数据。如果进程从消息队列中读取了消息体,内核就会把这个消息体删除。

图解:

在这里插入图片描述

此图是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操作


🌻信号

我们在上述中介绍了 信号量 ,而其是在正常状态下的工作模式。而 信号 则就是在 异常 下的工作模式。

信号是进程间通信机制中唯一的异步通信机制,因为可以在任何时候发送信号给某一进程,一旦有信号产生,我们就有下面这几种,用户进程对信号的处理方式。

  • 1.执行默认操作。Linux 对每种信号都规定了默认操作,例如,上面列表中的 SIGTERM 信号,就是终止进程的意思。

  • 2.捕捉信号 我们可以为信号定义一个信号处理函数。当信号发生时,我们就执行相应的信号处理函数。

  • 3.忽略信号 当我们不希望处理某些信号的时候,就可以忽略该信号,不做任何处理。有两个信号是应用进程无法捕捉和忽略的,即 SIGKILLSEGSTOP,它们用于在任何时候中断或结束某一进程。


🌻 Socket

Socket 实际上不仅用于不同的主机进程间通信,还可以用于本地主机进程间通信,可根据创建 Socket 的类型不同,分为三种常见的通信方式,一个是基于 TCP 协议的通信方式,一个是基于 UDP 协议的通信方式,一个是本地进程间通信方式。

附: 这个地方就对 Socket 做一个简单的了解,具体等后续的补充(因为博主也没弄明白5555),大家可以去看 小林coding

大佬的图解哦!!!


🌸

2.再来谈谈线程间的通信方式吧!

👨‍💼面试官:那你对线程间的通信又了解多少呢?

🙍‍♂️小宝:好的,面试官。由我慢慢道来。

🌻1、为什么需要线程通信

首先我们需要知道为什么需要 线程通信

一、Python所有方向的学习路线

Python所有方向路线就是把Python常用的技术点做整理,形成各个领域的知识点汇总,它的用处就在于,你可以按照上面的知识点去找对应的学习资源,保证自己学得较为全面。

二、学习软件

工欲善其事必先利其器。学习Python常用的开发软件都在这里了,给大家节省了很多时间。

三、入门学习视频

我们在看视频学习的时候,不能光动眼动脑不动手,比较科学的学习方法是在理解之后运用它们,这时候练手项目就很适合了。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024c (备注python)
img

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!


🌸

2.再来谈谈线程间的通信方式吧!

👨‍💼面试官:那你对线程间的通信又了解多少呢?

🙍‍♂️小宝:好的,面试官。由我慢慢道来。

🌻1、为什么需要线程通信

首先我们需要知道为什么需要 线程通信

一、Python所有方向的学习路线

Python所有方向路线就是把Python常用的技术点做整理,形成各个领域的知识点汇总,它的用处就在于,你可以按照上面的知识点去找对应的学习资源,保证自己学得较为全面。

二、学习软件

工欲善其事必先利其器。学习Python常用的开发软件都在这里了,给大家节省了很多时间。

三、入门学习视频

我们在看视频学习的时候,不能光动眼动脑不动手,比较科学的学习方法是在理解之后运用它们,这时候练手项目就很适合了。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024c (备注python)
[外链图片转存中…(img-OdVdT9FS-1713440014215)]

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 17
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值