Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1357245
  • 博文数量: 281
  • 博客积分: 8800
  • 博客等级: 中将
  • 技术积分: 3346
  • 用 户 组: 普通用户
  • 注册时间: 2006-05-17 22:31
文章分类

全部博文(281)

文章存档

2013年(1)

2012年(18)

2011年(16)

2010年(44)

2009年(86)

2008年(41)

2007年(10)

2006年(65)

我的朋友

分类: LINUX

2009-06-22 21:41:02

Linux设备模型


一、底层数据结构:kobject,kset
2.6内核引入了sysfs文件系统,与proc, devfs, devpty同类别,属于虚拟的文件系统。目的是展示设备驱动模型中各组件的层次关系,第一层目录:block, device, bus, drivers, class, power, firmware.
block 块设备;devices 系统所有的设备并根据设备挂接的总线类型组织成层次结构;bus 系统所有的总线类型;drivers 内核中所有已经注册的设备驱动程序;class 系统中的设备类型(如网卡 设备、声卡设备、输入设备等)。
在/sys/bus下和/sys/bus/下也会有设备文件,但那只是符号链接,它指向/sys/devices/下的真实设备。此即为Linux设备模型。

2   kobject是构成Linux2.6设备模型的核心结构,每个在内核中注册的kobject对象都对应于sysfs文件系统的一个目录。
kobject_add()用于将kobject对象加入linux设备层次,挂载该对象到kset的list链表中,增加父目录各级kobject的引用计数,在其parent指向的目录下创建文件节点,并启动该类型内核对象的hotplug函数。
结合面向对象的思维。这个kobject属于最基础的结构,也就是最高抽象层(有点像java中的Cobject类)。任何一个设备模型如总线,设备,驱动都属于一个kobject 。在实现上这种派生关系就是在结构体中包含一个kobject的变量。类似于C++中的基类,它嵌入于更大的对象的对象中--所谓的容器--用来描述设备模型的组件。如bus,devices, drivers 都是典型的容器。这些容器就是通过kobject连接起来了,形成了一个树状结构。这个树状结构就与/sys向对应。
这个在层次上处理最顶层的kobject结构提供了所有模型需要的最基本的功能:
1 对象的引用计数---跟踪对象的生命周期
2 sysfs---在sysfs中出现的每个对象都有一个kobject
3 数据结构粘合---各级数据结构之间的连接
4 热插拔事件处理---kobject子系统处理事件的产生,事件通知用户空间关于系统中硬件的来去

Kobjects 在内核中对应有一套申请,初始化,添加,注册,计数操作,释放等函数
struct kobject {
 const char *name;
 struct list_head entry;//用于连接到同类kobjects的链表
 struct kobject *parent;//用于实现层次,指向其父对象
 struct kset *kset;//用于实现层次,所属的集合
 struct kobj_type *ktype;//指向对象的类型
 struct kref  kref;//计数
...
};//kernel 2.6.29.1/include/linux/kobject.h

3  kobject通常通过kset组织成层次化的结构,包含在kset中的所有kobject被组织成一个双向循环链表, kset是具有相同类型的kobject的集合。与kobject相似,linux提供了一系统函数来操作kset。
Kset 在概念上是一个集合或者叫容器。实现了对象的层次。所有属于一个ksets的对象(kobject)的parent都指向该ksets的kobj.同时这个对象都连接到kset 的list表上。


kset最重要的是建立上层(sub-system)和下层的(kobject)的关联性。kobject 也会利用它了分辨自已是属于那一個类型,然後在/sys 下建立正确的目录位置。而kset 的优先权比较高,kobject会利用自已的*kset 找到自已所属的kset,並把*ktype 指定成該kset下的ktype,除非沒有定义kset,才会用ktype來建立关系。Kobject通过kset组织成层次化的结构,kset是具有相同类型的kobject的集合,在内核中用

kset数据结构表示,定义为:
struct kset {
 struct list_head list;
 spinlock_t list_lock;
 struct kobject kobj;//自身的kobjects
 struct kset_uevent_ops *uevent_ops;
};//kernel 2.6.29.1/include/linux/kobject.h

最后 属于同一个集合的对象可以拥有共同的属性:ktype 。
struct kobj_type {
 void (*release)(struct kobject *kobj);
 struct sysfs_ops *sysfs_ops;
 struct attribute **default_attrs;
};//kernel 2.6.29.1/include/linux/kobject.h

所谓的属性更具体一点说就是一些键值对。并且在sysfs_ops中的show函数被文件系统调用来显示sys/下面对应入口各属性的值。

