蒋豆芽的面试题专栏(17/操作系统之锁与IO模型)

  1. 简述互斥锁的机制,互斥锁与读写的区别?⭐⭐⭐⭐⭐

  2. 简述自旋锁和互斥锁的使用场景⭐⭐⭐⭐⭐

  3. 公平锁与非公平锁⭐⭐⭐⭐⭐

  4. 死锁与活锁⭐⭐⭐⭐⭐

  5. 说说sleep和wait的区别?⭐⭐⭐

  6. 请介绍一下5种IO模型⭐⭐⭐⭐⭐

  7. 简述同步与异步的区别,阻塞与非阻塞的区别?⭐⭐⭐⭐⭐

  8. BIO、NIO有什么区别?⭐⭐⭐⭐⭐

  9. 说说多路IO复用技术有哪些,区别是什么?⭐⭐⭐⭐⭐

  10. 说说epoll的原理⭐⭐⭐⭐⭐

  11. 简述epoll和select的区别,epoll为什么高效?⭐⭐⭐⭐⭐

  12. epoll水平触发与边缘触发的区别?⭐⭐⭐⭐⭐

=========================================================================================================

  • 本专栏适合于C/C++已经入门的学生或人士,有一定的编程基础。
  • 本专栏适合于互联网C++软件开发、嵌入式软件求职的学生或人士。
  • 本专栏针对面试题答案进行了优化,尽量做到好记、言简意赅。这才是一份面试题总结的正确打开方式。这样才方便背诵
  • 针对于非科班同学,建议学习本人专刊文章《蒋豆芽的秋招打怪之旅》,该专刊文章对每一个知识点进行了详细解析。
  • 如专栏内容有错漏,欢迎在评论区指出或私聊我更改,一起学习,共同进步。
  • 相信大家都有着高尚的灵魂,请尊重我的知识产权,未经允许严禁各类机构和个人转载、传阅本专栏的内容。

