zoukankan      html  css  js  c++  java
  • 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.     const char *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.     unsigned int state_initialized:1;
    10.     unsigned int state_in_sysfs:1;
    11.     unsigned int state_add_uevent_sent:1;
    12.     unsigned int state_remove_uevent_sent:1;
    13.     unsigned int uevent_suppress:1;
    14. };


     

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

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

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

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

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

    点击(此处)折叠或打开

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


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

    点击(此处)折叠或打开

    1. #include <linux/sysfs.h>
    2. struct attribute {
    3.     const char *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 *,const char *, 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.  
    8. MODULE_AUTHOR("xhzuoxin");
    9. MODULE_LICENSE("Dual BSD/GPL");
    10.  
    11. void my_obj_release(struct kobject *kobj)
    12. {
    13.     printk("release ok.n");
    14. }
    15.  
    16. ssize_t my_sysfs_show(struct kobject *kobj, struct attribute *attr, char *buf)
    17. {
    18.     printk("my_sysfs_show.n");
    19.     printk("attrname:%s.n", attr->name);
    20.     sprintf(buf"%s", attr->name);
    21.     return strlen(attr->name+ 1;
    22. }
    23.  
    24. ssize_t my_sysfs_store(struct kobject *kobj, struct attribute *attrconst char *buf,
    25.        size_t count)
    26. {
    27.     printk("my_sysfs_store.n");
    28.     printk("write:%sn", buf);
    29.  
    30.     return count;
    31. }
    32.  
    33. struct sysfs_ops my_sysfs_ops {
    34.     .show = my_sysfs_show,
    35.     .store = my_sysfs_store,
    36. };
    37.  
    38. struct attribute my_attrs {
    39.     .name "zx_kobj",
    40.     .mode = S_IRWXUGO,
    41. };
    42.  
    43. struct attribute *my_attrs_def[{
    44.     &my_attrs,
    45.     NULL,
    46. };
    47. struct kobj_type my_ktype {
    48.     .release = my_obj_release,
    49.     .sysfs_ops &my_sysfs_ops,
    50.     .default_attrs = my_attrs_def,
    51. };
    52.  
    53. struct kobject my_kobj ;
    54.  
    55. int __init kobj_test_init(void)
    56. {
    57.     printk("kobj_test init.n");
    58.     kobject_init_and_add(&my_kobj&my_ktypeNULL"zx");
    59.  
    60.     return 0;
    61. }
    62.  
    63. void __exit kobj_test_exit(void)
    64. {
    65.     printk("kobj_test exit.n");
    66.     kobject_del(&my_kobj);
    67. }
    68.  
    69. module_init(kobj_test_init);
    70. module_exit(kobj_test_exit);


     

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

    点击(此处)折叠或打开

    1. int kobject_init_and_add(struct kobject *kobj, struct kobj_type *ktype,
    2.             struct kobject *parentconst char *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. int kobject_set_name(struct kobject *kobjconst char *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. int kobject_add(struct kobject * kobj);
    11. // kobject注册函数,调用kobject init()初始化kobj,再调用kobject_add()完成该内核对象的注册
    12. int kobject_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.     const struct 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这些结构,比如pci,usb等。

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

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

    总线类型bus_type

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

    点击(此处)折叠或打开

    1. struct bus_type {
    2.     const char *name/* 总线类型名 */
    3.     struct bus_attribute *bus_attrs/* 总线的属性 */
    4.     struct device_attribute *dev_attrs/* 设备属性,为每个加入总线的设备建立属性链表 */
    5.     struct driver_attribute *drv_attrs/* 驱动属性,为每个加入总线的驱动建立属性链表 */
    6.  
    7. /* 驱动与设备匹配函数:当一个新设备或者驱动被添加到这个总线时,这个方法会被调用一次或多次,若指定的驱动程序能够处理指定的设备,则返回非零值。必须在总线层使用这个函数, 因为那里存在正确的逻辑,核心内核不知道如何为每个总线类型匹配设备和驱动程序 */
    8.     int (*match)(struct device *dev, struct device_driver *drv)
    9. /*在为用户空间产生热插拔事件之前,这个方法允许总线添加环境变量(参数和 kset 的uevent方法相同)*/
    10.     int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
    11.     int (*probe)(struct device *dev)/*/
    12.     int (*remove)(struct device *dev)/* 设备移除调用操作 */
    13.     void (*shutdown)(struct device *dev);
    14.  
    15.     int (*suspend)(struct device *dev, pm_message_t state);
    16.     int (*resume)(struct device *dev);
    17.  
    18.     const struct dev_pm_ops *pm;
    19.  
    20.     struct subsys_private *p/* 一个很重要的域,包含了device链表和drivers链表 */
    21. };


     

     

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

    [1] struct bus_attribute结构,device_attribute与driver_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 *busconst char *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.     const char *init_name/* 初始默认的设备名,但@device_add调用之后又重新设为NULL */
    6.     struct device_type *type;
    7.     struct mutex mutex/* mutex to synchronize calls to its driver */
    8.     struct bus_type *bus/* type of bus device is on */
    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.  
    13. #ifdef CONFIG_NUMA
    14.     int numa_node/* NUMA node this device is close to */
    15. #endif
    16.     u64 *dma_mask/* dma mask (if dma'able device*/
    17.     u64 coherent_dma_mask;/* Like dma_mask, but for
    18.                        alloc_coherent mappings as
    19.                        not all hardware supports
    20.                        64 bit addresses for consistent
    21.                        allocations such descriptors*/
    22.     struct device_dma_parameters *dma_parms;
    23.     struct list_head dma_pools/* dma pools (if dma'ble*/
    24.     struct dma_coherent_mem *dma_mem/* internal for coherent mem override */
    25.     /* arch specific additions */
    26.     struct dev_archdata archdata;
    27. #ifdef CONFIG_OF
    28.     struct device_node *of_node;
    29. #endif
    30.  
    31.     dev_t devt/* dev_t, creates the sysfs "dev" 设备号 */
    32.     spinlock_t devres_lock;
    33.     struct list_head devres_head;
    34.     struct klist_node knode_class;
    35.     struct class *class;
    36.     const struct attribute_group **groups/* optional groups */
    37.  
    38.     void (*release)(struct device *dev)
    39. };


     

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

    点击(此处)折叠或打开

    1. /* interface for exporting 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.             const char *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. int device_create_file(struct device *device, struct device_attribute * entry);
    7. void device_remove_file(struct device * dev, struct device_attribute * attr);


     

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

    点击(此处)折叠或打开

    1. int device_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.     const char *name/* 驱动名称,在sysfs中以文件夹名出现 */
    3.     struct bus_type *bus/* 驱动关联的总线类型 */
    4.     struct module *owner;
    5.     const char *mod_name/* used for built-in modules */
    6.     bool suppress_bind_attrs/* disables bind/unbind via sysfs */
    7.  
    8. #if defined(CONFIG_OF)
    9.     const struct of_device_id *of_match_table;
    10. #endif
    11.  
    12.     int (*probe(struct device *dev);
    13.     int (*remove(struct device *dev);
    14.     void (*shutdown(struct device *dev);
    15.     int (*suspend(struct device *dev, pm_message_t state);
    16.     int (*resume(struct device *dev);
    17.     const struct attribute_group **groups;
    18.  
    19.     const struct dev_pm_ops *pm;
    20.  
    21.     struct driver_private *p;
    22. };
    23. struct driver_private /* 定义device_driver中的私有数据类型 */
    24.    struct kobject kobj/* 内建kobject */
    25.    struct klist klist_devices/* 驱动关联的设备链表,一个驱动可以关联多个设备 */
    26.    struct klist_node knode_bus;
    27.    struct module_kobject *mkobj;
    28.    struct device_driver *driver/* 连接到的驱动链表 */
    29. };
    30. #define to_driver(obj) container_of(obj, struct driver_private, kobj)


     

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

    点击(此处)折叠或打开

    1. /* sysfs interface for exporting 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 *driverconst char *buf,
    6.             size_t count);
    7. };
    8. DRIVER_ATTR(_name,_mode,_show,_store)/* 最终创建变量driver_attr_##_name描述属性 */
    9. /*属性文件创建的方法:*/
    10. int driver_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. int driver_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.c(example/lddbus/)与lddbus.h(example/include/)。lddbus.h中使用extern申明了将要使用EXPORT_SYMBOL导出的变量ldd_bus_type,lddbus.c中创建了总线类型ldd_bus_type以及总线设备ldd_bus。

    lddbus.h

        -> extern ldd_bus_type

    lddbus.c

        -> ldd_bus_type (EXPORT_SYMBOL)

        -> ldd_bus

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

    分析源代码:作者定义了ldd_device与ldd_driver,两个变量分别内嵌device与device_driver结构,然后分别为ldd_device定义了注册函数register_ldd_device和注销函数unregister_ldd_device,对ldd_driver也做了类似的工作。还宏定义了to_ldd_driver和to_ldd_device来使用内嵌结构(device/device_driver)访问更上层的容器ldd_device和ldd_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也类似

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

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

    sculld模块分析

    sculld模块是接着lddbus在加载lddbus基础上进行的,sculld使用了lddbus中导出的ldd_device和ldd_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错误

    在修改lddbus与sculld中,装载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));
     
  • 相关阅读:
    JS站点
    1011 World Cup Betting (20分)
    1007 Maximum Subsequence Sum (25分)(动态规划DP)
    1006 Sign In and Sign Out (25分)
    1005 Spell It Right (20分)
    1004 Counting Leaves (30分)(DFS)
    1003 Emergency (25分)(Dijkstra算法)
    1002 A+B for Polynomials (25分)
    1001 A+B Format (20分)
    canvas
  • 原文地址:https://www.cnblogs.com/oracleloyal/p/5420149.html
Copyright © 2011-2022 走看看