Linux内核的是如何实现环形缓冲区机制的?

Posted 一口Linux

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux内核的是如何实现环形缓冲区机制的?相关的知识,希望对你有一定的参考价值。

Why is printk() so complicated?

https://linuxreviews.org/Why_is_printk()_so_complicated%3F

特点

  • 它将允许在没有死锁风险的任何情况下存储和读取消息

 它是一个多生产者,单个或多个消费者组成的循环缓冲区。

Linux内核的核心部分——环形缓冲区

 

struct kfifo {  
         unsigned char *buffer;  
         unsigned int size;  
         unsigned int in;  
         unsigned int out;  
         spinlock_t *lock;  
}; 
其中buffer指向存放数据的缓冲区,
size是缓冲区的大小,
in是写指针下标,
out是读指针下标,
lock是加到struct kfifo上的自旋锁(上面说不加锁不是这里的锁),
防止多个进程并发访问此数据结构。
当in==out时,说明缓冲区为空;
当(in-out)==size时,说明缓冲区已满。

 


  •  循环缓冲区日志记录由一个固定大小的内存缓冲区构成,进程使用这个内存缓冲区进行日志记录。

 

当然现在我们面对的大多是多线程的协同工作,

对于日志记录来说,倘若采取传统的加锁机制访问我们的存储文件,这些线程将在获得和释放锁上花费了大部分的时间,

所以采取循环缓冲区会是一个不错的办法。

通过使得每个线程将数据写入到它自己的内存块,就可以完全避免同步问题 【不懂这个】


- 生产者不能被缓慢的消费者所阻挡。取而代之的是,生产者只是简单地按照他们想要的速度写作,而消费者则尽其所能跟上

 

在通常情况下,环形缓冲区的读用户仅仅会影响读指针,而写用户仅仅会影响写指针。

如果仅仅有一个读用户和一个写用户,那么不需要添加互斥保护机制就可以保证数据的正确性。

 

如果有多个读写用户访问环形缓冲区,那么必须添加互斥保护机制来确保多个用户互斥访问环形缓冲区。

# 资料 https://www.cnblogs.com/dodng/p/4367791.html

 

.为多线程数据通信提供了一种高效的机制。

在最典型的生产者消费者模型中,如果引入环形队列,那么生成者只需要生成“东西”然后放到环形队列中即可,而消费者只需要从环形队列里取“东西”并且消费即可,没有任何锁或者等待,巧妙的高效实现了多线程数据通信。

.环形队列的工作场景

一般应用于需要高效且频繁进行多线程通信传递数据的场景,例如:linux捕包、发包等等,(linux系统中对PACKET_RX_RING和PACKET_TX_RING的支持实质就是内核实现的一种环形队列)

https://hedzr.com/algorithm/golang/ringbuf-01-intro/

https://segmentfault.com/a/1190000016329997

环形队列是一种 FIFO 数据结构。
它和普通 FIFO 队列数据结构的不同就在于队尾连接着队首,
当入列元素位于队尾时,该元素将被回绕并放在队首的位置,
从而完成有界性限定。而普通的 FIFO 队列总是增长队尾以入列新元素,
并不限制队列的有效长度。

环形队列具有先天优势,无需数据搬移
https://github.com/smallnest/ringbuffer

## Linux 网络协议栈收消息过程-Ring Buffer


https://ylgrgyq.github.io/2017/07/23/linux-receive-packet-1/
Linux kernel里面从来就不缺少简洁,优雅和高效的代码


. 2的次幂

  • 判断一个数是不是2的次幂
    kfifo要保证其缓存空间的大小为2的次幂,如果不是则向上取整为2的次幂。其对于2的次幂的判断方式也是很巧妙的。如果一个整数n是2的次幂,则二进制模式必然是1000...,而n-1的二进制模式则是0111...,也就是说n和n-1的每个二进制位都不相同,例如:8(1000)和7(0111);n不是2的次幂,则n和n-1的二进制必然有相同的位都为1的情况,例如:7(0111)和6(0110)。这样就可以根据 n & (n-1)的结果来判断整数n是不是2的次幂,实现如下
Linux内核中kfifo
实现技巧,主要集中在放入数据的put
方法和取数据的get方法。代码如下:

为什么kfifo实现的单生产/单消费模式的共享队列是不需要加锁同步的呢?
https://blog.csdn.net/chen19870707/article/details/39899743

 

https://blog.csdn.net/chen19870707/article/details/39899743

# 资料

https://unix.stackexchange.com/questions/198178/what-are-the-concepts-of-kernel-ring-buffer-user-level-log-level

https://zhuanlan.zhihu.com/p/118158992

https://www.jianshu.com/p/e6162bc984c8

想看能不能完整梳理一下收消息过程。从 NIC 收数据开始,到触发软中断,交付数据包到 IP 层再经由路由机制到 TCP 层,最终交付用户进程

Linux内核的核心部分——环形缓冲区

 

课堂练习如果你对 TCP 协议的结构不太熟悉,可以使用 tcpdump 命令截取一个 TCP 的包,看看里面的结构。

UDP 是面向数据报的,一个一个地发送。

TCP 是可以提供流量控制和拥塞控制的,既防止对端被压垮,也防止网络被压垮

在内核中,为每个 Socket 维护两个队列。

一个是已经建立了连接的队列,这时候连接三次握手已经完毕,处于 established 状态;

一个是还没有完全建立连接的队列,这个时候三次握手还没完成,处于 syn_rcvd 的状态。

struct sk_buff 是存储网络包的重要的数据结构,在应用层数据包叫 data,

在 TCP 层我们称为 segment,

在 IP 层我们叫 packet,

在数据链路层称为 frame。

在 struct sk_buff,首先是一个链表,将 struct sk_buff 结构串起来。

Linux内核的核心部分——环形缓冲区

 

网卡作为一个硬件,接收到网络包,应该怎么通知操作系统,这个网络包到达了呢?

没错,我们可以触发一个中断

 

如果一个网络包到来,触发了硬件中断,就会调用 ixgb_intr,这里面会调用 __napi_schedule。

我们知道,软中断 NET_RX_SOFTIRQ 对应的中断处理函数是 net_rx_action。

这两个工作都在 poll 中完成,上面说过 poll 是个 driver 实现的函数,所以每个 driver 实现可能都不相同。但 poll 的工作基本是一致的就是:

  1. 从 Ring Buffer 中将收到的 sk_buff 读取出来
  2. 对 sk_buff 做一些基本检查,可能会涉及到将几个 sk_buff 合并因为可能同一个 Frame 被分散放在多个 sk_buff 中
  3. 将 sk_buff 交付上层网络栈处理
  4. 清理 sk_buff,清理 Ring Buffer 上的 Descriptor 将其指向新分配的 sk_buff 并将状态设置为 ready
  5. 更新一些统计数据,比如收到了多少 packet,一共多少字节等

Linux内核的核心部分——环形缓冲区

 

以上是关于Linux内核的是如何实现环形缓冲区机制的?的主要内容,如果未能解决你的问题,请参考以下文章

linux内核之Kfifo环形队列

linux管道pipe详解

关于有名管道和无名管道

linux学习之缓存机制

Linux常用命令——dmesg

Linux系统清除缓存