1.说明
所谓虚拟串口设备意为这个串口是虚拟的,不能用来实现与下位机的串口收发。但是他可以将从用户那儿收到的数据,原封不动的回传给用户。相当于一个回环。
这一功能的实现主要是在驱动中实现一个FIFO。驱动接收到用户数据后,先将之放入FIFO,当用户需要数据(读取数据)时,驱动从FIFO中把数据读出,回传给用户。
2 . FIFO相关函数
FIFO在内核空间已经实现好了,只需要调用对应的宏 和函数即可:
DEFINE_KFIFO(fifo, type, size)
kfifo_from_user(fifo, from, len, copied)
kfifo_to_user(fifo, to, len, copied)
DEFINE_KFIFO
是用于初始化一个FIFO,名字叫fifo,FIFO里的数据类型为type,FIFO大小为size。
kfifo_from_user
用于从用户(应用层)获取数据并放入FIFO,fifo为初始化好的FIFO,from为数据,len为数据长度,copied用于返回实际拷贝进FIFO的数据的长度。
kfifo_to_user
用于从从FIFO中取出数据,to为返回的数据指针,fifo,len和copied与上面类似。
3.贴代码
代码有详细注释。
//包含必要的头文件
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/kfifo.h>
//定义主设备号 和从设备号
#define VSER_MAJOR 256
#define VSER_MINOR 0
#define DEV_CNT 1
#define VSER_DEV_NAME "vser"
MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("Mr.Zhong <1642131567@qq.com>");
MODULE_DESCRIPTION("A simple Module");
MODULE_ALIAS("Visualport");
/*init a FIFO*/
DEFINE_KFIFO(vsfifo,char,32);
/*declare a device*/
static struct cdev vsdev;
//下面两个接口在这里不是必须的,所以没必要实现,直接返回即可。
static int vser_open(struct inode* inode,struct file* filp)
{
return 0;
}
static int vser_relase(struct inode* inode,struct file* filp)
{
return 0;
}
//读数据,指的是用户从内核空间读取数据
static ssize_t vser_read(struct file *filp,char __user *buf,size_t count,loff_t* pos)
{
unsigned int copied = 0;
kfifo_to_user(&vsfifo,buf,count,&copied);//将FIFO里的数据传送到用户空间
return copied;
}
//从用户空间传数据到内核空间的FIFO,从用户角度就是write
static ssize_t vser_write(struct file* filp,const char __user *buf,size_t count,loff_t *pos)
{
unsigned int copied = 0;
/*from user space copy data to kernel space*/
kfifo_from_user(&vsfifo,buf,count,&copied);
return copied;
}
/*relize some file operations */
//这里是将实现的接口和内核关联起来,以后在应用层使用open read write 函数就会对应执行内核空间的//vser_open、read 、write 。
static struct file_operations vser_ops = {
.owner = THIS_MODULE,
.open = vser_open,
.release = vser_relase,
.read = vser_read,
.write = vser_write,
};
/*register device*/
static int __init vser_init(void)
{
int ret;
dev_t dev;
dev = MKDEV(VSER_MAJOR,VSER_MINOR); //由主从设备号得到最终设备号
ret = register_chrdev_region(dev,DEV_CNT,VSER_DEV_NAME); //注册设备
if(ret)
goto reg_err; //在内核编程时习惯用跳转指令到统一的地方去处理错误。
cdev_init(&vsdev,&vser_ops); //初始化设备,也就是将上面操作文件的函数实现和设备关联起来
vsdev.owner = THIS_MODULE;//相当于c++中的this指针,以后就可以通过这个指针访问到特定的设备了
ret = cdev_add(&vsdev,dev,DEV_CNT);//添加设备
if(ret)
goto add_err;
printk(KERN_EMERG "init KFIFO driver\n");
return 0;
add_err:
unregister_chrdev_region(dev,DEV_CNT);//出错,卸载设备
reg_err:
return ret;
}
static void __exit vser_exit(void)
{
dev_t dev;
dev = MKDEV(VSER_MAJOR,VSER_MINOR);
cdev_del(&vsdev);
unregister_chrdev_region(dev,DEV_CNT);卸载设备
printk(KERN_EMERG "exit KFIFO \n");
}
/*alias*/
module_init(vser_init); //这里是感受内核这是模块初始化的入口
module_exit(vser_exit);
4.测试
将编写好的代码编译得到驱动模块(拓展名*.ko)。
然后加载模块到内核:
sudo insmod virtualport.ko
然后查看设备是否加载:
cat /proc/devices | grep vser
可以看到设备已经加载到内核,主设备号为256。
然后在/dev
目录下新建节点,并发送数据,操作流程如下:
$ mknod /dev/vser0 c 256 0
$ echo "hello driver" > vser0
$ cat vser0
hello drives
从上面返回的数据可知,驱动实现了用户向FIFO发送数据,并且在用户需要的时候将数据原封不动的返回给用户。