内核实验(七):使用内核KFIFO环形缓冲区机制

简介: 本文通过一个内核模块实验,演示了如何在Linux内核中使用KFIFO环形缓冲区机制,包括定义KFIFO、编写驱动程序以及在Qemu虚拟机中进行编译、部署和测试,展示了KFIFO在无需额外加锁的情况下如何安全地在读者和写者线程间进行数据传输。

一、篇头

继续使用qemu调试内核的实验。

二、系列文章

略……

三、实验环境

  • 编译服务器+NFS:ubuntu 22.04
  • Qemu 虚拟机:Linux version 5.15.102 + Buysbox 1.3.36 + ARM_32bit

Qemu 启动命令:qemu-system-arm -nographic -M vexpress-a9 -m 1024M -kernel arch/arm/boot/zImage -initrd …/busybox/rootfs.ext4.img.gz -dtb arch/arm/boot/dts/vexpress-v2p-ca9.dtb

四、源码

4.1 KFIFO介绍

Linux内核实现了一个称为KFIFO的环形缓冲区的机制,它可以在一个读者线程和一个写者线程并发执行的场景下,无须使用额外的加锁来保证环形缓冲区的数据安全。同时由此机制来管理了缓冲区的使用情况,省去许多是,特别方便。

KFIFO提供的接口函数定义在include/linux/kfifo.h文件中。

4.1.1 声明及定义:DEFINE_KFIFO(fifo, type, size)
/**
 * DEFINE_KFIFO - macro to define and initialize a fifo
 * @fifo: name of the declared fifo datatype
 * @type: type of the fifo elements
 * @size: the number of elements in the fifo, this must be a power of 2
 *
 * Note: the macro can be used for global and local fifo data type variables.
 */
#define DEFINE_KFIFO(fifo, type, size) \
    DECLARE_KFIFO(fifo, type, size) = \
    (typeof(fifo)) { \
        { \
            { \
            .in    = 0, \
            .out    = 0, \
            .mask    = __is_kfifo_ptr(&(fifo)) ? \
                  0 : \
                  ARRAY_SIZE((fifo).buf) - 1, \
            .esize    = sizeof(*(fifo).buf), \
            .data    = __is_kfifo_ptr(&(fifo)) ? \
                NULL : \
                (fifo).buf, \
            } \
        } \
    }
4.1.2 用户写:kfifo_from_user(fifo, from, len, copied)
/**
 * kfifo_from_user - puts some data from user space into the fifo
 * @fifo: address of the fifo to be used
 * @from: pointer to the data to be added
 * @len: the length of the data to be added
 * @copied: pointer to output variable to store the number of copied bytes
 *
 * This macro copies at most @len bytes from the @from into the
 * fifo, depending of the available space and returns -EFAULT/0.
 *
 * Note that with only one concurrent reader and one concurrent
 * writer, you don't need extra locking to use these macro.
 */
#define    kfifo_from_user(fifo, from, len, copied) \
__kfifo_uint_must_check_helper( \
({ \
    typeof((fifo) + 1) __tmp = (fifo); \
    const void __user *__from = (from); \
    unsigned int __len = (len); \
    unsigned int *__copied = (copied); \
    const size_t __recsize = sizeof(*__tmp->rectype); \
    struct __kfifo *__kfifo = &__tmp->kfifo; \
    (__recsize) ? \
    __kfifo_from_user_r(__kfifo, __from, __len,  __copied, __recsize) : \
    __kfifo_from_user(__kfifo, __from, __len, __copied); \
}) \
)
4.1.3 用户读:kfifo_to_user(fifo, to, len, copied)
/**
 * kfifo_to_user - copies data from the fifo into user space
 * @fifo: address of the fifo to be used
 * @to: where the data must be copied
 * @len: the size of the destination buffer
 * @copied: pointer to output variable to store the number of copied bytes
 *
 * This macro copies at most @len bytes from the fifo into the
 * @to buffer and returns -EFAULT/0.
 *
 * Note that with only one concurrent reader and one concurrent
 * writer, you don't need extra locking to use these macro.
 */
#define    kfifo_to_user(fifo, to, len, copied) \
__kfifo_int_must_check_helper( \
({ \
    typeof((fifo) + 1) __tmp = (fifo); \
    void __user *__to = (to); \
    unsigned int __len = (len); \
    unsigned int *__copied = (copied); \
    const size_t __recsize = sizeof(*__tmp->rectype); \
    struct __kfifo *__kfifo = &__tmp->kfifo; \
    (__recsize) ? \
    __kfifo_to_user_r(__kfifo, __to, __len, __copied, __recsize) : \
    __kfifo_to_user(__kfifo, __to, __len, __copied); \
}) \
)