如此 ,kobjects与ksets实现层次树的底层骨架。
进一步地,通过封装这些底层结构来实现上层的设备驱动模型。
内核设备驱动模型层次划分三个方面:总线,设备,驱动。

4  subsystem是一系列kset的集合,描述某一类设备子系统,如block_subsys表示所有的块设备。所有挂接到同一subsystem的kset共享同一个rwsem信号量,用于同步访问kset中的链表。内核也提供了一系列操作subsystem的函数。
subsystem与kset的区别就是多了一个信号量,所以在后来的代码中,subsystem已经完全被kset取缔了。linux kernel 2.6.29.1里就没有。


二、linux设备模型层次关系:bus_type,device,device_driver

1  系统中的任一设备在设备模型中都有一个device对象描述。通常不单独使用,而是包含在一个更大的结构体中。
系统中每个驱动程序都有一个device_driver对象描述。
系统中总线由struc bus_type描述。每个bus_type对象都对应/sys/bus目录下的一个子目录,如PCI对应/sys/bus/pci,每个这样的目录下都存在2个子目录:devices/, drivers/,分别对应于bus_type结构体中的devices和drivers域。其中devices子目录描述连接在该总线上的所有设备,而drivers则描述与该总线关联的所有驱动程序。
struct class 描述某一类设备,所有的class对象都属于class_subsys子系统,对应于/sys/class目录。每个class对象包括一个class_device链表,每个class_device对象表示一个逻辑设备,并通过struct class_device中的dev成员(一个指向struct device的指针)关联一个物理设备。这样,一个逻辑设备总对应于一个物理设备,但一个物理设备却可能对应多个逻辑设备。

2 基本关系简要的概括如下:
驱动核心可以注册多种类型的总线。
每种总线下面可以挂载许多设备。(通过kset devices)
每种总线下可以用很多设备驱动。(通过包含一个kset drivers)}
每个驱动可以处理一组设备。

这种基本关系的建立源于实际系统中各种总线,设备,驱动结构的抽象。

下面看看三者数据结构的定义。

首先是总线,bus_type.
struct bus_type {
 const char  * name;
 struct subsystem subsys;//代表自身
 struct kset  drivers;   //当前总线的设备驱动集合
 struct kset  devices; //所有设备集合
 struct bus_attribute * bus_attrs;//总线属性
 struct device_attribute * dev_attrs;//设备属性
 struct driver_attribute * drv_attrs;//驱动属性
 int  (*match)(struct device * dev, struct device_driver * drv);//设备驱动匹配函数
 int  (*uevent)(struct device *, char **envp, int num_envp, char *buf, int buf_size);//热拔插事件
...
};

下面是设备device的定义:
struct device {
struct device *parent; //指向它所附着到的设备,通常是总线或主控制器
struct kobject kobj;  //自己。device->kobj->parent 通常等同于 device->parent->kobj.
char bus_id[BUS_ID_SIZE];  //唯一确定这个总线上的设备的字符串
struct bus_type *bus;  //确定设备位于哪种总线
struct device_driver *driver;  //管理这个设备的驱动
void *driver_data;  //一个可能被设备驱动使用的私有数据成员
void (*release)(struct device *dev);  //当对这个设备的最后引用被去除时调用的方法
...
};

下面是设备驱动定义:
struct device_driver {
 const char  * name;
 struct bus_type  * bus;//所属总线,驱动使用的总线类型
 struct kobject  kobj;//代表自身
 struct list_head devices;//当前绑定到这个驱动的所有设备的列表。之前说过,一个驱动可以处理一组设备
 int (*probe) (struct device * dev);//被调用来查询一个特定设备的存在(以及这个驱动是否可以使用它)
 int (*remove) (struct device * dev);
 void (*shutdown) (struct device * dev);
...
};

下面通过PCI驱动中设备模型的实例来看看细节。源码来自linux kernel 2.6.29.1

三、PCI设备驱动模型实例及设备,设备驱动注册源码的简单分析。围绕总线、设备、驱动程序三方面。

先看pci总线类型定义:
struct bus_type pci_bus_type = { //bus_type类型前面已定义,此时定义了一个对象(实例)pci_bus_type,后面__pci_register_driver()里用到
 .name  = "pci",
 .match  = pci_bus_match,
 .uevent  = pci_uevent,
 .probe  = pci_device_probe,
 .remove  = pci_device_remove,
 .shutdown = pci_device_shutdown,
 .dev_attrs  = pci_dev_attrs,
 .pm  = PCI_PM_OPS_PTR,
};//kernel 2.6.29.1/drivers/pci/pci-driver.c

