Linux kernel kfifo分析【转】

简介: 转自:https://zohead.com/archives/linux-kernel-kfifo/ 本文同步自(如浏览不正常请点击跳转):https://zohead.com/archives/linux-kernel-kfifo/ kfifo 是 Linux kernel 中的一个通用队列实现,对于 kernel 中常见的 FIFO 队列应用还是很有用的,本文主要简单介绍分析下 Linux kernel kfifo。

转自:https://zohead.com/archives/linux-kernel-kfifo/

本文同步自(如浏览不正常请点击跳转):https://zohead.com/archives/linux-kernel-kfifo/

kfifo 是 Linux kernel 中的一个通用队列实现,对于 kernel 中常见的 FIFO 队列应用还是很有用的,本文主要简单介绍分析下 Linux kernel kfifo。实际上 ChinaUnix 上有个 kfifo 的分析文章,但已经比较老(基于 Linux 2.6.10),而且我现在用的 2.6.34 版本 kernel 中 kfifo 实现有很多改动,故自己简单写下,ChinaUnix 上的 kfifo 介绍帖子在这里:

http://bbs.chinaunix.net/thread-1994832-1-1.html

kfifo 定义在 include/linux/kfifo.h 头文件中,我们经常使用的就是 kfifo 结构,看看它的定义:

1
2
3
4
5
6
struct kfifo {
     unsigned char *buffer;  /* the buffer holding the data */
     unsigned int size;  /* the size of the allocated buffer */
     unsigned int in;    /* data is added at offset (in % size) */
     unsigned int out;   /* data is extracted from off. (out % size) */
};

kfifo 也像其它队列那样提供了两个主要操作:入队列(in) 和 出队列(out),对应于上面结构中的 in 和 out 两个偏移量,in 偏移量为下次入队列的位置,out 为下次出队列的位置,很容易也能想到 out 值必须小于等于 in 值,当 out 值等于 in 值时表示队列为空,kfifo 中 buffer 为队列的空间,size 为空间大小,必须为 2 的 k 次幂值(原因在下面说明)。当然如果 in 值等于队列长度了,就表示队列已经满了。

先看看 kfifo 最简单的一些操作实现,在 kernel/kfifo.c 文件中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
static void _kfifo_init( struct kfifo *fifo, void *buffer,
         unsigned int size)
{
     fifo->buffer = buffer;
     fifo->size = size;
 
     kfifo_reset(fifo);
}
 
/**
  * kfifo_init - initialize a FIFO using a preallocated buffer
  * @fifo: the fifo to assign the buffer
  * @buffer: the preallocated buffer to be used.
  * @size: the size of the internal buffer, this has to be a power of 2.
  *
  */
void kfifo_init( struct kfifo *fifo, void *buffer, unsigned int size)
{
     /* size must be a power of 2 */
     BUG_ON(!is_power_of_2(size));
 
     _kfifo_init(fifo, buffer, size);
}
EXPORT_SYMBOL(kfifo_init);
 
/**
  * kfifo_alloc - allocates a new FIFO internal buffer
  * @fifo: the fifo to assign then new buffer
  * @size: the size of the buffer to be allocated, this have to be a power of 2.
  * @gfp_mask: get_free_pages mask, passed to kmalloc()
  *
  * This function dynamically allocates a new fifo internal buffer
  *
  * The size will be rounded-up to a power of 2.
  * The buffer will be release with kfifo_free().
  * Return 0 if no error, otherwise the an error code
  */
int kfifo_alloc( struct kfifo *fifo, unsigned int size, gfp_t gfp_mask)
{
     unsigned char *buffer;
 
     /*
      * round up to the next power of 2, since our 'let the indices
      * wrap' technique works only in this case.
      */
     if (!is_power_of_2(size)) {
         BUG_ON(size > 0x80000000);
         size = roundup_pow_of_two(size);
     }
 
     buffer = kmalloc(size, gfp_mask);
     if (!buffer) {
         _kfifo_init(fifo, NULL, 0);
         return -ENOMEM;
     }
 
     _kfifo_init(fifo, buffer, size);
 
     return 0;
}
EXPORT_SYMBOL(kfifo_alloc);
 
/**
  * kfifo_free - frees the FIFO internal buffer
  * @fifo: the fifo to be freed.
  */
void kfifo_free( struct kfifo *fifo)
{
     kfree(fifo->buffer);
     _kfifo_init(fifo, NULL, 0);
}
EXPORT_SYMBOL(kfifo_free);

调用 kfifo_alloc 可以自动分配空间并初始化,你也可以调用 kfifo_init 函数使用自己的空间来初始化队列,可以看到这两个函数中都用 is_power_of_2 做了检查队列空间的操作。kfifo_free 释放队列,它会调用 _kfifo_init 函数(参数为 NULL 和 0 清空队列),调用 kfifo_reset 可以重置队列(将 in 和 out 都设为 0)。你也可以用 DECLARE_KFIFO 和 INIT_KFIFO 静态定义一个 kfifo 队列,尽管这不太会被用到。

调用 kfifo_in 函数将数据加入队列,kfifo_out 将数据从队列中取出并从队列中删除(增加 out 值),Linux 还提供了 kfifo_out_peek 函数从队列中取数据但并不删除(不增加 out 值)。kfifo_in 中会先调用 __kfifo_in_data 将输入加入队列,然后调用 __kfifo_add_in 增加 in 的值,kfifo_out 相反则调用 __kfifo_out_data 和 __kfifo_add_out 函数取出数据并删除。