=========================================================================================================

  1. 简述互斥锁的机制,互斥锁与读写的区别?⭐⭐⭐⭐⭐

    1. 互斥锁机制:mutex,用于保证在任何时刻,都只能有一个线程访问该对象。当获取锁操作失败时,线程会进入阻塞,等待锁释放。

    2. 互斥锁和读写锁

      (1) 读写锁区分读者和写者,而互斥锁不区分

      (2)互斥锁同一时间只允许一个线程访问该对象,无论读写;读写锁同一时间内只允许一个写者,但是允许多个读者同时读对象。

  2. 简述自旋锁和互斥锁的使用场景⭐⭐⭐⭐⭐

    1. 互斥锁用于临界区持锁时间比较长的操作,比如下面这些情况都可以考虑

      (1)临界区有IO操作

      (2)临界区代码复杂或者循环量大

      (3)临界区竞争非常激烈

      (4)单核处理器

    1. 自旋锁就主要用在临界区持锁时间非常短且CPU资源不紧张的情况下。
  3. 公平锁与非公平锁⭐⭐⭐⭐⭐

    公平锁:就是很公平,在并发环境中,每个线程在获取锁时会先查看此锁维护的等待队列,如果为空,或者当前线程是等待队列的第一个,就占有锁,否则就会加入到等待队列中,以后会按照FIFO的规则从队列中取到自己
    非公平锁:非公平锁比较粗鲁,上来就直接尝试占有锁,如果尝试失败,就再采用类似公平锁那种方式。
    非公平锁的优点在于吞吐量比公平锁大。

  4. 死锁与活锁⭐⭐⭐⭐⭐

    死锁:是指两个或两个以上的进程(或线程)在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。
    活锁:是指线程1可以使用资源,但它很礼貌,让其他线程先使用资源,线程2也可以使用资源,但它很绅士,也让其他线程先使用资源。这样你让我,我让你,最后两个线程都无法使用资源。

  5. 说说sleep和wait的区别?⭐⭐⭐

    (1)sleep是一个延时函数,让进程或线程进入休眠。休眠完毕后继续运行。

    (2)wait是父进程回收子进程PCB(Process Control Block)资源的一个系统调用。

  6. 请介绍一下5种IO模型⭐⭐⭐⭐⭐

    IO(Input/Output,输入/输出)即数据的读取(接收)或写入(发送)操作。
    通常用户进程中的一个完整IO分为两阶段:用户进程空间与内核空间之间的相互切换、内核空间与设备空间的相互切换(磁盘、网络等)。我们通常说的IO是指网络IO磁盘IO两种。
    Linux中进程无法直接操作I/O设备,其必须通过系统调用请求内核来协助完成I/O动作;内核会为每个I/O设备维护一个缓冲区。
    对于一个输入操作来说,进程IO系统调用后,内核会先看缓冲区中有没有相应的缓存数据,没有的话再到设备中读取,因为设备IO一般速度较慢,需要等待;内核缓冲区有数据则直接复制到进程空间。
    所以,对于一个网络输入操作通常包括两个不同阶段:

    1. 等待网络数据到达网卡→读取到内核缓冲区,数据准备好;
    2. 从内核缓冲区复制数据到进程空间。

    5种IO模型如下:

    1. 阻塞IO:进程发起IO系统调用后,进程被阻塞,转到内核空间处理,整个IO处理完毕后返回进程。操作成功则进程获取到数据。调用者将一直等待,不停的检查这个函数有没有返回,必须等这个函数返回后才能进行下一步动作。
    2. 非阻塞IO:进程发起IO系统调用后,进程被阻塞,内核数据还没好,不想让进程等待,就返回一个错误,这样进程就不阻塞了。进程每隔一段时间就发起IO系统调用去检查IO事件是否就绪。这样就实现非阻塞了。每个进程都有一个时间片,轮询的时候读取IO,时间片到了就要换另一个进程做其他事情了,这样就做到了每隔一段时间发起IO系统调用。
    3. IO多路复用:Linux用select/poll函数实现IO复用模型,这两个函数也会使进程阻塞,但是和阻塞IO所不同的是这两个函数可以同时阻塞多个IO操作。而且可以同时对多个读操作、写操作的IO函数进行检查。select/poll会监听所有的IO,直到有数据可读或可写时,才真正调用IO操作函数。
    4. 信号驱动IO:Linux用套接口进行信号驱动IO,安装一个信号处理函数,进程继续运行并不阻塞,当IO事件就绪,进程收到SIGIO信号,然后处理IO事件。这个好理解,这个信号直接通知进程数据到了。
    5. 异步IO:进程发起IO系统调用后立即返回,当内核将数据拷贝到缓冲区后,再通知应用程序。用户可以直接去使用数据。具体操作是进程调用aio_read函数告诉内核描述字缓冲区指针和缓冲区的大小、文件偏移及通知的方式,然后立即返回。

    前四种属于同步IO,原因就在于进程发起IO系统调用读取数据时,这个真正拿到数据的过程依然是阻塞的,直到完成数据读取还要把数据拷贝到用户空间中,进程才能继续做其他事。
    而异步IO就不一样了,进程完全做自己的事情,数据都不需要它读取,而是由内核读取数据并将数据拷贝到缓冲区后,再通知应用程序。用户可以直接去使用数据。

  7. 简述同步与异步的区别,阻塞与非阻塞的区别?⭐⭐⭐⭐⭐

    1. 同步与异步的区别

      同步:所有的操作都做完,才返回给用户结果。即写完数据库之后,再响应用户,用户体验不好。双方的动作是经过双方协调的,步调一致的。

      异步:不用等所有操作都做完,就响应用户请求。即先响应用户请求,然后慢慢去写数据库,用户体验较好。双方并不需要协调,都可以随意进行各自的操作。

  1. 阻塞与非阻塞的区别

    阻塞:调用者调用了某个函数,等待这个函数返回,期间什么也不做,不停的检查这个函数有没有返回,必须等这个函数返回后才能进行下一步动作。

    非阻塞:非阻塞等待,每隔一段时间就去检查IO事件是否就绪。没有就绪就可以做其他事情。

  1. BIO、NIO有什么区别?⭐⭐⭐⭐⭐

    BIO、NIO的区别

    BIO(Blocking I/O)阻塞IO。进程发起IO系统调用后,进程被阻塞,转到内核空间处理,整个IO处理完毕后返回进程。操作成功则进程获取到数据。调用者将一直等待,不停的检查这个函数有没有返回,必须等这个函数返回后才能进行下一步动作。

    NIO(New I/O)同时支持阻塞与非阻塞模式,NIO的做法是叫一个线程不断的轮询每个IO的状态,看看是否有IO的状态发生了改变,从而进行下一步的操作。

  2. 说说多路IO复用技术有哪些,区别是什么?⭐⭐⭐⭐⭐

    1. select,poll,epoll都是IO多路复用的机制,I/O多路复用就是通过一种机制,可以监视多个文件描述符,一旦某个文件描述符就绪(一般是读就绪或者写就绪),能够通知应用程序进行相应的读写操作。

    2. 区别

      (1)poll与select不同,通过一个pollfd数组向内核传递需要关注的事件,故没有描述符个数的限制,pollfd中的events字段和revents分别用于标示关注的事件和发生的事件,故pollfd数组只需要被初始化一次。

      (2)sele

