在上一章节中实现了一个基本的字符设备驱动框架,这一章节在此基础上,利用Linux内核提供的kfifo实现一个全局FIFO。
kfifo常用宏定义介绍
KFIFO是内核提供的一个先进先出队列,它采用了无锁并发机制(在一个线程写一个线程读时无需考虑并发问题),另外它的读写都是以元素大小为单位。
定义一个FIFO
通过宏定义 DECLARE_KFIFO(fifo, type, size) 可以定义一个FIFO,宏定义的具体内容如下:
#define DECLARE_KFIFO(fifo, type, size) STRUCT_KFIFO(type, size) fifo
#define STRUCT_KFIFO(type, size) \
struct __STRUCT_KFIFO(type, size, 0, type)
#define __STRUCT_KFIFO(type, size, recsize, ptrtype) \
{ \
__STRUCT_KFIFO_COMMON(type, recsize, ptrtype); \
type buf[((size < 2) || (size & (size - 1))) ? -1 : size]; \
}
#define __STRUCT_KFIFO_COMMON(datatype, recsize, ptrtype) \
union { \
struct __kfifo kfifo; \
datatype *type; \
const datatype *const_type; \
char (*rectype)[recsize]; \
ptrtype *ptr; \
ptrtype const *ptr_const; \
}
/* 对DECLARE_KFIFO依次进行展开后如下,通过展开的宏可以发现程序定义的每一个fifo都属于不同的结构体类型,
* 所以内核中对fifo的操作都是通过宏定义来实现(这种操作有点类似C++中的模板)
* */
#define DECLARE_KFIFO(fifo, datatype, size) \
struct \
{ \
union { \
struct __kfifo kfifo; \
datatype *type; \
const datatype *const_type; \
char (*rectype)[0]; \
ptrtype *ptr; \
ptrtype const *ptr_const; \
} \
datatype buf[((size < 2) || (size & (size - 1))) ? -1 : size]; \
} fifo
定义并初始化FIFO
通过 DEFINE_KFIFO(fifo, type, size) 可以定义并初始化一个FIFO
定义一个指针FIFO
通过宏定义 DECLARE_KFIFO_PTR(fifo, type) 可以定义一个指针FIFO,与 DECLARE_KFIFO(fifo, type, size) 宏相比它没有分配buf空间,在使用时必须先通过 kfifo_init(fifo, buffer, size) 或 kfifo_alloc(fifo, size, gfp_mask) 进行初始化
struct kfifo对象
struct kfifo是一个元素大小为一个byte的指针FIFO,相应的宏定义为 struct kfifo __STRUCT_KFIFO_PTR(unsigned char, 0, void);
初始化FIFO
//初始化DECLARE_KFIFO定义的FIFO
DEFINE_KFIFO(fifo, type, size)
//采用指定内存初始化FIFO
kfifo_init(fifo, buffer, size)
//采用kmalloc_array分配的内存初始化FIFO
kfifo_alloc(fifo, size, gfp_mask)
//执行kfifo_alloc的反操作
kfifo_free(fifo)
获取FIFO长度和状态
//返回有效数据长度
kfifo_len(fifo)
//返回剩余空间长度
kfifo_avail(fifo)
//fifo是否空
kfifo_is_empty(fifo)
//fifo是否满
kfifo_is_full(fifo)
读写FIFO
//将内核空间的数据写入FIFO(注意:n的单位是元素个数)
kfifo_in(fifo, buf, n)
//将FIFO的输出出队到内核空间的buf
kfifo_out(fifo, buf, n)
//将用户空间的数据写入FIFO(注意:len和copied的单位是byte)
kfifo_from_user(fifo, from, len, copied)
//将FIFO的输出出队到用户空间的buf
kfifo_to_user(fifo, to, len, copied)
代码实现
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/kfifo.h>
#define USING_ALLOC_DEV_ID //采用动态分配设备号
#define MULITI_CDEV_MAJOR 500 //主设备号
#define MULITI_CDEV_MINOR 0 //第一个设备的次设备号
#define MULITI_CDEV_CNT 2 //设备数量
#define MULITI_CDEV_NAME "gfifo" //主设备号名称
//kfifo句柄
static struct kfifo gfifo[MULITI_CDEV_CNT];
//设备号
static dev_t dev_id;
//cdev对象
static struct cdev gfifo_cdev;
//打开设备
static int gfifo_open(struct inode *inode, struct file *filep)
{
uint32_t minor;
//提取次设备号
minor = MINOR(inode->i_rdev);
if((minor - MULITI_CDEV_MINOR) > (sizeof(gfifo) / sizeof(gfifo[0])))
return EINVAL;
//这里依据次设备号来区分不同的设备,绑定不同的资源到private_data中
filep->private_data = &gfifo[minor - MULITI_CDEV_MINOR];
return 0;
}
//释放设备,应用层执行最后一次close时执行
static int gfifo_release(struct inode *inode, struct file *filep)
{
return 0;
}
//读设备
static ssize_t gfifo_read(struct file *filep, char __user *buf, size_t len, loff_t *pos)
{
unsigned int copied;
struct kfifo *fifo;
//从private_data中获取绑定的参数
fifo = (struct kfifo*)filep->private_data;
//检查FIFO释放为空
if(kfifo_is_empty(fifo))
{
printk("fifo is empty\r\n");
return -EINVAL;
}
/* 将FIFO中的数据读取到应用层
* fifo FIFO句柄
* to 指向用户空间提供的buffer
* len 需要读去的长度,或者是buffer的大小,byte
* copied 返回成功读取的长度,byte
* 成功返回0
**/
if(kfifo_to_user(fifo, buf, len, &copied) != 0)
{
printk("Bad address\r\n");
return -EFAULT;
}
return copied;
}
//写设备
static ssize_t gfifo_write(struct file *filep, const char __user *buf, size_t len, loff_t *pos)
{
unsigned int copied;
struct kfifo *fifo;
//从private_data中获取绑定的参数
fifo = (struct kfifo*)filep->private_data;
//检查FIFO是否为满
if(kfifo_is_full(fifo))
{
printk("fifo is full\r\n");
return -EINVAL;
}
/* 将应用层的数据写入FIFO
* fifo FIFO句柄
* from 指向用户空间提供的buffer
* len 需要读去的长度,或者是buffer的大小,byte
* copied 返回成功写入的长度,byte
* 成功返回0
**/
if(kfifo_from_user(fifo, buf, len, &copied) != 0)
{
printk("Bad address\r\n");
return -EFAULT;
}
return copied;
}
//操作函数集合
static struct file_operations gfifo_ops = {
.owner = THIS_MODULE,
.write = gfifo_write,
.read = gfifo_read,
.open = gfifo_open,
.release = gfifo_release,
};
static int __init gfifo_init(void)
{
int err = 0;
int i;
//动态初始化FIFO
for(i=0; i<MULITI_CDEV_CNT; i++)
{
/* 采用kmalloc_array分配的内存初始化FIFO
* fifo FIFO句柄
* size FIFO大小,需要对其到2^n,否则会以2^n向下对其
* gfp_mask 内存分配掩码,无特殊情况一般为GFP_KERNEL
**/
err = kfifo_alloc(&gfifo[i], 32, GFP_KERNEL);
if(err != 0)
{
for(i=0; i<MULITI_CDEV_CNT; i++)
kfifo_free(&gfifo[i]);
printk("kfifo alloc failed\r\n");
return err;
}
}
#ifndef USING_ALLOC_DEV_ID
//合成设备号
dev_id = MKDEV(MULITI_CDEV_MAJOR, MULITI_CDEV_MINOR);
//静态注册字符设备号
err = register_chrdev_region(dev_id, MULITI_CDEV_CNT, MULITI_CDEV_NAME);
if(err != 0)
{
for(i = 0; i < MULITI_CDEV_CNT; i++)
kfifo_free(&gfifo[i]);
printk("register chrdev failed\r\n");
return err;
}
#else
//根据次设备号起始值动态注册字符设备号
err = alloc_chrdev_region(&dev_id, MULITI_CDEV_MINOR, MULITI_CDEV_CNT, MULITI_CDEV_NAME);
if(err != 0)
{
for(i=0; i<MULITI_CDEV_CNT; i++)
kfifo_free(&gfifo[i]);
printk("alloc chrdev failed\r\n");
return err;
}
#endif
//显示主设备号和次设备号
for(i=0; i<MULITI_CDEV_CNT; i++)
{
printk("gfifo %d ", i);
printk("MAJOR %d ", MAJOR(dev_id + i));
printk("MINOR %d ", MINOR(dev_id + i));
printk("\r\n");
}
//初始化CDEV对象
cdev_init(&gfifo_cdev, &gfifo_ops);
gfifo_cdev.owner = THIS_MODULE;
//添加cdev对象到内核
err = cdev_add(&gfifo_cdev, dev_id, MULITI_CDEV_CNT);
if(err != 0)
{
unregister_chrdev_region(dev_id, MULITI_CDEV_CNT);
for(i = 0; i < MULITI_CDEV_CNT; i++)
kfifo_free(&gfifo[i]);
printk("add cdev failed\r\n");
return err;
}
return 0;
}
static void __exit gfifo_exit(void)
{
int i;
//从系统删除CDEV对象
cdev_del(&gfifo_cdev);
//注销字符设备号
unregister_chrdev_region(dev_id, MULITI_CDEV_CNT);
//释放分配的FIFO
for(i = 0; i < MULITI_CDEV_CNT; i++)
kfifo_free(&gfifo[i]);
}
module_init(gfifo_init);
module_exit(gfifo_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("csdn");
MODULE_DESCRIPTION("global fifo");
MODULE_ALIAS("global_fifo");
实验
-
从这里下载代码,并进行编译,然后拷贝到目标板根文件系统的root目录中
-
执行insmod gfifo.ko加载驱动,然后在执行mknod /dev/gfifo0 c 241 0创建设备文件(同样还可以执行mknod /dev/gfifo1 c 241 1为主设备号为241次设备号为1的gfifo创建设备文件)
-
通过命令echo “123456789” >> /dev/gfifo0向gfifo0写入数据(echo 命令会调用write函数向gfifo0写入数据)
-
通过命令cat /dev/gfifo0可以读取echo写入到gfifo中的数据(这里报“error: Invalid argument”原因是因为FIFO为空后在执行read操作时返回-EINVAL造成的)