`
xitonga
  • 浏览: 585690 次
文章分类
社区版块
存档分类
最新评论

Linux设备驱动模型

 
阅读更多

尽管LDD3中说对多数程序员掌握设备驱动模型不是必要的,但对于嵌入式Linux的底层程序员而言,对设备驱动模型的学习非常重要。

Linux设备模型的目的:为内核建立一个统一的设备模型,从而又一个对系统结构的一般性抽象描述。换句话说,Linux设备模型提取了设备操作的共同属性,进行抽象,并将这部分共同的属性在内核中实现,而为需要新添加设备或驱动提供一般性的统一接口,这使得驱动程序的开发变得更简单了,而程序员只需要去学习接口就行了。

在正式进入设备驱动模型的学习之前,有必要把documentation/filesystems/sysfs.txt读一遍(不能偷懒)。sysfs.txt主要描述/sys目录的创建及其属性,sys目录描述了设备驱动模型的层次关系,我们可以简略看一下/sys目录,


block:所有块设备

devices:系统所有设备(块设备特殊),对应struct device的层次结构

bus:系统中所有总线类型(指总线类型而不是总线设备,总线设备在devices下),bus的每个子目录都包含

--devices:包含到devices目录中设备的软链接

--drivers:与bus类型匹配的驱动程序

class:系统中设备类型(如声卡、网卡、显卡等)

fs:一些文件系统,具体可参考filesystems /fuse.txt中例子

dev:包含2个子目录

--char:字符设备链接,链接到devices目录,以<major>:<minor>命名

--block:块设备链接

Linux设备模型学习分为:Linux设备底层模型,描述设备的底层层次实现(kobject);Linux上层容器,包括总线类型(bus_type)、设备(device)和驱动(device_driver)。

====Linux设备底层模型 ====

谨记:像上面看到的一样,设备模型是层次的结构,层次的每一个节点都是通过kobject实现的。在文件上则体现在sysfs文件系统。

kobject结构

内核中存在struct kobject数据结构,每个加载到系统中的kobject都唯一对应/sys或者子目录中的一个文件夹。可以这样说,许多kobject结构就构成设备模型的层次结构。每个kobject对应一个或多个struct attribute描述属性的结构。

点击(此处)折叠或打开

  1. struct kobject{
  2. constchar*name;/*对应sysfs的目录名*/
  3. struct list_head entry;/*kobjetct双向链表*/
  4. struct kobject*parent;/*指向kset中的kobject,相当于指向父目录*/
  5. struct kset*kset;/*指向所属的kset*/
  6. struct kobj_type*ktype;/*负责对kobject结构跟踪*/
  7. struct sysfs_dirent*sd;
  8. struct kref kref;/*kobject引用计数*/
  9. unsignedintstate_initialized:1;
  10. unsignedintstate_in_sysfs:1;
  11. unsignedintstate_add_uevent_sent:1;
  12. unsignedintstate_remove_uevent_sent:1;
  13. unsignedintuevent_suppress:1;
  14. };


kobject结构是组成设备模型的基本结构,最初kobject设计只用来跟踪模块引用计数,现已增加支持,

——sysfs表述:在sysfs中的每个对象都有对应的kobject

—— 数据结构关联:通过链接将不同的层次数据关联

—— 热插拔事件处理:kobject子系统将产生的热插拔事件通知用户空间

kobject一般不单独使用,而是嵌入到上层结构(比如struct devicestruct device_driver)当中使用。kobject的创建者需要直接或间接设置的成员有:ktypeksetparentkset我们后面再说,parent设置为NULL时,kobject默认创建到/sys顶层目录下,否则创建到对应的kobject目录中。重点来分析ktype成员的类型,

点击(此处)折叠或打开

  1. #include<kobject.h>
  2. struct kobj_type{
  3. void(*release)(struct kobject*kobj);/*释放*/
  4. conststruct sysfs_ops*sysfs_ops;/*默认属性实现*/
  5. struct attribute**default_attrs;/*默认属性*/
  6. conststruct kobj_ns_type_operations*(*child_ns_type)(struct kobject*kobj);
  7. constvoid*(*namespace)(struct kobject*kobj);
  8. };


ktype包含了释放设备、默认属性以及属性的实现方法几个重要成员。每个kobject必须有一个release方法,并且kobject在该方法被调用之前必须保持不变(处于稳定状态)。默认属性的结构如下,

点击(此处)折叠或打开

  1. #include<linux/sysfs.h>
  2. struct attribute{
  3. constchar*name;/*属性名称*/
  4. mode_t mode;/*属性保护:只读设为S_IRUGO,可写设为S_IWUSR*/
  5. }


kobj_type中的default_attrs为二级结构指针,可以对每个kobject使用多个默认属性,最后一个属性使用NULL填充。struct sysfs_ops结构则如下,

点击(此处)折叠或打开

  1. struct sysfs_ops{
  2. ssize_t(*show)(struct kobject*,struct attribute*,char*);
  3. ssize_t(*store)(struct kobject*,struct attribute*,constchar*,size_t);
  4. };


show方法用于将传入的指定属性编码后放到char *类型的buffer中,store则执行相反功能:将buffer中的编码信息解码后传递给struct attribute类型变量。两者都是返回实际的属性长度。

一个使用kobject的简单例子如下,

点击(此处)折叠或打开

  1. #include<linux/module.h>
  2. #include<linux/init.h>
  3. #include<linux/device.h>
  4. #include<linux/string.h>
  5. #include<linux/sysfs.h>
  6. #include<linux/kernel.h>

  7. MODULE_AUTHOR("xhzuoxin");
  8. MODULE_LICENSE("Dual BSD/GPL");

  9. void my_obj_release(struct kobject*kobj)
  10. {
  11. printk("release ok.n");
  12. }

  13. ssize_t my_sysfs_show(struct kobject*kobj,struct attribute*attr,char*buf)
  14. {
  15. printk("my_sysfs_show.n");
  16. printk("attrname:%s.n",attr->name);
  17. sprintf(buf,"%s",attr->name);
  18. return strlen(attr->name)+1;
  19. }

  20. ssize_t my_sysfs_store(struct kobject*kobj,struct attribute*attr,constchar*buf,
  21. size_t count)
  22. {
  23. printk("my_sysfs_store.n");
  24. printk("write:%sn",buf);

  25. return count;
  26. }

  27. struct sysfs_ops my_sysfs_ops={
  28. .show=my_sysfs_show,
  29. .store=my_sysfs_store,
  30. };

  31. struct attribute my_attrs={
  32. .name="zx_kobj",
  33. .mode=S_IRWXUGO,
  34. };

  35. struct attribute*my_attrs_def[]={
  36. &my_attrs,
  37. NULL,
  38. };
  39. struct kobj_type my_ktype={
  40. .release=my_obj_release,
  41. .sysfs_ops=&my_sysfs_ops,
  42. .default_attrs=my_attrs_def,
  43. };

  44. struct kobject my_kobj;

  45. int__init kobj_test_init(void)
  46. {
  47. printk("kobj_test init.n");
  48. kobject_init_and_add(&my_kobj,&my_ktype,NULL,"zx");

  49. return 0;
  50. }

  51. void __exit kobj_test_exit(void)
  52. {
  53. printk("kobj_test exit.n");
  54. kobject_del(&my_kobj);
  55. }

  56. module_init(kobj_test_init);
  57. module_exit(kobj_test_exit);


例子中有两个函数,用于初始化添加和删除kobject结构,

点击(此处)折叠或打开

  1. intkobject_init_and_add(struct kobject*kobj,struct kobj_type*ktype,
  2. struct kobject*parent,constchar*fmt,...);/*fmt指定kobject名称*/
  3. void kobject_del(struct kobject*kobj);


加载模块后,在/sys目录下增加了一个叫zx达到目录,zx目录下创建了一个属性文件zx_kobj,使用tree /sys/zx查看。

内核提供了许多与kobject结构相关的函数,如下:

点击(此处)折叠或打开

  1. //kobject初始化函数
  2. void kobject_init(struct kobject*kobj);
  3. //设置指定kobject的名称
  4. intkobject_set_name(struct kobject*kobj,constchar*format,...);
  5. //将kobj 对象的引用计数加,同时返回该对象的指针
  6. struct kobject*kobject_get(struct kobject*kobj);
  7. //将kobj对象的引用计数减,如果引用计数降为,则调用kobject release()释放该kobject对象
  8. void kobject_put(struct kobject*kobj);
  9. //将kobj对象加入Linux设备层次。挂接该kobject对象到kset的list链中,增加父目录各级kobject的引//用计数,在其parent指向的目录下创建文件节点,并启动该类型内核对象的hotplug函数
  10. intkobject_add(struct kobject*kobj);
  11. //kobject注册函数,调用kobject init()初始化kobj,再调用kobject_add()完成该内核对象的注册
  12. intkobject_register(struct kobject*kobj);
  13. //从Linux设备层次(hierarchy)中删除kobj对象
  14. void kobject_del(struct kobject*kobj);
  15. //kobject注销函数.与kobject register()相反,它首先调用kobject del从设备层次中删除该对象,再调//用kobject put()减少该对象的引用计数,如果引用计数降为,则释放kobject对象
  16. void kobject_unregister(struct kobject*kobj);


kset结构

我们先看上图,kobject通过kset组织成层次化的结构,kset将一系列相同类型的kobject使用(双向)链表连接起来,可以这样 认为,kset充当链表头作用,kset内部内嵌了一个kobject结构。内核中用kset数据结构表示为:

点击(此处)折叠或打开

  1. #include<linux/kobject.h>
  2. struct kset{
  3. struct list_head list;/*用于连接kset中所有kobject的链表头*/
  4. spinlock_t list_lock;/*扫描kobject组成的链表时使用的锁*/
  5. struct kobject kobj;/*嵌入的kobject*/
  6. conststruct kset_uevent_ops*uevent_ops;/*kset的uevent操作*/
  7. };


kobject相似,kset_init()完成指定kset的初始化,kset_get()kset_put()分别增加和减少kset对象的引用计数。Kset_add()kset_del()函数分别实现将指定keset对象加入设备层次和从其中删除;kset_register()函数完成kset的注册而kset_unregister()函数则完成kset的注销。

==== 设备模型上层容器 ====

这里要描述的上层容器包括总线类型(bus_type)、设备(device)和驱动(device_driver),这3个模型环环相扣,参考图9-2。为何称为容器?因为bus_type/device/device_driver结构都内嵌了Linux设备的底层模型(kobject结构)。为什么称为上层而不是顶层?因为实际的驱动设备结构往往内嵌bus_type/device/device_driver这些结构,比如pciusb等。

总线类型、设备、驱动3者之间关系:

在继续之前,自我感觉需要区分2个概念:总线设备与总线类型。总线设备本质上是一种设备,也需要像设备一样进行初始化,但位于设备的最顶层,总线类型是一种在设备和驱动数据结构中都包含的的抽象的描述(如图9-2),总线类型在/sys/bus目录下对应实体,总线设备在/devices目录下对应实体。

总线类型bus_type

内核对总线类型的描述如下:

点击(此处)折叠或打开

  1. struct bus_type{
  2. constchar*name;/*总线类型名*/
  3. struct bus_attribute*bus_attrs;/*总线的属性*/
  4. struct device_attribute*dev_attrs;/*设备属性,为每个加入总线的设备建立属性链表*/
  5. struct driver_attribute*drv_attrs;/*驱动属性,为每个加入总线的驱动建立属性链表*/

  6. /*驱动与设备匹配函数:当一个新设备或者驱动被添加到这个总线时,这个方法会被调用一次或多次,若指定的驱动程序能够处理指定的设备,则返回非零值。必须在总线层使用这个函数,因为那里存在正确的逻辑,核心内核不知道如何为每个总线类型匹配设备和驱动程序*/
  7. int(*match)(struct device*dev,struct device_driver*drv);
  8. /*在为用户空间产生热插拔事件之前,这个方法允许总线添加环境变量(参数和 kset 的uevent方法相同)*/
  9. int(*uevent)(struct device*dev,struct kobj_uevent_env*env);
  10. int(*probe)(struct device*dev);/**/
  11. int(*remove)(struct device*dev);/*设备移除调用操作*/
  12. void(*shutdown)(struct device*dev);

  13. int(*suspend)(struct device*dev,pm_message_t state);
  14. int(*resume)(struct device*dev);

  15. conststruct dev_pm_ops*pm;

  16. struct subsys_private*p;/*一个很重要的域,包含了device链表和drivers链表*/
  17. };


接着对bus_type中比较关注的几个成员进行简述,

[1] struct bus_attribute结构,device_attributedriver_attribute将分别在设备和驱动分析过程中看到,

点击(此处)折叠或打开

  1. struct bus_attribute{
  2. struct attribute attr;
  3. ssize_t(*show)(struct bus_type*bus,char*buf);
  4. ssize_t(*store)(struct bus_type*bus,constchar*buf,size_t count);
  5. };


[2] subsys_private中包含了对加入总线的设备的链表描述和驱动程序的链表描述,省略的部分结构如下

点击(此处)折叠或打开

  1. struct subsys_private{
  2. struct kset subsys;
  3. struct kset*devices_kset;/*使用kset构建关联的devices链表头*/
  4. struct kset*drivers_kset;/*使用kset构建关联的drivers链表头*/
  5. struct klist klist_devices;/*通过循环可访问devices_kset的链表*/
  6. struct klist klist_drivers;/*通过循环可访问drivers_kset的链表*/
  7. struct bus_type*bus;/*反指向关联的bus_type结构*/
  8. ......
  9. };


bus_type通过扫描设备链表和驱动链表,使用mach方法查找匹配的设备和驱动,然后将struct device中的*driver设置为匹配的驱动,将struct device_driver中的device设置为匹配的设备,这就完成了将总线、设备和驱动3者之间的关联。

bus_type只有很少的成员必须提供初始化,大部分由设备模型核心控制。内核提供许多函数实现bus_type的注册注销等操作,新注册的总线可以再/sys/bus目录下看到。

点击(此处)折叠或打开

  1. struct bus_type ldd_bus_type={/*bus_type初始化*/
  2. .name="ldd",
  3. .match=ldd_match,/*方法实现参见实例*/
  4. .uevent=ldd_uevent,/*方法实现参见实例*/
  5. };
  6. ret=bus_register(&ldd_bus_type);/*注册,成功返回0*/
  7. if(ret)
  8. return ret;
  9. void bus_unregister(struct bus_type*bus);/*注销*/


设备device

设备通过device结构描述,

点击(此处)折叠或打开

  1. struct device{
  2. struct device*parent;/*父设备,总线设备指定为NULL*/
  3. struct device_private*p;/*包含设备链表,driver_data(驱动程序要使用数据)等信息*/
  4. struct kobject kobj;
  5. constchar*init_name;/*初始默认的设备名,但@device_add调用之后又重新设为NULL*/
  6. struct device_type*type;
  7. struct mutex mutex;/*mutextosynchronize callstoits driver*/
  8. struct bus_type*bus;/*type of bus deviceison*/
  9. struct device_driver*driver;/*which driver has allocated this device*/
  10. void*platform_data;/*Platform specific data,device core doesn't touch it*/
  11. struct dev_pm_info power;

  12. #ifdef CONFIG_NUMA
  13. intnuma_node;/*NUMA node this deviceiscloseto*/
  14. #endif
  15. u64*dma_mask;/*dma mask(ifdma'able device)*/
  16. u64 coherent_dma_mask;/*Like dma_mask,butfor
  17. alloc_coherent mappings as
  18. notall hardware supports
  19. 64 bit addressesforconsistent
  20. allocations such descriptors.*/
  21. struct device_dma_parameters*dma_parms;
  22. struct list_head dma_pools;/*dma pools(ifdma'ble)*/
  23. struct dma_coherent_mem*dma_mem;/*internalforcoherent mem override*/
  24. /*arch specific additions*/
  25. struct dev_archdata archdata;
  26. #ifdef CONFIG_OF
  27. struct device_node*of_node;
  28. #endif

  29. dev_t devt;/*dev_t,creates the sysfs"dev"设备号*/
  30. spinlock_t devres_lock;
  31. struct list_head devres_head;
  32. struct klist_node knode_class;
  33. structclass*class;
  34. conststruct attribute_group**groups;/*optional groups*/

  35. void(*release)(struct device*dev);
  36. };


设备在sysfs文件系统中的入口可以有属性,这通过struct device_attribute单独描述,提供device_create_file类型函数添加属性。

点击(此处)折叠或打开

  1. /*interfaceforexporting device attributes*/
  2. struct device_attribute{
  3. struct attribute attr;
  4. ssize_t(*show)(struct device*dev,struct device_attribute*attr,
  5. char*buf);
  6. ssize_t(*store)(struct device*dev,struct device_attribute*attr,
  7. constchar*buf,size_t count);
  8. };


使用宏DEVICE_ATTR宏可以方便地再编译时构建设备属性,构建好属性之后就必须将属性添加到设备。

点击(此处)折叠或打开

  1. /*最终生成变量dev_attr_##_name描述属性,
  2. *比如DEVICE_ATTR(zx,S_IRUGO,show_method,NULL);
  3. *则create_file中entry传入实参为dev_attr_zx*/
  4. DEVICE_ATTR(_name,_mode,_show,_store);
  5. /*属性文件的添加与删除使用以下函数*/
  6. intdevice_create_file(struct device*device,struct device_attribute*entry);
  7. void device_remove_file(struct device*dev,struct device_attribute*attr);


总线设备的注册:总线设备与一般设备一样,需要单独注册,与一般设备不同,总线设备的parentbus域设为NULL。一般设备注册注销函数为

点击(此处)折叠或打开

  1. intdevice_register(struct device*dev);/*成功返回0,需要检查返回值*/
  2. void device_unregister(struct device*dev);


实际创建新设备时,不是直接使用device结构,而是将device结构嵌入到具体的设备结构当中,比如


点击(此处)折叠或打开

  1. struct ldd_device{
  2. char*name;/*设备名称*/
  3. struct ldd_driver*driver;/*ldd设备关联的驱动*/
  4. struct device dev;/*嵌入的device结构*/
  5. };
  6. /*同时提供根据device结构获取ldd_device结构的宏定义*/
  7. #define to_ldd_device(dev)container_of(dev,struct ldd_device,dev);


驱动device_driver

驱动结构描述,

点击(此处)折叠或打开

  1. struct device_driver{
  2. constchar*name;/*驱动名称,在sysfs中以文件夹名出现*/
  3. struct bus_type*bus;/*驱动关联的总线类型*/
  4. struct module*owner;
  5. constchar*mod_name;/*usedforbuilt-inmodules*/
  6. bool suppress_bind_attrs;/*disables bind/unbind via sysfs*/

  7. #ifdefined(CONFIG_OF)
  8. conststruct of_device_id*of_match_table;
  9. #endif

  10. int(*probe)(struct device*dev);
  11. int(*remove)(struct device*dev);
  12. void(*shutdown)(struct device*dev);
  13. int(*suspend)(struct device*dev,pm_message_t state);
  14. int(*resume)(struct device*dev);
  15. conststruct attribute_group**groups;

  16. conststruct dev_pm_ops*pm;

  17. struct driver_private*p;
  18. };
  19. struct driver_private{/*定义device_driver中的私有数据类型*/
  20. struct kobject kobj;/*内建kobject*/
  21. struct klist klist_devices;/*驱动关联的设备链表,一个驱动可以关联多个设备*/
  22. struct klist_node knode_bus;
  23. struct module_kobject*mkobj;
  24. struct device_driver*driver;/*连接到的驱动链表*/
  25. };
  26. #define to_driver(obj)container_of(obj,struct driver_private,kobj)


与设备和总线类似,驱动可以有属性,需要单独定义并添加。

点击(此处)折叠或打开

  1. /*sysfs interfaceforexporting driver attributes*/
  2. struct driver_attribute{
  3. struct attribute attr;
  4. ssize_t(*show)(struct device_driver*driver,char*buf);
  5. ssize_t(*store)(struct device_driver*driver,constchar*buf,
  6. size_t count);
  7. };
  8. DRIVER_ATTR(_name,_mode,_show,_store);/*最终创建变量driver_attr_##_name描述属性*/
  9. /*属性文件创建的方法:*/
  10. intdriver_create_file(struct device_driver*drv,struct driver_attribute*attr);
  11. void driver_remove_file(struct device_driver*drv,struct driver_attribute*attr);


驱动的注册与注销

点击(此处)折叠或打开

  1. /*注册device_driver 结构的函数*/
  2. intdriver_register(struct device_driver*drv);
  3. void driver_unregister(struct device_driver*drv);


与设备结构一样,在编写新设备的驱动程序时,常常将device_driver结构嵌入到新设备结构当中使用。

====实例分析 ====

实例源代码主要来自LDD3提供的示例代码,因为LDD3的代码是linux-2.6.10版本,因此需要对源代码做一些修改。所有源代码参见:device_model.zip。因为两个模块关联,我们这使用一个Makefile文件同时编译2个模块,如下


点击(此处)折叠或打开

  1. obj-m:=lddbus.o sculld.o


lddbus模块分析

包括2个文件,lddbus.cexample/lddbus/)与lddbus.hexample/include/)。lddbus.h中使用extern申明了将要使用EXPORT_SYMBOL导出的变量ldd_bus_typelddbus.c中创建了总线类型ldd_bus_type以及总线设备ldd_bus

lddbus.h

-> extern ldd_bus_type

lddbus.c

-> ldd_bus_type (EXPORT_SYMBOL)

-> ldd_bus

由于版本变迁,对源代码做了修改,(i)热插拔不再使用hotplug函数,因此将该操作去掉了;(iidev->bus_id[]改成了使用dev_set_name()设置设备名称,使用init_name也可以设置,但后来发现init_name会在调用device_add之后就被赋值为NULL,这导致一个重大内核错误(kernel panic),将在后面详述。

分析源代码:作者定义了ldd_deviceldd_driver,两个变量分别内嵌devicedevice_driver结构,然后分别为ldd_device定义了注册函数register_ldd_device和注销函数unregister_ldd_device,对ldd_driver也做了类似的工作。还宏定义了to_ldd_driverto_ldd_device来使用内嵌结构(device/device_driver)访问更上层的容器ldd_deviceldd_driver。但是不用着急,实际模块装载时没有使用ldd_device或者ldd_driver,而是将它们和相关的注册注销等操作使用EXPORT_SYMBOL导出到其它模块使用(这将在实例sculld模块中看到)。

struct ldd_device/register_ldd_device/unregister_ldd_device

-> struct device/ device_register/device_unregister

-> to_ldd_device

struct ldd_driver也类似

LDD3Makefile中普遍使用了CFLAGS变量,但在新的内核版本中,该变量与内核MakefileCFLAGS变量冲突,因此将所有的MakefileCFLAGS变量替换成了EXTRA_CFLAGS

装载模块后,查看/sys/bus目录下,增加了ldd文件夹,/sys/devices目录下增加了ldd0文件夹。

sculld模块分析

sculld模块是接着lddbus在加载lddbus基础上进行的,sculld使用了lddbus中导出的ldd_deviceldd_driver结构。我们大致分析下总体的设备和驱动注册的调用关系,

scull_init()

->register_ldd_driver() //lddbus模块导出

->driver_register()

->sculld_register_dev()

->register_ldd_dev() //lddbus模块导出

->device_register()

装载程序后查看bus/ldd/devices目录下,bus/ldd/drivers目录下多了驱动程序,多了4个设备,devices/ldd0下也多了4个设备。

关于kernel panic错误

在修改lddbussculld中,装载sculld模块时遇到如下错误,同时键盘大写字母指示灯闪烁,操作系统被锁定,只能强制关机。现在记录分析及解决错误的过程,

从网上找到资料,kernel panic类型错误要跟踪信息,还好,使用的虚拟机,把出错的状态截屏了。kernnel panic错误分硬件和软件,一般是由于指针指向了NULL。硬件有EIP指示出错位置,如上图有一行

EIP:[<c06044d1>] strncmp+0x11/0x38

好了,strncmp就是指示出错位置,然后到源代码中找到使用该函数地方,出错前为

!strncmp(dev->init_name,driver->name,strlen(driver->name));

前面说过,dev->init_name在调用device_register之后就被设置为NULL了,好了,就是它了,改成如下(通过kobj访问设备名称)就OK

!strncmp(dev->kobj.name,driver->name,strlen(driver->name));

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics