# 【NO.211】【网络】【操作系统】详解select、poll、epoll



![img](https://pic4.zhimg.com/80/v2-164fd025311cab5f387a438474e77c3f_720w.webp)

看到这张图你能够完全理解吗。

## **1.多路复用的意思：**

多路复用的意思，就是在任何一路 I/O 有“事件”发生的情况下，通知应用程序去处理相应的 I/O 事件，这样我们的程序就变成了“多面手”，在同一时刻仿佛可以处理多个 I/O 事件。

## **2.应用条件：**

- 标准输入文件描述符准备好可以读。
- 监听套接字准备好，新的连接已经建立成功。
- 已连接套接字准备好可以写。
- 如果一个 I/O 事件等待超过了 10 秒，发生了超时事件。

## **3.中断的概念：**

在cpu运行着进程A时，进程B发起一个中断请求IRQ，中断处理程序进行处理请求，将进程A挂起，然后运行进程B。

**中断的过程：**将当前运行的进程的运行信息保存到该进程的描述符。根据进程描述符的内核态堆栈指针切换到内核态。根据来到的IRQ在中断表里面查找所属的中断处理程序。运行该中断处理程序。（看似是进程执行的中断，但和进程没什么关系）

## **4.硬中断的概念：**

对于计算机硬件的，一般来说是与当时CPU请求是异步的（没关系的），比如网关来了一个报文，

## **5.软中断的理解：**

CPU在执行一段代码段是 遇到问题，进行中断，由用户态切换到内核态。

## **6.每次中断都有其对应的中断处理程序。**

## **7.每个进程都在用户态和内核态拥有一个堆栈。**

## **8.为什么要有两种状态（内核态、用户态）**

内核态和用户态的”权限不同“。**防止每个程序都分配过多资源。**用户态的进程能够访问的资源受到了极大的控制，而运行在内核态的进程可以“为所欲为”。

一个进程可以运行在用户态也可以运行在内核态，那它们之间肯定存在用户态和内核态切换的过程。

打一个比方：C库接口malloc申请动态内存，malloc的实现内部最终还是会调用brk（）或者mmap（）系统调用来分配内存。

## **9.从用户态到内核态切换可以通过三种方式：**

1. 系统调用，这个上面已经讲解过了，在我公众号之前的文章也有讲解过。其实系统调用本身就是中断，但是**软件中断**，跟硬中断不同。
2. 异常：如果当前进程运行在用户态，如果这个时候发生了异常事件，就会触发切换。例如：缺页异常。
3. 外设中断：当外设完成用户的请求时，会向CPU发送中断信号。

## **10.socket的小demo**

![img](https://pic1.zhimg.com/80/v2-1908a6bb8b382e4cfd6779565addc598_720w.webp)

![img](https://pic4.zhimg.com/80/v2-db0bfbf01fa386987908941c54abb4cb_720w.webp)

## **11.socket底层逻辑图**

![img](https://pic2.zhimg.com/80/v2-5e46360d0e1a8d2095ef77cd2bc1c689_720w.webp)

## **12.阻塞模式下的情况**

![img](https://pic4.zhimg.com/80/v2-9a94669ac2e140bc22be8444fb3674eb_720w.webp)

写

（1）要传输的数据大于 输出缓冲区的大小，需要分开传输，还没传输的挂起。

（2）输出缓冲区TCP正在输出别的数据，需要TCP释放输出缓冲区才能写。

读

（1）如果输入缓存区没有数据，则系统调用的方法挂起。

（2）缓冲区数据太多，每次read只能读一部分，需要一直慢慢读直到读完

## **13.非阻塞模式下**

写

（1）如果向输出缓冲区写的数据太多了 ，分批发送，但是会马上告诉你发送了多少。

（2）如果空间为0，会马上告诉你缓冲区满了，由上次程序决定怎么做。（而阻塞则会一直挂起，等待缓冲区释放）

## **14.进程进行用户态到内核态的切换过程：**

　　1.从当前进程的描述符中提取其内核栈的ss0及esp0信息。

　　2.使用ss0和esp0指向的内核栈将当前进程的cs,eip,eflags,ss,esp信息保存起来，这个过程也完成了由用户栈到内核栈的切换过程，同时保存了z暂停执行的程序的下一条指令。

　　3.将先前由中断向量检索得到的中断处理程序的cs,eip信息装入相应的寄存器，开始执行中断处理程序，这时就转到了内核态的程序执行了。

## **15.Linux的一切都是文件fd。**

## 16.select函数

```
#include <sys/select.h>   
    int select(int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset,struct timeval *timeout);
```

1）maxfdpl 　　最大有效位。（因为fd_set是以bitmap来保存数据的 linux默认1024位bitmap 最大有效位之后不需要检查）

2）fd_set *readset 　　可读文件描述符集

3)fd_set *writeset　　 可写文件描述符集

4)fd_set *exceptset　　 异常文件描述符集

5)timeout　　超时时间

Linux select函数的宏

```
#include <sys/select.h>   
int FD_ZERO(int fd, fd_set *fdset);  //将描述符集全部置0
int FD_CLR(int fd, fd_set *fdset);   //
int FD_SET(int fd, fd_set *fd_set);   //将某个位置位
int FD_ISSET(int fd, fd_set *fdset); //检查某个位是否被置位
```

![img](https://pic1.zhimg.com/80/v2-53cc1a75c89db88c54379aa8f3bc3ae4_720w.webp)

select函数缺点：

1）固定文件描述符1024位的bitmap，请求过大则无法描述，有大小限制

2）fd_set不可重用，每次都有重新置位

3）有用户态到内核态切换的开销

4）每次函数返回都需要O（n）的时间进行遍历

## 17.poll函数

我们将select函数的bitmap结构的fd_set变为 自己实现的pollfd

![img](https://pic4.zhimg.com/80/v2-f9364ebfef17a8664d0b5bcd76a63b1b_720w.webp)

使用链表来进行文件描述，不存在大小限制问题。

![img](https://pic3.zhimg.com/80/v2-b0e20d3e303ea72cf1b0295e420bf3ca_720w.webp)

## 18.epoll函数

![img](https://pic2.zhimg.com/80/v2-70b8611c49d96f83969b614fb11785d5_720w.webp)

## **19.同步调用、异步调用：**

同步：B来向A拿取数据，B一直等到A正确交付数据。

异步：B来向A拿取数据，B见A还在准备则返回 进行自己的事情。

## **20.阻塞、非阻塞**

A向B拿数据，C也向A要数据，C要等待 则是阻塞。

A向B拿数据，C也向A要数据，A同时服务B C 则是非阻塞。

同步与异步

- 同步： 同步就是发起一个调用后，被调用者未处理完请求之前，调用不返回。
- 异步： 异步就是发起一个调用后，立刻得到被调用者的回应表示已接收到请求，但是被调用者并没有返回结果，此时我们可以处理其他的请求，被调用者通常依靠事件，回调等机制来通知调用者其返回结果。

同步和异步的区别最大在于异步的话调用者不需要等待处理结果，被调用者会通过回调等机制来通知调用者其返回结果。

**阻塞和非阻塞**

- 阻塞： 阻塞就是发起一个请求，调用者一直等待请求结果返回，也就是当前线程会被挂起，无法从事其他任务，只有当条件就绪才能继续。
- 非阻塞： 非阻塞就是发起一个请求，调用者不用一直等着结果返回，可以先去干其他事情。

## **21.网络通信例子**

服务端

![img](https://pic1.zhimg.com/80/v2-7fd58166430da4022b1205bd65b04df0_720w.webp)

**客户端**

![img](https://pic2.zhimg.com/80/v2-f185a2f5a4567b52bbaf2e3d7bfdc12d_720w.webp)

![img](https://pic2.zhimg.com/80/v2-9f5bd298c21e0957e8ba6b71f9cba311_720w.webp)

我们可以看到服务端有2个方法 accept和read都在阻塞，这就是我们的BIO（同步阻塞I/O模式）

优化：我们可以使用多线程、线程池等来优化，关键是有很多不活跃的线程时，占用资源过多，上下文切换过多。

## **22.使用NIO优化通信例子（服务器端 客户端无所谓）**

![img](https://pic1.zhimg.com/80/v2-5403f6300311199babbc468b5a6755a4_720w.webp)

![img](https://pic2.zhimg.com/80/v2-cf6fb4e93394f16b2178f9d2a2e87a71_720w.webp)

![img](https://pic4.zhimg.com/80/v2-540655fd43e1f44b1e77c6e253da83ab_720w.webp)

## **23.windows的NIO是select linux的NIO底层是eqpoll。**

Redis的NIO是epoll，只能在Linux环境运行，但是有win版本，是大神修改了redis代码。

原文链接：https://zhuanlan.zhihu.com/p/367468443

作者：[Hu先生的Linux](https://www.zhihu.com/people/huhu520-10)