在嵌入式Linux开发中,字符设备驱动是最基础也最常用的驱动类型之一。这个"Hello驱动"项目虽然简单,但完整呈现了Linux内核模块从编写到加载的全过程。我第一次接触驱动开发时,也是从这样的demo开始,逐步理解file_operations结构体、设备号分配这些核心概念。
这个驱动实现的功能很简单:创建一个/dev/hello设备文件,当用户空间程序读取时会返回"Hello World"字符串。但麻雀虽小五脏俱全,它包含了模块初始化、设备注册、文件操作接口绑定等标准驱动开发流程。通过这个案例,新手可以快速建立对Linux设备驱动框架的直观认识。
虽然这个驱动不依赖特定硬件,但推荐使用以下开发板进行实践:
注意:开发主机与目标板的处理器架构需一致。x86主机编译的驱动无法直接在ARM板卡上运行。
在Ubuntu开发主机上需要安装:
bash复制sudo apt install build-essential linux-headers-$(uname -r)
如果是交叉编译还需配置工具链:
bash复制sudo apt install gcc-arm-linux-gnueabihf
内核源码树是必须的,有三种获取方式:
典型的Linux驱动包含以下要素:
c复制#include <linux/module.h>
#include <linux/fs.h>
MODULE_LICENSE("GPL");
static int __init hello_init(void) {
printk(KERN_INFO "Hello driver loaded\n");
return 0;
}
static void __exit hello_exit(void) {
printk(KERN_INFO "Hello driver unloaded\n");
}
module_init(hello_init);
module_exit(hello_exit);
关键点说明:
完整设备驱动需要实现file_operations结构体:
c复制static struct file_operations hello_fops = {
.owner = THIS_MODULE,
.read = hello_read,
};
static int hello_read(struct file *filp, char __user *buf,
size_t count, loff_t *f_pos) {
const char *msg = "Hello World\n";
size_t len = strlen(msg);
if (*f_pos >= len)
return 0;
if (copy_to_user(buf, msg + *f_pos, len - *f_pos))
return -EFAULT;
*f_pos += len - *f_pos;
return len - *f_pos;
}
设备注册流程:
c复制#define DEVICE_NAME "hello"
static int major;
static int __init hello_init(void) {
major = register_chrdev(0, DEVICE_NAME, &hello_fops);
if (major < 0) {
printk(KERN_ALERT "Register failed\n");
return major;
}
printk(KERN_INFO "Registered with major number %d\n", major);
return 0;
}
驱动编译需要特殊Makefile:
makefile复制obj-m := hello.o
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
all:
make -C $(KDIR) M=$(PWD) modules
clean:
make -C $(KDIR) M=$(PWD) clean
交叉编译时需要指定ARCH和CROSS_COMPILE:
makefile复制ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-
加载卸载驱动流程:
bash复制# 加载模块
sudo insmod hello.ko
# 查看内核日志
dmesg | tail
# 创建设备节点
sudo mknod /dev/hello c 250 0 # 250是major号
# 测试读取
cat /dev/hello
# 卸载模块
sudo rmmod hello
症状:模块加载时报"Invalid module format"
解决方法:
uname -r症状:open()返回Permission denied
解决方法:
bash复制sudo chmod 666 /dev/hello
或者编写udev规则自动设置权限。
症状:内核oops或系统崩溃
可能原因:
调试技巧:
这个基础驱动可以进一步扩展:
c复制.write = hello_write,
c复制int minor;
cdev_init(&hello_cdev, &hello_fops);
cdev_add(&hello_cdev, dev, 1);
c复制.unlocked_ioctl = hello_ioctl,
加入proc或sysfs调试接口
使用platform_device实现设备树匹配
我在实际项目中发现,驱动开发最难的不是代码本身,而是对内核机制的深入理解。比如什么时候需要用mutex保护共享数据、如何正确处理阻塞/非阻塞IO、怎样设计高效的内存管理策略等。建议新手在掌握基础后,多研读内核源码中drivers/char下的经典驱动实现。