微软交流社区

 找回密码
 立即注册
搜索
热搜: 活动 交友 discuz
查看: 113|回复: 0

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

[复制链接]

2

主题

3

帖子

7

积分

新手上路

Rank: 1

积分
7
发表于 2022-11-29 15:43:08 | 显示全部楼层 |阅读模式
一、前言

    之前学习字符设备驱动开发时,提到调用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, "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); // remove4.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
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Archiver|手机版|小黑屋|微软交流社区

GMT+8, 2025-1-7 05:02 , Processed in 0.072107 second(s), 19 queries .

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表