4.2 驱动源码

  • 文件名:linux-stable\my_kmodules\test_5.c
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/init.h>
#include <linux/miscdevice.h>
#include <linux/kfifo.h>


#define MY_DEV_NAME "my_dev"

DEFINE_KFIFO(my_kfifo, char, 128);
static char kbuf[128];


static int test_5_open(struct inode *inode, struct file *file)
{
   
   
    int major = MAJOR(inode->i_rdev);
    int minor = MINOR(inode->i_rdev);

    pr_info("%s: major=%d, minor=%d\n", __func__, major, minor);
    return 0;
}

static int test_5_release(struct inode *inode, struct file *file)
{
   
   
    pr_info("%s \n", __func__);
    return 0;
}

static ssize_t test_5_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
   
   
    int ret;
    unsigned int copied_count=0;
    pr_info("%s \n", __func__);

    ret = kfifo_to_user(&my_kfifo, buf,  count, &copied_count);
    if(ret != 0) {
   
   
        return -EIO;
    }
    pr_info("%s :%s, count=%d, copied_count=%d\n", __func__, my_kfifo.buf,  count, copied_count);

    return copied_count;
}

static ssize_t test_5_write(struct file *file, const char __user *buf, size_t count, loff_t *f_pos)
{
   
   
    int ret;
    unsigned int copied_count=0;
    pr_info("%s \n", __func__);

    ret = kfifo_from_user(&my_kfifo, buf, count, &copied_count);
    if(ret != 0) {
   
   
        return -EIO;
    }
    pr_info("%s , my_kfifo.buf=%s\n", __func__, my_kfifo.buf);
    //注意:使用用户空间的buf,需要先copy_from_user
    ret = copy_from_user(kbuf, buf, copied_count);
    pr_info("%s :%s, count=%d, copied_count=%d, ret=%d\n", __func__, kbuf,  count, copied_count, ret);

    return copied_count;
}

static const struct file_operations test_fops = {
   
   
    .owner = THIS_MODULE,
    .open = test_5_open,
    .release = test_5_release,
    .read = test_5_read,
    .write = test_5_write
};

static struct miscdevice test_5_misc_device ={
   
   

    .minor = MISC_DYNAMIC_MINOR,
    .name = MY_DEV_NAME,
    .fops = &test_fops,
};


static int __init test_5_init(void)
{
   
   
    int ret;
    pr_info("test_5_init\n");

    ret = misc_register(&test_5_misc_device);
    if (ret != 0 ) {
   
   
        pr_err("failed to misc_register");
        return ret;
    }

    pr_err("Minor number = %d\n", test_5_misc_device.minor);
    return 0;
}

static void __exit test_5_exit(void)
{
   
   
    pr_info("test_5_exit\n");
    misc_deregister(&test_5_misc_device);
}

module_init(test_5_init);
module_exit(test_5_exit);

MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("szhou <66176468@qq.com>");
MODULE_DESCRIPTION("test_5, 使用misc、KFIFO开发设备驱动");

4.3 APP源码

本次实验,直接使用 echo /cat 负责设备的写/读。

4.4 Makefile

  • 文件名:linux-stable\my_kmodules\Makefile
KDIR := /home/szhou/works/qemu_linux/linux-stable

obj-m := test_1.o test_2.o test_3.o test_4.o  test_5.o
all :
    $(MAKE) -C $(KDIR) M=$(PWD) modules
clean:
    $(MAKE) -C $(KDIR) SUBDIRS=$(PWD) clean
    rm -f *.ko

五、编译及部署

1)执行驱动KO编译
szhou@bc01:~/works/qemu_linux/linux-stable/my_kmodules$ make 
make -C /home/szhou/works/qemu_linux/linux-stable M=/home/szhou/works/qemu_linux/linux-stable/my_kmodules modules
make[1]: Entering directory '/home/szhou/works/qemu_linux/linux-stable'
  CC [M]  /home/szhou/works/qemu_linux/linux-stable/my_kmodules/test_5.o
  MODPOST /home/szhou/works/qemu_linux/linux-stable/my_kmodules/Module.symvers
  LD [M]  /home/szhou/works/qemu_linux/linux-stable/my_kmodules/test_5.ko
