Linux设备驱动开发详解-Note(11)--- Linux 文件系统与设备文件系统(3)
Linux 文件系统与设备文件系统(3)
成于坚持,败于止步
sysfs 文件系统与 Linux 设备模型
1.sysfs 文件系统
Linux 2.6 内核引入了 sysfs 文件系统,sysfs 被看成是与 proc、devfs 和 devpty 同类别的文件系统,该文件系统是一个虚拟的文件系统,它可以产生一个包括所有系统硬件的层级视图,与提供进程和状态信息的 proc 文件系统十分类似。
sysfs 把连接在系统上的设备和总线组织成为一个分级的文件,它们可以由用户空间存取,向用户空间导出内核数据结构以及它们的属性。sysfs 的一个目的就是展示设备驱动模型中各组件的层次关系,其顶级目录包括 block、device、bus、drivers、class、power 和 firmware。
block 目录包含所有的块设备,devices 目录包含系统所有的设备并根据设备挂接的总线类型组织成层次结构,bus 目录包含系统中所有的总线类型,drivers 目录包括内核中所有已注册的设备驱动程序,class 目录包含系统中的设备类型(如网卡设备、声卡设备、输入设备等)。在/sys 目录运行 tree 会得到一个相当长的树型目录,下面摘取一部分:
|-- block | |-- fd0 | |-- md0 | |-- ram0 | |-- ram1 | |-- ... |-- bus | |-- eisa | | |-- devices | | '-- drivers | |-- ide | |-- ieee1394 | |-- pci | | |-- devices | | | |-- 0000:00:00.0 -> ../../../devices/pci0000:00/0000:00:00.0 | | | |-- 0000:00:01.0 -> ../../../devices/pci0000:00/0000:00:01.0 | | | |-- 0000:00:07.0 -> ../../../devices/pci0000:00/0000:00:07.0 | | '-- drivers | | |-- PCI_IDE | | | |-- bind | | | |-- new_id | | | '-- unbind | | '-- pcnet32 | | |-- 0000:00:11.0 -> ../../../../devices/pci0000:00/0000:00:11.0 | | |-- bind | | |-- new_id | | '-- unbind | |-- platform | |-- pnp | '-- usb | |-- devices | '-- drivers | |-- hub | |-- usb | |-- usb-storage | '-- usbfs |-- class | |-- graphics | |-- hwmon | |-- ieee1394 | |-- ieee1394_host | |-- ieee1394_node | |-- ieee1394_protocol | |-- input | |-- mem | |-- misc | |-- net | |-- pci_bus | | |-- 0000:00 | | | |-- bridge -> ../../../devices/pci0000:00 | | | |-- cpuaffinity | | | '-- uevent | | '-- 0000:01 | | |-- bridge -> ../../../devices/pci0000:00/0000:00:01.0 | | |-- cpuaffinity | | '-- uevent | |-- scsi_device | |-- scsi_generic | |-- scsi_host | |-- tty | |-- usb | |-- usb_device | |-- usb_host | '-- vc |-- devices | |-- pci0000:00 | | |-- 0000:00:00.0 | | |-- 0000:00:07.0 | | |-- 0000:00:07.1 | | |-- 0000:00:07.2 | | |-- 0000:00:07.3 | |-- platform | | |-- floppy.0 | | |-- host0 | | |-- i8042 | |-- pnp0 | '-- system |-- firmware |-- kernel | '-- hotplug_seqnum |-- module | |-- apm | |-- autofs | |-- cdrom | |-- eisa_bus | |-- i8042 | |-- ide_cd | |-- ide_scsi | |-- ieee1394 | |-- md_mod | |-- ohci1394 | |-- parport | |-- parport_pc | |-- usb_storage | |-- usbcore | | |-- parameters | | |-- refcnt | | '-- sections | |-- virtual_root | | '-- parameters | | '-- force_probe | '-- vmhgfs | |-- refcnt | '-- sections | '-- _ _versions '-- power '-- state
在/sys/bus 的 pci 等子目录下,又会在分出 drivers 和 devices 目录,而 devices 目录中的文件是对/sys/devices 目录中文件的符号链接。同样地,/sys/class 目录下包含许多对/sys/devices 下文件的链接。如图 5.2 所示,这与设备、驱动、总线和类的现实状况是直接对应的,也正符合 Linux 2.6 内核的设备模型。
随着技术的不断进步,系统的拓扑结构越来越复杂,对智能电源管理、热插拔以及即插即用的支持要求也越来越高,Linux 2.4 内核已经难以满足这些需求。为适应这种形势的需要,Linux 2.6 内核开发了上述全新的设备、总线、类和驱动环环相扣的设备模型。图 5.3 也形象地表示了 Linux 驱动模型中设备、总线和类之间的关系。
大多数情况下,Linux 2.6 内核中的设备模型代码会处理好这些关系,内核中的总线级和其他内核子系统会完成与设备模型的交互,这使得驱动工程师几乎不需要关心设备模型。但是,理解 Linux 设备模型的实现机制对驱动工程师仍然是大有裨益的,具体而言,内核将借助下文将介绍的 kobject、kset、subsystem、bus_type、device、device_driver、class、class_device、class_interface 等重量级数据结构来完成设备模型的架构。
2.kobject 内核对象
kobject 是 Linux 2.6 引入的设备管理机制,在内核中由 kobject 结构体表示,这个数据结构使所有设备在底层都具有统一的接口。kobject 提供了基本的对象管理能力,是构成 Linux 2.6 设备模型的核心结构,每个在内核中注册的 kobject 对象都对应于sysfs 文件系统中的一个目录。 kobject 结构体的定义如代码所示。
1 struct kobject 2 { 3 char *k_name; 4 char name[KOBJ_NAME_LEN]; //对象名称 5 struct kref kref; //对象引用计数 6 struct list_head entry; //用于挂接该 kobject 对象到 kset 链表 7 struct kobject *parent; //指向父对象的指针 8 struct kset *kset; //所属 kset 的指针 9 struct kobj_type *ktype; //指向对象类型描述符的指针 10 struct dentry *dentry; //sysfs 文件系统中与该对象对应的文件节点入口 11 };
内核通过 kobject 的 kref 成员实现对象引用计数管理,且提供两个函数kobject_get()、kobject_put()分别用于增加和减少引用计数,当引用计数为 0 时,所有该对象使用的资源将被释放。
kobject 的 ktype 成员是一个指向 kobj_type 结构的指针,表示该对象的类型。如下面代码所示,kobj_type 数据结构包含 3 个成员:用于释放 kobject 占用的资源的release()函数、指向 sysfs 操作的 sysfs_ops 指针和 sysfs 文件系统默认属性列表。
1 struct kobj_type 2 { 3 void (*release)(struct kobject *);//release 函数 4 struct sysfs_ops * sysfs_ops;//属性操作 5 struct attribute ** default_attrs;//默认属性 6 };
kobj_type 结构体种的 sysfs_ops 包括 store()和 show()两个成员函数,用于实现属性的读写,下面代码给出了 sysfs_ops 结构体的定义。当从用户空间读取属性时,show()函数将被调用,该函数将指定属性值存入 buffer 中返回给用户,而 store()函数用于存储用户通过 buffer 传入的属性值。和 kobject 不同的是,属性在 sysfs 中呈现为一个文件,而 kobject 则呈现为 sysfs 中的目录。
1 struct sysfs_ops 2 { 3 ssize_t (*show)(struct kobject *, struct attribute *,char *); 4 ssize_t (*store)(struct kobject *,struct attribute *,const char *, size_t); 5 };
Linux 内核中提供一系列操作 kobject 的函数:
void kobject_init(struct kobject * kobj);
该函数用于初始化 kobject,它设置 kobject 引用计数为1,entry 域指向自身,其所属 kset 引用计数加 1。
int kobject_set_name(struct kobject *kobj, const char *format, ...);
该函数用于设置指定 kobject 的名称。
void kobject_cleanup(struct kobject * kobj) 和 void kobject_release(struct kref *kref);
该函数用于清除 kobject,当其引用计数为0时,释放对象占用的资源。
struct kobject *kobject_get(struct kobject *kobj);
该函数用于将 kobj 对象的引用计数加 1,同时返回该对象的指针。
void kobject_put(struct kobject * kobj);
该函数用于将 kobj 对象的引用计数减 1,如果引用计数降为 0,则调用kobject_release()释放该 kobject 对象。
int kobject_add(struct kobject * kobj);
该函数用于将 kobject 对象加入 Linux 设备层次,它会挂接该 kobject 对象到 kset的 list 链中,增加父目录各级 kobject 的引用计数,在其 parent 指向的目录下创建文件节点,并启动该类型内核对象的 hotplug 函数。
int kobject_register(struct kobject * kobj);
该函数用于注册kobject,它会先调用kobject_init()初始化kobj,再调用kobject_add()完成该内核对象的添加。
void kobject_del(struct kobject * kobj);
这个函数是 kobject_add()的反函数,它从 Linux 设备层次(hierarchy)中删除 kobject对象。
void kobject_unregister(struct kobject * kobj);
这个函数是 kobject_register()的反函数,用于注销 kobject。与 kobject_register()相反,它首先调用 kobject_del()从设备层次中删除该对象,再调用 kobject_put()减少该对象的引用计数,如果引用计数降为 0,则释放该 kobject 对象。
3.kset 内核对象集合
kobject 通常通过 kset 组织成层次化的结构,kset 是具有相同类型的 kobject 的集合,在内核中用 kset 数据结构表示,其定义如代码所示。
1 struct kset 2 { 3 struct subsystem * subsys; //所在的 subsystem 的指针 4 struct kobj_type * ktype; //指向该 kset 对象类型描述符的指针 5 struct list_head list; //用于连接该 kset 中所有 kobject 的链表头 6 spinlock_t list_lock; 7 struct kobject kobj; //嵌入的 kobject 8 struct kset_uevent_ops * uevent_ops;//事件操作集 9 };
包含在 kset 中的所有 kobject 被组织成一个双向循环链表,list 即是该链表的头。ktype 成员指向一个 kobj_type 结构,被该 kset 中的所有 kobject 共享,表示这些对象的类型。kset 数据结构还内嵌了一个 kobject 对象(kobj 成员表示),所有属于这个 kset的 kobject 对象的 parent 均指向这个内嵌的对象。此外,kset 还依赖于 kobj 维护引用计数, kset 的引用计数实际上就是内嵌的 kobject 对象的引用计数。
与 kobject 相似,Linux 提供一系列函数操作 kset。kset_init()完成指定 kset 的初始化,kset_get()和 kset_put()分别增加和减少 kset 对象的引用计数,kset_add()和 kset_del() 函数 分别 实现 将指定 keset 对 象 加入 设备 层次 和从 其中 删除 ,kset_register()函数完成 kset 的注册,kset_unregister()函数则完成 kset 的注销。 kobject 被创建或删除时会产生事件(event),kobject 所属的 kset 将有机会过滤事件或为用户空间添加信息。每个 kset 能支持一些特定的事件变量,在热插拔事件发生时,kset 的成员函数可以设置一些事件变量,这些变量将被导出到用户空间。kset 的uevent_ops 成员是执行该 kset 事件操作集 kset_uevent_ops 的指针,kset_uevent_ops 的定义如代码所示。
1 struct kset_uevent_ops 2 { 3 int (*filter)(struct kset *kset, struct kobject *kobj);//事件过滤 4 const char *(*name)(struct kset *kset, struct kobject *kobj); 5 int (*uevent)(struct kset *kset, struct kobject *kobj, char **envp, 6 int num_envp, char *buffer, int buffer_size);//环境变量设置 7 };
filter()函数用于过滤掉不需要导出到用户空间的事件,uevent()函数用于导出一些环境变量给用户的热插拔处理程序,各类设备导出的环境变量如下。
PCI 设备:ACTION(“add”/“remove”)、PCI_ CLASS(16 进制的 PCI 类、子类、接口,如 c0310)、PCI_ ID(Vendor:Device,如 0123:4567)、PCI_SUBSYS_ID(子系 统 Vendor: 子系 统 Device , 如 89ab:cdef )、PCI_ SLOT_ NAME(Bus:Slot.Func,如 00:07.2)。
USB 设备:ACTION(“add”/“remove”)、DEVPATH(/sys/DEVPATH)、PRODUCT( idVendor/ idProduct/bcdDevice , 如 46d/c281/108 ) 、 TYPE( bDeviceClass/bDeviceSubClass/bDeviceProtocol , 如 9/0/0 )、 INTERFACE(bInterfaceClass/bInterfaceSubClass/bInterfaceProtocol,如 3/1/1),如果内核配置了usbfs 文件系统,还会导出 DEVFS(USB 驱动列表的位置,如/proc/bus/usb)和DEVICE(USB 设备节点路径)。
网络设备:ACTION(“register”/“unregister”)、INTERFACE(接口名,如“eth0”)。
输 入 设 备 : ACTION (“ add ” / “ remove ”)、 PRODUCT(idbus/idvendor/idproduct/idversion,如 1/46d/c281/108)、NAME(设备名,如“ALCOR STRONG MAN KBD HUB”)、PHYS(设备物理地址 ID,如usb-00:07.2-2.3/input0)、EV(来自 evbit,如 120002)、KEY(来自 evbit,如e080ffdf 1dfffff ffffffff fffffe)、LED(来自 ledbit,如 7)。
IEEE1394 设备:ACTION(“add”/“remove”)、VENDOR_ID(24 位的 vendor id,如 123456)、GUID(64 位 ID)、SPECIFIER_ID(24 位协议标准拥有者ID)、VERSION(协议标准版本 ID)。
用户空间的热插拔脚本根据传入给它的参数(如 PCI 的参数为热插拔程序路径、“pci”和 0)以及内核导出的环境变量采取相应的行动,如下面的脚本程序会在PRODUCT 为“82d/100/0”的 USB 设备被插入时加载 visor 模块,在被拔出时卸载 visor模块,如下所示。
if [ "$1"="usb" ]; then if [ "$PRODUCT"="82d/100/0" ]; then if[ "$ACTION" = "add" ]; then /sbin/modprobe visor else /sbin/rmmod visor fi fi fi
4.subsystem 内核对象子系统
subsystem 是一系列 kset 的集合,它描述系统中某一类设备子系统,如block_subsys 表 示所 有的 块设 备,对 应 于 sysfs 文件系 统中 的 block 目录 。devices_subsys 对应于 sysfs 中的 devices 目录,描述系统中所有的设备。subsystem由 struct subsystem 数据结构描述,其定义如代码清单所示。
1 struct subsystem 2 { 3 struct kset kset; //内嵌的 kset 对象 4 struct rw_semaphore rwsem; //互斥访问信号量 5 };
每个 kset 必须属于某个 subsystem,通过设置 kset 结构中的 subsys 域指向指定的subsystem 可以将一个 kset 加入到该 subsystem。所有挂接到同一 subsystem 的 kset 共享同一个 rwsem 信号量,用于同步访问 kset 中的链表。
内核也提供了一组类似于 kobect 和 kset 的操作 subsystem 的操作,如下所示:
void subsystem_init(struct subsystem *subsys);
int subsystem_register(struct subsystem *subsys);
void subsystem_unregister(struct subsystem *subsys);
struct subsystem *subsys_get(struct subsystem *subsys)
void subsys_put(struct subsystem *subsys);
5.Linux 设备模型组件
系统中的任一设备在设备模型中都由一个 device 对象描述,其对应的数据结构struct device 定义如代码清单所示。
1 struct device 2 { 3 struct klist klist_children; // 设备列表中的孩子列表 4 struct klist_node knode_parent;// 兄弟节点 5 struct klist_node knode_driver; // 驱动结点 6 struct klist_node knode_bus; // 总线结点 7 struct device * parent; // 指向父设备 8 9 struct kobject kobj; // 内嵌一个 kobject 对象 10 char bus_id[BUS_ID_SIZE];// 总线上的位置 11 struct device_attribute uevent_attr; 12 13 struct semaphore sem; 14 15 struct bus_type * bus; // 总线 16 struct device_driver *driver;// 使用的驱动 17 void *driver_data; // 驱动私有数据 18 void *platform_data; //平台特定的数据 19 void *firmware_data; //固件特定的数据(如 ACPI、BIOS 数据) 20 struct dev_pm_info power; 21 22 u64 *dma_mask; //dma 掩码 23 u64 coherent_dma_mask; 24 struct list_head dma_pools; // DMA 缓冲池 25 struct dma_coherent_mem *dma_mem; 26 27 void (*release)(struct device * dev); // 释放设备方法 28 };
device 结构体用于描述设备相关的信息设备之间的层次关系,以及设备与总线、驱动的关系。 内核提供了相应的函数用于操作 device 对象。其中 device_register()函数将一个新的 device 对象插入设备模型,并自动在/sys/devices 下创建一个对应的目录。device_unregister()完成相反的操作,注销设备对象。get_device()和 put_device()分别增加与减少设备对象的引用计数。通常 device 结构体不单独使用,而是包含在更大的结构体中,比如描述 PCI 设备的 struct pci_dev,其中的 dev 域就是一个 device 对象。 系统中的每个驱动程序由一个 device_driver 对象描述,对应的数据结构的定义如代码清单所示。
1 struct device_driver 2 { 3 const char * name; //设备驱动程序的名称 4 struct bus_type * bus; //总线 5 6 struct completion unloaded; 7 struct kobject kobj;//内嵌的 kobject 对象 8 struct klist klist_devices; 9 struct klist_node knode_bus; 10 11 struct module * owner; 12 13 int (*probe) (struct device * dev); //指向设备探测函数 14 int (*remove) (struct device * dev); //指向设备移除函数 15 void (*shutdown) (struct device * dev); 16 int (*suspend) (struct device * dev, pm_message_t state); 17 int (*resume) (struct device * dev); 18 };
与 device 结构体类似,device_driver 对象依靠内嵌的 kobject 对象实现引用计数管理和层次结构组织。内核提供类似的函数用于操作 device_driver 对象。如 get_driver()增加引用计数,driver_register()用于向设备模型插入新的 driver 对象,同时在 sysfs 文件系统中创建对应的目录。device_driver()结构体还包括几个函数,用于处理探测、移除和电源管理事件。
系统中总线由 struct bus_type 描述,其定义如代码清单所示。
1 struct bus_type 2 { 3 const char * name;//总线类型的名称 4 5 struct subsystem subsys;//与该总线相关的 subsystem 6 struct kset drivers;//所有与该总线相关的驱动程序集合 7 struct kset devices;//所有挂接在该总线上的设备集合 8 struct klist klist_devices; 9 struct klist klist_drivers; 10 11 struct bus_attribute * bus_attrs;//总线属性 12 struct device_attribute * dev_attrs;//设备属性 13 struct driver_attribute * drv_attrs; //驱动程序属性 14 15 int (*match)(struct device * dev, struct device_driver * drv); 16 int (*uevent)(struct device *dev, char **envp, 17 int num_envp, char *buffer, int buffer_size);//事件 18 int (*probe)(struct device * dev); 19 int (*remove)(struct device * dev); 20 void (*shutdown)(struct device * dev); 21 int (*suspend)(struct device * dev, pm_message_t state); 22 int (*resume)(struct device * dev); 23 };
每个 bus_type 对象都内嵌一个 subsystem 对象,bus_subsys 对象管理系统中所有总线类型的 subsystem 对象。每个 bus_type 对象都对应/sys/bus 目录下的一个子目录,如 PCI 总线类型对应于/sys/bus/pci。在每个这样的目录下都存在两个子目录:devices和 drivers(分别对应于 bus_type 结构中的 devices 和 drivers 域)。其中 devices 子目录描述连接在该总线上的所有设备,而 drivers 目录则描述与该总线关联的所有驱动程序。与 device_driver 对象类似,bus_type 结构还包含几个函数处理热插拔、即插即拔和电源管理事件的函数。
系统中的设备类由 struct class 描述,表示某一类设备。所有的 class 对象都属于class_subsys 子系统,对应于 sysfs 文件系统中的/sys/class 目录。代码清单给出了class 结构体的定义。
1 struct class 2 { 3 const char * name; //类名 4 struct module * owner; 5 6 struct subsystem subsys; //对应的 subsystem 7 struct list_head children; //class_device 链表 8 struct list_head interfaces; //class_interface 链表 9 struct semaphore sem; //children 和 interfaces 链表锁 10 11 struct class_attribute * class_attrs;//类属性 12 struct class_device_attribute * class_dev_attrs;//类设备属性 13 14 int (*uevent)(struct class_device *dev, char **envp, 15 int num_envp, char *buffer, int buffer_size);//事件 16 17 void (*release)(struct class_device *dev); 18 void (*class_release)(struct class *class); 19 };
每个 class 对象包括一个 class_device 链表,每个 class_device 对象表示一个逻辑设备,并通过 struct class_device 中的 dev 成员(一个指向 struct device 的指针)关联一个物理设备。这样,一个逻辑设备总是对应于一个物理设备,但是一个物理设备却可能对应于多个逻辑设备,代码清单给出了 class_device 的定义。此外,class 结构中还包括用于处理热插拔、即插即拔和电源管理事件的函数,这与 bus_type 对象相似。
1 struct class_device 2 { 3 struct list_head node; 4 5 struct kobject kobj;//内嵌的 kobject 6 struct class * class; //所属的类 7 dev_t devt; // dev_t 8 struct class_device_attribute *devt_attr; 9 struct class_device_attribute uevent_attr; 10 struct device * dev; //如果存在,创建到/sys/devices 相应入口的符号链接 11 void * class_data; //私有数据 12 struct class_device *parent; //父设备 13 14 void (*release)(struct class_device *dev); 15 int (*uevent)(struct class_device *dev, char **envp, 16 int num_envp, char *buffer, int buffer_size); 17 char class_id[BUS_ID_SIZE]; //类标识 18 };
下面两个函数用于注册和注销 class。
int class_register(struct class * cls);
void class_unregister(struct class * cls);
下面两个函数用于注册和注销 class_device。
int class_device_register(struct class_device *class_dev);
void class_device_unregister(struct class_device *class_dev);
除了 class、class_device 结构体外,还存在一个 class_interface 结构体,当设备加入或离开类时,将引发 class_interface 中的成员函数被调用,class_interface 的定义如代码清单所示。
1 struct class_interface 2 { 3 struct list_head node; 4 struct class *class;//对应的 class 5 int (*add)(struct class_device *, struct class_interface *);//设备加入时触发 6 void (*remove)(struct class_device *, struct class_interface *);//设备移出时触发 7 };
下面两个函数用于注册和注销 class_interface。
int class_interface_register(struct class_interface *class_intf);
void class_interface_unregister(struct class_interface *class_intf);
6.属性
在 bus、device、driver 和 class 层次上都分别定义了其属性结构体,包括bus_attribute、driver_attribute、class_attribute、class_device_attribute,这几个结构体的定义在本质是完全相同的,如代码清单所示。
1 /* 总线属性 */ 2 struct bus_attribute 3 { 4 struct attribute attr; 5 ssize_t (*show)(struct bus_type *, char * buf); 6 ssize_t (*store)(struct bus_type *, const char * buf, size_t count); 7 }; 8 /* 驱动属性 */ 9 struct driver_attribute 10 { 11 struct attribute attr; 12 ssize_t (*show)(struct device_driver *, char * buf); 13 ssize_t (*store)(struct device_driver *, const char * buf, size_t count); 14 }; 15 /* 类属性 */ 16 struct class_attribute 17 { 18 struct attribute attr; 19 ssize_t (*show)(struct class *, char * buf); 20 ssize_t (*store)(struct class *, const char * buf, size_t count); 21 }; 22 /* 类设备属性 */ 23 struct class_device_attribute 24 { 25 struct attribute attr; 26 ssize_t (*show)(struct class_device *, char * buf); 27 ssize_t (*store)(struct class_device *, const char * buf, size_t count); 28 };
下面一组宏分别用于创建和初始化 bus_attribute、driver_attribute、class_attribute、class_device_ attribute。
BUS_ATTR(_name,_mode,_show,_store) DRIVER_ATTR(_name,_mode,_show,_store) CLASS_ATTR(_name,_mode,_show,_store) CLASS_DEVICE_ATTR(_name,_mode,_show,_store)
下面一组函数分别用于添加和删除 bus、driver、class、class_device 属性。
int bus_create_file(struct bus_type * bus, struct bus_attribute * attr); void bus_remove_file(struct bus_type * bus, struct bus_attribute * attr); int device_create_file(struct device * dev, struct device_attribute * attr); void device_remove_file(struct device * dev, struct device_attribute * attr); int class_create_file(struct class * cls, const struct class_attribute * attr); void class_remove_file(struct class * cls, const struct class_attribute * attr); int class_device_create_file(struct class_device * class_dev, const struct class_device_attribute * attr); void class_device_remove_file(struct class_device * class_dev, const struct class_device_attribute * attr);
xxx_create_file()函数中会调用 sysfs_create_file(),而 xxx_remove_file()函数中会调用 sysfs_ remove_file()函数,xxx_create_file()会创建对应的 sysfs 文件节点,而xxx_remove_file()会删除对应的 xxx 文件节点。
就到这里了,O(∩_∩)O~
我的专栏地址:http://blog.csdn.net/column/details/linux-driver-note.html
待续。。。。