Linux设备驱动分析之热拔插

时间:2021-10-21 19:00:39

一、kernel

内核2个分支到达device_register(),之后相同;


(1) device_create()//creates a device and registers it with sysfs

    device_create_vargs()

        device_register(dev);

(2) class_dev_create()

    device_register(dev);

        device_add(dev);

            ...

            device_create_file(dev, &uevent_attr);

            ...

            error = device_create_file(dev, &devt_attr);

            ...

            device_create_sys_dev_entry(dev);

            devtmpfs_create_node(dev);

            ...

           device_add_class_symlinks(dev);

           device_add_attrs(dev);

           bus_add_device(dev);

           dpm_sysfs_add(dev);

           device_pm_add(dev);

           kobject_uevent(&dev->kobj, KOBJ_ADD);//热拔插(uevent)

               kobject_uevent_env(kobj, action, NULL);

                   /* environment buffer 用于保存环境变量 */
                   env = kzalloc(sizeof(struct kobj_uevent_env), GFP_KERNEL);

                    ...

                   /* default keys 用户保存设备驱动动作(加载add/卸载remove) */
                   retval = add_uevent_var(env, "ACTION=%s", action_string);
                    ...
                   retval = add_uevent_var(env, "DEVPATH=%s", devpath);//设备驱动路径
                    ...
                   retval = add_uevent_var(env, "SUBSYSTEM=%s", subsystem);//子系统

                    ...

                   #if defined(CONFIG_NET)//实现内核态与应用态通讯--netlink

                        skb = alloc_skb(len + env->buflen, GFP_KERNEL);

                        ...

                       retval = netlink_broadcast_filtered(uevent_sock, skb,
                                                                             0, 1, GFP_KERNEL,
                                                                              kobj_bcast_filter,
                                                                              kobj);

                   #endif

                   ...

                   /* call uevent_helper, usually only enabled during early boot */

                   /*辅助函数uevent_helper(/sbin/mdev,mdev是busybox的杰作)用于对热拔插设备进行创建及删除 */
                    if (uevent_helper[0] && !kobj_usermode_filter(kobj)) {
                       char *argv [3];
                       argv [0] = uevent_helper;
                       argv [1] = (char *)subsystem;
                       argv [2] = NULL;
                       retval = add_uevent_var(env, "HOME=/");
                       if (retval)
                           goto exit;
                       retval = add_uevent_var(env, "PATH=/sbin:/bin:/usr/sbin:/usr/bin");
                       if (retval)
                           goto exit;

                       /*调用热拔插辅助函数uevent_helper(即/sbin/mdev */
                       retval = call_usermodehelper(argv[0], argv, env->envp, UMH_WAIT_EXEC);
                   }

           bus_probe_device(dev);



其中:

uevent_helper为:/sbin/mdev(根据启动脚本/etc/init.d/rcS中的echo /sbin/mdev > /proc/sys/kernel/hotplug)

uevent_helper根据envp[]中环境变量(如:HOME, ACTION, PATH, DEVPATH, SUBSYSTEM, SEQNUM, MAJOR, MINOR)


假设打印环境变量(envp)时终端输出内容如下:

HOME = /

PATH=/sbin:/bin:/usr/sbin;/usr/bin

ACTION=add                                    //表明驱动要执行加载动作

DEVPATH=/module/mytest_driver    //mytest_driver为设备名

SUBSYSTEM=module

SEQNUM=500

MAJOR=250                                     //主设备号

MINOR=0                                          //次设备号



二、/etc/init.d/rcS:

mount -t sysfs sysfs /sys    //busybox将会使用/sys目录,因此/sys必须要挂载(见下文)

mdev -s//-s: 在系统启动时扫描/sys目录,在/dev下创建设备节点

echo /sbin/mdev > /proc/sys/kernel/hotplug//热拔插由/sbin/mdev支持,内核讲调用/sbin/mdev创建及删除设备节点



三、busybox:

mdev.c


mdev_main()

{

    /* Scan扫描 */

    if (argc == 2 && !strcmp(argv[1], "-s")){//如果是系统启动时被调用,则执行mdev -s

    }

    /* Hotplug热拔插 */

    else {

        action = getenv("ACTION");

        evn_path = getevn("DEVPATH");

        sprintf(temp, "/sys%s", evn_path);

        if (!strcmp(action,"remove"))//卸载

            make_device(tmp, 1);//删除设备

        else if (!strcmp(action,"add"))//装载

            make_device(tmp, 0);//创建设备

    }

}


make_device(char *path, int delete)

{

    if (!delete) {//添加设备

        strcat(path, "/dev");

        ...

    }

    /* detemine device name, type, major and minor设备名,类型,主次设备号 */

    /* 假设path=/sys/class/dev/mytest_driverbb_basename从path末尾查找‘/’,并找到‘/’后面的mytest_driver字串 */

    device_name = bb_basename(path);


    /* /sys/class都是字符设备驱动,因此第5个字符为‘c’时,该设备为字符设备 */

    type = path[5]=='c' ?S_IFCHR : S_IFBLK;//如果path[5]为字符c,则该设备类型为字符设备,否则为块设备


        /* 通过cat  /sys/class/dev/mytest_driver/dev命令可以得到设备的主次设备号 */


    /* If we have a config file, look up permissions for this device */

    /* 如果/etc目录中有mdev.conf配置文件,则根据配置文件执行相应脚本创建/删除设备 */

    if (ENABLE_FEATURE_MDEV_CONFG) {//见mdev.conf

        ...

        /* mmap the config file */

        fd = open("/etc/mdev.conf", O_RDONLY);

        ...

    }

   

    if (!delete) {//添加设备

        if (sscanf(tmp, "%d:%d", &major, &minor) != 2)    return;//tmp=/sys/class/dev/mytest_driver/dev

        mknod(device_name, mode | type, mkdev(major, minor))//创建设备节点

    }

    ...

}



四、mdev.conf:


busybox/doc/mdev.txt


mdev.conf的格式:
<device regex> <uid>:<gid> <octal permissions> [<@|$|*> <command>]

device regex:正则表达式,表示哪一个设备
uid: owner
gid: 组ID
octal permissions:以八进制表示的属性
@:创建设备节点之后执行命令
$:删除设备节点之前执行命令
*: 创建设备节点之后 和 删除设备节点之前 执行命令
command:要执行的命令



例1:leds 0:0 777

例2:leds?[123]? 0:0 777

例3:leds?[123]? 0:0 777 @ echo create /dev/$MDEV > /dev/console

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

例5:leds?[123]? 0:0 777 * /bin/add_remove_led.sh

add_remove_led.sh内容如下:

#!/bin/sh
if [ $ACTION = "add" ];
then
    echo create /dev/$MDEV > /dev/console;
else
    echo remove /dev/$MDEV > /dev/console;
fi

例6:U盘自动加载

sda[1-9]+ 0:0 777 * /bin/add_remove_udisk.sh

add_remove_udisk.sh
#!/bin/sh
if [ $ACTION = "add" ];
then
    mount /dev/$MDEV /mnt;
else
    umount /mnt;
fi



五、通配符:


. : 表示除换行符以外的任意字符

* : 重复0次或跟多次

+ : 重复1次或更多次

? :重复0次或1次

[abc] : 表示abc三个字符中的某一个,又如[1-9]表示1至9中的某一个




例1:leds? :

表示?前面的字符s重复0次或1次


例2:leds?[123]?:

表示?(左起第1个)前面的字符s可能重复出现0次或1次,?(左起第2个)前面[123]中1、2、3可能会出现0次或1次