三、linux字符驱动详解
- IT业界
- 2025-08-26 14:15:02

在上一节完成NFS开发环境的搭建后,本节将探讨Linux字符设备驱动的开发。字符设备驱动作为Linux内核的重要组成部分,主要负责管理与字符设备(如串口、键盘等)的交互,并为用户空间程序提供统一的读写操作接口。
驱动代码 #include <linux/module.h> #include <linux/fs.h> #include <linux/device.h> #include <linux/kernel.h> #include <linux/uaccess.h> #include <linux/cdev.h> #define DEVICE_NAME "hello_chrdev" #define BUFFER_SIZE 100 // 设备结构体 typedef struct { char buffer[BUFFER_SIZE]; struct class *class; struct device *device; dev_t dev_num; struct cdev cdev; } HelloDevice; static HelloDevice hello_dev; // 打开设备 static int hello_open(struct inode *inode, struct file *filp) { printk(KERN_INFO "Hello device opened\n"); return 0; } // 读取设备 static ssize_t hello_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos) { size_t len = strlen(hello_dev.buffer); if (*f_pos >= len) { return 0; } if (count > len - *f_pos) { count = len - *f_pos; } if (copy_to_user(buf, hello_dev.buffer + *f_pos, count)) { return -EFAULT; } *f_pos += count; return count; } // 写入设备 static ssize_t hello_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos) { if (count > BUFFER_SIZE - 1) { count = BUFFER_SIZE - 1; } if (copy_from_user(hello_dev.buffer, buf, count)) { return -EFAULT; } hello_dev.buffer[count] = '\0'; *f_pos += count; return count; } // 关闭设备 static int hello_release(struct inode *inode, struct file *filp) { printk(KERN_INFO "Hello device closed\n"); return 0; } // 文件操作结构体 static struct file_operations hello_fops = { .owner = THIS_MODULE, .open = hello_open, .read = hello_read, .write = hello_write, .release = hello_release, }; // 模块初始化函数 static int __init hello_init(void) { int ret; // 分配设备号 ret = alloc_chrdev_region(&hello_dev.dev_num, 0, 1, DEVICE_NAME); if (ret < 0) { printk(KERN_ERR "Failed to allocate character device number\n"); return ret; } // 创建类 hello_dev.class = class_create(THIS_MODULE, DEVICE_NAME); if (IS_ERR(hello_dev.class)) { unregister_chrdev_region(hello_dev.dev_num, 1); printk(KERN_ERR "Failed to create class\n"); return PTR_ERR(hello_dev.class); } // 创建设备 hello_dev.device = device_create(hello_dev.class, NULL, hello_dev.dev_num, NULL, DEVICE_NAME); if (IS_ERR(hello_dev.device)) { class_destroy(hello_dev.class); unregister_chrdev_region(hello_dev.dev_num, 1); printk(KERN_ERR "Failed to create device\n"); return PTR_ERR(hello_dev.device); } // 初始化 cdev 结构体 cdev_init(&hello_dev.cdev, &hello_fops); hello_dev.cdev.owner = THIS_MODULE; // 添加字符设备到系统 ret = cdev_add(&hello_dev.cdev, hello_dev.dev_num, 1); if (ret < 0) { device_destroy(hello_dev.class, hello_dev.dev_num); class_destroy(hello_dev.class); unregister_chrdev_region(hello_dev.dev_num, 1); printk(KERN_ERR "Failed to add character device\n"); return ret; } printk(KERN_INFO "Hello device initialized. Major: %d, Minor: %d\n", MAJOR(hello_dev.dev_num), MINOR(hello_dev.dev_num)); return 0; } // 模块卸载函数 static void __exit hello_exit(void) { cdev_del(&hello_dev.cdev); device_destroy(hello_dev.class, hello_dev.dev_num); class_destroy(hello_dev.class); unregister_chrdev_region(hello_dev.dev_num, 1); printk(KERN_INFO "Hello device removed\n"); } module_init(hello_init); module_exit(hello_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Your Name"); MODULE_DESCRIPTION("A simple hello world character device driver"); 函数接口详解 1. 模块初始化与退出相关函数 alloc_chrdev_region int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name); 功能:动态分配一组连续的字符设备号。参数 dev:用于存储分配到的设备号。baseminor:起始的次设备号。count:要分配的设备号数量。name:设备的名称,用于在 /proc/devices 中显示。 返回值:成功返回 0,失败返回负数错误码。 class_create struct class *class_create(struct module *owner, const char *name); 功能:在 /sys/class 目录下创建一个设备类。参数 owner:指向模块的指针,通常为 THIS_MODULE。name:类的名称。 返回值:成功返回指向 struct class 的指针,失败返回错误指针。 device_create struct device *device_create(struct class *class, struct device *parent, dev_t devt, void *drvdata, const char *fmt, ...); 功能:在 /sys/class/<class_name> 目录下创建设备节点,并在 /dev 目录下创建对应的设备文件。参数 class:指向设备类的指针。parent:父设备指针,通常为 NULL。devt:设备号。drvdata:设备驱动数据,通常为 NULL。fmt:设备名称的格式化字符串。 返回值:成功返回指向 struct device 的指针,失败返回错误指针。 cdev_init void cdev_init(struct cdev *cdev, const struct file_operations *fops); 功能:初始化字符设备结构体 struct cdev,并关联文件操作结构体 struct file_operations。参数 cdev:指向 struct cdev 的指针。fops:指向 struct file_operations 的指针。 cdev_add int cdev_add(struct cdev *p, dev_t dev, unsigned count); 功能:将字符设备添加到内核中。参数 p:指向 struct cdev 的指针。dev:设备号。count:设备数量。 返回值:成功返回 0,失败返回负数错误码。 module_init 和 module_exit module_init(hello_init); module_exit(hello_exit); 功能:分别指定模块加载和卸载时调用的函数。 2. 文件操作相关函数在 Linux 内核中,struct file_operations 结构体是字符设备驱动与用户空间进行交互的关键桥梁,其中 open、read、write 和 release 是比较常用的操作函数。
struct file_operations` 结构体中相关成员介绍
open 函数 int (*open) (struct inode *inode, struct file *filp); 功能:当用户空间使用 open() 系统调用打开设备文件时,内核会调用驱动中注册的 open 函数。该函数通常用于执行设备的初始化操作,如分配资源、检查设备状态等。参数 struct inode *inode:指向文件对应的索引节点,包含了文件的元信息,如文件类型、权限等。struct file *filp:指向文件对象,代表了一个打开的文件实例,包含了文件的当前状态、偏移量等信息。 返回值:成功时返回 0,失败时返回负数错误码。 read 函数 ssize_t (*read) (struct file *filp, char __user *buf, size_t count, loff_t *f_pos); 功能:当用户空间使用 read() 系统调用从设备文件读取数据时,内核会调用驱动中的 read 函数。该函数负责将设备中的数据复制到用户空间的缓冲区。参数 struct file *filp:指向文件对象。char __user *buf:用户空间的缓冲区指针,用于存储从设备读取的数据。size_t count:用户请求读取的字节数。loff_t *f_pos:文件的当前偏移量指针,可通过修改该指针来更新文件的读写位置。 返回值:成功时返回实际读取的字节数,返回 0 表示已到达文件末尾,失败时返回负数错误码。 write 函数 ssize_t (*write) (struct file *filp, const char __user *buf, size_t count, loff_t *f_pos); 功能:当用户空间使用 write() 系统调用向设备文件写入数据时,内核会调用驱动中的 write 函数。该函数负责将用户空间缓冲区中的数据复制到设备中。参数 struct file *filp:指向文件对象。const char __user *buf:用户空间的缓冲区指针,包含了要写入设备的数据。size_t count:用户请求写入的字节数。loff_t *f_pos:文件的当前偏移量指针。 返回值:成功时返回实际写入的字节数,失败时返回负数错误码。 release 函数 int (*release) (struct inode *inode, struct file *filp); 功能:当用户空间使用 close() 系统调用关闭设备文件时,内核会调用驱动中的 release 函数。该函数通常用于执行设备的清理操作,如释放资源、关闭设备等。参数 struct inode *inode:指向文件对应的索引节点。struct file *filp:指向文件对象。 返回值:成功时返回 0,失败时返回负数错误码。 3. 模块卸载相关函数 cdev_del void cdev_del(struct cdev *p); 功能:从内核中移除字符设备。参数 p:指向 struct cdev 的指针。 device_destroy void device_destroy(struct class *class, dev_t devt); 功能:销毁 /sys/class/<class_name> 目录下的设备节点和 /dev 目录下的设备文件。参数 class:指向设备类的指针。devt:设备号。 class_destroy void class_destroy(struct class *cls); 功能:销毁 /sys/class 目录下的设备类。参数 cls:指向 struct class 的指针。 unregister_chrdev_region void unregister_chrdev_region(dev_t from, unsigned count); 功能:释放之前分配的字符设备号。参数 from:起始的设备号。count:要释放的设备号数量。 编译和测试 编写 Makefile obj-m += helloworld.o KDIR := linux-5.15.18/ PWD := $(shell pwd) default: $(MAKE) -C $(KDIR) M=$(PWD) modules clean: $(MAKE) -C $(KDIR) M=$(PWD) clean将 linux-5.15.18/ 替换为实际的 Linux 5.15.18 内核源码路径。
编译驱动在终端中执行 make 命令编译驱动模块。
测试驱动在 QEMU 终端中:
使用 insmod helloworld.ko 加载驱动模块。使用 echo “Hello World” > /dev/helloworld 向设备写入数据。使用 cat /dev/helloworld 从设备读取数据。使用 rmmod hello_chrdev.ko 卸载驱动模块。三、linux字符驱动详解由讯客互联IT业界栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“三、linux字符驱动详解”
上一篇
Java反射
下一篇
千峰React:组件使用(1)