2.2基于字符设备框架的全局fifo

上一章节中实现了一个基本的字符设备驱动框架,这一章节在此基础上,利用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");

实验

  1. 这里下载代码,并进行编译,然后拷贝到目标板根文件系统的root目录中

  2. 执行insmod gfifo.ko加载驱动,然后在执行mknod /dev/gfifo0 c 241 0创建设备文件(同样还可以执行mknod /dev/gfifo1 c 241 1为主设备号为241次设备号为1的gfifo创建设备文件)
    在这里插入图片描述

  3. 通过命令echo “123456789” >> /dev/gfifo0向gfifo0写入数据(echo 命令会调用write函数向gfifo0写入数据)

  4. 通过命令cat /dev/gfifo0可以读取echo写入到gfifo中的数据(这里报“error: Invalid argument”原因是因为FIFO为空后在执行read操作时返回-EINVAL造成的)
    在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值