努力成为linux kernel hacker的人李万鹏原创作品,转载请标明出处
http://blog.youkuaiyun.com/woshixingaaa/archive/2011/05/05/6396620.aspx
Linux设备模型是由总线(bus_type),设备(device),驱动(device_driver)这三个数据结构来描述的。在设备模型中,所有的设备都通过总线来连接。即使有些设备没有连接到一根物理上的总线,Linux为其设置了一个内部的,虚拟的platform总线,来维持总线,驱动,设备的关系。总线是处理器与一个或者多个设备之间的通道。比如一个USB控制器通常是一个PCI设备,设备模型展示了总线和他们所控制的设备之间的连接。
一般来说可以这么理解,整个的设备模型是一个OO的体系结构,总线,设备,驱动都是其中鲜活存在的对象,kobject是他们的基类,所实现的只是一些公共的接口,kset是同种类型的kobject对象的集合,也可以说是对象的容器。只是因为C语言里不可能会有C++语言里类的class继承等概念,只有通过kobject嵌入到对象结构中来实现。这样,内核使用kobject将各个对象连接起来组成一个分层的体系结构。kobject结构中包含了parent成员,指向了另一个kobject结构,也就是这个分层结构的上一层结点。而kset是通过链表来实现的。
kobject是Linux在2.6中新引进的统一的设备管理模型,目的是对Linux的2.6系统所有的设备进行统一的管理。kobject是组成设备模型的基本结构。kobject是驱动程序模型中的一个核心数据结构,与sysfs文件系统自然的绑定在一起:——每个kobject对应sysfs文件系统中的一个目录。kobject往往被嵌入到设备驱动程序模型中的组件中,如总线,设备和驱动程序的描述符。kobject的作用是,为所属“容器”提供
.引用计数器
.维持容器的层次列表或组
.为容器的属性提供一种用户态查看的视图
kset是同类型kobject结构的一个集合体,通过kset数据结构可将kobjects组成一棵层次树。
struct bus_type { const char *name; //总线类型的名称 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 *dev, struct kobj_uevent_env *env); 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 (*suspend_late)(struct device *dev, pm_message_t state); int (*resume_early)(struct device *dev); int (*resume)(struct device *dev); struct dev_pm_ops *pm; struct bus_type_private *p; }; struct bus_type_private { struct kset subsys; //该总线的subsystem struct kset *drivers_kset; //所有与该总线相关的驱动集合 struct kset *devices_kset; //所有挂接在该总线上的设备集合 struct klist klist_devices; struct klist klist_drivers; struct blocking_notifier_head bus_notifier; unsigned int drivers_autoprobe:1; struct bus_type *bus; }; struct bus_attribute { struct attribute attr; ssize_t (*show)(struct bus_type *bus, char *buf); ssize_t (*store)(struct bus_type *bus, const char *buf, size_t count); };
subsys描述该总线的子系统,subsys是一个kset结构,他连接到一个全局变量kset bus_subsys中。这样,每一根总线系统都会通过bus_subsys结构连接起来。kset *devices_kset是指向该总线所有设备的集合的指针,kset *drivers_kset是指向该总线所有驱动的集合的指针。该总线上的设备和驱动分别用一个链表连接在一起,分别是klist_devices,klist_drivers。每次都用kset *drivers_kset,kset *devices_kset遍历所有设备/驱动很麻烦,用klist比较直接方便。
struct device { struct klist klist_children; struct klist_node knode_parent; /* node in sibling list */ struct klist_node knode_driver; struct klist_node knode_bus; struct device *parent; struct kobject kobj; char bus_id[BUS_ID_SIZE]; /* position on parent bus */ unsigned uevent_suppress:1; const char *init_name; /* initial name of the device */ struct device_type *type; struct semaphore sem; /* semaphore to synchronize calls to * its driver. */ struct bus_type *bus; /* type of bus device is on */ struct device_driver *driver; /* which driver has allocated this device */ void *driver_data; /* data private to the driver */ void *platform_data; /* Platform specific data, device core doesn't touch it */ struct dev_pm_info power; #ifdef CONFIG_NUMA int numa_node; /* NUMA node this device is close to */ #endif u64 *dma_mask; /* dma mask (if dma'able device) */ u64 coherent_dma_mask;/* Like dma_mask, but for alloc_coherent mappings as not all hardware supports 64 bit addresses for consistent allocations such descriptors. */ struct device_dma_parameters *dma_parms; struct list_head dma_pools; /* dma pools (if dma'ble) */ struct dma_coherent_mem *dma_mem; /* internal for coherent mem override */ /* arch specific additions */ struct dev_archdata archdata; dev_t devt; /* dev_t, creates the sysfs "dev" */ spinlock_t devres_lock; struct list_head devres_head; struct klist_node knode_class; struct class *class; struct attribute_group **groups; /* optional groups */ void (*release)(struct device *dev); }; struct device_private { struct klist klist_children; struct klist_node knode_parent; struct klist_node knode_driver; struct klist_node knode_bus; struct device *device; }; struct device_attribute { struct attribute attr; ssize_t (*show)(struct device *dev, struct device_attribute *attr, char *buf); ssize_t (*store)(struct device *dev, struct device_attribute *attr, const char *buf, size_t count); };
需要注意的是,总线也是设备,也必须按设备注册。这里的parent是指该设备所属的父设备,struct kobject kobj;表示该设备并把它连接到结构体系中的kobject。请注意,作为一个通用准则,device->kobj->parent与&device->parent->kobj是相同的。bus_id是在总线上唯一标识该设备的字符串。struct bus_type *bus;标识了该设备连接在何种类型的总线上。struct device_driver *driver;管理该设备的驱动。void (*release)(struct device *dev);当指向设备的最后一个引用被删除时,内核调用该方法。它将从内嵌的kobject的release方法中调用。device_private中的knode_parent,knode_driver,knode_bus分别是挂入parent,驱动,总线链表中的指针。
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 dev_pm_ops *pm; struct driver_private *p; }; struct driver_private { struct kobject kobj; struct klist klist_devices; //该驱动所管理的设备链表头 struct klist_node knode_bus; //挂入总线链表中的指针 struct module_kobject *mkobj; struct device_driver *driver; }; struct driver_attribute { struct attribute attr; ssize_t (*show)(struct device_driver *driver, char *buf); ssize_t (*store)(struct device_driver *driver, const char *buf, size_t count); };
name指向驱动的名字,上边的device中也有一个名为bus_id的字符数组。查看一下,struct bus_type中有一个match,函数,这个是干什么用的呢。设备有了驱动才可以工作,只有驱动没有设备也是不行,驱动和设备需要关联上,这就需要这个match函数。驱动和设备是通过name来管理的,所以在match函数中要比较device的bus_id和driver中的name是否相等。如果相等,就说明驱动和设备互相找到了,这时device_driver中的probe函数被调用。我下边的例子中是这样实现的:
static ssize_t show_driver_author(struct device_driver *driver, char *buf){ return snprintf(buf, PAGE_SIZE, "%s\n", author); }
下面是一个测试程序:
BUS:
#include <linux/module.h> #include <linux/init.h> #include <linux/string.h> #include <linux/device.h> #include <linux/kernel.h> static char *author = "LiWanPeng"; static ssize_t show_bus_author(struct bus_type *bus, char *buf){ return snprintf(buf, PAGE_SIZE, "%s\n", author); } void my_bus_release(struct device *dev){ printk(KERN_DEBUG "my bus release\n"); } static int virtual_bus_match(struct device *dev, struct device_driver *drv){ return !strncmp(dev->bus_id, drv->name, strlen(drv->name)); } struct bus_type virtual_bus = { .name = "my_bus", .match = virtual_bus_match, }; struct device my_bus = { .init_name = "my_bus0", .release = my_bus_release, }; EXPORT_SYMBOL(my_bus); EXPORT_SYMBOL(virtual_bus); static BUS_ATTR(author, S_IRUGO, show_bus_author, NULL); static int __init bus_init(void){ int ret; ret = bus_register(&virtual_bus); if(ret) return ret; if(bus_create_file(&virtual_bus, &bus_attr_author)) printk(KERN_NOTICE "Unable to create author attribute\n"); ret = device_register(&my_bus); if(ret) printk(KERN_NOTICE "Fail to register device\n"); printk("bus regiter success\n"); return ret; } static void __exit bus_exit(void){ bus_unregister(&virtual_bus); device_unregister(&my_bus); } module_init(bus_init); module_exit(bus_exit); MODULE_LICENSE("GPL");
DEVICE:
#include <linux/module.h> #include <linux/init.h> #include <linux/string.h> #include <linux/device.h> char *author = "LiWanPeng"; extern struct bus_type virtual_bus; extern struct device my_bus; static ssize_t show_device_author(struct device *dev, struct device_attribute *attr, char *buf){ return snprintf(buf, PAGE_SIZE, "%s\n", author); } void virtual_device_release(struct device *dev){ printk("virtual_device is released\n"); } struct device virtual_device ={ .bus_id = "my_dev", .bus = &virtual_bus, .parent = &my_bus, .release = virtual_device_release, }; static DEVICE_ATTR(author, S_IRUGO, show_device_author, NULL); static int __init device_init(void){ int ret; ret = device_register(&virtual_device); if(ret) return ret; if(device_create_file(&virtual_device, &dev_attr_author)) printk(KERN_NOTICE "Unable to create author attribute\n"); printk("device register success\n"); return ret; } static void __exit device_exit(void){ device_unregister(&virtual_device); } module_init(device_init); module_exit(device_exit); MODULE_AUTHOR("liwanpeng"); MODULE_LICENSE("GPL");
DRIVER:
#include <linux/module.h> #include <linux/init.h> #include <linux/string.h> #include <linux/device.h> #include <linux/kernel.h> extern struct bus_type virtual_bus; char *author = "LiWanPeng"; static ssize_t show_driver_author(struct device_driver *driver, char *buf){ return snprintf(buf, PAGE_SIZE, "%s\n", author); } int my_driver_remove(struct device *dev){ printk("driver is removed\n"); return 0; } int my_driver_probe(struct device *dev){ printk("driver can handle the device\n"); return 0; } struct device_driver virtual_driver = { .name = "my_dev", .bus = &virtual_bus, .probe = my_driver_probe, .remove = my_driver_remove, }; static DRIVER_ATTR(author, S_IRUGO, show_driver_author, NULL); static int __init my_driver_init(void){ int ret; ret = driver_register(&virtual_driver); if(ret) return ret; if(driver_create_file(&virtual_driver, &driver_attr_author)) printk(KERN_NOTICE "Unable to create author attribute\n"); printk("driver register success\n"); return ret; } static void __exit my_driver_exit(void){ driver_unregister(&virtual_driver); } module_init(my_driver_init); module_exit(my_driver_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("liwanpeng");
Makefile:
ifneq ($(KERNELRELEASE),) obj-m:= driver.o bus.o device.o else KERNELDIR ?= /lib/modules/$(shell uname -r)/build PWD := $(shell pwd) modules: $(MAKE) -C $(KERNELDIR) M=$(PWD) modules clear: rm -rf *.o endif
测试:
root@hacker:/home/hacker/program# dmesg [ 500.120888] bus regiter success [ 503.635832] device register success [ 515.237701] driver can handle the device [ 515.237772] driver register success root@hacker:/home/hacker/program# dmesg [ 627.552494] bus regiter success [ 631.652273] driver register success [ 641.867854] driver can handle the device [ 641.867861] device register success root@hacker:/sys/bus/my_bus/drivers/my_dev# ls -l total 0 -r--r--r-- 1 root root 4096 2011-05-06 22:46 author --w------- 1 root root 4096 2011-05-06 22:46 bind lrwxrwxrwx 1 root root 0 2011-05-06 22:46 my_dev -> ../../../../devices/my_bus0/my_dev --w------- 1 root root 4096 2011-05-06 22:46 uevent --w------- 1 root root 4096 2011-05-06 22:46 unbind root@hacker:/sys/bus/my_bus/drivers/my_dev# cat author LiWanPeng
参考:
《Linux设备驱动程序(第三版)》
《Linux那些事儿之我是USB》