linux支持的设备越来越多,种类越来越多,设备本身的功能也是越来越复杂,而操作系统内核必须有一种很有效的方式来管理这些设备,最起码的要控 制它们的开启关闭,更进一步要控制它们进行协同工作,实际上要内核仅仅做到这些并不难,关键问题是如何与用户进行交互,那么多设备怎么以统一的方式提供给 用户, 毕竟最终要控制设备的还是用户啊,在2.6内核中引出了一个叫做kobject的数据结构,它的作用和著名的list_head一样,只不过后者是一条环 链而它却是一棵树。学习2.6内核的驱动有两个意义:1.学会以后写个驱动;2.学习这一切的思想,作者为什么能想到这些。我自己写过一些驱动,根据经验 2.6的内核框架有两条线索,一条就是以kobject为中心往上走,一直和vfs相接直取用户空间;另一条就是内核内部的一些链表,底层意义上把设备分 类,按照设备的性质进行汇总。先看看第一条线索的基础设施: struct kobject { const char * k_name; char name[KOBJ_NAME_LEN]; struct kref kref; struct list_head entry; struct kobject * parent; struct kset * kset; struct kobj_type * ktype; struct dentry * dentry; wait_queue_head_t poll; }; 再看看第二条线索的基础设施: struct klist { spinlock_t k_lock; struct list_head k_list; void (*get)(struct klist_node *); void (*put)(struct klist_node *); }; struct klist_node { struct klist *n_klist; struct list_head n_node; struct kref n_ref; struct completion n_removed; }; 这
是最底层的数据结构了,你可以把它们当作“基类”,基类在面向对象的思想中就是什么也不做仅仅提供接口的类,它们更实质的意义是为管理设备提供了一个切入
点,这些数据结构主要是为了给上层一个统一的操作视图,不管是内核本身使用还是用户使用,可以参看sysfs,这样用户端的操作就靠kobject搞定
了,内核的操作就靠klist搞定。接下来设备本身怎么管理呢?所有的设备被分为“类”,叫class,比如一块via的声卡和一块realtek的声卡
就属于一个类,余下的就是一堆链表了,一条总线有两个重要链表,一条挂载所有设备,一条挂载所有驱动,类也有一个链表,挂载属于这个类的设备,所有的类串
成串,所有的总线也串成串,不要以为操作系统多复杂,基本就是一堆链表,那些在书上看到的十分牛叉的算法在内核基本是见不到的,作为一个统一的管理者,内
核数据结构和算法要在维护开销,自身开销,综合性能之间找到一个平衡点,比如说你把大量的技巧用到了内核的设备管理上了,那么结果有二,不是浪费空间就是
浪费时间,只要你有规则有技巧,规则和技巧越复杂你为之付出的代价就越大,这是个真理,结果用户的资源全被内核耗尽了,主次不分,因果倒置。 int device_add(struct device *dev) { struct device *parent = NULL; struct class_interface *class_intf; int error = -EINVAL; ... parent = get_device(dev->parent); setup_parent(dev, parent); if (parent)//下面的设置时第二条线索相关的 set_dev_node(dev, dev_to_node(parent)); //设置第一条线索,这是个基础,一切从kobject开始 error = kobject_add(&dev->kobj, dev->kobj.parent, "%s", dev->bus_id); ... ... //设置第一条线索,加入一个事件属性文件,以便用户空间写入数据可以触发内核的一些动作 error = device_create_file(dev, &uevent_attr); ... //设置第一条线索,加入其它属性文件,以便用户空间可以读取或更改设备的属性 if (MAJOR(dev->devt)) { error = device_create_file(dev, &devt_attr); if (error) goto ueventattrError; error = device_create_sys_dev_entry(dev); if (error) goto devtattrError; } //设置第一条线索,设备所属的类被导入sysfs,此处正是将设备加入sysfs的相应类 error = device_add_class_symlinks(dev); if (error) goto SymlinkError; error = device_add_attrs(dev);//设置第一条线索,将sysfs的类信息链入本设备 if (error) goto AttrsError; error = bus_add_device(dev); //设置第一条线索,将sysfs的总线信息链入本设备 if (error) goto BusError; error = dpm_sysfs_add(dev); if (error) goto DPMError; ... kobject_uevent(&dev->kobj, KOBJ_ADD); bus_attach_device(dev); //设置第二条线索,在总线上加入设备 if (parent)//设置第二条线索,将父子关系确定 klist_add_tail(&dev->knode_parent, ∥ent->klist_children); if (dev->class) {//设置第二条线索,将本设备加入相应的类别 mutex_lock(&dev->class->p->class_mutex); /* tie the class to the device */ list_add_tail(&dev->node, &dev->class->p->class_devices); list_for_each_entry(class_intf, &dev->class->p->class_interfaces, node) if (class_intf->add_dev) class_intf->add_dev(dev, class_intf); mutex_unlock(&dev->class->p->class_mutex); } ... } 第二条线索的关键操作就是bus_attach_device
了,它本质上就是把本设备加入它所属总线的klist,然后总线会遍历它的另一个klist驱动链表来寻找能驱动本设备的驱动程序,如果找到便开始
probe本设备。相应的在driver_register的时候会将driver加入bus的driver链表,然后遍历设备链表看它能驱动哪些设备,
要注意的是,虽然device和driver在bus的角度看是如此对称,实际上它们是不对称的,因为device才是我们要管理的对象,driver的
出现就是为了我们的设备可以工作,所以代码中driver_register远远没有device_register复杂,因为它只需要设置第一条线索和
部分第二条线索就可以了。 |
|