浅谈 NIO 和 Epoll 实现原理
1 前置知识
1.1 socket 是什么?
就是传输控制层 tcp 连接通信。
1.2 fd 是什么?fd 既 file descriptor-文件描述符。Java 中用对象代表输入输出流等...在 Linux 系统中不是面向对象的,是一切皆文件的,拿文件来代表输入输出流。其实是一个数值,例如 1,2,3,4...0 是标准输入,1 是标准输出,2 是错误输出...
相关视频讲解:
任何进程在操作系统中都有自己 IO 对应的文件描述符,参考如下:
2 从 BIO 到 epoll
2.1 BIO
计算机有内核,客户端和内核连接产生 fd,计算机的进程或线程会读取相应的 fd。因为 socket 在这个时期是 blocking 的,线程读取 socket 产生的文件描述符时,如果数据包没到,读取命令不能返回,会被阻塞。导致会有更多的线程被抛出,单位 CPU 在某一时间片只能处理一个线程,出现数据包到了的线程等着数据包没到的线程的情况,造成 CPU 资源浪费,CPU 切换线程成本巨大。
例如早期 Tomcat7.0 版默认就是 BIO。
![](https://static001.geekbang.org/infoq/3c/3c642c53de93d74e68e827d3914a88ba.jpeg?x-oss-process=image/resize,p_80/auto-orient,1)
2.2 早期 NIO
socket 对应的 fd 是非阻塞的
单位 CPU 只用一个线程,就一颗 CPU 只跑一个线程,没有 CPU 切换损耗,在用户空间轮询遍历所有的文件描述符。从遍历到取数据都是自己完成,所以是同步非阻塞 IO。
问题是如果有 1000 个 fd,代表用户进程轮询调用 1000 次 kernel,
用户空间查询一次文件描述符就得调用一次系统调用,让后内核态用户态来回切换成本很大。
![](https://static001.geekbang.org/infoq/dc/dcc5bee58f40627c36c3ad439524d447.jpeg?x-oss-process=image/resize,p_80/auto-orient,1)
2.3 多路复用 NIO
内核向前发展,轮询放到内核里,内核增加一个系统调用 select 函数,统一把一千个 fd 传给 select 函数,内核操作 select 函数,fd 准备好后返回给线程,拿着返回的文件描述符找出 ready 的再去调 read。如果 1000 个 fd 只有 1 个有数据,以前要调 1000 次,现在只调 1 次,相对前面在系统调用上更加精准。还是同步非阻塞的,只是减少了用户态和内核态的切换。
注意 Linux 只能实现 NIO,不能实现 AIO。问题:用户态和内核态沟通的 fd 相关数据要来回拷贝。
![](https://static001.geekbang.org/infoq/8f/8f1858dc80a3a5d1344ffee8e6e7b25b.jpeg?x-oss-process=image/resize,p_80/auto-orient,1)
2.4 epoll
内核通过 mmap 实现共享空间,用户态和内核态有一个空间是共享的,文件描述符 fd 存在共享空间实现用户态和内核态共享。epoll 里面有三个调用,用户空间先 epoll_create 准备一个共享空间 mmap,里面维护一个红黑树,内核态将连接注册进红黑树,epoll_ctl 写入。当有数据准备好了,调用 epoll_wait 中断阻塞,取链表 fd,再单独调用 read。
mmap 应用:kafka 实现数据通过 socket 存到服务器文件上的过程也是 mmap。
epoll 应用:redis 和 nginx 的 worker 进程等等。
![](https://static001.geekbang.org/infoq/90/90f9cab481113a3184f603e619ee0890.jpeg?x-oss-process=image/resize,p_80/auto-orient,1)
想学习 C++工程化、高性能及分布式、深入浅出。性能调优、TCP,协程,Nginx 源码分析 Nginx,ZeroMQ,MySQL,Redis,MongoDB,ZK,Linux 内核,P2P,K8S,Docker,TCP/IP,协程,DPDK 学习资料视频获取点击:C++架构师学习资料
C++后台开发视频链接:C/C++Linux服务器开发高级架构师/Linux后台架构师
![](https://static001.geekbang.org/infoq/8f/8fb9e15dc0799c6c1b82a4ae4b4ba5f2.png)
评论