《Linux驱动:设备节点文件的创建过程》

一、前言

之前学习字符设备驱动开发时,提到调用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等.
在这里插入图片描述
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
在这里插入图片描述

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: owner
gid: 组ID
octal permissions:以八进制表示的属性(设备节点文件)
@:创建设备节点之后执行命令
$:删除设备节点之前执行命令
*: 创建设备节点之后 和 删除设备节点之前 执行命令
command:要执行的命令()

四、实例分析

根据构建一个Led字符设备驱动的过程,分析设备节点文件创建的流程。并通过配置mdev的配置文件使得加载和移除时执行相应的命令。

4.1 uevent机制

class_device_create(leds_class, NULL, MKDEV(LED_MAJOR, minor), NULL, "led%d", minor) ->
	class_device_register ->
    	class_device_add ->
        	kobject_set_name(&class_dev->kobj, "%s", 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("ACTION");
    env_path = getenv("DEVPATH");
    if (!action || !env_path)
        bb_show_usage();

    sprintf(temp, "/sys%s", env_path);
    if (!strcmp(action, "remove"))
        make_device(temp, 1);
    else if (!strcmp(action, "add")) {
    
    
        make_device(temp, 0);

        if (ENABLE_FEATURE_MDEV_LOAD_FIRMWARE)
            load_firmware(getenv("FIRMWARE"), temp);
    }

make_device -> 
    device_name = bb_basename(path);
	type = path[5]=='c' ? 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); // remove

4.3 mdev配置文件使用

  • led字符设备驱动测试使用
vi /etc/mdev.conf

led?[0123]? 0:0 777 * if [ $ACTION = "add" ]; then echo create /dev/$MDEV > /dev/console; else echo remove /dev/$MDEV > /dev/console; fi

// led?[0123]? 正则表达式,匹配led0~led3
// 777 创建的设备节点文件权限属性
// * 创建设备节点之后 和 删除设备节点之前 执行后面的命令
// 执行的命令:
	if [ $ACTION = "add" ]; 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 = "add" ]; then mount /dev/$MDEV /mnt; else umount /mnt; fi
// sda[1-9]+  正则表达式,匹配sda1~sda9
// 777 创建的设备节点文件权限属性
// * 创建设备节点之后 和 删除设备节点之前 执行后面的命令
// 执行的命令,插入u盘创建设备节点文件后,将其挂载到/mnt,拔出u盘后,卸载驱动之前,执行umount:
	if [ $ACTION = "add" ]; then mount /dev/$MDEV /mnt; else umount /mnt; fi

猜你喜欢

转载自blog.csdn.net/qq_40709487/article/details/128069218
今日推荐