浅谈 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。

2.2 早期 NIO
socket 对应的 fd 是非阻塞的
单位 CPU 只用一个线程,就一颗 CPU 只跑一个线程,没有 CPU 切换损耗,在用户空间轮询遍历所有的文件描述符。从遍历到取数据都是自己完成,所以是同步非阻塞 IO。
问题是如果有 1000 个 fd,代表用户进程轮询调用 1000 次 kernel,
用户空间查询一次文件描述符就得调用一次系统调用,让后内核态用户态来回切换成本很大。

2.3 多路复用 NIO
内核向前发展,轮询放到内核里,内核增加一个系统调用 select 函数,统一把一千个 fd 传给 select 函数,内核操作 select 函数,fd 准备好后返回给线程,拿着返回的文件描述符找出 ready 的再去调 read。如果 1000 个 fd 只有 1 个有数据,以前要调 1000 次,现在只调 1 次,相对前面在系统调用上更加精准。还是同步非阻塞的,只是减少了用户态和内核态的切换。
注意 Linux 只能实现 NIO,不能实现 AIO。问题:用户态和内核态沟通的 fd 相关数据要来回拷贝。

2.4 epoll
内核通过 mmap 实现共享空间,用户态和内核态有一个空间是共享的,文件描述符 fd 存在共享空间实现用户态和内核态共享。epoll 里面有三个调用,用户空间先 epoll_create 准备一个共享空间 mmap,里面维护一个红黑树,内核态将连接注册进红黑树,epoll_ctl 写入。当有数据准备好了,调用 epoll_wait 中断阻塞,取链表 fd,再单独调用 read。
mmap 应用:kafka 实现数据通过 socket 存到服务器文件上的过程也是 mmap。
epoll 应用:redis 和 nginx 的 worker 进程等等。

想学习 C++工程化、高性能及分布式、深入浅出。性能调优、TCP,协程,Nginx 源码分析 Nginx,ZeroMQ,MySQL,Redis,MongoDB,ZK,Linux 内核,P2P,K8S,Docker,TCP/IP,协程,DPDK 学习资料视频获取点击:C++架构师学习资料
C++后台开发视频链接:C/C++Linux服务器开发高级架构师/Linux后台架构师

评论