自动创建设备节点

Last edited
Last updated July 5, 2023
Pages
Tags

1. 创建设备节点的机制

  • mknod:手动创建设备节点的命令
  • devfs:自动创建设备节点的机制,创建设备节点的逻辑在内核(2.4 版本之前使用)
  • udev:自动创建设备节点的机制,创建设备节点的逻辑在用户空间(使用至今)
  • mdev:轻量级 udev 机制,用于一些嵌入式设备

2. udev 机制创建设备节点的原理

/sys/class/<dir>/<file> hotplug ─────────────► udev ──────► /dev/<file> ▲ 用户层 ───┼────────────────────────────────────────────────── │ 发起 hotplug event ┌──┼──────────────────────────────────┐ 内核层 │ │ Driver │ │ ▲ 1. submit struct class │ │ │ 2. submit struct device │ └──┼──────────────────────────────────┘ │ 硬件信息加载到内核中 ────┼────────────────────────────────────────────────── │ 硬件层
当硬件接入的时候,驱动向 udev 的守护进程 systmd-udevd 发起热插拔事件,将结构体提交,系统会在 /sys/class 下创建对应的文件,udev 会自动依据新的文件来创建 /dev 下的设备文件。如此一来,不需要我们手动去使用 mknod 来创建设备文件了。

3. 自动创建设备节点相关的 API

3.1 创建类型

#include <linux/device.h> struct class *class_create(struct module *owner, const char *name)
功能:向上提交目录,在内核创建一个 struct class 类型的空间并初始化
参数
  • owner:模块指针,填写 THIS_MODULE(宏定义,指向当前模块自身的一个指针)
  • name:向上提交的目录名
返回值:成功返回创建好的 struct class 类型的指针,失败返回错误码指针

3.2 错误码指针

在 3-4G 的内核区域,最顶层预留了 4K 的空间,class_create 函数指向失败返回值对应的指针是指向这 4K 预留空间的。
bool IS_ERR(const void *ptr) //(unsigned long)(void *)(x) >= (unsigned long)-MAX_ERRNO
功能:用于判断指针是不是指向内核顶层预留的 4K 空间
返回值:如果指针指向 4K 预留空间,则返回真,否则返回假
long __must_check PTR_ERR(__force const void *ptr)
功能:通过错误码指针得到一个错误码
用例
struct class *cls=class_create(THIS_MODULE,"mychrdev"); if(IS_ERR(cls)) { printk("向上提交目录失败\n"); return -PTR_ERR(cls); }

3.2 销毁目录

void class_destroy(struct class *cls)
功能:销毁目录
参数
  • clsclass_create 函数创建的空间的首地址
返回值:无

4. 实例代码

这里节选重要的部分:
static int __init mycdev_init(void) { int i; //字符设备驱动注册 major = register_chrdev(0, "mychrdev", &fops); if (major < 0) { printk("字符设备驱动注册失败\n"); return major; } printk("字符设备驱动注册成功major=%d\n", major); // 向上提交目录 cls = class_create(THIS_MODULE, "mychrdev"); if (IS_ERR(cls)) { printk("向上提交目录失败\n"); return -PTR_ERR(cls); } // 向上提交设备节点信息 // 为三盏灯创建三个设备文件 for (i = 0; i < 3; i++) { dev = device_create(cls, NULL, MKDEV(major, i), NULL, "mychrdev%d", i); if (IS_ERR(dev)) { printk("向上提交设备节点信息失败\n"); return -PTR_ERR(dev); } } //映射物理寄存器 vir_moder = ioremap(PHY_LED1_MODER, 4); if (vir_moder == NULL) { printk("MODER寄存器地址映射表失败\n"); return -EFAULT; } vir_odr = ioremap(PHY_LED1_ODR, 4); if (vir_odr == NULL) { printk("ODR寄存器地址映射表失败\n"); return -EFAULT; } vir_rcc = ioremap(PHY_RCC, 4); if (vir_rcc == NULL) { printk("RCC寄存器地址映射表失败\n"); return -EFAULT; } printk("寄存器地址映射成功\n"); //寄存器初始化 (*vir_rcc) |= (0x1 << 4); //rcc使能 (*vir_moder) &= (~(0X3 << 20)); //设置为输出 (*vir_moder) |= (0x1 << 20); (*vir_odr) &= (~(0x1 << 10)); //灭灯 printk("硬件寄存器初始化成功\n"); return 0; } static void __exit mycdev_exit(void) { int i; //取消寄存器地址映射 iounmap(vir_moder); iounmap(vir_odr); iounmap(vir_rcc); // 销毁设备节点信息 for (i = 0; i < 3; i++) { device_destroy(cls, MKDEV(major, i)); } // 销毁目录信息 class_destroy(cls); //字符设备驱动的注销 unregister_chrdev(major, "mychrdev"); }
在注册和销毁中,使用了 device_createdevice_destroyclass_createclass_destroy ,其中 device_create 会创建出对应的设备文件在 /dev 目录下。