在这一篇文章中,我尝试从用户的角度去观察Linux的设备与设备驱动,以及呈现方式。
- kobject是如何与sysfs中关连的。
- device与driver与bus是怎么关联起来的,又以什么形式在linux展现出来的。
还没有写完,陆续更新中。。。
=======================================================================
在进行Linux driver的分析时,我们首先要搞清楚的就是Linux devicer driver的模型,其在设计时用了面向对象的思想,用C写的相当的有技巧,这句话相信大家在csdn上一搜都是这样子写的,希望大家已经很清晰。
既然是面向对象,那就有子类,以及方法以及继承等关系,我们后面都会遇到
不过我们还是要在这里说几句关于它的实现的方式。且听我慢慢胡扯起来。【借用wowotech的图片一张】
1. 传说中的kobject
我们知道在java中,会有object对象,在Linux kernel也有一个,就是我们阅读代码时经常看到的kobject(Kernel Ojbect的缩写),而且它与sysfs关联,化作sys 下的一个目录。也可派生出很多子类(kset,device,device_driver, bus_type等),也表现为一个目录。
对于kobject子类的实现,如果一个对象里面包含kobject对象,则我们就认为他是kobject中的一个子类,
不过有一个细节device_driver中,会有一个device_driver_private的指针,而不是直接在device_driver中包含一个kobject。
1.1 kojbect真面目
kobject中比较重要的几个成员是什么呢,先说说我的理解:
1. entry表示自身, 通过entry到如kset中的list中,阅读下面的函数摘要kobj_kset_join(), list_add_tail(&kobj->entry, &kobj->kset->list); 2. *parent,这个更重要,目录中的层级关系就是通过他来实现的。 如指定parent,则在parent下建立,如果没有指定,则会尝试使用kset中的obj为parent。 3. *kset,kojbect可以属于一个kset,kset简单说就是kobj的一个集合,kset的list实现。 4. *sd,就是这个kobject在sysfs中的目录节点了,要不我们怎么知道他是哪个目录。 5. 其他的用到后再说 |
为了说明的需要,顺便在这里写一下kset的定义,有list,就是有“串串”
1.2 kobject要怎么玩
我们可以用kobject派生出子类,然后将子类或者object加入到一个set中(属于一个set),或者给他安排parent。
1.3 kobject的运用实例
kobject提供的函数有下面几个:
void kobject_init(struct kobject *kobj, struct kobj_type *ktype);
int kobject_add(struct kobject *kobj, struct kobject *parent,
const char *fmt, ...)
int kobject_init_and_add(struct kobject *kobj, struct kobj_type *ktype,
struct kobject *parent, const char *fmt, ...)
int kobject_move(struct kobject *kobj, struct kobject *new_parent)
void kobject_del(struct kobject *kobj)
我们用一个函数流程将这几个概念串起来。
当我们添加一个devcie时,先不说device具体是怎么实现,主线只关注kobject的走向与parent,以及kset的关系。
device_add---->kobject_add_internal----->kobject_set_join
就可以看到几个变量的使用,
1. kobject的parent,每次去add时,都会设置parent来创建kobject的目录关系
如果有设置parent,那么ojbect的父目录就是parent, 如果没有设置parent,不过设置了kset,则其parent就是kset中的, 如果都没有,那就直接出现在sys目录下【kset就是没有parent的一个例子,参考下面的例子】 int __init devices_init(void) } |
2.在add时, 如果有kset,还会将自己加入到kset的“串串”list中【见kobj_kset_join】。
int device_add(struct device *dev){
......
parent = get_device(dev->parent);
setup_parent(dev, parent);
......
error = kobject_add(&dev->kobj, dev->kobj.parent, "%s", dev->bus_id);
|
} |
|
|
---> kobject_add_varg
|
|
|--->kobject_add_internal
|
|
2 kobject的子类:kset,devcie,device_driver, bus_type
他的子类有下面的这些:
- kset
- device
- device_driver
- bus_type
2.1 kset :
在上面的例子中,我们已经看到了kset的,也知道它了kobject与其子类的容器,在实现时就是利用了kobject中的list与parent。我们再仔细看一下:
struct kset {
struct list_head list;
spinlock_t list_lock;
struct kobject kobj;
struct kset_uevent_ops *uevent_ops;
};
在其定义里面还有一个是*uevent_ops,这里会有一个uevent的概念,如果之前没有听说过,我们再开一章来说。
【简单说来:kernel有什么变化时,要在一个合适的时机通知userspace,如一个设置增加了,一个设备被一处了】
稍微总结一下:
kset的set字眼点明了他的作用,相同类别的kobject子类可以加入到kset中【由他的list串联】,
什么是相同类别的kobject,举个例子,我们常见的device,在其初始化时,,也代表现实世界中的一个设备(当然也可以是我们虚拟的一个设备),而我们说的 当将一个kojbect加入到kset时,这个kojbect的parent指向kset的kobject,这样了就可以在sysfs中建立树状关系。
- 他是一个kobject的个容器,如他的名字所展现的一样;表现在sys目录中,就是一个目录。虽然kobject也是一个目录,但是他是一类的;但是加进去的是哪个case,理论上任何一个kobject的子类都可以进入,但是我们知道,在实际使用过程中会哪一类,如果人以类聚一样,在代码中,也是以功能来分类的。
- 那到底有哪些呢,我们要有一个直接的表现,那就看看, 这个在Linux启动时,也是很重要的一个流程
2.1.1 Kernel中kset的使用场景
void __init driver_init(void)
{
/* These are the core pieces */
devices_init(); //kset
buses_init(); //kset
classes_init(); //kset
firmware_init(); //kset
hypervisor_init();
/* These are also core pieces, but must come after the
* core core pieces.
*/
platform_bus_init();//kset
system_bus_init(); //kset
cpu_dev_init();
memory_dev_init();
}
目录 devices,bus,class,system这些目录,悉数登场。
/sys$ ls
block class devices fs kernel power
bus dev firmware hypervisor module
可以观察下sys子目录,我们在代码中都可以找到他的创建之处。如devices目录:
int __init devices_init(void)
{
devices_kset = kset_create_and_add("devices", &device_uevent_ops, NULL);
if (!devices_kset)
return -ENOMEM;
......
}
当然kset也可以出现在其他的地方,如platform_bus的目录下【/sys/platform/devices, /sys/platform/drivers两个目录】
int __init buses_init(void)
{
bus_kset = kset_create_and_add("bus", &bus_uevent_ops, NULL);
......
}
int __init classes_init(void)
{
class_kset = kset_create_and_add("class", NULL, NULL);
......
}
int __init system_bus_init(void)
{
system_kset = kset_create_and_add("system", NULL, &devices_kset->kobj);
if (!system_kset)
return -ENOMEM;
return 0;
}
那kset与一个普通的kobject一个重要的区别就是 有一个kset_uevent_ops ,如下:
bus_kset = kset_create_and_add("bus", &bus_uevent_ops, NULL);
红字的部分就是他的差异,至于他什么时候用,我们后面的时候再去做。
函数最终调用到kobject_add_internal,参照上面的流程。
2.2 bus_type
Bus_type是kobject重要子类,从实现来看他是kset直接子类。
先看一下bus_type的定义,重点看bus_type_private数据,bus_type_private是framework的范畴,应用层不用去修改。
在现实生活中,会有很多bus(总线),如IIC, CAN, 以及USB. 虽然不尽相同, 不过可以抽象下,一个具体的设备device会挂在一条总线上,然后通过总线bus协议进行通信。例如IIC设备,当然也可以有虚拟总线platform_bus。
如果我们自己设计,这个bus上挂的设备(device)会是在哪里,这个bus上有的driver有哪些?看一个截图,让我们有整体的认识。
2.2.1 Bus汽车总站
bus的起始点是什么呢, 用户在使用时经常用到的/sys/bus在哪里创建的?
【driver_init->buses_init】in bus.c
从实现看,/sys/bus本身是一个kset,那么在kernel中是如何使用的这个bus_kset呢?
来看一个特定的bus注册函数如下:
上面的代码可以看出:
1. 按照层关系,bus_type这个kset被放在bus_kset(/sys/bus)里面,devices与drivers这两个我们在后面会经常用到。
2. 看下真实系统中的截图,常见的platform,i2c,usb等会经常看到。(/sys/bus/platform, /sys/bus/i2c, sys/bus/usb )
其代码摘录如下
platform注册完后,其在/sys/bus/platform如下
那么bus_type上怎么添加devcie与device_driver呢。我们在章节3里面讲解。
2.2.2 bus_type小结一下
- 总线的目录在/sys/bus, bus目录的创建是在driver_init->buses_init,
- 之后就是每一个bus的注册,例如platform bus的初始化platform_bus_init, 由此我们platform的bus就已经注册完成。
- 当注册完成后,就会有bus目录下呈现不同的目录
/sys/bus/platform$ ls
devices drivers drivers_autoprobe drivers_probe uevent
/sys/bus/i2c$ ls
devices drivers drivers_autoprobe drivers_probe uevent
2.3 device
分析一下device的定义,以及device对外呈现,以及代码的流程,先从我们眼睛看到的开始。
2.3.1 引出/sys/devices 目录
系统中,所有通过device_register的device都加入到了devcies_kset中去了。我们遍历这个kset就可以找到他。其实现如下
int device_register(struct device *dev)
{
device_initialize(dev);
return device_add(dev);
}
void device_initialize(struct device *dev)
{
dev->kobj.kset = devices_kset;
.....
}
但我们看到的/sys/devices的目录下,还是非常有规律的,也是有相应的目录规律,例如我们下面要说的platform目录
2.3.2 devcie 与/sys/devcies的关系
以platform_device为例子说明,因为platform_device都在这个目录下。
但是之前先说platform_bus这个device (/sys/devices/platform)的注册,其定义超级简单,不过他的意义是什么?分门别类,
[ device初始化时,指定其kset为devces_kset ]
int device_register(struct device *dev)
{
device_initialize(dev);
return device_add(dev);
}
而前面的则是device_register, 在注册时,它是放在devices_kset,所以遍历这个set就OK可以到所有的device【见devcie_initialize】
void device_initialize(struct device *dev)
{
dev->kobj.kset = devices_kset;
.....
}
/sys/devices/platform目录可以看很多的设备,其注册流程如下:(通过parent来实现的)
int platform_device_add(struct platform_device *pdev)
{
int i, ret = 0;
if (!pdev)
return -EINVAL;
//如果没有指定,则默认为platform_bus
if (!pdev->dev.parent)
pdev->dev.parent = &platform_bus;//这个不是bus,一个devcie,
//说明是platform_bus_type.
pdev->dev.bus = &platform_bus_type;
....
}
2.3.3 devcie的kojbect中的parent参数是如何定义。
先说一个概念:我们都知道在device中最后产生目录层次关系的是kobject,kobj的parent的依次关系是:
1. 首先尝试使用devcie的parent设备,存在与否未知
1.1 如果没有parent,但是dev的class非空,则产生一个virtual parent /sys/devices/virtual,
1.2 如果parent不为空,parent的class非空且dev的class的namespace为空,则parent确定,【看注释是避免名字空间污染】
1.3 除了以上情况,还需要从glue_dirs中查找,【这里还要再分析】
2. 经过上面的步骤,还不ok,parent就是空了,那么在add时,看kset的,以kset.kobj为parent。
所以platform_bus device在add时,没有parent,也没有class,但是有kset:devices_kset[device_initialize时],所以出现的目录在 /sys/devices/platform.
2.3.4 platform_devcie_register()
后面调用流程如下
devcie_add时会有与device_driver关联,
2.4 device_driver
【在kobject中,我们是通过kobject中的parent来建立目录的树形关系的,如果有kset,则使用的是kset的kobject。】
【如果我们使用内核框架提供的接口,基本上不会设计到适应目录以及框架的修改,我们只要去填写数据就OK了】
上面我们说到,kobject的子类时,说到只要有kobject对象就是他的一个子类,这里其实是有点奇怪的。
对于deivce,其中直接嵌入一个,这个我还比较好理解,但是到了device_driver时,其实现如下,使用了一个指针。但是从其描述文档中,有如下描述(kernel 4.9)
* @p: Driver core's private data, no one other than the driver
* core can touch this.
所以看来,这一部分是framework要使用的部分,对于driver的开发者这是不需要的。
struct device_driver {
const char *name;
struct bus_type *bus;
struct module *owner;
const char *mod_name; /* used for built-in modules */
int (*probe) (struct device *dev);
int (*remove) (struct device *dev);
void (*shutdown) (struct device *dev);
int (*suspend) (struct device *dev, pm_message_t state);
int (*resume) (struct device *dev);
struct attribute_group **groups;
struct pm_ops *pm;
struct driver_private *p;//why this part use a pointer??
};
不过在private data中有嵌入一个Kobject,这是一个本体,然后再保存一个指向device driver的一个指针,这样子就可以找到,
struct driver_private {
struct kobject kobj;
struct klist klist_devices; //一个driver可以驱动很多实体的设备
struct klist_node knode_bus;
struct module_kobject *mkobj;
struct device_driver *driver;
};
#define to_driver(obj) container_of(obj, struct driver_private, kobj)
我们仔细看一下,这里主要是与Kobject相关的内容,这一部分对于driver应用层来说,如果再去考虑,从分层的角度来看就有交叉的问题了。
面向对象就会有一个概念,虚函数,以及重载的概念,还有可能是需要重写与重新定义。
2.5 device与device_driver实例
以他的子类platform_driver来看: 里面有probe,remove等都需要重载的。
struct platform_driver {
int (*probe)(struct platform_device *);
int (*remove)(struct platform_device *);
void (*shutdown)(struct platform_device *);
int (*suspend)(struct platform_device *, pm_message_t state);
int (*suspend_late)(struct platform_device *, pm_message_t state);
int (*resume_early)(struct platform_device *);
int (*resume)(struct platform_device *);
struct pm_ext_ops *pm;
struct device_driver driver;
};
那么为什么device中没有一个类似device_private呢?从设计的层次来说,一个device就是一个具体的设备,看了一下,在device中会有一个void的类型,里面有相应的数据处理。
struct device {
struct kobject kobj;
void *driver_data; /* data private to the driver */
void *platform_data; /* Platform specific data, device
core doesn't touch it */
}
不过我们看到这些driver core都不会去处理,而且留给driver的开发者自行利用。