字符设备驱动入门
本文是 嵌入式 Linux 学习路径 第六阶段:实现 最小字符设备驱动,通过 /dev、sysfs、ioctl 与用户态交互。完成后你应能:insmod/rmmod 干净、理解 file_operations 生命周期、安全处理 copy_from_user。
学习目标
- 编写 内核模块:
module_init/module_exit、MODULE_LICENSE。 - 注册 字符设备:
alloc_chrdev_region/register_chrdev、cdev、file_operations。 - 暴露 sysfs 属性 或 ioctl 命令与用户态工具通信。
- 使用 dmesg、/sys、strace 调试。
架构位置
用户 open/read/write/ioctl → VFS → char dev → 你的 fops → 硬件/内存缓冲区
字符设备 按字节流 抽象;块设备、网络设备是其他子系统(概念对比即可)。
最小模块骨架
// SPDX-License-Identifier: GPL-2.0
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
static int __init demo_init(void)
{
pr_info("demo: loaded\n");
return 0;
}
static void __exit demo_exit(void)
{
pr_info("demo: unloaded\n");
}
module_init(demo_init);
module_exit(demo_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("demo");Makefile(内核外树或内核树内)
obj-m += demo.o
KDIR ?= /lib/modules/$(shell uname -r)/build
all:
$(MAKE) -C $(KDIR) M=$(PWD) modules目标机 需 与编译内核一致的 vermagic(交叉编译模块时用 板子内核源码树)。
字符设备注册流程(现代写法概览)
alloc_chrdev_region或register_chrdev_region获取 dev_t。cdev_init+cdev_add绑定file_operations。class_create(或 legacydevice_create)生成/dev/mydev与 sysfs 类目录。unregister_chrdev_region/ cdev_del / device_destroy` 在 exit 中对称释放。
file_operations 要点
| 方法 | 典型用途 |
|---|---|
| open | 分配 per-file 私有数据 filp->private_data |
| read/write | copy_to_user / copy_from_user |
| llseek | 可随机访问设备缓冲时 |
| ioctl | 结构化命令(优先 unlocked_ioctl) |
| release | 对应 close,释放 open 中资源 |
并发:多进程打开同一设备时,自旋锁/mutex 保护共享缓冲;考虑 wait_queue 阻塞读。
示例:环形缓冲 + read/write(思路)
- 内核
kmalloc环形队列; - write 入队,read 出队;队列空时 read 阻塞(
wait_event_interruptible); - 用户态
cat /dev/mydev/echo测试。
(完整代码建议参考 Linux Device Drivers 示例或 Linux 内核模块开发实战。)
sysfs 属性(调试友好)
device_create_file+DEVICE_ATTR暴露/sys/class/.../mydev/state;- show/store 中
sprintf/kstrtoint;store 返回 count 或负 errno。
适合 开关 LED 模拟值、统计计数 而不定义 ioctl 命令号。
ioctl 设计
- 命令编码:
_IO/_IOR/_IOW/_IOWR(<linux/ioctl.h>); - 用户态与内核 共用同一头文件
mydev_ioctl.h; - ioctl 参数 若是指针,必须
copy_from_user到内核栈/堆再使用。
#define MYDEV_IOC_MAGIC 'm'
#define MYDEV_IOC_RESET _IO(MYDEV_IOC_MAGIC, 0)
#define MYDEV_IOC_GET_VAL _IOR(MYDEV_IOC_MAGIC, 1, int)用户态测试
int fd = open("/dev/mydev", O_RDWR);
ioctl(fd, MYDEV_IOC_GET_VAL, &v);
write(fd, "test", 4);strace 跟踪:strace -e trace=open,read,write,ioctl ./app
与设备树结合(platform 字符设备)
platform_driver+.probe里做 字符设备注册;.remove里逆序注销;- 资源(IO 内存、IRQ)从
platform_get_resource/devm_ioremap获取(MMIO 设备)。
GPIO LED 教学可先 gpio-leds(DT 阶段),再 自定义 misc 驱动 练习 fops。
常见错误
| 错误 | 后果 |
|---|---|
| exit 未 device_destroy | rmmod 失败或 oops |
| copy_to_user 未检查返回值 | 部分拷贝仍返回成功 |
| ioctl 未 CAP_SYS_ADMIN 等权限设计 | 安全漏洞 |
| 中断上下文调用 copy_to_user | 禁止 |
实践练习
- 模块加载后
/dev节点存在,major/minor 与/proc/devices一致。 - 实现 read 阻塞 + poll 返回可读。
- 用 sysfs 读驱动内部计数器。
- insmod → 运行测试 → rmmod,dmesg 无 warn。
阶段验收
- 画 open/read/release 调用顺序图(单进程)。
- 解释 为什么必须 copy_from_user。
- 说明 字符设备与 platform_driver probe 的注册顺序。
下一阶段衔接
- I2C/SPI:数据路径变为 总线 transfer + 字符设备或 hwmon/input 子系统。
- 内核子系统深入:块层、网络 sk_buff 是另一条驱动线。
参考
- Linux 内核模块开发实战
- Linux Device Drivers(LDD3,API 需对照新内核)
Documentation/driver-api/
模块开发与 GPL 导出符号、staging 驱动 规范有关;产品化前做 lockdep 与 并发测试。