Ring Buffer解析
Ring Buffer,即环形缓冲区,也称作环形队列,是一种用于表示一个固定尺寸、头尾相连的缓冲区的数据结构,适合缓存数据流。
结构与原理
环形缓冲区通常使用一个固定大小的数组作为底层结构,并通过两个指针来管理数据的读写位置,即读指针和写指针。当写指针到达缓冲区末尾时,会自动回绕到缓冲区的起始位置,继续写入数据,从而形成一个环形的存储结构。读指针的移动方式与之类似,当读取完缓冲区末尾的数据后,会回到起始位置继续读取。
特点
- 高效空间复用:环形缓冲区写满后继续回绕,无需频繁申请和释放内存,具有高效空间复用性,且顺序访问性能好,适合嵌入式与内核编程。
- 数据顺序性:它是一种先进先出(FIFO)的数据结构,当一个数据元素被读取出后,其余数据元素不需要移动其存储位置。
- 固定容量:缓冲区的容量一般固定,适合于事先明确了缓冲区最大容量的情形,若缓冲区大小需要经常调整,用链表实现更为合适。
状态判断
判断环形缓冲区的空满状态是一个关键问题。常见的方法有以下两种:
- 镜像指示位方法:规定读写指针的地址空间为 0 至 2n-1,其中 n 为缓冲区长度,低半部分对应常规逻辑地址空间,高半部分为镜像逻辑地址空间。用一位表示写指针或读指针是否进入镜像存储区,若读写指针的值相同且指示位相同,说明缓冲区为空;若二者指示位不同,说明缓冲区为满。
- 位运算方法:如果缓冲区长度是 2 的幂,可以省略镜像指示位。若读写指针的值相等,则缓冲区为空;若读写指针相差 n,则缓冲区为满,这可以用条件表达式(写指针 == (读指针异或缓冲区长度))来判断。
接收缓存区
- 结构与原理:网卡接收缓存区通常采用环形缓冲区(Ring Buffer)结构。驱动程序会先在内存中创建一个接收描述符环(rx descriptor ring),并将其总线地址写入网卡寄存器。每个描述符对应一个用于存储数据包的缓冲区(如 sk_buff),通过流式 DMA 映射将缓冲区的总线地址保存到描述符中。当网卡接收到数据包时,会先将数据写入自身的 FIFO 缓冲区,然后 DMA 通过 PCI 总线将 FIFO 中的数据包复制到描述符指向的数据缓存区。复制完成后,网卡会向 CPU 发起硬件中断,通知有新的数据包到达,CPU 则执行中断处理函数,将数据从缓存区取出并交给上层网络栈处理。
- 作用:当设备的 CPU 繁忙时,端口不能立即将收到的报文交给 CPU 处理,接收缓存区可以将数据暂时存储起来,避免数据丢失。
发送缓存区
- 工作流程:应用程序通过系统调用将数据放入 socket 的发送缓冲区,网络协议栈从发送缓冲区中取出数据,添加各种头部信息后,触发软件中断通知网卡驱动程序。网卡驱动程序再将数据写到发送缓存区(如 ringbuffer),由网卡负责从缓存区中读取数据并发送到网络上。发送成功后,网卡会触发硬件中断,释放相关的缓存资源。
- 作用:当网络拥塞时,端口不能立即发送数据,发送缓存区可以将数据暂时存储,防止数据丢失。
相关配置与调优
- 缓存区大小调整:可以通过相关工具(如 ethtool)调整接收和发送缓存区的大小。一般来说,缓存区队列越大,丢包的可能性越小,但数据延迟会增加。
- 多队列处理:在多核 CPU 环境下,网卡内部可能有多个 Ring Buffer,支持 Receive Side Scaling(RSS)或 multiqueue 功能,NIC 会根据一个 Hash 函数对收到的数据包进行分发,将数据分配给不同的 CPU 核心处理,提高数据的并行处理能力。
参考链接:ring buffer,一篇文章讲透它? - +liang - 博客园