Linux统一设备模型
简介
Linux2.6内核提供了新的设备模型,目的是为了对计算机上的所有设备进行统一地表示和操作,包括设备本身和设备之间的连接关系。这个模型是在 分析了 PCI 和 USB 的总线驱动过程中得到的,这两个总线类型能代表当前系统中的大多数设备类型,它们都有完善的热挺拔机制和电源管理的支持,也都有级连机制的支持,以桥接的 PCI/USB 总线控制器的方式可以支持更多的 PCI/USB 设备。[2] 总体来说,Linux统一设备模型具有以下的特点,
* 代码重复最小化
* 提供诸如引用计数这样的统一机制
* 可以列举系统中所有的设备,观察它们的状态,并且查看它们连接的总线
* 可以将系统中的全部设备结构以树的形式完整、有效的展现出来-包括所有的总线和内部连接
* 可以将设备和其对应的驱动联系起来,反之亦然
*可以将设备按照类型加以归类,比如分类为输入设备,而无需理解物理设备的拓扑结构
*可以讲设备树的叶子向其根的方向依次遍历,以保证能以正确顺序关闭各设备的电源。
最后一点是实现设备模型的最初动机。若想在内核中实现智能的电源管理,就需要来建立表示系统中设备拓扑关系的树结构[2]。如在一个典型的 PC 系统中,中央处理器(CPU)能直接控制的是 PCI 总线设备,而 USB 总线设备是以一个 PCI 设备(PCI-USB桥)的形式接入在 PCI 总线设备上,外部 USB 设备再接入在 USB 总线设备上;当计算机执行挂起(suspend)操作时, Linux 内核应该以 “外部USB设备->USB总线设备->PCI总线设备” 的顺序通知每一个设备将电源挂起;执行恢复(resume)时则以相反的顺序通知;反之如果不按此顺序则将有设备得不到正确的电源状态变迁的通知,将无法正常工作[1]。
内核对象机制关键数据结构
kobject内核对象
Kobject是Linux 2.6引入的新的设备管理机制,在内核中由struct kobject表示。通过这个数据结构使所有设备在底层都具有统一的接口,kobject提供基本的对象管理,是构成Linux 2.6设备模型的核心结构,它与sysfs文件系统紧密关联,每个在内核中注册的kobject对象都对应于sysfs文件系统中的一个目录。
Kobject结构定义为:
struct kobject {
char * k_name; 指向设备名称的指针
char name[KOBJ_NAME_LEN]; 设备名称
struct kref kref; 对象引用计数
struct list_head entry; 挂接到所在kset中去的单元
struct kobject * parent; 指向父对象的指针
struct kset * kset; 所属kset的指针
struct kobj_type * ktype; 指向其对象类型描述符的指针
struct dentry * dentry; sysfs文件系统中与该对象对应的文件节点路径指针
};
其中的kref域表示该对象引用的计数,内核通过kref实现对象引用计数管理,内核提供两个函数kobject_get()、kobject_put()分别用于增加和减少引用计数,当引用计数为0时,所有该对象使用的资源将被释放。
Ktype 域是一个指向kobj_type结构的指针,表示该对象的类型。Kobj_type数据结构包含三个域:一个release方法用于释放kobject占 用的资源;一个sysfs_ops指针指向sysfs操作表和一个sysfs文件系统缺省属性列表。Sysfs操作表包括两个函数store()和 show()。当用户态读取属性时,show()函数被调用,该函数编码指定属性值存入buffer中返回给用户态;而store()函数用于存储用户态 传入的属性值。
kset内核对象集合
Kobject通常通过kset组织成层次化的结构,kset是具有相同类型的kobject的集合,在内核中用kset数据结构表示,定义为:
struct kset {
struct subsystem * subsys; 所在的subsystem的指针
struct kobj_type * ktype; 指向该kset对象类型描述符的指针
struct list_head list; 用于连接该kset中所有kobject的链表头
struct kobject kobj; 嵌入的kobject
struct kset_hotplug_ops * hotplug_ops; 指向热插拔操作表的指针
};
包 含在kset中的所有kobject被组织成一个双向循环链表,list域正是该链表的头。Ktype域指向一个kobj_type结构,被该 kset中的所有kobject共享,表示这些对象的类型。Kset数据结构还内嵌了一个kobject对象(由kobj域表示),所有属于这个kset 的kobject对象的parent域均指向这个内嵌的对象。此外,kset还依赖于kobj维护引用计数:kset的引用计数实际上就是内嵌的 kobject对象的引用计数。
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中的链表。
Linux统一设备模型基本结构
类型 | 所包含的内容 | 对应内核数据结构 | 对应/sys项 |
---|---|---|---|
设备(Devices) | 设备是此模型中最基本的类型,以设备本身的连接按层次组织 | struct device |
/sys/devices/*/*/.../ |
设备驱动(Device Drivers) | 在一个系统中安装多个相同设备,只需要一份驱动程序的支持 | struct device_driver |
/sys/bus/pci/drivers/*/ |
总线类型(Bus Types) | 在整个总线级别对此总线上连接的所有设备进行管理 | struct bus_type |
/sys/bus/*/ |
设备类别(Device Classes) | 这是按照功能进行分类组织的设备层次树;如 USB 接口和 PS/2 接口的鼠标都是输入设备,都会出现在 /sys/class/input/ 下 | struct class |
/sys/class/*/ |
<linux/device.h> 中对于struct device的定义如下:
struct device {
struct klist klist_children;
struct klist_node knode_parent; /* node in sibling list */
struct klist_node knode_driver;
struct klist_node knode_bus;
struct device *parent;
struct kobject kobj;
char bus_id[BUS_ID_SIZE]; /* position on parent bus */
struct device_type *type;
unsigned is_registered:1;
unsigned uevent_suppress:1;
struct semaphore sem; /* semaphore to synchronize calls to
* its driver.
*/
struct bus_type *bus; /* type of bus device is on */
struct device_driver *driver; /* which driver has allocated this
device */
void *driver_data; /* data private to the driver */
void *platform_data; /* Platform specific data, device
core doesn't touch it */
struct dev_pm_info power;
#ifdef CONFIG_NUMA
int numa_node; /* NUMA node this device is close to */
#endif
u64 *dma_mask; /* dma mask (if dma'able device) */
u64 *dma_mask; /* dma mask (if dma'able device) */
u64 coherent_dma_mask;/* Like dma_mask, but for
alloc_coherent mappings as
not all hardware supports
64 bit addresses for consistent
allocations such descriptors. */
struct device_dma_parameters *dma_parms;
struct list_head dma_pools; /* dma pools (if dma'ble) */
struct dma_coherent_mem *dma_mem; /* internal for coherent mem
override */
/* arch specific additions */
struct dev_archdata archdata;
spinlock_t devres_lock;
struct list_head devres_head;
/* class_device migration path */
struct list_head node;
struct class *class;
dev_t devt; /* dev_t, creates the sysfs "dev" */
struct attribute_group **groups; /* optional groups */
void (*release)(struct device *dev);
};
其中比较重要的有:
struct device *parent;
struct kobject kobj;
struct bus_type *bus;
struct device_driver *driver;
struct class *class;
dev_t devt;
对于部分字段,上面的struct定义里面有解释。
<linux/device.h>
struct device_driver {
const char *name;
struct bus_type *bus;
struct module *owner;
const char *mod_name; /* used for built-in modules */
int (*probe) (struct device *dev);
int (*remove) (struct device *dev);
void (*shutdown) (struct device *dev);
int (*suspend) (struct device *dev, pm_message_t state);
int (*resume) (struct device *dev);
struct attribute_group **groups;
struct driver_private *p;
};
device_driver的定义比较短,包含了name和bus等一些名称,以及probe, remove, shutdown, suspend和resume这五个动作。
<linux/device.h>
struct bus_type {
const char *name;
struct bus_attribute *bus_attrs;
struct device_attribute *dev_attrs;
struct driver_attribute *drv_attrs;
int (*match)(struct device *dev, struct device_driver *drv);
int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
int (*probe)(struct device *dev);
int (*remove)(struct device *dev);
void (*shutdown)(struct device *dev);
int (*suspend)(struct device *dev, pm_message_t state);
int (*suspend_late)(struct device *dev, pm_message_t state);
int (*resume_early)(struct device *dev);
int (*resume)(struct device *dev);
struct bus_type_private *p;
};
struct class {
const char *name;
struct module *owner;
struct kset subsys;
struct list_head children;
struct list_head devices;
struct list_head interfaces;
struct kset class_dirs;
struct semaphore sem; /* locks children, devices, interfaces */
struct class_attribute *class_attrs;
struct class_device_attribute *class_dev_attrs;
struct device_attribute *dev_attrs;
int (*uevent)(struct class_device *dev, struct kobj_uevent_env *env);
int (*dev_uevent)(struct device *dev, struct kobj_uevent_env *env);
void (*release)(struct class_device *dev);
void (*class_release)(struct class *class);
void (*dev_release)(struct device *dev);
int (*suspend)(struct device *dev, pm_message_t state);
int (*resume)(struct device *dev);
};
struct class定义了按照功能进行分类的标准,但是这个标准真正体现在/sys/class/里面是通过struct class_device这个结构体进行的:
struct class_device {
struct list_head node;
struct kobject kobj;
struct class *class;
dev_t devt;
struct device *dev;
void *class_data;
struct class_device *parent;
struct attribute_group **groups;
void (*release)(struct class_device *dev);
int (*uevent)(struct class_device *dev, struct kobj_uevent_env *env);
char class_id[BUS_ID_SIZE];
};
SYS文件系统
sysfs 是在这个 Linux 统一设备模型的开发过程中的一项副产品,sysfs is a ram-based filesystem initially based on ramfs. It provides a means to export kernel data structures, their attributes, and the linkages between them to userspace
SYS文件系统目录:
sys: block, bus, , devices, firmware, fs, kernel, module, power 等
这些目录展示了内核对各种设备进行统一管理的模型和方式。
/sys/devices
这是内核对系统中所有设备的分层次表达模型,也是 /sys 文件系统管理设备的最重要的目录结构。
/sys/dev
这个目录下维护一个按字符设备和块设备的主次号码(major:minor)链接到真实的设备(/sys/devices下)的符号链接文件。
/sys/bus
内核设备按总线类型分层放置的目录结构, devices 中的所有设备都是连接于某种总线之下,在这里的每一种具体总线之下可以找到每一个具体设备的符号链接.
/sys/class
这是按照设备功能分类的设备模型,如系统所有输入设备都会出现在 /sys/class/input 之下,而不论它们是以何种总线连接到系统。
/sys/block deprecated
/sys/firmware
系统加载固件机制的对用户空间的接口
/sys/fs
这 里按照设计是用于描述系统中所有文件系统,包括文件系统本身和按文件系统分类存放的已挂载点,但目前只有 fuse,gfs2 等少数文件系统支持 sysfs 接口,一些传统的虚拟文件系统(VFS)层次控制参数仍然在 sysctl (/proc/sys/fs) 接口中中;
/sys/kernel
这里是内核所有可调整参数的位置,目前只有 uevent_helper, kexec_loaded, mm, 和新式的 slab 分配器等几项较新的设计在使用它,其它内核可调整参数仍然位于 sysctl (/proc/sys/kernel) 接口中 ;
/sys/module
这里有系统中所有模块的信息,不论这些模块是以内联(inlined)方式编译到内核映像文件(vmlinuz)中还是编译为外部模块(ko文件),都可能会出现在 /sys/module 中:
* 编译为外部模块(ko文件)在加载后会出现对应的 /sys/module/<module_name>/, 并且在这个目录下会出现一些属性文件和属性目录来表示此外部模块的一些信息,如版本号、加载状态、所提供的驱动程序等;
* 编译为内联方式的模块则只在当它有非0属性的模块参数时会出现对应的 /sys/module/<module_name>, 这些模块的可用参数会出现在 /sys/modules/<modname>/parameters/<param_name> 中,
o 如 /sys/module/printk/parameters/time 这个可读写参数控制着内联模块 printk 在打印内核消息时是否加上时间前缀;
o 所有内联模块的参数也可以由 "<module_name>.<param_name>=<value>" 的形式写在内核启动参数上,如启动内核时加上参数 "printk.time=1" 与 向 "/sys/module/printk/parameters/time" 写入1的效果相同;
* 没有非0属性参数的内联模块不会出现于此。
/sys/power
这里是系统中电源选项,这个目录下有几个属性文件可以用于控制整个机器的电源状态,如可以向其中写入控制命令让机器关机、重启等。
参考文献:
[1]陈莉君 Linux设备驱动模型 part 1, part 2, part 3, part 4
[2]程任全 使用sys文件系统访问Linux 内核 full text
[3]http://blog.chinaunix.net/u2/86638/showart_1850276.html