时序图:
drive device 分别attach到bus总线上,probe用来使drive驱动device
可以看到,attach后的device会找到对应的drive,并调用相应的probe函数。
代码基于 内核5.11
这里以usb设备为代表,但其他总线设备道理类似。
device_attach函数中会有一个查找对应驱动的函数,算法大致是遍历总线,然后对device 和 drive对应的数据结构进行对比。
为了实时的通知用户态内核的设备管理情况,内核设计uevent的方案,
当完成设备的配对后,内核通过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独自添加了。
};
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函数中。