|
一、前言
之前学习字符设备驱动开发时,提到调用class_device_create函数会为设备创建一个设备节点文件(/dev/xxx)。那么这个创建的过程是怎样的呢?在这里便来简单分析一下。涉及的知识点主要有uevent机制、Sysfs文件系统、mdev程序的调用和实现(创建设备节点文件)。
二、uevent机制
Uevent是Kobject的一部分,用于在Kobject状态发生改变时,例如增加、移除等,通知用户空间程序。用户空间程序收到这样的事件后,会做相应的处理。
该机制通常是用来支持热拔插设备的,例如U盘插入后,USB相关的驱动软件会动态创建用于表示该U盘的device结构(相应的也包括其中的kobject),并告知用户空间程序,为该U盘动态的创建/dev/目录下的设备节点,更进一步,可以通知其它的应用程序,将该U盘设备mount到系统中,从而动态的支持该设备。
2.1 Sysfs文件系统
简单的说,sysfs是一个基于内存的文件系统,它的作用是将内核信息以文件的方式提供给用户程序使用。 sysfs可以看成与proc,devfs和devpty同类别的文件系统,该文件系统是虚拟的文件系统,可以更方便对系统设备进行管理。它可以产生一个包含所有系统硬件层次视图,与提供进程和状态信息的proc文件系统十分类似。 sysfs把连接在系统上的设备和总线组织成为一个分级的文件,它们可以由用户空间存取,向用户空间导出内核的数据结构以及它们的属性。sysfs的一个目的就是展示设备驱动模型中各组件的层次关系,其顶级目录包括block,bus,drivers,class,power和firmware等.
image.png
sysfs提供一种机制,使得可以显式的描述内核对象、对象属性及对象间关系。sysfs有两组接口,一组针对内核,用于将设备映射到文件系统中,另一组针对用户程序,用于读取或操作这些设备。表2描述了内核中的sysfs要素及其在用户空间的表现: sysfs在内核中的组成要素 | 在用户空间的显示 | 内核对象(kobject) | 目录 | 对象属性(attribute) | 文件 | 对象关系(relationship) | 链接(Symbolic Link) |
sysfs引用于: 作者:JalynFong 链接:https://www.jianshu.com/p/98606bee1dad 2.2 Kobject的事件类型
enum kobject_action {
KOBJ_ADD = (__force kobject_action_t) 0x01, // Kobject(或上层数据结构)的添加事件
KOBJ_REMOVE = (__force kobject_action_t) 0x02, // Kobject(或上层数据结构)的移除事件
KOBJ_CHANGE = (__force kobject_action_t) 0x03, // Kobject(或上层数据结构)的状态或者内容发生改变事件
KOBJ_OFFLINE = (__force kobject_action_t) 0x04, // Kobject(或上层数据结构)的上线(使能)事件
KOBJ_ONLINE = (__force kobject_action_t) 0x05, // Kobject(或上层数据结构)的下线(失能)事件
KOBJ_MOVE = (__force kobject_action_t) 0x06, // Kobject(或上层数据结构)更改名称或者更改Parent(意味着在sysfs中更改了目录结构)
};
// CHANGE,如果设备驱动需要上报的事件不再上面事件的范围内,或者是自定义的事件,可以使用该event,并携带相应的参数。三、mdev应用程序
用于创建设备节点文件的用户空间程序,由内核空间通过Uevent机制根据kobject的状态通知用户空间执行。mdev是linux系统中udev的简化版本,一般用于嵌入式系统中,而udev一般用在PC上的linux中,相对mdev来说要复杂些。本质上来说mdev和udev都是一个应用程序,它们具有配置文件,根据uevent机制调用传递的参数和配置文件内容执行相应的功能。对于mdev可以使用busybox中自带的,udev可以下载源码去编译移植。
mdev 是基于uevent_helper机制的,在系统启动时修改内核中的uevnet_helper变量(通过写/proc/sys/kernel/hotplug),写入值为“/sbin/mdev”。这样内核产生uevent 时会调用uevent_helper 所指的用户空间程序,也就是mdev,来执行相应的热拔插动作(add\remove)。 uevent_helper 的初始值在内核编译时可配置的,默认值为/sbin/hotplug。如果想修改它的值,写/proc/sys/kernel/hotplug 文件就可以了,例如: echo “/sbin/mdev” > /proc/sys/kernel/hotplug
image.png
3.1 mdev的配置文件
// busybox-1.7.0/util-linux/mdev.c
mdev_main ->
make_device ->
fd = open("/etc/mdev.conf", O_RDONLY); // 读取/etc/mdev.conf文件,根据里面的配置项执行相应操作mdev.conf的格式:
<device regex> <uid>:<gid> <octal permissions> [<@|$|*> <command>]
device regex:正则表达式,表示哪些设备
uid: owner
gid: 组ID
octal permissions:以八进制表示的属性(设备节点文件)
/ @:创建设备节点之后执行命令
$:删除设备节点之前执行命令
*: 创建设备节点之后 和 删除设备节点之前 执行命令
command:要执行的命令 四、实例分析
根据构建一个Led字符设备驱动的过程,分析设备节点文件创建的流程。并通过配置mdev的配置文件使得加载和移除时执行相应的命令。
4.1 uevent机制
class_device_create(leds_class, NULL, MKDEV(LED_MAJOR, minor), NULL, &#34;led%d&#34;, minor) ->
class_device_register ->
class_device_add ->
kobject_set_name(&class_dev->kobj, &#34;%s&#34;, class_dev->class_id);
kobject_add(&class_dev->kobj);
kobject_uevent(&class_dev->kobj, KOBJ_ADD); ->
kobject_uevent_env(kobj, action, NULL); ->
argv [0] = uevent_helper;
argv [1] = (char *)subsystem;
argv [2] = NULL;
/*
uevent_helper = /sbin/mdev
envp[0] = HOME=/
envp[1] = PATH=/sbin:/bin:/usr/sbin:/usr/bin
envp[2] = ACTION=add
envp[3] = DEVPATH=/class/ledAll/led0
envp[4] = SUBSYSTEM=ledAll
envp[5] = SEQNUM=754
envp[6] = MAJOR=231
envp[7] = MINOR=0
*/
call_usermodehelper (argv[0], argv, envp, 0); ->
call_usermodehelper_keys ->
INIT_WORK(&sub_info->work, __call_usermodehelper); ->
__call_usermodehelper ->
____call_usermodehelper ->
kernel_execve
.......4.2 mdev应用程序
/*
envp[0] = HOME=/
envp[1] = PATH=/sbin:/bin:/usr/sbin:/usr/bin
envp[2] = ACTION=add
envp[3] = DEVPATH=/class/ledAll/led0
envp[4] = SUBSYSTEM=ledAll
envp[5] = SEQNUM=754
envp[6] = MAJOR=231
envp[7] = MINOR=0
*/
mdev_main ->
action = getenv(&#34;ACTION&#34;);
env_path = getenv(&#34;DEVPATH&#34;);
if (!action || !env_path)
bb_show_usage();
sprintf(temp, &#34;/sys%s&#34;, env_path);
if (!strcmp(action, &#34;remove&#34;))
make_device(temp, 1);
else if (!strcmp(action, &#34;add&#34;)) {
make_device(temp, 0);
if (ENABLE_FEATURE_MDEV_LOAD_FIRMWARE)
load_firmware(getenv(&#34;FIRMWARE&#34;), temp);
}
make_device ->
device_name = bb_basename(path);
type = path[5]==&#39;c&#39; ? S_IFCHR : S_IFBLK;
if (ENABLE_FEATURE_MDEV_CONF)
{
.... // 解析配置文件 /etc/mdev.conf
}
if (!delete)
{
// add
mknod(device_name, mode | type, makedev(major, minor)) // 创建节点文件
if (ENABLE_FEATURE_MDEV_CONF) chown(device_name, uid, gid); // 根据配置文件项设置权限
}
if (command) { ... } // 执行命令
if (delete) unlink(device_name); // remove4.3 mdev配置文件使用
vi /etc/mdev.conf
led?[0123]? 0:0 777 * if [ $ACTION = &#34;add&#34; ]; then echo create /dev/$MDEV > /dev/console; else echo remove /dev/$MDEV > /dev/console; fi
// led?[0123]? 正则表达式,匹配led0~led3
// 777 创建的设备节点文件权限属性
// * 创建设备节点之后 和 删除设备节点之前 执行后面的命令
// 执行的命令:
if [ $ACTION = &#34;add&#34; ]; then echo create /dev/$MDEV > /dev/console; else echo remove /dev/$MDEV > /dev/console; fi
// 也可以将上述命令封装成sh脚本文件,tip.sh, 并给tip.sh执行权限(chmod 777 tip.sh)
mdev.conf配置文件内容可写成:
led?[0123]? 0:0 777 * /etc/tip.sh
// u盘的设备节点文件
/dev/sda /dev/sda1
vi /etc/mdev.conf
sda[1-9]+ 0:0 777 * if [ $ACTION = &#34;add&#34; ]; then mount /dev/$MDEV /mnt; else umount /mnt; fi
// sda[1-9]+ 正则表达式,匹配sda1~sda9
// 777 创建的设备节点文件权限属性
// * 创建设备节点之后 和 删除设备节点之前 执行后面的命令
// 执行的命令,插入u盘创建设备节点文件后,将其挂载到/mnt,拔出u盘后,卸载驱动之前,执行umount:
if [ $ACTION = &#34;add&#34; ]; then mount /dev/$MDEV /mnt; else umount /mnt; fi |
|