当前位置:首页>Linux>Linux编写一个字符设备驱动实例

Linux编写一个字符设备驱动实例

  • 2026-10-21 13:27:05
Linux编写一个字符设备驱动实例
大家好,我是王鸽,这篇文章主要是Linux 编写一个字符设备驱动实例。Linux系统的设备可以划分为三大类:字符设备、块设备和网络设备。内核针对每一类设备都提供了对应的驱动模型框架,包括基本的内核设施和文件系统接口。这样设备驱动程序员在写某类设备驱动程序时,就有一套完整的驱动模型框架可以使用,从而可将大量的精力放在设备本身的操作上。

内核版本不一样,导致API可能有变化,在较新的内核版本中,结构体的某些成员函数可能有所变化,或者设备注册的函数可能有更新。

字符设备包括模块的初始化、设备的注册、以及基本的读写操作。同时,他们可能希望了解如何测试这个驱动,比如使用用户空间的程序进行读写操作。因此,除了驱动代码,可能还需要提供一个简单的Makefile和测试步骤。

接下来,我需要考虑代码的结构。通常,一个字符设备驱动包括以下部分:

  1. 模块的初始化和退出函数(init_module和cleanup_module)。
  2. 设备的注册和注销,使用register_chrdev和unregister_chrdev。
  3. 实现file_operations结构体中的open、release、read、write等函数。
  4. 内存管理,比如使用或静态数组来存储设备的数据。
  5. 处理并发访问,可能需要使用互斥锁或信号量。

需要注意的是,在Linux内核编程中,不能使用标准库函数,必须使用内核提供的函数,比如printk代替printf,以及处理错误码的方式(返回负的错误码)。

simple_char_driver.c

#include<linux/module.h>#include<linux/fs.h>// file_operations#include<linux/uaccess.h>// copy_to_user/copy_from_user#include<linux/init.h>#include<linux/slab.h>// kmalloc/kfree#include<linux/device.h>// class_create/device_create#define DEVICE_NAME "simple_char"#define CLASS_NAME "simple_char_class"static int major_num;static char *device_buffer; // 设备内存缓冲区static struct class *char_class;static struct device *char_device;// 定义互斥锁防止并发访问staticDEFINE_MUTEX(buffer_mutex);// ----------------------------// 文件操作函数// ----------------------------staticintdevice_open(struct inode *inode, struct file *file){if (!mutex_trylock(&buffer_mutex)) { printk(KERN_ERR "Device is busy\n"); return -EBUSY;} return 0;}staticintdevice_release(struct inode *inode, struct file *file){ mutex_unlock(&buffer_mutex); return 0;}staticssize_tdevice_read(struct file *filp, char __user *buf, size_t len, loff_t *off){int bytes_read = 0;// 将内核缓冲区数据复制到用户空间if (copy_to_user(buf, device_buffer, len)) {return -EFAULT;} bytes_read = len; return bytes_read;}staticssize_tdevice_write(struct file *filp, constchar __user *buf, size_t len, loff_t *off){// 将用户空间数据复制到内核缓冲区 if (copy_from_user(device_buffer, buf, len)) { return -EFAULT;}return len;}// 定义文件操作结构体static struct file_operations fops = { .owner = THIS_MODULE, .open = device_open, .release = device_release, .read = device_read, .write = device_write,};// ----------------------------// 模块初始化和退出// ----------------------------staticint __init simple_char_init(void){// 1. 动态分配主设备号major_num = register_chrdev(0, DEVICE_NAME, &fops);if (major_num < 0) { printk(KERN_ALERT "Failed to register char device\n"); return major_num;}// 2. 创建设备类(用于自动创建设备节点)char_class = class_create(THIS_MODULE, CLASS_NAME);if (IS_ERR(char_class)) { unregister_chrdev(major_num, DEVICE_NAME); return PTR_ERR(char_class);}// 3. 创建设备文件 /dev/simple_charchar_device = device_create(char_class, NULL, MKDEV(major_num, 0), NULL, DEVICE_NAME);if (IS_ERR(char_device)) { class_destroy(char_class); unregister_chrdev(major_num, DEVICE_NAME); return PTR_ERR(char_device);}// 4. 分配缓冲区内存(1KB)device_buffer = kmalloc(1024, GFP_KERNEL);if (!device_buffer) { device_destroy(char_class, MKDEV(major_num, 0)); class_destroy(char_class); unregister_chrdev(major_num, DEVICE_NAME); return -ENOMEM;}printk(KERN_INFO "Simple char device loaded (major=%d)\n", major_num);return 0;}staticvoid __exit simple_char_exit(void){// 清理资源kfree(device_buffer);device_destroy(char_class, MKDEV(major_num, 0));class_destroy(char_class);unregister_chrdev(major_num, DEVICE_NAME);printk(KERN_INFO "Simple char device unloaded\n");}module_init(simple_char_init);module_exit(simple_char_exit);MODULE_LICENSE("GPL");MODULE_AUTHOR("Your Name");MODULE_DESCRIPTION("A simple character device driver");

2. 编译驱动的Makefile

obj-m += simple_char_driver.oKDIR ?= /lib/modules/$(shell uname -r)/buildall: make -C $(KDIR) M=$(PWD) modulesclean: make -C $(KDIR) M=$(PWD) clean

3. 编译与测试步骤

(1) 编译驱动

make

生成 simple_char_driver.ko 内核模块。

(2) 加载驱动

sudo insmod simple_char_driver.ko

检查设备号和节点:

dmesg | tail                     查看加载日志(确认 major number)ls -l /dev/simple_char           # 检查设备节点是否存在

(3) 测试读写

写入数据到设备echo "Hello, Kernel!" > /dev/simple_char# 从设备读取数据cat /dev/simple_char             # 输出应为 "Hello, Kernel!"

(4) 卸载驱动

sudo rmmod simple_char_driver

4. 关键代码解析

代码部分
功能说明
register_chrdev
动态注册字符设备,返回分配的主设备号
class_create / device_create
自动在 /dev 下创建设备节点(无需手动 mknod)
copy_to_user / copy_from_user
安全地在用户空间和内核空间之间复制数据
mutex_trylock / mutex_unlock
防止多个进程同时访问设备导致竞态条件
kmalloc / kfree
内核态动态内存分配与释放

5. 常见问题解决

权限不足:确保用户有访问 /dev/simple_char 的权限:

sudo chmod 666 /dev/simple_char

模块加载失败:

检查内核日志使用命令:dmesg | tail

确认内核头文件已安装:sudo apt install linux-headers-$(uname -r)

读写数据异常:确保测试字符串长度不超过缓冲区大小(代码中为1KB)。

此示例实现了一个基本的字符设备驱动,包含并发控制和自动设备节点创建功能,适用于学习或快速原型开发。

最新文章

随机文章