当 PCI 子系统通过对 bus_register 的调用被加载入内核时,这个 pci_bus_type 变量被注册到驱动内核. 当这个发生时, 驱动核心创建一个 sysfs 目录在 /sys/bus/pci 里, 它包含 2 个目录: devices 和 drivers.

然后是pci设备和驱动。pci设备和pci驱动没有直接使用device和device_driver,而是将二者封装起来,加上pci特定信息构成pci_dev和pci_driver。当然,意义是一样的。


编写驱动的第一步可能就是定义你的设备、对应驱动、相关操作函数,也即xxx_device, xxx_driver, xxx_operations,当然,名字不会就是这个样子,这里只是抽象一下,具体的还得根据具体情况来定义,如S3C2410下的UART驱动,它的情况可能是struct s3c24xx_uart_port, struct uart_dirver, struct uart_ops,然后分别用这3个类型定义实例。对于pci,可能就是struct pci_dev, struct pci_driver, struct pci_ops,然后分别根据需要来定义实例。

//没有直接用device 类型来定义一个PCI的设备对象,而是封装了它

struct pci_dev {   

 struct list_head bus_list; /* node in per-bus list */
 struct pci_bus *bus;  /* bus this device is on */  //所属pci总线
 struct pci_bus *subordinate; /* bus this device bridges to */

 void  *sysdata; /* hook for sys-specific extension */
 struct proc_dir_entry *procent; /* device entry in /proc/bus/pci */
 struct pci_slot *slot;  /* Physical slot this device is in */

 unsigned int devfn;  /* encoded device & function index */
 unsigned short vendor;
 unsigned short device;
 unsigned short subsystem_vendor;
 unsigned short subsystem_device;
 unsigned int class;  /* 3 bytes: (base,sub,prog-if) */
 u8  revision; /* PCI revision, low byte of class word */
 u8  hdr_type; /* PCI header type (`multi' flag masked out) */
 u8  pcie_type; /* PCI-E device/port type */
 u8  rom_base_reg; /* which config register controls the ROM */
 u8  pin;    /* which interrupt pin this device uses */

 struct pci_driver *driver; /* which driver has allocated this device */  //所属的pci驱动
 struct device dev;  /* Generic device interface */ //设备自身
 /* ... */ 
}; //kernel 2.6.29.1/include/linux/pci.h

当一个PCI 设备被发现, PCI 核心在内存中创建一个 struct pci_dev 类型的新变量。这个 PCI 设备的总线特定的成员被 PCI 核心初始化( devfn, vendor, device, 和其他成员), 并且 struct device 变量的 parent 变量被设置为 PCI 总线设备(注意总线也不仅有一个bus_type 结构,还对应一个设备device) bus 变量被设置指向 pci_bus_type 结构. 接下来 name 和 bus_id 变量被设置, 根据读自 PCI 设备的 name 和 ID.

在 PCI 设备结构被初始化之后, pci设备被注册到驱动核心, 调用 device_register(&dev->dev); 在device_register函数中,kobject被注册到驱动核心,pci设备被添加到pci总线的设备列表中,热拔插事件产生,同时kobject被添加到parent的链表中,sysfs入口也被添加。

前面我们说编写驱动的第一步可能就是定义你的设备,它会继承struct device,从而引出了device的结果,那么对于操作device的函数:device_register,它又在哪儿会被用到呢?这里列举了pci目录下用到的3个地方:

struct hotplug_slot_ops ibmphp_hotplug_slot_ops = {
 .enable_slot =   enable_slot,
...
}
enable_slot() -> ibm_configure_device() -> pci_do_scan_bus() -> pci_bus_add_devices() -> pci_bus_add_child() -> device_register()

pci_scan_bus_parented() -> pci_create_bus() -> device_register()


static struct pci_driver pcie_portdriver = {
 .probe  = pcie_portdrv_probe,
 .remove  = pcie_portdrv_remove,
 ...
};
pcie_portdrv_probe() -> pcie_port_device_register() -> device_register()

这里定义的pci_driver结构,后面马上就要提到。

PCI设备的发现是通过特定代码探测PCI空间来实现的。PCI设备由内核自动生成的。这样在注册pci驱动的时候PCI设备已经注册,其属性如ID的信息都已经是被初始化好了。

