赞
踩
在我看来模型就是对一系列事务,进行抽象、统一、管理。所呈现出的一种层次关系。就像一个公司有部门、小组、个人这样的一个组织关系。那么这样做的好处就是便于管理。
在Linux的世界里将C语言运用到极致,在这里,不能单单的想象成结构体,要体会其数据结构背后的含义,而不能单单的理解成结构体类型。在c++中有个更好的说法叫类。所有的类型都想象成类更好理解。其中结构体的成员变量类比成属性,其中函数指针类比这个类所具有的方法。类比一个人物的固有血条(属性)以及这个人物的攻击技能(方法)。这样的结构在Linux里面很常见。
在Linux中最常见的驱动模型,就是设备(dev)-总线(bus)-驱动(drv),这样的模型结构。在Linux中类一般都是事务的抽象,提取一些通用的共同的内容。设备类型更多的是来描述这个设备具体的一些通用属性。bus是dev和drv的桥梁,纽带。dev和drv的关系就是靠bus来衔接。drv描述更多的方法,这一类的设备他具有哪些技能,一般在drv中进行描述。
dev bus drv的类型定义在kernel\include\linux\device.h文件中
在Linux中设备是一个宽泛的概念,并不是指某一个实体设备。是一个抽象出来的设备,不要当一个实体设备看待。
struct device {
struct device *parent;
struct device_private *p;
struct kobject kobj;
struct bus_type *bus; /* type of bus device is on */
struct device_driver *driver; /* which driver has allocated this
device */
void *platform_data; /* Platform specific data, device
core doesn't touch it */
void *driver_data; /* Driver data, set and get with
dev_set/get_drvdata */
....
}
struct device_private {
struct klist klist_children;
struct klist_node knode_parent;
struct klist_node knode_driver;
struct klist_node knode_bus;
struct list_head deferred_probe;
struct device *device;
};
- 其中最重要的几个成员变量是*bus *driver指针,指向和这个dev相关联的总线和驱动。
- 同时还有这个*p的成员变量。这个list node届时就会插入到bus管理的那个dev链表中去,被管理起来,还有drv的节点也会插入到drv管理的dev链表中去,被drv管理起来。
- 而根据这个结构体,我就能知道自己这个设备是属于哪个bus,以及知道我的驱动是哪个。
- dev的name存在于kobj的成员变量name中,他两用同一个name。
这里的bus,同样是宽泛的概念,并不是cpu中实实在在的总线,是一种程序上虚拟出的一种代码结构。
struct bus_type {
const char *name;
const char *dev_name;
struct device *dev_root;
struct subsys_private *p;
int (*match)(struct device *dev, struct device_driver *drv);
int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
int (*probe)(struct device *dev);
}
struct subsys_private {
struct klist klist_devices;
struct klist klist_drivers;
struct bus_type *bus;
}
- match方法为这个bus中dev和drv他们两个匹配的方法,入参刚好一个是dev一个是drv,只有他们俩某个属性对上了(通常我们用dev和drv的名字作为匹配的条件),那么我们称这个dev和drv匹配上了然后就会去执行probe的方法,后面代码中会介绍。
- bus类型最重要的是*p这个成员变量。他管理了2个链表,分别是dev链表和drv链表。这样bus就起到一个管理者的角色。他就知道他当前的这个bus拥有哪些设备和驱动。
drv同样也是广泛意义上的驱动,并不是某一个具体外设的驱动。这里同样抽象出来了,但是具体的外设驱动肯定是基于这个数据结构的。有点像c++中外设驱动的父类那种感觉。
struct device_driver {
const char *name;
struct bus_type *bus;
struct driver_private *p;
int (*probe) (struct device *dev);
}
struct driver_private {
struct kobject kobj;
struct klist klist_devices;
struct klist_node knode_bus;
struct module_kobject *mkobj;
struct device_driver *driver;
};
- 根据*bus 知道我这个驱动是属于哪个bus的。
- 同样有个p,p里面有一个bus的节点,届时会插入到bus的那个p的drv链表下,被管理起来
- 除此之外还有一个dev的链表,而这个链表是用来管理这个驱动所匹配上所有的设备的,为什么有这样的结构,是应为一个设备只能有一个驱动,而一个驱动可以拥有好几个设备。多个相同或者类似的设备共享一个驱动。这个应该好理解。
每个注册的接口都有一个register,同样和他相对应的有一个unregister接口,比如int driver_register(struct device_driver *drv) 与他对应有一个void driver_unregister(struct device_driver *drv)接口。主要用于资源的释放等相关操作。
因为drv和dev的初始化都要涉及到bus,所以,在linux中bus都要先做初始化才能被dev和drv注册时使用。
接口定义在\drivers\base\bus.c
int bus_register(struct bus_type *bus)
int bus_register(struct bus_type *bus)
{
int retval;
struct subsys_private *priv;
priv = kzalloc(sizeof(struct subsys_private), GFP_KERNEL); //申请bus的p空间
priv->bus = bus;//p的bus指向这个将要注册的bus
bus->p = priv;//将要注册的bus的p指向这个刚申请出来的p空间
/* 初始化bus中p带的两个链表,分别是dev链表和drv链表 */
klist_init(&priv->klist_devices, klist_devices_get, klist_devices_put);
klist_init(&priv->klist_drivers, NULL, NULL);
}
接口定义在\drivers\base\core.c,实体大部分工作其实是device_add完成的。
下面介绍的dev注册过程,是指bus上已经注册好drv的情况下,dev注册函数的调用过程,
假如bus上没有drv,那么调用过程没有这么复杂。
假如bus没有匹配的drv说明当前dev的驱动没有或者还没有注册上,没关系,等drv注册的时候还是会来扫一遍设备然后在执行bus的probe方法。
int device_register(struct device *dev)—》int device_add(struct device *dev)
int device_add(struct device *dev)
{
if (dev->init_name) {//用init_name 来初始化这个dev的名字
dev_set_name(dev, "%s", dev->init_name);
dev->init_name = NULL;
}
//一系列检查无误后最后调用bus_add_device->\
// klist_add_tail(&dev->p->knode_bus, &bus->p->klist_devices);
//把这个dev成员p的bus节点插入到-》bus成员p的dev链表上去,被bus管理起来。
error = bus_add_device(dev);
bus_probe_device(dev);
}
//调用过程简化
bus_probe_device(dev)-》device_initial_probe(dev)-》__device_attach(dev, true)\
-》ret = bus_for_each_drv(dev->bus, NULL, &data,__device_attach_driver)\
-》int __device_attach_driver;
//重要函数--循环取bus上的drv链表上的驱动来挨个和这个注册的dev尝试匹配是否能匹配上。\
//而尝试匹配函数即为fn---也就是-》__device_attach_driver
int bus_for_each_drv(struct bus_type *bus, struct device_driver *start,
void *data, int (*fn)(struct device_driver *, void *))
{
while ((drv = next_driver(&i)) && !error)
error = fn(drv, data);
}
__device_attach_driver(struct device_driver *drv, void *_data)
{
//直接调用bus的match接口,所以是否匹配成功的条件是,bus在注册的时候match方法决定的。\
//-》drv->bus->match(dev, drv)
ret = driver_match_device(drv, dev);
if (ret == 0) {
/* no match */
return 0;
}
return driver_probe_device(drv, dev);
}
driver_probe_device(drv, dev);-》really_probe(dev, drv);
really_probe(struct device *dev, struct device_driver *drv)
{
dev->driver = drv;
if (dev->bus->probe) {
ret = dev->bus->probe(dev);
}
else if (drv->probe) {
ret = drv->probe(dev);
}
//绑定驱动,dev的p拥有的drv节点插入到drv的成员变量p的dev链表中,被drv管理起来。
//最后实现klist_add_tail(&dev->p->knode_driver, &dev->driver->p->klist_devices);
driver_bound(dev);
}
- 正常情况下__device_attach_driver返回0,目的bus_for_each_drv里面循环不退出取每个drv进行匹配,假如一个返回不等于的值,取drv跳出循环,后面的drv也就不匹配了。
- bus_for_each_drv的返回值还和driver_probe_device的返回值有关而driver_probe_device 最底层和probe相关一般正常调用执行返回也是0,所以当这个设备与之相对应的驱动匹配成功并执行probe后正常是不会退出的。继续取drv与之匹配判断。
同样下面drv的注册也是基于bus上面dev已有的情况下进行分析。drv注册函数大部分工作在bus_add_driver完成。
接口定义在\drivers\base\driver.c
int driver_register(struct device_driver *drv)-》bus_add_driver(drv);
int bus_add_driver(struct device_driver *drv)
{
struct bus_type *bus;
struct driver_private *priv;
priv = kzalloc(sizeof(*priv), GFP_KERNEL);//申请drv结构的成员变量p
//初始化p的dev链表,届时dev注册的时候,会吧dev挂载这个链表下面,被drv所管理起来。
klist_init(&priv->klist_devices, NULL, NULL);
priv->driver = drv;//p的drv指针指向这个drv
drv->p = priv;//drv的p指向这块申请的内存,
//上面这两个操作意思就是随便拿到p还是drv都能拿到对方的结构。
//把drv的p成员的bus节点,插到bus成员p的drv链表上,被bus管理起来,操作和dev的注册类似。
klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers);
error = driver_attach(drv);
}
driver_attach-》bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
int __driver_attach(struct device *dev, void *data)
{
//这个函数在dev注册的时候也出现过,调用bus的match接口用来判断这个dev和drv是否匹配。
ret = driver_match_device(drv, dev);
if (ret == 0) {
/* no match */
return 0;
} else if (ret < 0) {
dev_dbg(dev, "Bus failed to match device: %d", ret);
return ret;
} /* ret > 0 means positive match */
if (!dev->driver)
//这个函数同样在dev注册的时候也出现过,其作用,判断bus的probe假如有则调用,、
//否则在指向drv的probe,同时要把这个dev绑定到这个驱动上去,被这个驱动管理起来。
driver_probe_device(drv, dev);
return 0;
}
- bus_for_each_dev和drv注册的时候类似,取bus上的每一个dev和你这个驱动进行匹配。匹配的方法为 __driver_attach
- 正常情况下__driver_attach返回总是0目的为了bus_for_each_dev中的循环取dev节点。假如一个驱动注册时,bus的设备链表上一个匹配出错,后面的也就不匹配了。以前的内核版本的这个接口没有这个错误分支,总是返回0.
总的来说Linux中的代码确实很多,全部看懂实在不大可能,包括这篇文章中我也是删减了很多和主题不大相关的代码和内容。比如代码中有很多和调试相关和文件系统相关的内容我都没要贴,因为这些内容主要是在/sys/class/目录下的一些层次关系的管理,和本文的主题没什么太大的关联,所以看代码也一样,不是太相关的就直接略过,毕竟经历有限。而如何判断哪些是相关的,哪些是次要的,那就要好好的积累经验了,万事都有一个过程,你慢慢的熟悉了,接触的多了,就会有意识。
当初学习的时候也会去网上查资料,先看别人的理解,摸摸大体主干,然后自己再去看代码,验证是否理解正确。看了很多资料后,理论架构说的很清楚了,但心里还是有一个疑问,这套东西,用在什么地方,有什么好。说白了就是缺少理论和实际的结合。所以我后面还会在写一篇,介绍这个驱动模型的实例平台总线的运用。理论和实例的结合分析会让你更好的理解这个知识点。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。