操作系统面经(三)

17、死锁发生的条件以及如何解决死锁

1、定义:死锁是指两个或两个以上进程在执行过程中,因争夺资源而造成的相互等待的现象。

2、四个必要条件下:

(1)互斥条件:进程对所分配到的资源不允许其他进程访问,若其他进程访问该资源,只能等待,直至占有该资源的进程使用完成后释放该资源;

(2)请求和保持条件:进程获得一定的资源后,又对其他资源发出请求,但是该资源可能被其他进程占有,此时请求阻塞,但该进程不会释放自己已经占有的资源(吃着碗里的想着锅里的)

(3)不可剥夺条件:进程已获得的资源,在未完成使用之前,不可被剥夺(剥夺是被其他进程所剥夺),只能在使用后自己释放

(4)环路等待条件: 存在一个封闭的进程链,使得每个进程至少占有此链中下一个进程所需要的一个资源。

【一句话:资源互斥(给了我别人就没了)、不可剥夺(给我了你不能抢)、保持请求(你想要只能等)、环路等待(我变成了小丑,我也要等,形成了回环)】

3、产生死锁的原因

1)系统资源不足;

2)进程(线程)推进的顺序不恰当;

3)资源分配不当。

4、解决死锁:

(1)破坏请求保持: 必须一次性获得所有资源,如果无法获得完全,释放已经获得的资源

(2)破坏不可剥夺: 只要当一个进程申请一个资源,然而却申请不到的时候,必须释放已经申请到的所有资源 【第三种条件】

(3)破坏环路等待:系统给每类资源赋予一个序号,每个进程按编号递增的请求资源,释放则相反,从而破坏环路等待的条件

【一句话:解决不可剥夺(我要是得不到想要的就放弃自己拥有的)、解决保持请求(一次性给我所有的资源,如果还满足不了,那我就释放)、解决环路等待(按顺序申请资源和释放资源)】

18、Linux中的锁

互斥锁:mutex,用于保证在任何时刻,都只能有一个线程访问该对象。当获取锁操作失败时,线程会进入睡眠,等待锁释放时被唤醒

读写锁:分为读锁和写锁。处于读操作时,可以允许多个线程同时获得读操作。但是同一时刻只能有一个线程可以获得写锁。其它获取写锁失败的线程都会进入睡眠状态,直到写锁释放时被唤醒。注意:写锁会阻塞其它读写锁。当有一个线程获得写锁在写时,读锁也不能被其它线程获取;写者优先于读者。

自旋锁:spinlock,在任何时刻同样只能有一个线程访问对象。但是当获取锁操作失败时,不会进入睡眠,而是会在原地自旋,直到锁被释放。这样节省了线程从睡眠状态到被唤醒期间的消耗,在加锁时间短暂的环境下会极大的提高效率。但如果加锁时间过长,则会非常浪费CPU资源。【获取锁失败会原地自旋,节省了从休眠到唤醒的时间】

乐观锁:乐观锁在操作数据时非常乐观,认为别人不会同时修改数据。 因此乐观锁不会上锁,只是在执行更新的时候判断一下在此期间别人是否修改了数据:如果别人修改了数据则放弃操作,否则执行操作。【先不上锁,要更新时看一下数据是否被修改,如果修改就放弃更新】

悲观锁:悲观锁在操作数据时比较悲观,认为别人会同时修改数据。 因此操作数据时直接把数据锁住,直到操作完成后才会释放锁;上锁期间其他人不能修改数据。【先上锁】

19、信号量和锁的区别

信号量:那是多线程同步用的,一个线程完成了某个动作就通过信号告诉别的线程,别的线程再进行某些动作,如P、V操作。

互斥量(锁):这是多线程互斥用的,比如说,一个线程占用了某一个资源,那么别的线程就无法访问,知道这个线程离开,其他的线程才开始可以利用这个资源。

20、一个程序从开始运行到结束的完整过程

(1)预编译: 主要处理源代码文件中的以“#”开头的预编译指令。

(2)编译: 把预编译之后生成的xxx.i或xxx.ii文件,进行一系列词法分析、语法分析、语义分析及优化后,生成相应 的汇编代码文件。

(3)汇编: 将汇编代码转变成机器可以执行的指令(机器码文件)。

(4)链接: 将不同的源文件产生的目标文件进行链接,从而形成一个可以执行的程序。

21、fork函数的作用

fork 函数的作用是从已经存在的进程中创建一个子进程,而原进程称为父进程。

调用 fork(),当控制转移到内核中的 fork 代码后,内核开始做:

1、分配新的内存块和内核数据结构给子进程。

2、将父进程部分数据结构内容拷贝至子进程。

3、将子进程添加到系统进程列表。

4、fork返回开始调度器,调度。

特点:

1)调用一次,返回两次并发执行

2)相同但是独立的地址空间

3)fork 的返回值:fock 函数调用一次却返回两次;向父进程返回子进程的 ID,向子进程中返回 0,

4)fork 的子进程返回为 0;

5)父进程返回的是子进程的 pid。

