转自:https://blog.csdn.net/W1107101310/article/details/80211885
简介:
本文主要介绍uevent机制是什么,并通过代码分析使用uevent机制生成设备节点的过程。而本文将分为两部分,第一部分我们介绍一些预备知识和uevent的原理,而第二部分——通过代码介绍使用uevent机制创建设备节点。
Linux内核:linux-2.6.22.6
所用开发板:JZ2440 V3(S3C2440A)
声明:
本文主要是看完韦东山老师视频并结合一些博客内容所写,因此文中可能会有其他文章中的内容,如果你觉得我的文章对你构成了侵犯,您可以告诉我,我会对文章进行改正,同时如果文中可能有不正确的地方,敬请指正。谢谢。
第一部分:预备知识和uevent的原理
下面我们开始讲解预备知识。在讲解之前,我们先来看一个介绍uevent机制的框架图:
该图片来自:Linux设备模型(3)_Uevent
我们很多人可能不清楚:uevent机制是什么,uevent机制到底做了什么工作?他的那些方面是值得我们研究的?
我们在刚学习驱动程序的时候并没有使用uevent机制,也就是说我们在程序中没有用到class_create和class_device_create函数,来自动的在用户空间为设备驱动创建设备节点。那时候我们要手动的在用户空间使用mknod命令来创建设备节点。而当我们使用class_create和class_device_create函数后,他们会为我们在用户空间创建设备节点而不用我们再手动的去完成这项工作。而这就是我们对于uevent机制的宏观认识。而我们知道我们的设备节点是为设备驱动所创建的,而设备device和驱动driver都是以链表的形式连接在总线bus上的,而设备——驱动——总线的更上一层就是sysfs层。因此这就引出了我们就要介绍一个组合了:sysfs+mdev。而这对组合将为我们解释uevent机制的原理。我们先来了解sysfs。
sysfs是一个基于内存的虚拟文件系统,有kernel提供,挂载到/sys 目录下(用mount查看得到 sysfs on /sys type sysfs (rw,nosuid,nodev,noexec,relatime)),负责以设备树的形式向user namespace提供直观的设备和驱动信息。同时sysfs以不同的视角为我们展示当前系统接入的设备:
上面展现了在sys目录下总线,设备,驱动和类所对应的文件,而他们的关系为:
下面我们介绍总线,设备,驱动和类更下一层的结构体。sysfs的功能基于Linux的统一设备模型,他有以下结构体构成:kobject,kset,ktype。同时我们从上面框图中可以看出uevent是在kobject结构体的基础上实现的。
kobject:统一设备模型中最基本的对象。
注:Uevent提供了“用户空间通知”的功能实现,通过该功能,当内核中有Kobject的增加、删除、修改等动作时,会通知用户空间。
Ktype:代表Kobject(严格地讲,是包含了Kobject的数据结构)的属性操作集合(由于通用性,多个Kobject可能共用同一个属性操作集,因此把Ktype独立出来了)。
实际上这里实现的类似于对 kobject 的派生,包含不同 kobj_type 的kobject 可以看做不同的子类。通过实现相同的函数来实现多态。在这样的设计下,每一个内嵌Kobject的数据结构(如kset、device、device_driver等),都要实现自己的 kobj_type ,并定义其中的回调函数。
Kset:一个特殊的Kobject(因此它也会在"/sys/“文件系统中以目录的形式出现),它用来集合相似的Kobject(这些Kobject可以是相同属性的,也可以不同属性的)。
而kset的uevent操作回调函数为:
注意Kset和 ktype 的关联,kobject 会利用成员 kset 找到自已所属的kset,然后才设置自身的 ktype 为 kobj.ktype 。当没有指定 kset 成员时,才会用 ktype 来建立关系。
由于 kobject 调用的是它所属 kset 的uevent操作函数,所以 kset 可以对其行为进行控制。如果kobject不属于任何 kset ,则无法发送uevent。
总结,Ktype以及整个Kobject机制的理解。
Kobject的核心功能是:保持一个引用计数,当该计数减为0时,自动释放(由本文所讲的kobject模块负责) Kobject所占用的meomry空间。这就决定了Kobject必须是动态分配的(只有这样才能动态释放)。
而Kobject大多数的使用场景,是内嵌在大型的数据结构中(如Kset、device_driver等),因此这些大型的数据结构,也必须是动态分配、动态释放的。那么释放的时机是什么呢?是内嵌的Kobject释放时。但是Kobject的释放是由Kobject模块自动完成的(在引用计数为0时),那么怎么一并释放包含自己的大型数据结构呢?
这时Ktype就派上用场了。我们知道,Ktype中的release回调函数负责释放Kobject(甚至是包含Kobject的数据结构)的内存空间,那么Ktype及其内部函数,是由谁实现呢?是由上层数据结构所在的模块!因为只有它,才清楚Kobject嵌在哪个数据结构中,并通过Kobject指针以及自身的数据结构类型,找到需要释放的上层数据结构的指针,然后释放它。
讲到这里,就清晰多了。所以,每一个内嵌Kobject的数据结构,例如kset、device、device_driver等等,都要实现一个Ktype,并定义其中的回调函数。同理,sysfs相关的操作也一样,必须经过ktype的中转,因为sysfs看到的是Kobject,而真正的文件操作的主体,是内嵌Kobject的上层数据结构!
顺便提一下,Kobject是面向对象的思想在Linux kernel中的极致体现,但C语言的优势却不在这里,所以Linux kernel需要用比较巧妙(也很啰嗦)的手段去实现。
mdev原理
上面我们分析了sysfs,下面我们就开始分析mdev,我们通过分析mdev了解他与sysfs的关系。mdev在busybox的代码包中能找到,位于busybox/util-linux/mdev.c文件中,他通过uevent_helper函数被调用。在mdev中主要完成两件事情:
第一件事:
执行mdev -s命令时,mdev扫描/sys/block(块设备保存在/sys/block目录下,内核2.6.25版本以后,块设备也保存在/sys /class/block目录下。mdev扫描/sys/block是为了实现向后兼容)和/sys/class两个目录下的dev属性文件,从该dev 属性文件中获取到设备编号(dev属性文件以”major:minor\n”形式保存设备编号),并以包含该dev属性文件的目录名称作为设备名 device_name(即包含dev属性文件的目录称为device_name,而/sys/class和device_name之间的那部分目录称为 subsystem。也就是每个dev属性文件所在的路径都可表示为/sys/class/subsystem/device_name/dev),在 /dev目录下创建相应的设备文件。例如,cat /sys/class/tty/tty0/dev会得到4:0,subsystem为tty,device_name为tty0。
第二件事:
当mdev因uevnet事件(以前叫hotplug事件)被调用时,mdev通过由uevent事件传递给它的环境变量获取到:引起该uevent 事件的设备action及该设备所在的路径device path。然后判断引起该uevent事件的action是什么。并根据action的不同做相应操作。若该action是add,即有新设备加入到系统中,不管该设备是虚拟设备还是实际物理设备,mdev都会通过device path路径下的dev属性文件获取到设备编号,然后以device path路径最后一个目录(即包含该dev属性文件的目录)作为设备名,在/dev目录下创建相应的设备文件。若该action是remote,即设备已 从系统中移除,则删除/dev目录下以device path路径最后一个目录名称作为文件名的设备文件。如果该action既不是add也不是remove,mdev则什么都不做。
由上面可知,如果我们想在设备加入到系统中或从系统中移除时,由mdev自动地创建和删除设备文件,那么就必须做到以下三点:
1. 在/sys/class 的某一subsystem目录下,
2. 创建一个以设备名device_name作为名称的目录,
3. 并且在该device_name目录下还必须包含一个 dev属性文件,该dev属性文件以”major:minor\n”形式输出设备编号。
而从上面这些内容我们可以知道,sysfs为uevent机制做前期的准备工作,即创建相应的目录,而mdev则是在sysfs的基础上通过调用sysfs创建的目录或文件来实现设备节点的创建。
第二部分:结合代码介绍使用uevent机制创建设备节点
现在我们就要结合代码分析uevent机制了,而要分析这个机制我们就要从class_create和class_device_create这两个函数来分析这个过程是怎么实现的。我们现在先分析class_create:
下面是class_create函数的层级关系:
从上面我们可以看出kobject在sysfs中对应的是目录(dir),当我们注册一个kobject时,会调用kobject_add(&k->kobj);然后在其后创建class设备目录。而同时我们可以看出class_create函数是为class_device_create函数做了目录的准工作。
而从文章开始介绍的图片我们知道当kobject的状态发生改变(如,add, remove等)时,会通知用户空间,用户空间接收到事件通知后可以做相应的处理。
而uevent把事件上报给用户空间有两种途径:
1.通过kmod模块,直接调用用户空间的可执行程序或脚本。
2.通过netlink通信机制,将事件从内核空间传递到用户空间。
而本文主要讲解通过kmod模块,直接调用用户空间的可执行程序或脚本。而通过kobject.h,uevent模块提供了如下的API(这些API的实现是在"lib/kobject_uevent.c”文件中):
下面我们从class_device_create函数开始分析,看他是如何走到kobject_uevent函数的。我们看class_device_create函数的层级关系:
从前面的代码看class_create和class_device_create做了很多相似的工作——就是创建目录,而当到kobject_uevent函数后,他们就不一样了,下面我们分析kobject_uevent函数
他调用了kobject_uevent_env函数,而上面的介绍中我们知道了要发送事件,那么都有什么事件那?
我们看linux-3.5/include/linux/kobject.h
下面我们接着分析kobject_uevent_env函数:
uevent模块通过kmod上报uevent时,会通过call_usermodehelper函数,调用用户空间的可执行文件(或者脚本,简称uevent helper)处理该event。而该uevent helper的路径保存在uevent_helper数组中。可以在编译内核时,通过CONFIG_UEVENT_HELPER_PATH配置项,静态指定uevent helper。
但这种方式会为每个event fork一个进程,随着内核支持的设备数量的增多,这种方式在系统启动时将会是致命的(可以导致内存溢出等)。因此只有在早期的内核版本中会使用这种方式,现在内核不再推荐使用该方式。因此内核编译时,需要把该配置项留空。在系统启动后,大部分的设备已经ready,可以根据需要,重新指定一个uevent helper,以便检测系统运行过程中的热拔插事件。
这可以通过把helper的路径写入到"/sys/kernel/uevent_helper"文件中实现。实际上,内核通过sysfs文件系统的形式,将uevent_helper数组开放到用户空间,供用户空间程序修改访问,具体可参考"./kernel/ksysfs.c”中相应的代码。
在/etc/init.d/rcS脚本中添加 echo "/sbin/mdev" > /proc/sys/kernel/hotplug,会发现cat /sys/kernel/uevent_helper 即是/sbin/mdev。说明/proc/sys/kernel/hotplug中的可执行文件路径最终还是会写到/sys/kernel/uevent_helper中。自己手动echo "/kernel/main" > uevent_helper(之前的/sbin/mdev会被覆盖),当lsmod、rmmod时,/sys/kernel/uevent_helper中的/kernel/main会执行,表明事件已经上报给用户空间。
下面我们看在Busybox中是如何创建设备节点的。
轮到mdev出场了,前面的描述都是在sysfs文件系统中创建目录或者文件,而应用程序访问的设备文件则需要创建在/dev/目录下。该项工作由mdev完成。
mdev的原理是解释/etc/mdev.conf文件定义的命名设备文件的规则,并在该规则下根据环境变量的要求来创建设备文件。mdev.conf由用户层指定,因此更具灵活性。本文无意展开对mdev配置脚本的分析。相关知识可以看我的翻译:mdev.conf翻译
mdev相应的程序在Busybox/util-linux/mdev.c
最终我们会跟踪到mknod在/dev/目录下创建了设备文件。
参考文献:
sysfs、udev 和 它们背后的 Linux 统一设备模型
Linux设备模型(2)_Kobject
Linux 设备文件的创建和mdev
内核发送uevent的API,用户空间解析uevent
Linux设备模型(3)_Uevent
设备模型的uevent机制
嵌入式Linux——uevent机制:uevent原理分析【转】
原文:https://www.cnblogs.com/sky-heaven/p/14042108.html