现在的位置: 首页 > 综合 > 正文

谈谈linux2.6内核的驱动框架

2018年04月11日 ⁄ 综合 ⁄ 共 4543字 ⁄ 字号 评论关闭

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, &parent->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复杂,因为它只需要设置第一条线索和部分第二条线索就可以了。

在下一步就是沿着各自的线索开始一步一步前进了,我就不再分析了,最好的办法就是读源代码,源代码本身的更改变动很大,比如2.6.18版本有一个 class_eevice,为了设备分类,但是这是多余的,所以后面的版本将其去除了,所以分析源码并不难,关键是没有意义,实质性的东西不变,就是那两条线索,关于怎么实现却一直在变动,我都懒得跟踪了。关键问题,作者怎么想到这些的。

首先,要知道管理设施和被管理对象一定要分离,不要有任何双向关系,单向关系一定要保持一个,这就是著名的“好莱坞法则”,比如,klist永远不知道自己的属主是个设备还是个驱动,但是不管设备还是驱动都要有若干klist,想想看如果klist里面有一个设备寄存器信息,那就麻烦了,驱动就必须另实现 一套机制,然后考虑如何和带有寄存器信息的klist通信,事情会越来越糟糕。于是将设备和驱动还有总线结构提取公因子,再考虑它们需要什么样的操作,当 想到它们仅仅需要简单的链接关系的时候,klist就出来了,但是用户这边就没有那么简单了,用户需要的不是一个简单的链接关系,而是一个树形结构,于是
kobject和kset就出来了,这就是一切。
其实内核中处处在用这种方式来管理信息,著名的list_head就不说了,另外还有prio_tree,rb_tree等等都是。

抱歉!评论已关闭.