struct pci_driver { //__pci_register_driver()里会初始化
 struct list_head node;
 char *name;
 const struct pci_device_id *id_table; /* must be non-NULL for probe to be called */  /* 驱动支持的设备ID列表 */
 int  (*probe)  (struct pci_dev *dev, const struct pci_device_id *id); /* New device inserted */
 void (*remove) (struct pci_dev *dev); /* Device removed (NULL if not a hot-plug capable driver) */
 int  (*suspend) (struct pci_dev *dev, pm_message_t state); /* Device suspended */
 int  (*suspend_late) (struct pci_dev *dev, pm_message_t state);
 int  (*resume_early) (struct pci_dev *dev);
 int  (*resume) (struct pci_dev *dev);                 /* Device woken up */
 void (*shutdown) (struct pci_dev *dev);
 struct pci_error_handlers *err_handler;
 struct device_driver driver; //设备驱动
 struct pci_dynids dynids;
};//kernel 2.6.29.1/include/linux/pci.h

 这里列出了pci_bus,pci_dev,pci_driver的定义。它们的关系与bus,device,driver一样。pci_bus直接是一个bus_type结构初始化的实体。
pci_dev由内核探测,并且注册到驱动核心。pci设备的初始化和注册分两个方面,一是pci设备信息如ID,资源等,二是pci_dev.dev的注册。调用register_device(struct  device * dev)来完成。
pci_driver一般由模块定义并且在模块初始化函数中向内核注册。也要两个方面,一是pci_driver中特定于PCI的方法,支持的ID列表等的初始化;二是内嵌的device_driver的注册,使用register_driver(struct device_driver * drv)。
这就有点像面向对象中子类与父类的关系,子类构造函数的调用隐含父类构造函数的调用。

没有register_device(dev)和register_driver(drv)的注册,驱动核心就不知道设备和驱动的存在,sysfs也没有相关的入口。
现在来看看register_device(dev)和register_driver(drv)的代码。

int device_register(struct device *dev)
{
 device_initialize(dev);
 return device_add(dev);
}

void device_initialize(struct device *dev)
{
 kobj_set_kset_s(dev, devices_subsys); //所有的dev属于devices_subsys这个集合
 kobject_init(&dev->kobj); //初始kobj
 klist_init(&dev->klist_children, klist_children_get,
 klist_children_put);
 INIT_LIST_HEAD(&dev->dma_pools);
 INIT_LIST_HEAD(&dev->node);
 init_MUTEX(&dev->sem);
 device_init_wakeup(dev, 0);
}

int device_add(struct device *dev) //主要流程
{
 dev = get_device(dev);
 parent = get_device(dev->parent);
 kobject_set_name(&dev->kobj, "%s", dev->bus_id);
 dev->kobj.parent = &parent->kobj;
 kobject_add(&dev->kobj);//将自身kobject加入到层次结构中,并且建立sysfs entry.
 
 //设置uevent_attr:
 dev->uevent_attr.attr.name = "uevent";
 dev->uevent_attr.attr.mode = S_IWUSR;
  
 //建立显示设备号的sysfs入口,即当前设备入口下的"dev"文件显示设备主从设备号,可到/sys/devices/目录下查看具体情况
 if (MAJOR(dev->devt)) {
  attr->attr.name = "dev";
  attr->attr.mode = S_IRUGO;
  if (dev->driver)
  attr->attr.owner = dev->driver->owner;
  attr->show = show_dev;
  error = device_create_file(dev, attr);
 }

 //建立类的sysfs符号连接 
 if (dev->class) {
  sysfs_create_link(&dev->kobj, &dev->class->subsys.kset.kobj,"subsystem");
  sysfs_create_link(&dev->class->subsys.kset.kobj, &dev->kobj,dev->bus_id);}
  sysfs_create_link(&dev->kobj, &dev->parent->kobj, "device");
  class_name = make_class_name(dev->class->name, &dev->kobj);
  sysfs_create_link(&dev->parent->kobj, &dev->kobj, class_name);
 }
 
 error = bus_add_device(dev);//添加一些bus相关的sysfs符号连接
 
 /*设置环境变量,然后调用call_usermodehelper (argv[0], argv, envp, 0); 引起热拔插事件用户空间脚本执行。*/
 kobject_uevent(&dev->kobj, KOBJ_ADD);
 
 bus_attach_device(dev); /*如果dev->driver已经存在,调用device_bind_driver(dev)进行绑定,否则遍历dev->bus上drivers列表,调用dev->bus.match(dev,drv)来看是否有一个驱动与该dev匹配。如果匹配则绑定。*/
}

