当前位置:首页>Linux>Linux内核KFIFO:一个高效的环形缓冲区

Linux内核KFIFO:一个高效的环形缓冲区

  • 2026-09-06 09:00:21
Linux内核KFIFO:一个高效的环形缓冲区

Linux驱动入门课程:

https://www.bilibili.com/cheese/play/ss635583370

Linux驱动进阶课程:

https://www.bilibili.com/cheese/play/ss254727759

01

kfifo简介

kfifo 是 Linux 内核中提供的一个轻量级、高效的先进先出(FIFO)无锁环形缓冲区实现(定义在 <linux/kfifo.h> 中)。它专门用于单生产者-单消费者场景下的数据传输与异步解耦。

02

核心数据结构

struct __kfifo {    unsigned int    in;      // 写入位置索引(累加值)    unsigned int    out;     // 读取位置索引(累加值)    unsigned int    mask;    // 缓冲区大小减 1(即 size - 1,用于位与运算取模)    unsigned int    esize;   // 每个元素的大小    void            *data;   // 动态分配的缓冲区指针};
  • 已用空间: in - out

  • 剩余空间: capacity - (in - out)

  • 写入索引定位: in & mask

  • 读取索引定位: out & mask

03

常用API接口

下面都是一些宏

// include/linux/kfifo.h//编译时静态定义并初始化一个 kfifoDEFINE_KFIFO(fifo, type, size)kfifo_init(fifo, buffer, size)//运行时动态分配内存并初始化kfifo_alloc(fifo, size, gfp_mask)//释放动态分配的kfifo内存kfifo_free(fifo)//放数据进入fifokfifo_put(fifo, val)kfifo_in(fifo, buf, n)//从fifo中获取数据kfifo_get(fifo, val)kfifo_out(fifo, buf, n)//返回fifo中可存放的数量(剩余空间)kfifo_avail(fifo)//判断 fifo 是否已满kfifo_is_full(fifo)//判断 fifo 是否为空kfifo_is_empty(fifo)

04

源码分析

Linux:5.10

  • __kfifo_init

int __kfifo_init(struct __kfifo *fifo, void *buffer,       unsigned int size, size_t esize){  size /= esize;  //大小要是2的幂次方  if (!is_power_of_2(size))    size = rounddown_pow_of_two(size);  fifo->in = 0;  fifo->out = 0;  fifo->esize = esize;   //每个元素大小  fifo->data = buffer;  //buffer指针  if (size < 2) {    fifo->mask = 0;    return -EINVAL;  }  fifo->mask = size - 1;  return 0;}

静态初始化 fifo,需要自己提供 buffer 来初始化 kfifo 结构体。如果大小不是 2 的幂次方,它会自动降为 2 的幂次方的大小。

  • __kfifo_alloc

int __kfifo_alloc(struct __kfifo *fifo, unsigned int size,    size_t esize, gfp_t gfp_mask){  /*   * round up to the next power of 2, since our 'let the indices   * wrap' technique works only in this case.   */  size = roundup_pow_of_two(size);  fifo->in = 0;  fifo->out = 0;  fifo->esize = esize;  if (size < 2) {    fifo->data = NULL;    fifo->mask = 0;    return -EINVAL;  }  //动态分配buffer空间  fifo->data = kmalloc_array(esize, size, gfp_mask);  if (!fifo->data) {    fifo->mask = 0;    return -ENOMEM;  }  fifo->mask = size - 1;  return 0;}

动态初始化 fifo,动态分配 buffer 空间,这是它和__kfifo_init 的区别。

  • __kfifo_in

unsigned int __kfifo_in(struct __kfifo *fifo,    const void *buf, unsigned int len){  unsigned int l;  //获取未使用的buffer大小  l = kfifo_unused(fifo);  //最多写入未使用的大小  if (len > l)    len = l;  //写入  kfifo_copy_in(fifo, buf, len, fifo->in);  //递增写入索引  fifo->in += len;  return len;}

写入数据到 fifo 的 buffer 中

staticvoidkfifo_copy_in(struct __kfifo *fifo, constvoid *src,    unsigned int len, unsigned int off){  unsigned int size = fifo->mask + 1;  unsigned int esize = fifo->esize;  unsigned int l;  //写入索引的位置  off &= fifo->mask;  if (esize != 1) {    off *= esize;    size *= esize;    len *= esize;  }  l = min(len, size - off);  //写到末尾位置  memcpy(fifo->data + off, src, l);  //到开头位置写入(末尾不够写)  memcpy(fifo->data, src + l, len - l);  /*   * make sure that the data in the fifo is up to date before   * incrementing the fifo->in index counter   */  smp_wmb();}
  1. 先确定要写入的索引位置

  2. 判断索引位置到末尾这些空间够不够存放要写入的数据

  3. 写入 (末尾不够写,就回到开头继续写)

  • __kfifo_out

unsigned int __kfifo_out(struct __kfifo *fifo,    void *buf, unsigned int len){  //读取数据  len = __kfifo_out_peek(fifo, buf, len);  //递增读取索引  fifo->out += len;  return len;}

从 fifo 中读取数据出来

unsigned int __kfifo_out_peek(struct __kfifo *fifo,    void *buf, unsigned int len){  unsigned int l;  //未读取的数据大小  l = fifo->in - fifo->out;  //可读取最大长度  if (len > l)    len = l;  kfifo_copy_out(fifo, buf, len, fifo->out);  return len;}
staticvoidkfifo_copy_out(struct __kfifo *fifo, void *dst,    unsigned int len, unsigned int off){  unsigned int size = fifo->mask + 1;  unsigned int esize = fifo->esize;  unsigned int l;  //读取索引位置  off &= fifo->mask;  if (esize != 1) {    off *= esize;    size *= esize;    len *= esize;  }  l = min(len, size - off);  //末尾读取  memcpy(dst, fifo->data + off, l);  //回到开头读取  memcpy(dst + l, fifo->data, len - l);  /*   * make sure that the data is copied before   * incrementing the fifo->out index counter   */  smp_wmb();}
  1. 先确定要读取的索引位置

  2. 读取(先从末尾开始读取,如果还有需要读的,就回到开头读)

05

图解

(1)空的 kfifo
(2)写入数据
(3)读出数据
(4)写入的 buffer 长度超出 in 到末尾长度时,则将剩下的移到头部去
END
往期推荐:

一文读懂 Linux 内核 Initcall 机制

写Linux驱动,必须掌握的Runtime PM机制

Linux通知链

驱动之间如何共享寄存器?Syscon 给出了答案

延迟函数别乱用,Linux 内核开发者必须掌握的延迟“潜规则”

最新文章

随机文章