4. 设备文件自动创建
任务目标
在Android内核驱动开发中,设备节点(如/dev/xxx)是用户空间访问驱动的入口。本案例实现一个简单的字符设备驱动,通过内核提供的class_create和device_create接口,在驱动加载时自动创建设备类与设备节点,无需手动执行mknod命令。重点展示设备文件自动生成的内核标准路径。
核心实现
驱动模块加载时完成以下动作:注册字符设备、创建设备类、在类下创建设备节点。卸载时依次销毁。关键API为class_create(创建/sys/class/<name>)与device_create(在类目录下生成设备,并触发udev在/dev下创建节点)。
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#define DEVICE_NAME "demo_dev"
#define CLASS_NAME "demo_class"
#define BUF_LEN 64
static int major_num;
static struct class *demo_class;
static struct device *demo_device;
static char kernel_buf[BUF_LEN] = "hello from kernel\n";
static int demo_open(struct inode *inode, struct file *filep) {
return 0;
}
static ssize_t demo_read(struct file *filep, char __user *buf,
size_t len, loff_t *offset) {
size_t msg_len = strlen(kernel_buf);
if (*offset >= msg_len)
return 0;
if (len > msg_len - *offset)
len = msg_len - *offset;
if (copy_to_user(buf, kernel_buf + *offset, len))
return -EFAULT;
*offset += len;
return len;
}
static struct file_operations fops = {
.owner = THIS_MODULE,
.open = demo_open,
.read = demo_read,
};
static int __init demo_init(void) {
// 1. 动态分配主设备号
major_num = register_chrdev(0, DEVICE_NAME, &fops);
if (major_num < 0) {
pr_err("Failed to register chrdev\n");
return major_num;
}
// 2. 创建设备类(/sys/class/demo_class)
demo_class = class_create(CLASS_NAME);
if (IS_ERR(demo_class)) {
unregister_chrdev(major_num, DEVICE_NAME);
return PTR_ERR(demo_class);
}
// 3. 在类下创建设备节点(/dev/demo_dev)
demo_device = device_create(demo_class, NULL,
MKDEV(major_num, 0),
NULL, DEVICE_NAME);
if (IS_ERR(demo_device)) {
class_destroy(demo_class);
unregister_chrdev(major_num, DEVICE_NAME);
return PTR_ERR(demo_device);
}
pr_info("Demo driver loaded: major=%d, device=/dev/%s\n",
major_num, DEVICE_NAME);
return 0;
}
static void __exit demo_exit(void) {
device_destroy(demo_class, MKDEV(major_num, 0));
class_destroy(demo_class);
unregister_chrdev(major_num, DEVICE_NAME);
pr_info("Demo driver unloaded\n");
}
module_init(demo_init);
module_exit(demo_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Kernel Dev");
MODULE_DESCRIPTION("Auto create device node via class_create/device_create");
编译与部署
将上述代码保存为demo_dev.c,编写Makefile:
obj-m := demo_dev.o
KDIR := /path/to/android/kernel/source
all:
$(MAKE) -C $(KDIR) M=$(PWD) modules
clean:
$(MAKE) -C $(KDIR) M=$(PWD) clean
编译生成demo_dev.ko后,通过adb push推送到设备,执行:
adb push demo_dev.ko /data/local/tmp/
adb shell su -c "insmod /data/local/tmp/demo_dev.ko"
验证方法与判定标准
- 设备节点存在:执行
ls -l /dev/demo_dev,应显示字符设备文件,主设备号为加载时打印的major值。 - 类目录生成:执行
ls /sys/class/demo_class/,应包含demo_dev子目录。 - 读取功能:执行
cat /dev/demo_dev,预期输出hello from kernel(该结果由代码逻辑直接唯一推出,非实测数据)。 - 卸载清理:执行
rmmod demo_dev后,/dev/demo_dev与/sys/class/demo_class应自动消失。
关键说明
Android用户空间通常由ueventd(而非标准udev)监听内核事件并创建设备节点,但内核侧接口一致:device_create会发送uevent,ueventd根据/ueventd.rc中的权限配置在/dev下生成节点。若设备节点未出现,请确认:驱动已正确加载(dmesg无错误)、device_create的name参数与期望节点名一致、SELinux策略允许ueventd访问该设备。