目录
功能点
1、支持基于字符设备文件进行发送和接收数据;
2、支持阻塞式接收数据,没有数据的情况下,等待数据就绪;
具体实现
#include "vEth.h" #include <linux/module.h> #include <linux/fs.h> #include <linux/uaccess.h> #include <linux/init.h> #include <linux/miscdevice.h> #include <linux/kfifo.h> #include <linux/wait.h> #include <linux/sched.h> #include <linux/slab.h> #define ETH_NAME "vEth0" /* 设备名 */ struct veth_device_st { const char *name; struct device *dev; struct miscdevice *miscdev; wait_queue_head_t read_queue; wait_queue_head_t write_queue; }; static struct veth_device_st *veth_device; DEFINE_KFIFO(my_kfifo, char, 1024*1024*1024); /* * @description : 打开设备 * @param – inode : 传递给驱动的 inode * @param - filp : 设备文件,file 结构体有个叫做 private_data 的成员变量 * 一般app在 open 的时候将 private_data 指向设备结构体。 * @return : 0 成功;其他 失败 */ static int eth_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); file->private_data = veth_device; pr_info("%s: file->private_data=0x%p \n", __func__, file->private_data); return 0; } /* * @description : 从设备读取数据 * @param - filp : 要打开的设备文件(文件描述符) * @param - buf : 返回给用户空间的数据缓冲区 * @param - cnt : 要读取的数据长度 * @param - offt : 相对于文件首地址的偏移 * @return : 读取的字节数,如果为负值,表示读取失败 */ static ssize_t eth_read(struct file *file, char __user *buf, size_t count, loff_t *offt) { int ret; unsigned int copied_count = 0; struct veth_device_st *veth_device = file->private_data; /* * (1)add for O_NONBLOCK reading */ if(kfifo_is_empty(&my_kfifo)){ if(file->f_flags & O_NONBLOCK) { return -EAGAIN; } // (2)若用户发起的是非BLOCK读,则因为空间为空,调度进程到等等队列,进程睡眠 ret = wait_event_interruptible(veth_device->read_queue, !kfifo_is_empty(&my_kfifo)); if (ret) { return ret; } } ret = kfifo_to_user(&my_kfifo, buf, count, &copied_count); if(ret != 0) { return -EFAULT; } // (3)如果FIFI有空余,唤醒等待写入的其他进程 if (!kfifo_is_full(&my_kfifo)) { wake_up_interruptible(&veth_device->write_queue); } return copied_count; } /* * @description : 向设备写数据 * @param - filp : 设备文件,表示打开的文件描述符 * @param - buf : 要写给设备写入的数据 * @param - cnt : 要写入的数据长度 * @param - offt : 相对于文件首地址的偏移 * @return : 写入的字节数,如果为负值,表示写入失败 */ static ssize_t eth_write(struct file *file, const char __user *buf, size_t count, loff_t *offt) { int ret; unsigned int copied_count=0; struct veth_device_st *data = file->private_data; /* * (1)add for O_NONBLOCK writing */ if (kfifo_is_full(&my_kfifo)) { if (file->f_flags & O_NONBLOCK){ return -EAGAIN; } // (2)若用户发起的是非BLOCK写,则因为空间已满,调度进程到等等队列,进程睡眠 ret = wait_event_interruptible(data->write_queue, !kfifo_is_empty(&my_kfifo)); if (ret) { return ret; } } ret = kfifo_from_user(&my_kfifo, buf, count, &copied_count); if(ret != 0) { return -EFAULT; } // (3)如果数据非空,唤醒等待读取的其他进程 if (!kfifo_is_empty(&my_kfifo)) { wake_up_interruptible(&data->read_queue); } return copied_count; } /* * @description : 关闭/释放设备 * @param - filp : 要关闭的设备文件(文件描述符) * @return : 0 成功;其他 失败 */ static int eth_release(struct inode *inode, struct file *file) { return 0; } /* * 设备操作函数结构体 */ static struct file_operations eth_fops = { .owner = THIS_MODULE, .open = eth_open, .read = eth_read, .write = eth_write, .release = eth_release, }; static struct miscdevice misc_device = { .minor = MISC_DYNAMIC_MINOR, .name = ETH_NAME, .fops = ð_fops, }; /* * @description : 驱动入口函数 * @param : 无 * @return : 0 成功;其他 失败 */ static int __init eth_init(void) { int ret; pr_info("eth_init\n"); if (veth_device != NULL) { return 0; } //(1) 创建共享的数据结构,包含IO读和写的等待队列 veth_device = kmalloc(sizeof(struct veth_device_st), GFP_KERNEL); if (!veth_device) { pr_err("failed to kmalloc"); return -ENOMEM; } veth_device->miscdev = &misc_device; init_waitqueue_head(&veth_device->read_queue); init_waitqueue_head(&veth_device->write_queue); ret = misc_register(&misc_device); if (ret != 0 ) { pr_err("failed to misc_register"); return ret; } return 0; } /* * @description : 驱动出口函数 * @param : 无 * @return : 无 */ static void __exit eth_exit(void) { pr_info("eth_exit\n"); if(veth_device) { kfree(veth_device); } misc_deregister(&misc_device); } module_init(eth_init); module_exit(eth_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("test");
Makefile编写
KERNELDIR := /lib/modules/$(shell uname -r)/build CURRENT_PATH := $(shell pwd) obj-m := vEth.o build: kernel_modules kernel_modules: $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules clean: $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
驱动加载
sudo insmod vEth.ko
字符驱动测试程序
#include <stdio.h> #include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdlib.h> #include <string.h> static char usrdata[] = {"test data!"}; int main(int argc, char* argv[]) { int fd, retvalue;//fd 文件描述符 char *filename; char readbuf[100], writebuf[100]; if(argc != 3){//传参个数不为3,说明主设备号等缺少,因此不能执行应用 printf("Error Usage!\r\n"); return -1; } filename = argv[1]; /* 打开驱动文件 open API 通过传参 判断要使用哪个驱动文件 以及命令是什么 */ fd = open(filename, O_RDWR); if(fd < 0){ printf("Can't open file %s\r\n", filename); return -1; } if(atoi(argv[2]) == 1){ /* 从驱动文件读取数据 */ retvalue = read(fd, readbuf, 50); if(retvalue < 0){ printf("read file %s failed!\r\n", filename); }else{ /* 读取成功,打印出读取成功的数据 */ printf("read data:%s\r\n",readbuf); } } if(atoi(argv[2]) == 2){ /* 向设备驱动写数据 */ memcpy(writebuf, usrdata, sizeof(usrdata)); retvalue = write(fd, writebuf, 50); if(retvalue < 0){ printf("write file %s failed!\r\n", filename); } printf("write file retvalue %d !\r\n", retvalue); } printf("=======================\n"); /* 关闭设备 */ retvalue = close(fd); if(retvalue < 0) { printf("Can't close file %s\r\n", filename); return -1; } return 0; }
测试程序编译
本测试测试程序是基于Cmake进行编译的,也可以直接使用gcc编译。
Cmakelist编写
cmake_minimum_required(VERSION 3.22) project(test_eth C) set(CMAKE_C_STANDARD 99) add_executable(test_eth main.c)