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)
功能:销毁目录
参数:
cls
:class_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_create
和 device_destroy
,class_create
和 class_destroy
,其中 device_create
会创建出对应的设备文件在 /dev
目录下。