20150517 Linux文件系统与设备文件系统
2015-05-17 Lover雪儿
注:本文参考书籍:华清远见-《Linux 设备驱动开发详解》第五章,大概内容如下,具体内容还请观看原书.
一.devfs(设备文件系统)
devfs(设备文件系统)是由linux2.4内核引入的,具有如下优点:
①可以通过程序在设备初始化时在/dev目录下创建设备文件,卸载时把它删除。
②设备驱动程序可以指定设备名、所有者和权限位,用户空间中人可以修改。
③不需要为设备驱动程序分配主设备号以及处理次设备号,在程序中直接可以给register_chrdev()传递0主设备号可以动态获取可用的主设备号,并在devfs_register()中指定次设备号。
在使用时必须包括头文件:linux/devfs_fs_kernel.h,再2.4内核之前编译测试
驱动程序应调用下而这些函数来进行设备文件的创建和删除工昨。
/*创建设备目录*/
devfs_handle_t devfs_mk_dir (devfs_handle_t dir, const char *name, void *info);
/*创建设备文件*/
devf s_handle_t devfs_register (devf s_handle_t dir, const char *name,
unsigned int flags, unsigned int major, unsigned int minor,
umode_t mode, void *ops,void *info);
/* 撤销设备文件 */
void devfs_unregister(devfs_handle_t de);
附上驱动程序devfs.c:
1 #include <linux/init.h>
2 #include <linux/module.h>
3 #include <linux/errno.h>
4 #include <linux/fs.h>
5 #include <linux/interrupt.h>
6 #include <linux/kernel.h>
7 #include <linux/version.h>
8 #include <linux/clk.h>
9 #include <linux/devfs_fs_kernel.h>
10
11 #define DEVICE_NAME "devfs_test"
12
13 int major; //保存主设备号
14 static devfs_handle_t devfs_handle;
15
16 //操作函数
17 static struct file_operations devfs_fops = {
18 .owner = THIS_MODULE,
19 };
20
21 static int __init devfs_init(void)
22 {
23
24 /*在内核中注册设备*/
25 major = register_chrdev(0, DEVICE_NAME, &devfs_fops);
26 if (major < 0)
27 {
28 printk(DEVICE_NAME " can't register major number\n");
29 return -EIO;
30 }
31 /*创建设备文件*/
32 //devfs_mk_cdev(MKDEV(major, 0), S_IFCHR | S_IRUSR | S_IWUSR | S_IRGRP, DEVICE_NAME);
33 devfs_handle = devfs_register(NULL,DEVICE_NAME,DEVFS_FL_DEFAULT,major, 0, S_IFCHR | S_IRUSR | S_IWUSR, &devfs_fops, NULL);
34
35 printk(DEVICE_NAME " initialized\n");
36 return 0;
37 }
38
39 static void __exit devfs_exit(void)
40 {
41 //devfs_remove(DEVICE_NAME);
42 devfs_unregister(devfs_handle); /*撤销设备文件*/
43 unregister_chrdev(major, DEVICE_NAME); /*注销设备*/
44 }
45
46 module_init(xxx_init);
47 module_exit(xxx_exit);
48 MODULE_LICENSE("GPL");
二.udev(设备文件系统)
1.udev与devfs的区别
尽管devfs有那些优点,但是在linux2.6内核中,devfs被认为是过时的方法,使用udev去带了devfs的几点原因:
①devfs 所做的工作被确信可以在用户态来完成。
②一些 bug 相当长的时间内未被修复。
③devfs 的维护者和作者停止了对代码的维护工作
udev 完全在用户态工作,利用设备加入或移除时内核所发送的热插拔事件(hotplugevent) 来工作。在热插拔时,设备的详细信息会由内核输出到位于/sys 的 sysfs 文件系统。
udev 的设备命名策略、 权限控制和事件处理都是在用户态下完成的, 它利用 sysfs 中的信息来进行创建设备文件节点等工作。
由于 udev 根据系统中硬件设备的状态动态更新设备文件, 进行设备文件的创建和删除等,因此,在使用 udev 后,/dev 目录下就会只包含系统中真正存在的设备了。
devfs 与 udev 的另一个显著区别在于:采用 devfs,当一个并不存在的/dev 节点被打开的时候,devfs 能自动加载对应的驱动,而 udev 则不能。这是因为 udev 的设计者认为 Linux 应该在设备被发现的时候加载驱动模块,而不是当它被访问的时候。udev的设计者认为 devfs 所提供的打开/dev 节点时自动加载驱动的功能对于一个配置正确的计算机是多余的。系统中所有的设备都应该产生热插拔事件并加载恰当的驱动,而udev 能注意到这点并且为它创建对应的设备节点。
2.sysfs文件系统与linux设备模型
Linux 2.6 内核引入了 sysfs 文件系统,sysfs 被看成是与 proc、devfs 和 devpty 同类别的文件系统,该文件系统是一个虚拟的文件系统,它可以产生一个包括所有系统硬件的层级视图,与提供进程和状态信息的 proc 文件系统十分类似。
sysfs 把连接在系统上的设备和总线组织成为一个分级的文件, 它们可以由用户空间存取, 向用户空间导出内核数据结构以及它们的属性。 sysfs 的一个目的就是展示设备驱动模型中各组件的层次关系,其顶级目录包括
①block:包含所有的块设备
②device:包含系统所有的设备并根据设备挂接的总线类型组织成层次结构
③bus:包含系统中所有的总线类型
④drivers:包含内核中所有的已注册的设备驱动程序
⑤class:包含系统中的设备类型
⑥power和 firmware
具体而言,内核将借助下文将介绍的 kobject、kset、subsystem、bus_type、device、device_driver、class、class_device、class_interface 等重量级数据结构来完成设备模型的架构。
3.kobject 内核对象
这个数据结构使所有设备在底层都具有统一的接口.
kobject 提供了基本的对象管理能力,是构成 Linux 2.6 设备模型的核心结构,每个在内核中注册的 kobject 对象都对应于sysfs 文件系统中的一个目录。
1 struct kobject{
2 char *k_name;
3 char name[KOBJ_NAME_LEN]; //对象名称
4 struct kref kref; //对象引用计数
5 //提供两个函数kobject_get()、kobject_put()分别用于增加和减少引用计数,当引用计数为0时,所有该对象使用的资源将被释放
6 struct list_head entry; //用于挂接该 kobject 对象到 kset 链表
7 struct kobject *parent; //指向父对象的指针
8 struct kset *kset; //所属 kset 的指针
9 struct kobj_type *ktype; //指向对象类型描述符的指针
10 //指向 kobj_type 结构的指针, 表示该对象的类型
11 struct dentry *dentry; //sysfs 文件系统中与该对象对应的文件节点入口
12 };
kobj_type 数据结构包含 3 个成员:用于释放 kobject 占用的资源的
release()函数、指向 sysfs 操作的 sysfs_ops 指针和 sysfs 文件系统默认属性列表
1 struct kobj_type{
2 void (*release)(struct kobject *);//release 函数
3 struct sysfs_ops * sysfs_ops;//属性操作
4 struct attribute ** default_attrs;//默认属性
5 };
kobj_type 结构体种的 sysfs_ops 包括 store()和 show()两个成员函数,用于实现属
性的读写。
当从用户空间读取属性时,show()函数将被调用,该函数将指定属性值存入 buffer 中返回给用户,而 store()函数用于存储用户通过 buffer 传入的属性值。
和 kobject 不同的是,属性在 sysfs 中呈现为一个文件,而 kobject 则呈现为 sysfs 中的目录。
struct sysfs_ops{
ssize_t (*show)(struct kobject *, struct attribute *,char *);
ssize_t (*store)(struct kobject *,struct attribute *,const char *,size_t);
};
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 函数
-------------------------------------------------------------------
void kobject_del(struct kobject * kobj);
//这个函数是 kobject_add()的反函数, 它从 Linux 设备层次(hierarchy)中删除 kobject
对象。
-------------------------------------------------------------------
int kobject_register(struct kobject * kobj);
//该函数用于注册 kobject,它会先调用 kobject_init()初始化 kobj,再调用kobject_add()完成该内核对象的添加
-------------------------------------------------------------------
void kobject_unregister(struct kobject * kobj);
//这个函数是 kobject_register()的反函数,用于注销 kobject。与kobject_register()相反,它首先调用 kobject_del()从设备层次中删除该对象,再调用 kobject_put()减少该对象的引用计数,如果引用计数降为 0,则释放该 kobject 对象。
4.kset 内核对象集合
kobject 通常通过 kset 组织成层次化的结构,kset 是具有相同类型的 kobject 的集合,在内核中用 kset 数据结构表示:
struct kset{
struct subsystem * subsys; //所在的 subsystem 的指针
struct kobj_type * ktype;
//指向该 kset 对象类型描述符的指针
struct list_head list;
spinlock_t list_lock;
struct kobject kobj; //嵌入的 kobject
struct kset_uevent_ops * uevent_ops;//事件操作集
};
包含在 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 的定义
struct kset_uevent_ops{
int (*filter)(struct kset *kset, struct kobject *kobj);//事件过滤
const char *(*name)(struct kset *kset, struct kobject *kobj);
int (*uevent)(struct kset *kset, struct kobject *kobj, char **envp,
int num_envp, char *buffer, int buffer_size);//环境变量设置
};
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 位的 vendorid,如 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
5.subsystem 内核对象子系统
subsystem 是一系列kset的集合,它描述系统中某一类设备子系统,如block_subsys表示所有的块设备,对应于sysfs文件系统中的block目录。
devices_subsys对应于sysfs中的devices目录,描述系统中所有的设备。
subsystem由 struct subsystem 数据结构描述
struct subsystem{
struct kset kset; //内嵌的 kset 对象
struct rw_semaphore rwsem; //互斥访问信号量
};
每个 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);
6.Linux 设备模型组件
系统中的任一设备在设备模型中都由一个device对象描述,其对应的数据结构struct device.
struct device{
struct klist klist_children; // 设备列表中的孩子列表
struct klist_node knode_parent;// 兄弟节点
struct klist_node knode_driver; // 驱动结点
struct klist_node knode_bus; // 总线结点
struct device
parent; // 指向父设备
// 内嵌一个 kobject 对象
struct kobject kobj;
char struct device_attribute uevent_attr;
bus_id[BUS_ID_SIZE]; // 总线上的位置
struct semaphoresem;
// 总线
struct bus_type * bus;
struct device_driver *driver; // 使用的驱动
void *driver_data;
void *platform_data; //平台特定的数据
void *firmware_data; //固件特定的数据(如 ACPI、BIOS 数据)
struct dev_pm_info power;
// 驱动私有数据
u64 *dma_mask; //dma 掩码
u64 coherent_dma_mask;
struct list_head;
struct dma_coherent_mem *dma_mem;
dma_pools; // DMA 缓冲池
void (*release)(struct device * dev); // 释放设备方法
};
device 结构体用于描述设备相关的信息设备之间的层次关系,以及设备与总线、驱动的关系。
内核提供了相应的函数用于操作 device 对象。
1 device_register()函数将一个新的 device 对象插入设备模型, 并自动在/sys/devices下创建一个对应的目录。
2 device_unregister()完成相反的操作,注销设备对象。
3 get_device()和 put_device()分别增加与减少设备对象的引用计数。通常 device 结构体不单独使用,而是包含在更大的结构体中,比如描述 PCI 设备的 struct pci_dev,其中的 dev 域就是一个 device 对象。
7.device_driver 对象
系统中的每个驱动程序由一个 device_driver 对象描述,对应的数据结构的定义
1 struct device_driver{
2 const char * name;//设备驱动程序的名称
3 struct bus_type * bus; //总线
4 struct completion unloaded;
5 struct kobject kobj;//内嵌的 kobject 对象
6 struct klist klist_devices;
7 struct klist_node knode_bus;
8 struct module * owner;
9 int (*probe) (struct device * dev); //指向设备探测函数
10 int (*remove) (struct device * dev); //指向设备移除函数
11 void (*shutdown) (struct device * dev);
12 int (*suspend) (struct device * dev, pm_message_t state);
13 int (*resume)(struct device * dev);
14 };
与 device 结构体类似, device_driver 对象依靠内嵌的 kobject 对象实现引用计数管
理和层次结构组织。
内核提供类似的函数用于操作 device_driver 对象。如
get_driver()增加引用计数,
driver_register()用于向设备模型插入新的 driver 对象,同时在 sysfs 文件系统中创建对应的目录。
device_driver()结构体还包括几个函数,用于处理探测、移除和电源管理事件。
8.总线bus_type结构体
系统中总线由 struct bus_type 描述
1 struct bus_type{
2 const char * name;//总线类型的名称
3 struct subsystem subsys;//与该总线相关的 subsystem
4 struct kset drivers;//所有与该总线相关的驱动程序集合
5 struct kset devices;//所有挂接在该总线上的设备集合
6 struct klist klist_devices;
7 struct klist klist_drivers;
8 struct bus_attribute * bus_attrs;//总线属性
9 struct device_attribute * dev_attrs;//设备属性
10 struct driver_attribute * drv_attrs; //驱动程序属性
11 int (*match)(struct device * dev, struct device_driver * drv);
12 int (*uevent)(struct device *dev, char **envp,int num_envp, char *buffer, int buffer_size);//事件
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 };
每个 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 结构还包含几个函数处理热插拔、即插即拔
和电源管理事件的函数。
9.struct class
系统中的设备类由 struct class 描述,表示某一类设备。所有的 class 对象都属于class_subsys 子系统,对应于 sysfs 文件系统中的/sys/class 目录。
给出了class 结构体的定义
1 struct class{
2 const char* name; //类名
3 struct module * owner;
4 struct subsystem subsys; //对应的 subsystem
5 struct list_head children; //class_device 链表
6 struct list_head interfaces; //class_interface 链表
7 struct semaphore sem;
8 //children 和 interfaces 链表锁
9 struct class_attribute * class_attrs;//类属性
10 struct class_device_attribute * class_dev_attrs;//类设备属性
11 int (*uevent)(struct class_device *dev, char **envp,int num_envp, char *buffer, int buffer_size);//事件
12 void (*release)(struct class_device *dev);
13 void (*class_release)(struct class *class);
14 };
每个 class 对象包括一个 class_device 链表,每个 class_device 对象表示一个逻辑设备,并通过 struct class_device 中的 dev 成员(一个指向 struct device 的指针)关联一个物理设备。
这样,一个逻辑设备总是对应于一个物理设备,但是一个物理设备却可能对应于多个逻辑设备
class 结构中还包括用于处理热插拔、即插即拔和电源管理事件的函数,这与 bus_type 对象相似。
10.class_device结构体
1 struct class_device{
2 struct list_head node;
3 struct kobject kobj;//内嵌的 kobject
4 struct class * class;//所属的类
5 dev_t devt; // dev_t
6 struct class_device_attribute *devt_attr;
7 struct class_device_attribute uevent_attr;
8 struct device * dev; //如果存在,创建到/sys/devices 相应入口的符号链接
9 void * class_data; //私有数据
10 struct class_device *parent;//父设备
11 void (*release)(struct class_device *dev);
12 int (*uevent)(struct class_device *dev, char **envp,
13 int num_envp, char *buffer, int buffer_size);
14 char class_id[BUS_ID_SIZE]; //类标识
15 };
1 下面两个函数用于注册和注销 class。
2 int class_register(struct class * cls);
3 void class_unregister(struct class * cls);
4 下面两个函数用于注册和注销 class_device。
5 int class_device_register(struct class_device *class_dev);
6 void class_device_unregister(struct class_device *class_dev);
11.class_interface接口
除了 class、class_device 结构体外,还存在一个 class_interface 结构体,当设备加
入或离开类时,将引发 class_interface 中的成员函数被调用,class_interface 的定义
1 struct class_interface{
2 struct list_head node;
3 struct class *class;//对应的 class
4 int (*add)(struct class_device *, struct class_interface *);
5 //设备加入时触发
6 void (*remove)(struct class_device *, struct class_interface *);
7 //设备移出时触发
8 };
9 下面两个函数用于注册和注销 class_interface。
10 int class_interface_register(struct class_interface *class_intf);
11 void class_interface_unregister(struct class_interface *class_intf);
13.属性
在 bus 、 device 、 driver 和 class 层 次 上都分 别定义 了其属 性结构 体,包 括bus_attribute、driver_attribute、class_attribute、class_device_attribute,这几个结构体的定义在本质是完全相同的,
1 /* 总线属性 */
2 struct bus_attribute{
3 struct attribute attr;
4 ssize_t (*show)(struct bus_type *, char * buf);
5 ssize_t (*store)(struct bus_type *, const char * buf, size_t count);
6 };
7 /* 驱动属性 */
8 struct driver_attribute{
9 struct attribute attr;
10 ssize_t (*show)(struct device_driver *, char * buf);
11 ssize_t (*store)(struct device_driver *, const char * buf, size_t count);
12 };
13 /* 类属性 */
14 struct class_attribute{
15 struct attribute attr;
16 ssize_t (*show)(struct class *, char * buf);
17 ssize_t (*store)(struct class *, const char * buf, size_t count);
18 };
19 /* 类设备属性 */
20 struct class_device_attribute{
21 struct attribute attr;
22 ssize_t (*show)(struct class_device *, char * buf);
23 ssize_t (*store)(struct class_device *, const char * buf, size_t count);
24 };
下面一组宏分别用于创建和初始化 bus_attribute、driver_attribute、class_attribute、class_device_ attribute
1 BUS_ATTR(_name,_mode,_show,_store) 2 DRIVER_ATTR(_name,_mode,_show,_store) 3 CLASS_ATTR(_name,_mode,_show,_store) 4 CLASS_DEVICE_ATTR(_name,_mode,_show,_store)
下面一组函数分别用于添加和删除 bus、driver、class、class_device 属性。
1 int bus_create_file(struct bus_type * bus, struct bus_attribute * attr); 2 void bus_remove_file(struct bus_type * bus, struct bus_attribute * attr); 3 int device_create_file(struct device * dev, struct device_attribute * attr); 4 void device_remove_file(struct device * dev, struct device_attribute * attr); 5 int class_create_file(struct class * cls, const struct class_attribute * attr); 6 void class_remove_file(struct class * cls, const struct class_attribute * attr); 7 int class_device_create_file(struct class_device * class_dev,const struct class_device_attribute * attr); 8 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 文件节点
14.udev的组成
udev 的主页位http://www.kernel.org/pub/linux/utils/kernel/hotplug/udev.html,上面包含了 udev 的详细介绍,从http://www.us.kernel.org/pub/linux/utils/kernel/hotplug/上可以下载最新的 udev 包。udev 的设计目标如下
①在用户空间中执行。
②动态建立/删除设备文件。
③允许每个人都不用关心主/次设备号。
④提供 LSB 标准名称。
⑤如果需要,可提供固定的名称。
为了提供这些功能,udev 以 3 个分割的子计划发展: namedev、libsysfs 和 udev。
namedev 为设备命名子系统,
libsysfs 提供访问 sysfs 文件系统从中获取信息的标准接口,
udev 提供/dev 设备节点文件的动态创建和删除策略。
udev 程序承担与 namedev和 libsysfs 库交互的任务,当/sbin /hotplug 程序被内核调用时,udev 将被运行
udev的工作过程如下。
(1) 当内核检测到在系统中出现了新设备后,内核会在 sysfs 文件系统中为该新设备生成新的记录并导出一些设备特定的信息及所发生的事件。
(2) udev 获取内核导出的信息,它调用 namedev 决定应该给该设备指定的名称,如果是新插入设备,udev 将调用 libsysfs 决定应该为该设备的设备文件指定的主/次设备号, 并用分析获得的设备名称和主/次设备号创建/dev 中的设备文件; 如果是设备移除,则之前已经被创建的/dev 文件将被删除。
在 namedev 中使用 5 个步骤来决定指定设备的命名。
(1)标签(label)/序号(serial):这一步检查设备是否有惟一的识别记号,例如USB 设备有惟一的 USB 序号,SCSI 有惟一的 UUID。如果 namedev 找到与这种惟一编号相对应的规则,它将使用该规则提供的名称。
(2)设备总线号:这一步会检查总线设备编号,对于不可热插拔的环境,这一步足以辨别设备。例如,PCI 总线编号在系统的使用期间内很少变更。如果 namedev 找到相对应的规则,规则中的名称就会被使用。
(3)总线上的拓扑:当设备在总线上的位置匹配用户指定的规则时,就会使用该规则指定的名称。
(4)替换名称:当内核提供的名称匹配指定的替代字符串时,就会使用替代字符串指定的名称。
(5)内核提供的名称:如果以前的几个步骤都没有被提供,缺省的内核将被指定给该设备
namedev 命名规则
1 # USB Epson printer to be called lp_epson 2 LABEL, BUS="usb", serial="HXOLL0012202323480", NAME="lp_epson" 3 # USB HP printer to be called lp_hp, 4 LABEL, BUS="usb", serial="W09090207101241330", NAME="lp_hp" 5 # sound card with PCI bus id 00:0b.0 to be the first sound card 6 NUMBER, BUS="pci", id="00:0b.0", NAME="dsp" 7 # sound card with PCI bus id 00:07.1 to be the second sound card 8 NUMBER, BUS="pci", id="00:07.1", NAME="dsp1" 9 # USB mouse plugged into the third port of the first hub to be 10 # called mouse0 11 TOPOLOGY, BUS="usb", place="1.3", NAME="mouse0" 12 # USB tablet plugged into the second port of the second hub to be 13 # called mouse1 14 TOPOLOGY, BUS="usb", place="2.2", NAME="mouse1" 15 # ttyUSB1 should always be called visor 16 REPLACE, KERNEL="ttyUSB1", NAME="visor"
15:udev规则文件
udev 规则文件以行为单位,以“#”开头的行代表注释行。其余的每一行代表一个规则。每个规则分成一个或多个匹配和赋值部分。匹配部分用匹配专用的关键字来表示,相应的赋值部分用赋值专用的关键字来表示。
匹配关键字包括:
ACTION(用于匹配行为)、KERNEL(用于匹配内核设备名)、BUS (用于匹配总线类型)、 SYSFS (用于匹配从 sysfs 得到的信息, 比如 label、 vendor、USB 序列号)、SUBSYSTEM(匹配子系统名)等,赋值关键字包括:NAME(创建的设备文件名)、 SYMLINK (符号创建链接名)、 OWNER (设置设备的所有者)、 GROUP(设置设备的组)、IMPORT(调用外部程序)等。
例如如下规则:
SUBSYSTEM=="net", ACTION=="add", SYSFS{address}=="00:0d:87:f6:59:f3",IMPORT="/sbin/ rename_netiface %k eth0"
其中的匹配部分有 3 项,分别是 SUBSYSTEM、ACTION 和 SYSFS。而赋值部分有一项,是 IMPORT。这个规则的意思是:当系统中出现的新硬件属于 net 子系统范畴,系统对该硬件采取的动作是加入这个硬件,且这个硬件在 sysfs 文件系统中的“address”信息等于“00:0d:87:f6:59:f3”时,对这个硬件在 udev 层次施行的动作是调用外部程序/sbin/rename_netiface,并给该程序传递两个参数,一个是“%k”,代表内核对该新设备定义的名称,另一个是“eth0”。
udev 规则的写法非常灵活,在匹配部分,可以通过“*”、“?”、[a-c]、[1-9]等 shell通配符来灵活匹配多个项目。“*”类似于 shell 中的“*”通配符,代替任意长度的任意字符串,“?”代替一个字符,[x-y]是访问定义。此外,“%k”就是 KERNEL,“%n”则是设备的 KERNEL 序号(如存储设备的分区号)。
可以借助 udev 中的 udevinfo 工具查找规则文件可以利用的信息,如
运行“udevinfo -a -p /sys/ block/sda”命令
三.创建和配置udev
udev 包括 udev、 udevcontrol、 udevd、 udevsend、 udevmonitor、 udevsettle、 udevstart、udevinfo、udevtest 和 udevtrigger 等处于用户空间的程序。在嵌入式系统中,只需要udevd 和 udevstart 就能使 udev 工作。我们可以按照下面的步骤来生成和配置 udev。
(1)从 http://www.us.kernel.org/pub/linux/utils/kernel/hotplug/下载 udev 程序,如笔者下载的是 udev-114.tar.gz。
(2)运行“tar zxvf udev-114.tar.gz”命令解压缩 udev 程序包。
(3)运行 make 编译 udev,当前目录下会生成 test-udev、udevcontrol、udevd、udevinfo、udevmonitor、udevsettle、udevstart、udevtest 和 udevtrigger 共 9 个工具程序。
(4)把第(3)步生成的工具程序复制到/sbin 目录,同时把解压缩 udev-114.tar.gz后获得的 etc 目录下的 udev 目录复制到系统的/etc 下,etc/udev 下包含 udev.conf 配置文件、rules.d 目录(该目录中的文件给出了设备规则)等。
(5)编写完成启动、停止、重新启动等工作的 udev 脚本,
给出了一个范例。
1 #!/bin/sh -e 2 # udev 初始脚本 3 # 检查包是否已经被安装 4 [ -x /sbin/udevd ] || exit 0 5 6 case "$1" in 7 8 start) 9 # 需要 2.6.15 中引入的 uevent 的支持,如果系统中没有,使用静态/dev 10 if [ ! -f /sys/class/mem/null/uevent ]; then 11 if mountpoint -q /dev; then 12 umount -l /dev/.static/dev 13 umount -l /dev 14 fi 15 exit 1 16 fi 17 if ! mountpoint -q /dev; then 18 # initramfs 没有挂载/dev,因此,这里需要完成 19 mount -n --bind /dev /etc/udev 20 mount -n -t tmpfs -o mode=0755 udev /dev 21 mkdir -m 0700 -p /dev/.static/dev 22 mount -n --move /etc/udev /dev/.static/dev 23 fi 24 # 复制默认的设备树 25 cp -a -f /lib/udev/devices/* /dev 26 # 现在全部通过 netlink 进行,所以之前的热插拔程序不再使用 27 if [ -e /proc/sys/kernel/hotplug ]; then 28 echo "" > /proc/sys/kernel/hotplug 29 fi 30 # 开始 udevd 31 log_begin_msg "Starting kernel event manager..." 32 if start-stop-daemon --start --quiet --exec /sbin/udevd -- --daemon; then 33 log_end_msg 0 34 else 35 log_end_msg $? 36 fi 37 # 打开 udevtrigger 的事务日志 38 /sbin/udevmonitor -e >/dev/.udev.log & 39 UDEV_MONITOR_PID=$! 40 # 弥补丢失的事件 41 log_begin_msg "Loading hardware drivers..." 42 /sbin/udevtrigger 43 if /sbin/udevsettle; then 44 log_end_msg 0 45 else 46 log_end_msg $? 47 fi 48 # 杀死 udevmonitor 49 kill $UDEV_MONITOR_PID 50 ;; 51 52 53 stop) 54 log_begin_msg "Stopping kernel event manager..." 55 if start-stop-daemon --stop --quiet --oknodo --exec /sbin/udevd; 56 then 57 log_end_msg 0 58 else 59 log_end_msg $? 60 fi 61 umount -l /dev/.static/dev 62 umount -l /dev 63 ;; 64 65 66 restart) 67 cp -au /lib/udev/devices/* /dev 68 log_begin_msg "Loading additional hardware drivers..." 69 /sbin/udevtrigger 70 if /sbin/udevsettle; then 71 log_end_msg 0 72 else 73 log_end_msg $? 74 fi 75 ;; 76 77 reload|force-reload) 78 log_begin_msg "Reloading kernel event manager..." 79 if start-stop-daemon --stop --signal 1 --exec /sbin/udevd; then 80 log_end_msg 0 81 else 82 log_end_msg $? 83 fi 84 ;; 85 *) 86 echo "Usage: /etc/init.d/udev {start|stop|restart|reload|force-reload}" 87 exit 1 88 ;; 89 esac 90 91 exit 0
如果已经知道了设备对应的 sysfs 路径,则可以使用 udevtest 工具测试 udev 对该
设备所采取的行动,这可以帮助进行udev规则的调试 。
如运行“udevtest /sys/class/tty/ttys0”命令
1 This program is for debugging only, it does not run any program, 2 specified by a RUN key. It may show incorrect results, because 3 some values may be different, or not available at a simulation run. 4 parse_file: reading '/etc/udev/rules.d/05-udev-early.rules' as rules 5 file 6 parse_file: reading '/etc/udev/rules.d/60-persistent-input.rules' as 7 rules file 8 parse_file: reading '/etc/udev/rules.d/60-persistent-storage.rules' 9 as rules file 10 parse_file: reading '/etc/udev/rules.d/95-udev-late.rules' as rules 11 file 12 main: looking at device '/class/tty/ttys0' from subsystem 'tty' 13 udev_rules_get_name: no node name set, will use kernel name '' 14 udev_db_get_device: found a symlink as db file 15 udev_device_event: device '/class/tty/ttys0' already in database, 16 cleanup 17 udev_node_add: creating device node '/dev/ttys0', major=3, minor=48, 18 mode=0660, uid=0, gid=0 19 main: run: 'socket:/org/kernel/udev/monitor'
从中可以看出 udev 将为与/sys/class/tty/ttys0 对应的设备使用内核中的名称通过
udev_node_ add 创建/dev/ttys0 设备文件,主、次设备号分别为 3 和 48。
* 博客园: http://www.cnblogs.com/lihaiyan/
* 邮箱:1063385677@qq.com
* QQ: 1063385677
* Copyright ©2014 Lover雪儿
********************************************
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接,否则保留追究法律责任的权利。