设备被添加到总线特定的列表中,  接着注册到这个总线的所有驱动的列表被检查, 并且总线的匹配功能被调用给每个驱动, 指定这个设备. 对于 pci_bus_type 总线, 在设备被提交给驱动核心前,匹配函数被 PCI 核心设定为指向 pci_bus_match 函数。
pci_bus_match 函数转换驱动核心传递给它的 struct device 为一个 struct pci_dev. 它还转换 struct device_driver 为一个 struct pci_driver , 并接着查看设备的 PCI 设备特定信息和驱动, 看是否这个驱动声明它能够支持这类设备. 如果匹配不成功, 函数返回 0 给驱动核心, 并且驱动核心移向列表中的下一个驱动.
如果匹配成功, 函数返回 1 给驱动核心. 这使驱动核心设置struct device 中的驱动指针指向这个驱动, 并且接着调用在 struct device_driver 中特定的 probe 函数.
早些时候, 在 PCI 驱动注册到驱动核心之前, probe 变量被设为指向 pci_device_probe 函数. 这个函数转换 struct device 为一个struct pci_dev, 在设备中设置的 struct driver 为一个 struct pci_driver. 它再次验证这个驱动声明它可以支持这个设备( 这意味着一个重复的额外检查, 某些未知的原因), 递增设备的引用计数, 并且接着调用 PCI 驱动的 probe 函数, 用一个指向它应当被绑定到的 struct pci_dev 结构的指针.
如果由于某些原因这个 PCI 驱动的 probe 函数认为它不能处理这个设备, 它返回一个负的错误值, 这个值被传递回驱动核心并且使它继续深入设备列表来和这个设备匹配一个. 如果这个 probe 函数能够认领这个设备, 它做所有的需要的初始化来正确处理这个设备, 并且接着它返回 0 给驱动核心. 这使驱动核心来添加设备到当前被这个特定驱动所绑定的所有设备列表, 并且创建一个符号连接到这个它现在控制的设备, 在这个驱动在 sysfs 的目录. 这个符号连接允许用户准确见到哪个设备被绑定到哪个设备.

一个例子是pci目录下store_new_id()函数会调用driver_attach(),该store函数用来添加一个新的pci设备号到driver并重新探测所有的devices。

下面是register_driver(drv)函数:

int driver_register(struct device_driver * drv)
{
 ..
 klist_init(&drv->klist_devices, klist_devices_get, klist_devices_put);
 init_completion(&drv->unloaded);
 return bus_add_driver(drv);
}

int bus_add_driver(struct device_driver * drv)
{
 struct bus_type * bus = get_bus(drv->bus);
 
 error = kobject_set_name(&drv->kobj, "%s", drv->name);
 drv->kobj.kset = &bus->drivers; //驱动隶属于总线的驱动集合
 error = kobject_register(&drv->kobj);//注册自身kobject
 
 driver_attach(drv);//添加驱动到总线
 klist_add_tail(&drv->knode_bus, &bus->klist_drivers);
 module_add_driver(drv->owner, drv);
 
 driver_add_attrs(bus, drv);
 add_bind_files(drv);
}

void driver_attach(struct device_driver * drv)
{
 bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
}

对总线上的每个设备dev,调用__driver_attach(dev,drv);最终调用
driver_probe_device(drv, dev);

int driver_probe_device(struct device_driver * drv, struct device * dev)
{
 if (drv->bus->match && !drv->bus->match(dev, drv))
  goto Done;//优先调用总线提供匹配方法
 
 dev->driver = drv;
 if (dev->bus->probe) {
  ret = dev->bus->probe(dev);//总线的探测方法
 }
 else if (drv->probe)
 {
  ret = drv->probe(dev); //用dev->driver的探测方法
 }
 device_bind_driver(dev); /*探测成功则绑定设备到驱动,添加dev到drv的设备列表并且建立驱动与设备在sys/入口中相互关联的符号连接*/
 
 goto Done;
 
Done:
 return ret;
}

参考文献:
1 Linux设备模型 学习总结
http://blog.mcuol.com/User/lvembededsys/Article/6820_1.htm
2 Linux那些事儿 之 我是SysfsLinux内核中设备模型sysfs分析
http://blog.csdn.net/fudan_abc/category/332148.aspx
3 《Linux设备驱动开发详解》(宋宝华)
4《LDD3》

推荐博文:

1 Linux下PCI设备驱动程序开发   
http://www.ibm.com/developerworks/cn/linux/l-pci/index.html
2 浅析linux 2.6.23驱动自动匹配设备driver_attach()函数
http://blog.chinaunix.net/u1/38994/showart_407237.html

阅读(1130) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~