当前位置:首页>Linux>Linux字符设备驱动开发初体验

Linux字符设备驱动开发初体验

  • 2026-10-21 13:28:08
Linux字符设备驱动开发初体验

字符设备驱动

设备驱动分类

Linux系统将设备分为三类:

  • • 字符设备:按字节流顺序访问的设备,如键盘、串口、LED等
  • • 块设备:可以随机访问固定大小数据块的设备,如硬盘、U盘等
  • • 网络设备:用于网络通信的设备,如网卡

字符设备驱动特点

  • • 面向流的设备,数据按顺序传输
  • • 通常不需要缓冲
  • • 通过设备文件(/dev目录下)访问
  • • 使用主设备号和次设备号标识

驱动开发环境

内核源码安装

# 安装内核源码sudo apt-get install linux-source# 解压源码tar -jxvf /usr/src/linux-source-*.tar.bz2

开发工具安装

sudo apt-get install build-essential linux-headers-$(uname -r)

字符设备驱动开发流程

初始化驱动模块
分配设备号
注册字符设备
创建设备文件
实现file_operations
编译为内核模块
加载驱动模块
测试驱动功能
卸载驱动模块

字符设备驱动实现

驱动框架结构

创建一个名为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.ko:内核模块文件
  • • simple_char.mod.c:模块依赖信息
  • • simple_char.mod.o:编译后的模块依赖对象
  • • simple_char.o:编译后的驱动对象
  • • modules.order:模块顺序文件
  • • 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():从用户空间复制数据到内核空间
  • • get_user():获取单个用户空间数据
  • • put_user():写入单个数据到用户空间

数据交换流程图

内核缓冲区内核驱动用户空间程序内核缓冲区内核驱动用户空间程序write(fd, user_buf, count)检查参数和权限获取信号量copy_from_user()数据复制完成释放信号量返回写入字节数read(fd, user_buf, count)检查参数和权限获取信号量读取数据copy_to_user()释放信号量返回读取字节数

同步机制

  • • 信号量:用于保护共享资源,防止并发访问冲突
  • • 互斥锁:用于确保同一时间只有一个进程访问资源
  • • 自旋锁:用于内核态下的短时间同步

总结

驱动开发关键

  1. 1. 设备号管理:分配、注册和释放设备号
  2. 2. cdev管理:初始化、添加和删除cdev结构
  3. 3. 设备文件管理:创建设备文件和类别
  4. 4. file_operations实现:实现设备的各种操作方法
  5. 5. 同步机制:保护共享资源,防止并发访问冲突
  6. 6. 数据交换:正确处理用户空间和内核空间的数据传输

最新文章

随机文章