kfifo 中同时提供了 kfifo_from_user 函数用户将用户空间的数据加入到队列中,它会先调用 __kfifo_from_user_data 将用户空间的数据加入队列,再调用 __kfifo_add_in 增加 in 的值。看看 __kfifo_from_user_data 的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
static inline int __kfifo_from_user_data( struct kfifo *fifo,
      const void __user *from, unsigned int len, unsigned int off,
      unsigned *lenout)
{
     unsigned int l;
     int ret;
 
     /*
      * Ensure that we sample the fifo->out index -before- we
      * start putting bytes into the kfifo.
      */
 
     smp_mb();
 
     off = __kfifo_off(fifo, fifo->in + off);
 
     /* first put the data starting from fifo->in to buffer end */
     l = min(len, fifo->size - off);
     ret = copy_from_user(fifo->buffer + off, from, l);
     if (unlikely(ret)) {
         *lenout = ret;
         return -EFAULT;
     }
     *lenout = l;
 
     /* then put the rest (if any) at the beginning of the buffer */
     ret = copy_from_user(fifo->buffer, from + l, len - l);
     *lenout += ret ? ret : len - l;
     return ret ? -EFAULT : 0;
}

可以看到 __kfifo_from_user_data 中是直接调用 copy_from_user 将用户空间的数据拷贝到 kfifo 队列的空间中。相应的也有 kfifo_to_user 函数将队列中的数据取出到用户空间的地址,他就调用 copy_to_user 将队列中数据拷贝到用户空间。

需要注意的是 __kfifo_from_user_data 中用到的 __kfifo_off 函数:

1
2
3
4
static inline unsigned int __kfifo_off( struct kfifo *fifo, unsigned int off)
{
     return off & (fifo->size - 1);
}

__kfifo_off 是根据指定的偏移量得到索引值,由这里也可以看出为什么队列的大小为什么必须是 2 的 k 次幂值,否则无法得到正确的值。而且从代码中可以看到 __kfifo_from_user_data、__kfifo_in_n、__kfifo_in_rec 等函数中都用到了 __kfifo_off 函数指定加入队列时的偏移量。

另外从 include/linux/kfifo.h 中你也可以看到新的 kfifo 实现中默认 EXPORT 了非常多的 API 函数给 kernel 开发者使用。

以上为个人分析结果,有任何问题欢迎指正哦 ^_^

【作者】 张昺华
【新浪微博】 张昺华--sky
【twitter】 @sky2030_
【facebook】 张昺华 zhangbinghua
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利.
目录
相关文章
|
1月前
|
Linux Android开发
嵌入式linux中Framebuffer 驱动程序框架分析
嵌入式linux中Framebuffer 驱动程序框架分析
26 0
|
1月前
|
Linux C语言 SoC
嵌入式linux总线设备驱动模型分析
嵌入式linux总线设备驱动模型分析
32 1
|
1月前
|
Linux
嵌入式linux系统设备树实例分析
嵌入式linux系统设备树实例分析
34 0
|
28天前
|
监控 Shell Linux
【Shell 命令集合 网络通讯 】Linux 分析串口的状态 statserial命令 使用指南
【Shell 命令集合 网络通讯 】Linux 分析串口的状态 statserial命令 使用指南
32 0
|
16天前
|
Prometheus 监控 数据可视化
linux分析方法与技巧
【4月更文挑战第3天】在Linux环境中,进行日志分析和系统性能分析的关键方法包括:使用`cat`, `less`, `tail`查看和过滤日志,`logrotate`管理日志文件,`rsyslog`或`syslog-ng`聚合日志,以及通过`top`, `mpstat`, `pidstat`, `free`, `iostat`, `netstat`, `strace`, `sar`, `dstat`等工具监控CPU、内存、磁盘I/O和网络。对于高级分析,可利用Brendan Gregg的性能工具,以及Grafana、Prometheus等可视化工具。
16 2
linux分析方法与技巧
|
24天前
|
监控 Linux Shell
Linux 进程问题调查探秘:分析和排查频繁创建进程问题
Linux 进程问题调查探秘:分析和排查频繁创建进程问题
39 0
|
24天前
|
消息中间件 存储 网络协议
Linux IPC 进程间通讯方式的深入对比与分析和权衡
Linux IPC 进程间通讯方式的深入对比与分析和权衡
65 0
|
24天前
|
存储 监控 Linux
Linux 使用getrusage系统调用获取cpu信息:一个C++实例分析
Linux 使用getrusage系统调用获取cpu信息:一个C++实例分析
48 0
|
24天前
|
存储 算法 Linux
【Linux 系统标准 进程资源】Linux 创建一个最基本的进程所需的资源分析,以及线程资源与之的差异
【Linux 系统标准 进程资源】Linux 创建一个最基本的进程所需的资源分析,以及线程资源与之的差异
25 0
|
1月前
|
安全 Linux 开发者
分析Linux桌面操作系统的迅速增长及其未来前景
最近技术圈新闻“层出不穷”,尤其是在最近,Linux桌面操作系统的市场份额迅速增长,Linux桌面操作系统的市场份额近期呈现火速增长的趋势,这一数据虽然看似不太引人注目,但实际上却具有重要的意义,达到了历史新高。了解Linux的开发者想必都知道,历经30年的努力,Linux系统的份额才在不久前达到了3%,而如今仅用了八个月的时间就新增了1%,显示出开源操作系统正迅速升温。尽管Windows和macOS仍然主导着桌面操作系统市场,但前者的份额波动较小,后者则略有下滑。虽然Linux的表现出色,但要想取得主导地位还有一段距离,有些开发者认为这是因为缺乏一个适用于所有Linux发行版的标准化桌面界面
24 1
分析Linux桌面操作系统的迅速增长及其未来前景