前言:
今天我主要想学习linux驱动开发中的对象关联关系,网上搜索了下linux设备模型其实是从Kobject和Kset开始的。之前我是直接从bus/device/drive开始学习的,所以就补充下这块内容吧
一,Kobject和Kset代码分析
Kobject和Kset若站在sysfs角度来看就是创建对应的文件夹。从而建立起设备驱动框架。
我从driver_init开始看的。做下笔记
devices_init函数主要作用就是建立/sys/devices、/sys/dev这两个顶级容器节点和/sys/dev/block、/sys/dev/char这两个二级节点,如上图灰色虚线框。
int __init devices_init(void)
{
devices_kset = kset_create_and_add("devices", &device_uevent_ops, NULL);
if (!devices_kset)
return -ENOMEM;
dev_kobj = kobject_create_and_add("dev", NULL);
if (!dev_kobj)
goto dev_kobj_err;
sysfs_dev_block_kobj = kobject_create_and_add("block", dev_kobj);
if (!sysfs_dev_block_kobj)
goto block_kobj_err;
sysfs_dev_char_kobj = kobject_create_and_add("char", dev_kobj);
if (!sysfs_dev_char_kobj)
goto char_kobj_err;
return 0;
char_kobj_err:
kobject_put(sysfs_dev_block_kobj);
block_kobj_err:
kobject_put(dev_kobj);
dev_kobj_err:
kset_unregister(devices_kset);
return -ENOMEM;
}
主要关系
二,bus_register代码分析
platform_bus_init->bus_register传输参数为
struct bus_type platform_bus_type = {
.name = "platform",
.dev_groups = platform_dev_groups,
.match = platform_match,
.uevent = platform_uevent,
.dma_configure = platform_dma_configure,
.pm = &platform_dev_pm_ops,
};
主要对私有成员subsys_private进行初始化,然后创建platform根目标及它的driver和device文件夹及bus属性文件。如下灰色虚线框
对应c代码
int bus_register(struct bus_type *bus)
{
int retval;
struct subsys_private *priv;
struct lock_class_key *key = &bus->lock_key;
priv = kzalloc(sizeof(struct subsys_private), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->bus = bus;
bus->p = priv;
BLOCKING_INIT_NOTIFIER_HEAD(&priv->bus_notifier);
retval = kobject_set_name(&priv->subsys.kobj, "%s", bus->name);
if (retval)
goto out;
priv->subsys.kobj.kset = bus_kset;
priv->subsys.kobj.ktype = &bus_ktype;
priv->drivers_autoprobe = 1;
retval = kset_register(&priv->subsys);
if (retval)
goto out;
retval = bus_create_file(bus, &bus_attr_uevent);
if (retval)
goto bus_uevent_fail;
priv->devices_kset = kset_create_and_add("devices", NULL,
&priv->subsys.kobj);
if (!priv->devices_kset) {
retval = -ENOMEM;
goto bus_devices_fail;
}
priv->drivers_kset = kset_create_and_add("drivers", NULL,
&priv->subsys.kobj);
if (!priv->drivers_kset) {
retval = -ENOMEM;
goto bus_drivers_fail;
}
INIT_LIST_HEAD(&priv->interfaces);
__mutex_init(&priv->mutex, "subsys mutex", key);
klist_init(&priv->klist_devices, klist_devices_get, klist_devices_put);
klist_init(&priv->klist_drivers, NULL, NULL);
retval = add_probe_files(bus);
if (retval)
goto bus_probe_files_fail;
retval = bus_add_groups(bus, bus->bus_groups);
if (retval)
goto bus_groups_fail;
pr_debug("bus: '%s': registered\n", bus->name);
return 0;
bus_groups_fail:
remove_probe_files(bus);
bus_probe_files_fail:
kset_unregister(bus->p->drivers_kset);
bus_drivers_fail:
kset_unregister(bus->p->devices_kset);
bus_devices_fail:
bus_remove_file(bus, &bus_attr_uevent);
bus_uevent_fail:
kset_unregister(&bus->p->subsys);
out:
kfree(bus->p);
bus->p = NULL;
return retval;
}
EXPORT_SYMBOL_GPL(bus_register);
三,小插曲container_of和list_for_each_entry
TYPE *将整型常量0强制转换为TYPE型的指针,且这个指针指向的地址为0,也就是将地址0开始的一块存储空间映射为TYPE型的对象,接下来再对结构体中MEMBER成员进行取址,而整个TYPE结构体的首地址是0,这里获得的地址就是MEMBER成员在TYPE中的相对偏移量。再将这个偏移量强制转换成size_t型数据(无符号整型)
list_for_each_entry实际上是一个 for 循环,利用传入的 pos 作为循环变量,从表头 head 开始,逐项向后(next 方向)移动 pos,直至又回head。参考网址# list_for_each_entry解析
linux中双向链表及链表头的用法:
建立一个双向链表通常有一个独立的用于管理链表的链表头,链表头一般是不含有实体数据的,必须用INIT_LIST_HEAD()进行初始化,表头建立以后,就可以将带有数据结构的实体链表成员加入到链。
四,device_register函数
platform_bus_init->device_register归纳为初始化device的链表和锁及创建文件夹及属性文件,灰色虚线框中主要是设置了自动匹配的话,则会尝试搜索对应驱动进行绑定。
注:此时经常看到klist_add_tail
理解上的技巧,就是将node添加到list中,所以看到xxx->node和xxx->list就是是哪个加入到哪里了。
klist_add_tail(&dev->p->knode_parent,&parent->p->klist_children);
klist_add_tail(&dev->p->knode_driver, &dev->driver->p->klist_devices);
driver_register和device_register类似
module_init中我们添加驱动一般都会调用platform_driver_register,此函数会调用driver_register。都是通过bus总线这个媒人来互相配对,哈哈~
driver_match_device先调用匹配函数,然后在会调用of_driver_match_device这个函数最终调用到__of_match_node()函数,在这个函数里,通过把device_driver的of_match_table(of_device_id结构体的数组)和device里的of_node(device_node结构体)进行匹配,匹配方式是分别比较两者的name、type、和compatible字符串,三者要同时相同(一般name、和type为空,只比较compatible字符串,比较compatible时,是比较整个字符串,不管字符串里的逗号的,直接compare整个字符串)。
五,总结
通过从初始化函数中看了Kobject和Kset,同时复习了下bus/device/driver的匹配过程。终于从点到面,更加理解sys文件夹下的目录结构已经了解了sysfs的文件创建过程,device和driver搜索是通过bus中的list挨个查询,感觉理解又变的生动了,大脑中已经建立一个右bus作为树干device和driver作为树枝的关系图了。
注:class的概念是软连接,用来统筹管理做回调函数时候用的。参考http://www.elecfans.com/emb/20190402899134.html。
六,参考网址
https://blog.csdn.net/xubin341719/article/details/7090635
https://blog.csdn.net/engerled/article/details/6237884
https://www.cnblogs.com/gdt-a20/archive/2011/05/17/2291988.html
https://www.cnblogs.com/tfanalysis/articles/3758614.html
https://blog.csdn.net/ruanjianruanjianruan/article/details/61622053
https://blog.csdn.net/happy987818/article/details/74741466