剩余60%内容,订阅专栏后可继续查看/也可单篇购买

- 本专栏适合于C/C++已经入门的学生或人士,有一定的编程基础。 - 本专栏特点: 本专刊囊括了C语言、C++、操作系统、计算机网络、嵌入式、算法与数据结构、数据库等一系列知识点,总结出了高频面试考点(附有答案)共计309道,事半功倍,为大家春秋招助力。 - 本专栏内容分为七章:共计309道高频面试题(附有答案)

全部评论
epoll水平触发与边缘触发的区别:首先,TCP会保证数据有序、不重、不丢。因此内核保证了这点,用户层数据由用户自己保证。假如tcp接到9个a,用户每次接收5个a。水平模式:接连两次报告,第一次5个a,紧接着4个a,即报告完为止。边缘模式:只报告5个a,下次tcp又接到一个b,此时报告是aaaab,即边缘模式在每次触发中都会先报告之前的!!!
1 回复 分享
发布于 2021-05-07 10:15
poll,作为select和epoll的中间过渡件,(1)和select类似的是:同样存在用户态拷贝到内核态,从内核态拷贝到用户态,poll返回需要轮询才能直到那个事件发生了,相当于3次操作(拷贝+轮询)(2)和epoll类似的是提出了POLLIN、POLLOUT等概念。区别:(1)和select区别:最大的区别是少了while循环开始的临时拷贝,其次是1024监听数量的限制。这里是最有意思的,源于pollfd结构,有三个成员:fd、events关心的事件类型(输入,POLLIN等宏)、revents内核返回事件发生了(返回),我觉得最有意思的就是revents,这东西小于等于0内核直接忽略(pollfd的数组中大量是小于等于0的),poll返回的是revents大于0的那些事件,也就避免了while刚开始的临时拷贝(2)select/poll没有避免用户到内核、内核到用户、用户态的轮询问题。怎么解决,epoll出现了--用户到内核需要拷贝(select/poll/epoll三个都需要),内核到用户需要拷贝(注意:若100W的socket,此时只有1w事件发生了,select/poll需要拷贝100W个,而epoll需要拷贝1w个),轮询(select/poll需要轮询100W个,而epoll需要轮询1w个),epoll之所以高效,核心是因为它只关心此时已经发生的事件!!!这样想来,这个idea是平凡的,还能在优化了,不,因为epoll只处理发生的事件,已经最优了
1 回复 分享
发布于 2021-05-06 22:34
三月二十六凌晨一点,整章内容进行了完善更新。在此留言。
1 回复 分享
发布于 2021-03-26 01:11
epoll是最快的么,什么场景下。 不一定。epoll的一个缺点,当事件触发比较频繁时,回调函数也会被频繁触发,此时效率就未必比select 或 poll高了。所以epoll的最佳使用情景是:连接数量多,但活跃的连接数量少。
点赞 回复 分享
发布于 2023-01-11 20:50 广东
很好的文章:https://www.ahjmgzs.com/rjjc/483320.html
点赞 回复 分享
发布于 2023-01-11 20:46 广东
内核中epoll检测发生事件的epoll_data并将其插入到一个链表当中rdlist,然后通过epoll_wait()拷贝到用户态。
点赞 回复 分享
发布于 2022-04-25 15:54
epoll,出发点是拷贝与轮询只处理当下发生的,而不会盲目的拷贝所有的。epoll底层是复杂的,但是是智慧的,这里所有的内容在内核源码都可以找到(1): epoll三把斧头epoll_create、epoll_ctl、epoll_wait。相比与select/poll一个API复杂了 (2): epoll_create创建epoll文件句柄,epoll_ctl将感兴趣的事件加入红黑树、epoll_wait将rdlist分批拷贝至用户态 (3): epoll_create分配eventpoll内存,获取可用的fd,创建匿名文件并绑定 (4): epoll_ctl分配epitem对象内存,分配感兴趣的事件(内核把所有的事件都初始化为1,~0ul,通过&快速定位,避免对底层数据结构频繁的删改), 设置回调函数ep_poll_callback,将新epitem插入红黑树(红黑树是特殊的BST,中序是升序的,其比较方法也很有意思ep_cmp_ffd) (5): epoll_ctl将空的socket进行包装--初始化、分配内存、维护红黑树等基础操作,epoll_wait是另外一个核心,ep_poll源码中,首先将当前线程加入eventpoll 等待队列中,接着放弃CPU的使用权schedule_hrtimeout_range-睡觉,一旦事件到来/超时,就从wq等待队列中移除__remove_wait_queue,最后将rdlist(就绪双向循环链表) 拷贝到用户态--__put_user
点赞 回复 分享
发布于 2021-05-06 23:03
select,在32位机器上最大只能监听1024个socket(这个值可以修改,需要重新编译内核且不再保证内核的稳定性)。内核中使用了一种很巧的方式:位图,每一位表示一个socket(0 1 2 表示stdin,stdout,stderr,从3开始分配,总共1024位),1024位等价于32个unsigned long的数组(每个unsigned long占32位)--__kernel_fd_set. (1)fd_set[1024]数组:内核态和用户态共用它,它在用户态建立-->拷贝到内核,内核处理后覆盖fd_set-->重新拷贝到用户态-->用户通过轮询得到哪些事件发生了。这里有一个最重要的信息,这个数组内核和用户共用,会导致两个最严重的问题,1. 内核处理后,会改变原始值,需要重新拷贝到用户态!(以mask方式返回)2. 既然内核已经把原始的数组改变了,必定导致在while的开始必须要用临时的fd_set暂存--这也是每次循环的开始都要拷贝一个临时的tempFdSet (2)效率低的原因:有3次拷贝+select返回后的遍历fd_set,一共4次。第一次:临时fd_set暂存;第二次,select调用开始从用户态拷贝到内核态;第二次,select调用后期从内核态拷贝到用户态;第四次,select只返回有几个事件ok了,但是并没有说哪几个事件发生了,需要再次遍历fd_set(这其实是个mask,有1的地方事件就发生了--FD_ISSET) (3)应用场景:适合小数量的socket,因为判断一个unsigned long是否为0就可以判断32个socket,若否直接处理接下来的32个。这里一直再用socket举例,其实I/O复用可以监控的不仅是socket,还可以是stdin、stdout、strerr、字符设备、管道等,而socket是最重要的--服务器。这样想来,1024是合理的,如果不考虑socket,我们连接键盘、鼠标、字符设备、管道等是不会有1000多个的,这样select效率是极高的
点赞 回复 分享
发布于 2021-05-06 22:05
首先,提及下I/O复用的地位,可以说select,poll.epoll是基础中最深的一块,因为你需要C/C++基础、socket编程基础、Linux线程、进程、通信基础。总之,select,poll.epoll各有不同应用场景,不能一概而论,它们都是单进程实现多进程。例如,一个进程就可以处理10、100、1W个socket等。其中epoll使用10w、100w的socket连接中,如Nginx,Redis底层都是epoll
点赞 回复 分享
发布于 2021-05-06 21:20
操作系统我们就总结完了,接下来会总结计算机网络方面的面试题
点赞 回复 分享
发布于 2021-03-09 21:37

相关推荐

劝退式:感觉有人回才是不正常的
点赞 评论 收藏
分享
被普调的六边形战士很高大:项目经历貌似和专业或者求职方向没大关系?
点赞 评论 收藏
分享
好兄弟们,不愁找不到工作了,东哥还有10万骑手HC待发 还有五险一金,话不多说我要去投递了
婉拒腾讯保洁岗:都让让,鄙人骑电动车贼溜,ssp骑手offer应该有我一份吧?在坐的谁赞同,谁反对?查看图片
点赞 评论 收藏
分享
评论
7
3
分享

创作者周榜

更多
牛客网
牛客企业服务