USB设备管理过程

驱动关于usb整体流程

USB设备管理过程_第1张图片

时序图:
USB设备管理过程_第2张图片
drive device 分别attach到bus总线上,probe用来使drive驱动device
USB设备管理过程_第3张图片
可以看到,attach后的device会找到对应的drive,并调用相应的probe函数。

关键数据结构

代码基于 内核5.11
USB设备管理过程_第4张图片
这里以usb设备为代表,但其他总线设备道理类似。

USB设备管理过程_第5张图片
device_attach函数中会有一个查找对应驱动的函数,算法大致是遍历总线,然后对device 和 drive对应的数据结构进行对比。

通知用户态

为了实时的通知用户态内核的设备管理情况,内核设计uevent的方案,
USB设备管理过程_第6张图片
当完成设备的配对后,内核通过Netlink通知用户态。设备状态通过kobject表现在用户文件中。也就是sysfs。

struct kobject {
    const char *name;  //name,该Kobject的名称,同时也是sysfs中的目录名称。
                //由于Kobject添加到Kernel时,需要根据名字注册到sysfs中,之后就不能再直接修改该字段。
               //如果需要修改Kobject的名字,需要调用kobject_rename接口,该接口会主动处理sysfs的相关事宜。
        struct list_head    entry; //entry,用于将Kobject加入到Kset中的list_head。
        struct kobject      *parent; //parent,指向parent kobject,以此形成层次结构(在sysfs就表现为目录结构)。
        struct kset     *kset; //kset,该kobject属于的Kset。可以为NULL。
                //如果存在,且没有指定parent,则会把Kset作为parent
                //(别忘了Kset是一个特殊的Kobject)。
        struct kobj_type    *ktype;  //ktype,该Kobject属于的kobj_type。每个Kobject必须有一个ktype,或者Kernel会提示错误。
        struct sysfs_dirent *sd;   //sd,该Kobject在sysfs中的表示。
 
        struct kref     kref;  //kref,"struct kref”类型(在include/linux/kref.h中定义)的变量,为一个可用于原子操作的引用计数。
        unsigned int state_initialized:1; //state_initialized,指示该Kobject是否已经初始化,
                      //以在Kobject的Init,Put,Add等操作时进行异常校验。
        unsigned int state_in_sysfs:1;   //state_in_sysfs,指示该Kobject是否已在sysfs中呈现,以便在自动注销时从sysfs中移除。
        unsigned int state_add_uevent_sent:1;  // state_add_uevent_sent/state_remove_uevent_sent,记录是否已经向用户空间发送ADD uevent,
                        //如果有,且没有发送remove uevent,则在自动注销时,补发REMOVE uevent,
                        //以便让用户空间正确处理。
        unsigned int state_remove_uevent_sent:1;
        unsigned int uevent_suppress:1;  //uevent_suppress,如果该字段为1,则表示忽略所有上报的uevent事件。
    };
struct kset {
     struct list_head list; //list用于保存该kset下所有的kobject的链表。
     spinlock_t list_lock;  //list自旋锁
     struct kobject kobj;  //kobj,该kset自己的kobject(kset是一个特殊的kobject,也会在sysfs中以目录的形式体现)。
     const struct kset_uevent_ops *uevent_ops; //uevent_ops,该kset的uevent操作函数集。
                        //当任何Kobject需要上报uevent时,都要调用它所从属的kset的uevent_ops,添加环境变量,
                        //或者过滤event(kset可以决定哪些event可以上报)。
                        //因此,如果一个kobject不属于任何kset时,是不允许发送uevent的。
 };
struct kset_uevent_ops {
     int (* const filter)(struct kset *kset, struct kobject *kobj);
        //filter,当任何Kobject需要上报uevent时,它所属的kset可以通过该接口过滤,
        //阻止不希望上报的event,从而达到从整体上管理的目的。
     const char *(* const name)(struct kset *kset, struct kobject *kobj);
        //name,该接口可以返回kset的名称。如果一个kset没有合法的名称,
        //则其下的所有Kobject将不允许上报uvent
     int (* const uevent)(struct kset *kset, struct kobject *kobj,
         struct kobj_uevent_env *env);
    //uevent,当任何Kobject需要上报uevent时,它所属的kset可以通过该接口统一为这些event添加环境变量。
    //因为很多时候上报uevent时的环境变量都是相同的,因此可以由kset统一处理,就不需要让每个Kobject独自添加了。
 };

uevent事件发送

struct uevent_sock {
        struct list_head list;
        struct sock *sk;
};
static LIST_HEAD(uevent_sock_list);
 list_for_each_entry(ue_sk, &uevent_sock_list, list) {}

每个注册过的用户态程序将形成 uevent_sock---->list。广播时将会遍历每个netlink实现多进程接受数据的能力。
分析 kobject_uevent_env

			struct sk_buff *skb;
			char *scratch;
			for (i = 0; i < env->envp_idx; i++) {
				len = strlen(env->envp[i]) + 1;
				scratch = skb_put(skb, len);
				strcpy(scratch, env->envp[i]);
			}

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

可以看到,广播的内容其实就是各种环境变量。这个skb_put(skb, len);就是往skb->head后面不断的写字节。长度就是要写入len。skb->len用来记录已写入的总长度。

[ 3029.502576]  ? kobject_uevent+0xb/0x10
[ 3029.502578]  blk_integrity_add+0x45/0x50
[ 3029.502579]  __device_add_disk+0x308/0x460
[ 3029.502580]  device_add_disk+0x13/0x20
[ 3029.502581]  sd_probe+0x2ff/0x4b0
[ 3029.502583]  really_probe+0x357/0x460
[ 3029.502585]  driver_probe_device+0xe9/0x160
[ 3029.502586]  __device_attach_driver+0x71/0xd0
[ 3029.502588]  ? driver_allows_async_probing+0x50/0x50
[ 3029.502589]  bus_for_each_drv+0x84/0xd0

通过dump_stack()可以了解到,usb通知事件发生在找到device对应的drive后的probe函数中。

你可能感兴趣的:(linux,linux,系统安全,数据结构)