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"

验证方法与判定标准

  1. 设备节点存在:执行ls -l /dev/demo_dev,应显示字符设备文件,主设备号为加载时打印的major值。
  2. 类目录生成:执行ls /sys/class/demo_class/,应包含demo_dev子目录。
  3. 读取功能:执行cat /dev/demo_dev,预期输出hello from kernel(该结果由代码逻辑直接唯一推出,非实测数据)。
  4. 卸载清理:执行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访问该设备。

设备文件自动创建机制 insmod demo_dev.ko 模块加载 register_chrdev() 动态分配主设备号 class_create() 创建 /sys/class/demo_class device_create() 生成设备并发送uevent uevent ueventd (Android) 监听内核uevent mknod /dev/demo_dev 根据uevent.rc权限自动创建 验证结果 • /dev/demo_dev 字符设备节点存在 • /sys/class/demo_class/ 类目录存在 • cat /dev/demo_dev → hello from kernel • rmmod 后节点自动消失 卸载: device_destroy → class_destroy → unregister_chrdev