字符设备驱动
设备驱动分类
Linux系统将设备分为三类:
- • 字符设备:按字节流顺序访问的设备,如键盘、串口、LED等
- • 块设备:可以随机访问固定大小数据块的设备,如硬盘、U盘等
字符设备驱动特点
驱动开发环境
内核源码安装
# 安装内核源码sudo apt-get install linux-source# 解压源码tar -jxvf /usr/src/linux-source-*.tar.bz2
开发工具安装
sudo apt-get install build-essential linux-headers-$(uname -r)
字符设备驱动开发流程
字符设备驱动实现
驱动框架结构
创建一个名为simple_char的字符设备驱动,实现基本的读写操作。
驱动实现示例
#include<linux/module.h>#include<linux/fs.h>#include<linux/cdev.h>#include<linux/uaccess.h>#include<linux/device.h>#include<linux/slab.h>// 设备名称#define DEVICE_NAME "simple_char"// 设备类别名称#define CLASS_NAME "simple_class"// 设备结构体structsimple_char_dev {structcdevcdev;// 字符设备结构体char data[256]; // 设备数据缓冲区structsemaphoresem;// 信号量,用于同步}; // 全局变量staticdev_t dev_num; // 设备号staticstructsimple_char_dev *simple_dev;// 设备结构体指针staticstructclass *simple_class;// 设备类别staticstructdevice *simple_device;// 设备// 文件操作函数声明staticintsimple_open(struct inode *inode, struct file *file);staticintsimple_release(struct inode *inode, struct file *file);staticssize_tsimple_read(struct file *file, char __user *buf, size_t count, loff_t *pos);staticssize_tsimple_write(struct file *file, constchar __user *buf, size_t count, loff_t *pos);staticlongsimple_ioctl(struct file *file, unsignedint cmd, unsignedlong arg);// 文件操作结构体staticstructfile_operationssimple_fops = { .owner = THIS_MODULE, .open = simple_open, .release = simple_release, .read = simple_read, .write = simple_write, .unlocked_ioctl = simple_ioctl,};// 模块初始化函数staticint __init simple_init(void){int ret; printk(KERN_INFO "Simple Char Driver: Initializing\n");// 1. 分配设备号 ret = alloc_chrdev_region(&dev_num, 0, 1, DEVICE_NAME);if (ret < 0) { printk(KERN_ERR "Simple Char Driver: Failed to allocate device number\n");return ret; } printk(KERN_INFO "Simple Char Driver: Major = %d, Minor = %d\n", MAJOR(dev_num), MINOR(dev_num));// 2. 创建设备类别 simple_class = class_create(THIS_MODULE, CLASS_NAME);if (IS_ERR(simple_class)) { unregister_chrdev_region(dev_num, 1); printk(KERN_ERR "Simple Char Driver: Failed to create class\n");return PTR_ERR(simple_class); }// 3. 创建设备结构体 simple_dev = kmalloc(sizeof(struct simple_char_dev), GFP_KERNEL);if (!simple_dev) { class_destroy(simple_class); unregister_chrdev_region(dev_num, 1); printk(KERN_ERR "Simple Char Driver: Failed to allocate device structure\n");return -ENOMEM; }// 初始化数据缓冲区memset(simple_dev->data, 0, sizeof(simple_dev->data));// 初始化信号量 sema_init(&simple_dev->sem, 1);// 4. 初始化cdev并添加到内核 cdev_init(&simple_dev->cdev, &simple_fops); simple_dev->cdev.owner = THIS_MODULE; ret = cdev_add(&simple_dev->cdev, dev_num, 1);if (ret < 0) { kfree(simple_dev); class_destroy(simple_class); unregister_chrdev_region(dev_num, 1); printk(KERN_ERR "Simple Char Driver: Failed to add cdev\n");return ret; }// 5. 创建设备文件 simple_device = device_create(simple_class, NULL, dev_num, NULL, DEVICE_NAME);if (IS_ERR(simple_device)) { cdev_del(&simple_dev->cdev); kfree(simple_dev); class_destroy(simple_class); unregister_chrdev_region(dev_num, 1); printk(KERN_ERR "Simple Char Driver: Failed to create device\n");return PTR_ERR(simple_device); } printk(KERN_INFO "Simple Char Driver: Initialized successfully\n");return0;}// 模块退出函数staticvoid __exit simple_exit(void){ printk(KERN_INFO "Simple Char Driver: Exiting\n");// 1. 销毁设备文件 device_destroy(simple_class, dev_num);// 2. 删除cdev cdev_del(&simple_dev->cdev);// 3. 释放设备结构体 kfree(simple_dev);// 4. 销毁设备类别 class_destroy(simple_class);// 5. 释放设备号 unregister_chrdev_region(dev_num, 1); printk(KERN_INFO "Simple Char Driver: Exited successfully\n");}// 打开设备函数staticintsimple_open(struct inode *inode, struct file *file){structsimple_char_dev *dev;// 获取设备结构体指针 dev = container_of(inode->i_cdev, struct simple_char_dev, cdev); file->private_data = dev; printk(KERN_INFO "Simple Char Driver: Device opened\n");return0;}// 释放设备函数staticintsimple_release(struct inode *inode, struct file *file){ printk(KERN_INFO "Simple Char Driver: Device released\n");return0;}// 读取设备函数staticssize_tsimple_read(struct file *file, char __user *buf, size_t count, loff_t *pos){structsimple_char_dev *dev = file->private_data;ssize_t ret = 0;// 等待信号量if (down_interruptible(&dev->sem)) {return -ERESTARTSYS; }// 检查读取位置是否超出范围if (*pos >= sizeof(dev->data)) {goto out; }// 调整读取长度if (count > sizeof(dev->data) - *pos) { count = sizeof(dev->data) - *pos; }// 从内核空间复制到用户空间if (copy_to_user(buf, dev->data + *pos, count)) { ret = -EFAULT;goto out; }// 更新读取位置 *pos += count; ret = count; printk(KERN_INFO "Simple Char Driver: Read %ld bytes\n", count);out: up(&dev->sem);return ret;}// 写入设备函数staticssize_tsimple_write(struct file *file, constchar __user *buf, size_t count, loff_t *pos){structsimple_char_dev *dev = file->private_data;ssize_t ret = 0;// 等待信号量if (down_interruptible(&dev->sem)) {return -ERESTARTSYS; }// 检查写入位置是否超出范围if (*pos >= sizeof(dev->data)) {goto out; }// 调整写入长度if (count > sizeof(dev->data) - *pos) { count = sizeof(dev->data) - *pos; }// 从用户空间复制到内核空间if (copy_from_user(dev->data + *pos, buf, count)) { ret = -EFAULT;goto out; }// 更新写入位置 *pos += count; ret = count; printk(KERN_INFO "Simple Char Driver: Written %ld bytes\n", count);out: up(&dev->sem);return ret;}// IOCTL函数staticlongsimple_ioctl(struct file *file, unsignedint cmd, unsignedlong arg){structsimple_char_dev *dev = file->private_data;int ret = 0;// 等待信号量if (down_interruptible(&dev->sem)) {return -ERESTARTSYS; }switch (cmd) {case0x100: // 清除缓冲区memset(dev->data, 0, sizeof(dev->data)); printk(KERN_INFO "Simple Char Driver: Buffer cleared\n");break;case0x101: // 获取缓冲区大小 ret = copy_to_user((int __user *)arg, &(sizeof(dev->data)), sizeof(int));if (ret) { ret = -EFAULT; }break;default: ret = -EINVAL;break; } up(&dev->sem);return ret;}// 模块注册module_init(simple_init);module_exit(simple_exit);// 模块信息MODULE_LICENSE("GPL");MODULE_AUTHOR("Your Name");MODULE_DESCRIPTION("A simple character device driver");MODULE_VERSION("1.0");
Makefile编写
创建一个Makefile用于编译内核模块:
obj-m += simple_char.oKDIR := /lib/modules/$(shell uname -r)/buildPWD := $(shell pwd)default:$(MAKE) -C $(KDIR) M=$(PWD) modulesclean:$(MAKE) -C $(KDIR) M=$(PWD) clean
编译与加载驱动
编译驱动
make
编译成功后,会生成以下文件:
- •
simple_char.mod.c:模块依赖信息 - •
simple_char.mod.o:编译后的模块依赖对象 - •
Module.symvers:模块符号版本文件
加载驱动
# 加载内核模块sudo insmod simple_char.ko# 查看是否加载成功lsmod | grep simple_char# 查看设备号cat /proc/devices | grep simple_char
创建设备文件
# 手动创建设备文件(如果device_create没有自动创建)sudomknod /dev/simple_char c <major> <minor># 设置设备文件权限sudochmod 666 /dev/simple_char
查看驱动日志
dmesg | grep "Simple Char Driver"
用户空间测试
用户空间测试程序test_simple_char.c验证:
#include<stdio.h>#include<fcntl.h>#include<unistd.h>#include<string.h>#include<sys/ioctl.h>#define DEVICE_FILE "/dev/simple_char"intmain() {int fd;char buffer[256];int size;// 打开设备文件 fd = open(DEVICE_FILE, O_RDWR);if (fd < 0) { perror("Failed to open device file");return1; }printf("Device opened successfully\n");// 写入数据constchar *test_data = "Hello, Linux Character Driver!";if (write(fd, test_data, strlen(test_data)) < 0) { perror("Failed to write to device"); close(fd);return1; }printf("Written: %s\n", test_data);// 重置文件位置 lseek(fd, 0, SEEK_SET);// 读取数据memset(buffer, 0, sizeof(buffer));if (read(fd, buffer, sizeof(buffer)) < 0) { perror("Failed to read from device"); close(fd);return1; }printf("Read: %s\n", buffer);// 使用IOCTL获取缓冲区大小if (ioctl(fd, 0x101, &size) < 0) { perror("Failed to get buffer size"); close(fd);return1; }printf("Buffer size: %d bytes\n", size);// 使用IOCTL清除缓冲区if (ioctl(fd, 0x100) < 0) { perror("Failed to clear buffer"); close(fd);return1; }printf("Buffer cleared\n");// 验证缓冲区已清除 lseek(fd, 0, SEEK_SET);memset(buffer, 0, sizeof(buffer));if (read(fd, buffer, sizeof(buffer)) < 0) { perror("Failed to read from device"); close(fd);return1; }printf("Read after clear: %s\n", buffer);// 关闭设备文件 close(fd);printf("Device closed\n");return0;}
编译并运行测试程序:
gcc -o test_simple_char test_simple_char.c./test_simple_char
卸载驱动
# 卸载内核模块sudo rmmod simple_char# 查看日志确认卸载
核心概念
设备号
- • 设备号类型:
dev_t(32位,高12位主设备号,低20位次设备号)
file_operations结构体
| |
owner | |
open | |
release | |
read | |
write | |
unlocked_ioctl | |
llseek | |
用户空间与内核空间数据交换
数据交换机制
Linux内核提供了专门的函数用于用户空间和内核空间之间的数据交换:
- •
copy_to_user():从内核空间复制数据到用户空间 - •
copy_from_user():从用户空间复制数据到内核空间
数据交换流程图
同步机制
总结
驱动开发关键
- 2. cdev管理:初始化、添加和删除cdev结构
- 4. file_operations实现:实现设备的各种操作方法
- 6. 数据交换:正确处理用户空间和内核空间的数据传输