make[1]: Leaving directory '/home/szhou/works/qemu_linux/linux-stable'
szhou@bc01:~/works/qemu_linux/linux-stable/my_kmodules$ cp test_5.ko ~/works/nfs_share/
szhou@bc01:~/works/qemu_linux/linux-stable/my_kmodules$ 

(2)将KO和APP存放到NFS共享目录
szhou@bc01:~/works/qemu_linux/linux-stable/my_kmodules$ cp test_5.ko ~/works/nfs_share/

六、运行及测试

1)启动之前编译组建的QEMU虚拟机
----------------------------------------
Welcome to szhou's tiny Linux
----------------------------------------

Please press Enter to activate this console.2)挂载NFS共享目录
~ #  mount -t nfs -o nolock 192.168.3.67:/home/szhou/works/nfs_share /mnt

(3) 加载ko
~ # cd /mnt/
/mnt # insmod test_5.ko 
test_5: loading out-of-tree module taints kernel.
test_5_init
Minor number = 125
/mnt # 
/mnt # mdev  -s
/mnt # 

(4)写入字符串 
/mnt # echo "hellow kfifo" >  /dev/my_dev 
test_5_open: major=10, minor=125
test_5_write 
test_5_write , my_kfifo.buf=hellow kfifo

test_5_write :hellow kfifo
, count=13, copied_count=13, ret=0
test_5_release 
/mnt # 

(5)读取字符串 
/mnt # cat /dev/my_dev 
test_5_open: major=10, minor=125
test_5_read 
test_5_read :hellow kfifo
, count=4096, copied_count=13
hellow kfifo
test_5_read 
test_5_read :hellow kfifo
, count=4096, copied_count=0
test_5_release 
/mnt #

操作画面如下图:

在这里插入图片描述

相关文章
|
6月前
|
缓存 C++ 索引
用户态网络缓冲区设计
用户态网络缓冲区设计
47 1
|
7月前
|
算法 Linux 调度
深度解析:Linux内核的进程调度机制
【5月更文挑战第29天】 在现代操作系统中,尤其是类Unix系统如Linux中,进程调度机制是保证多任务高效运行的核心。本文将深入探讨Linux操作系统内核的进程调度器——负责管理CPU资源分配的关键组件。我们会详细分析其调度策略、调度器的演进及其在多核处理器环境下的表现。通过剖析进程调度器的工作原理和设计哲学,旨在为读者提供一个清晰的视角来理解这一复杂的系统功能。
261 0
|
7月前
|
算法 Linux 调度
内核:linux进程原理
内核:linux进程原理
66 0
|
存储 安全 调度
DPDK环形缓冲区(Ring)详解及性能优化
DPDK环形缓冲区(Ring)详解及性能优化
|
存储 索引 Windows
驱动开发:内核物理内存寻址读写
在某些时候我们需要读写的进程可能存在虚拟内存保护机制,在该机制下用户的`CR3`以及`MDL`读写将直接失效,从而导致无法读取到正确的数据,本章我们将继续研究如何实现物理级别的寻址读写。首先,驱动中的物理页读写是指在驱动中直接读写物理内存页(而不是虚拟内存页)。这种方式的优点是它能够更快地访问内存,因为它避免了虚拟内存管理的开销,通过直接读写物理内存,驱动程序可以绕过虚拟内存的保护机制,获得对系统中内存的更高级别的访问权限。
7079 1
|
存储
驱动开发:内核读写内存多级偏移
让我们继续在`《内核读写内存浮点数》`的基础之上做一个简单的延申,如何实现多级偏移读写,其实很简单,读写函数无需改变,只是在读写之前提前做好计算工作,以此来得到一个内存偏移值,并通过调用内存写入原函数实现写出数据的目的。以读取偏移内存为例,如下代码同样来源于本人的`LyMemory`读写驱动项目,其中核心函数为`WIN10_ReadDeviationIntMemory()`该函数的主要作用是通过用户传入的基地址与偏移值,动态计算出当前的动态地址。
132 0
|
存储 缓存 索引
【操作系统】第四章:非连续内存分配(Part2:页表)
【操作系统】第四章:非连续内存分配(Part2:页表)
324 0
|
Java Linux
JVM内存划分、Linux用户态、内核态简介(下)
JVM内存划分、Linux用户态、内核态简介(下)
306 0
JVM内存划分、Linux用户态、内核态简介(下)
|
Java Linux
JVM内存划分、Linux用户态、内核态简介(上)
JVM内存划分、Linux用户态、内核态简介(上)
208 0
JVM内存划分、Linux用户态、内核态简介(上)