22、select、poll 和 epoll 的区别⭐️

这三种方法都是IO复用技术

select/poll方式

select 实现多路复用的方式是,将已连接的 Socket 都放到一个**文件描述符集合**,然后调用 select 函数将文件描述符集合**拷贝**到内核里,让内核来检查是否有网络事件产生,检查的方式很粗暴,就是通过**遍历**文件描述符集合的方式,当检查到有事件产生后,将此 Socket 标记为可读或可写, 接着再把整个文件描述符集合**拷贝**回用户态里,然后用户态还需要再通过**遍历**的方法找到可读或可写的 Socket,然后再对其处理。 

对于 select 这种方式,需要进行 **2 次「遍历」文件描述符集合**,一次是在内核态里,一个次是在用户态里 ,而且还会发生 **2 次「拷贝」文件描述符集合**,先从用户空间传入内核空间,由内核修改后,再传出到用户空间中。 

​ 默认最大值为 1024,只能监听 0~1023 的文件描述符。

​ poll 不再用 BitsMap 来存储所关注的文件描述符,取而代之用动态数组,以链表形式来组织,突破了 select 的文件描述符个数限制,当然还会受到系统文件描述符限制。

​ poll 和 select 并没有太大的本质区别,都是使用「线性结构」存储进程关注的 Socket 集合,因此都需要遍历文件描述符集合来找到可读或可写的 Socket,时间复杂度为 O(n),而且也需要在用户态与内核态之间拷贝文件描述符集合,这种方式随着并发数上来,性能的损耗会呈指数级增长。

epoll方式

第一点,epoll 在内核里使用红黑树来跟踪进程所有待检测的文件描述字,把需要监控的 socket 通过epoll_ctl() 函数加入内核中的红黑树里,红黑树是个高效的数据结构,增删查一般时间复杂度是 O(logn),通过对这棵黑红树进行操作,这样就不需要像 select/poll 每次操作时都传入整个 socket 集合,只需要传入一个待检测的 socket,减少了内核和用户空间大量的数据拷贝和内存分配。

*第二点*, epoll 使用事件驱动的机制,内核里**维护了一个链表来记录就绪事件**,当某个 socket 有事件发生时,通过回调函数内核会将其加入到这个就绪事件列表中,当用户调用 `epoll_wait()` 函数时,只会返回有事件发生的文件描述符的个数,不需要像 select/poll 那样轮询扫描整个 socket 集合,大大提高了检测的效率。

三方面的区别:
(1)、最大连接数

select有上限,最大为1024个。

poll没有上限,因为他是用离岸边存储。

epoll连接数的上限很大,采用红黑树实现。

(2)、fd增加后带来的IO效率问题

select/poll: 因为每次调用时都会对连接进行线性遍历,所以随着fd的增加会造成遍历速度降低。

epoll: 因为epoll内核中实现是根据每个fd上的回调函数来实现的,只有活跃的socket才会主动调用回调函数,所以在活跃socket较少的情况下,使用epoll没有前面两者的性能下降问题,但是所有socket都很活跃的情况下,可能会有性能问题。

(3)、消息传递方式

select/poll: 每次调用,都须要把fd集合从用户态拷贝到内核态 ,再对所有的fd进行遍历。

epoll通过内核和用户空间共享一块内存来实现的。

(4)、工作模式

select/poll只能工作在性能较低的LT模式,epoll可以在LT和ET下工作。

23、请你来介绍一下5种IO模型

(1)、阻塞IO:调用者调用了某个函数,等待这个函数返回,期间什么也不做,不停的去检查这个函数有没有返回,必须等这个函数返回才能进行下一步动作
(2)、非阻塞IO:非阻塞等待,每隔一段时间就去检测IO事件是否就绪。没有就绪就可以做其他事。
(3)、信号驱动IO:当进程发起一个 IO 操作,会向内核注册一个信号处理函数,然后进程返回不阻塞;当内核数据就绪时会发送一个信号给进程,进程便在信号处理函数中调用 IO 读取数据。【IO复用是轮寻,这个不是】
(4)、IO复用:linux用select/poll函数实现IO复用模型,这两个函数也会使进程阻塞,但是和阻塞IO所不同的是这两个函数可以同时阻塞多个IO操作。而且可以同时对多个读操作、写操作的IO函数进行检测。知道有数据可读或可写时,才真正调用IO操作函数【IO复用是采用select/poll/epoll等函数监控多个fd,每当某个fd发出请求时,再去处理这个fd】
(5)、异步IO: 当进程发起一个 IO 操作,进程返回不阻塞,但也不能返回结果;内核把整个 IO 处理完后,会通知进程结果。如果IO操作成功则进程直接获取到数据。

24、请你说一说内存溢出和内存泄漏

1、内存溢出

指程序申请内存时,没有足够的内存供申请者使用。

2、内存泄漏

内存泄漏是指由于疏忽或错误造成了程序未能释放掉不再使用的内存的情况。内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,失去了对该段内存的控制,因而造成了内存的浪费。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值