博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
《Linux Device Driver》——Linux设备模型
阅读量:4157 次
发布时间:2019-05-26

本文共 19031 字,大约阅读时间需要 63 分钟。

Linux设备模型的目的是:为内核建立起一个统一的设备模型,从而有一个对系统结构的一般性抽象描述。

现在内核使用设备模型支持多种不同的任务:

设备模型 描述
电源管理和系统关机 这些需要对系统结构的理解,设备模型使OS能以正确顺序遍历系统硬件。
与用户空间的通讯 sysfs 虚拟文件系统的实现与设备模型的紧密相关, 并向外界展示它所表述的结构。向用户空间提供系统信息、改变操作参数的接口正越来越多地通过 sysfs , 也就是设备模型来完成。
热插拔设备 外围设备可根据用户的需要安装与卸载,内核中的热插拔机制可以处理热插拔设备
设备类型 设备模型包括了将设备分类的机制,在一个更高的功能层上描述这些设备, 并使设备对用户空间可见。
对象生命周期 设备模型的实现需要创建一系列机制来处理对象的生命周期、对象间的关系和对象在用户空间的表示。

Linux 设备模型是一个复杂的数据结构。但对模型的大部分来说, Linux 设备模型代码会处理好这些关系, 而不是把他们强加于驱动作者。模型隐藏于交互的背后,与设备模型的直接交互通常由总线级的逻辑和其他的内核子系统处理。所以许多驱动作者可完全忽略设备模型, 并相信设备模型能处理好他所负责的事。


Kobject、Kset 和 Subsystem

Kobject

kobject是一种数据结构,定义在 <linux/kobject.h> 。

struct kobject {
const char * k_name;/*kobject 的名字数组(sysfs 入口使用的名字)指针;如果名字数组大小小于KOBJ_NAME_LEN,它指向本数组的name,否则指向另外分配的一个名字数组空间 */ char name[KOBJ_NAME_LEN];/*kobject 的名字数组,若名字数组大小不小于KOBJ_NAME_LEN,只储存前KOBJ_NAME_LEN个字符*/ struct kref kref;/*kobject 的引用计数*/ struct list_head entry;/*kobject 之间的双向链表,与所属的kset形成环形链表*/ struct kobject * parent;/*在sysfs分层结构中定位对象,指向上一级kset中的struct kobject kobj*/ struct kset * kset;/*指向所属的kset*/ struct kobj_type * ktype;/*负责对该kobject类型进行跟踪的struct kobj_type的指针*/ struct dentry * dentry;/*sysfs文件系统中与该对象对应的文件节点路径指针*/ wait_queue_head_t poll;/*等待队列头*/};

kobject是组成设备模型的基本结构,最初只是被理解为一个简单的引用计数,但是随若时间的推移,它的任务越来越多,因此也有了许多成员。现在 kobject结构所能处理的任务以及它所支持的代码包括:

对象的引用计数
通常一个内核对象被创建时,不可能知道该对象存活的时间。跟踪此对象生命周期的一个方法是使用引用计数。当内核中没有代码持有该对象的引用时,该对象将结束自己的有效生命周期,并且可以被删除。
sysfs表述
在sysfs中显示的每一个对象,都对应一个kobject,它被用来与内核交互并创建它的可见表述。
数据结构关联
从整体上看,设备模型是一个友好而复杂的数据结构,通过在其间的大量连接而构成一个多层次的体系结构。 kobject实现了该结构并把它们聚合在一起
热插拔事件处理
当系统中的硬件被热插拔时,在 kobject子系统控制下,将产生事件以通知用户空间


kobject初始化

kobject的初始化较为复杂,但是必须的步骤如下:

1.将整个kobject清零,通常使用memset函数。
2.调用kobject_init()函数,设置结构内部一些成员。所做的一件事情是设置kobject的引用计数为1。
具体的源码如下:

void kobject_init(struct kobject * kobj)/*inkobject.c*/{
if (!kobj) return; kref_init(&kobj->kref);/*设置引用计数为1*/ INIT_LIST_HEAD(&kobj->entry);/*初始化kobject 之间的双向链表*/ init_waitqueue_head(&kobj->poll);/*初始化等待队列头*/ kobj->kset = kset_get(kobj->kset);/*增加所属kset的引用计数(若没有所属的kset,则返回NULL)*/}void kref_init(struct kref *kref)/*in kobject.c*/{
atomic_set(&kref->refcount,1); smp_mb();}static inline struct kset * to_kset(struct kobject * kobj)/*in kobject.h*/{
return kobj ? container_of(kobj,struct kset,kobj) : NULL;}static inline struct kset * kset_get(struct kset * k)/*in kobject.h*/{
return k ? to_kset(kobject_get(&k->kobj)) : NULL;/*增加引用计数*/}

3.设置kobject的名字

int kobject_set_name(struct kobject * kobj, const char * fmt, ...);

4.直接或间接设置其它成员:ktype、kset和parent。(重要)


对引用计数的操作

kobject 的一个重要函数是为包含它的结构设置引用计数。只要对这个对象的引用计数存在, 这个对象( 和支持它的代码) 必须继续存在。底层控制 kobject 的引用计数的函数有:

struct kobject *kobject_get(struct kobject *kobj);/*若成功,递增 kobject 的引用计数并返回一个指向 kobject 的指针,否则返回 NULL。必须始终测试返回值以免产生竞态*/void kobject_put(struct kobject *kobj);/*递减引用计数并在可能的情况下释放这个对象*/

注意:kobject _init 设置这个引用计数为 1,因此创建一个 kobject时, 当这个初始化引用不再需要,应当确保采取 kobject_put 调用。

struct cdev 的引用计数实现如下:

struct kobject *cdev_get(struct cdev *p) {
struct module *owner = p->owner; struct kobject *kobj; if (owner && !try_module_get(owner)) return NULL; kobj = kobject_get(&p->kobj); if (!kobj) module_put(owner); return kobj;}

创建一个对 cdev 结构的引用时,还需要创建包含它的模块的引用。因此, cdev_get 使用 try_module_get 来试图递增这个模块的使引用计数。如果这个操作成功, kobject_get 被同样用来递增 kobject 的引用计数。kobject_get 可能失败, 因此这个代码检查 kobject_get 的返回值,如果调用失败,则释放它的对模块的引用计数。


release 函数和 kobject 类型

一个被kobject所保护的结构,不能在驱动程序生命周期的任何可预知的、单独的时间点上被释放掉。

引用计数不由创建 kobject 的代码直接控制,当 kobject 的最后引用计数消失时,必须异步通知,而后kobject中ktype所指向的kobj_type结构体包含的release函数会被调用。通常原型如下:

void my_object_release(struct kobject *kobj){
struct my_object *mine = container_of(kobj, struct my_object, kobj); /* Perform any additional cleanup on this object, then... */ kfree(mine);}

每个 kobject 必须有一个release函数, 并且这个 kobject 必须在release函数被调用前保持不变( 稳定状态 ) 。这样,每一个 kobject 需要有一个关联的 kobj_type 结构,指向这个结构的指针能在 2 个不同的地方找到:

1.kobject 结构自身包含一个成员(ktype)指向kobj_type ;
2.如果这个 kobject 是一个 kset 的成员, kset 会提供kobj_type 指针。

struct kset {
struct kobj_type * ktype; /*指向该kset对象类型的指针*/ struct list_head list;/*用于连接该kset中所有kobject以形成环形链表的链表头*/ spinlock_t list_lock;/*用于避免竞态的自旋锁*/ struct kobject kobj; /*嵌入的kobject*/ struct kset_uevent_ops * uevent_ops;/*原有的struct kset_hotplug_ops * hotplug_ops;已经不存在,被kset_uevent_ops 结构体替换,在热插拔操作中会介绍*/};

kobj_type结构的声明:

struct kobj_type{
void (*release)(struct kobject *); struct sysfs_ops *sysfs_ops; struct attribute **default_attrs; };

以下宏用以查找指定kobject的kobj_type 指针:

struct kobj_type *get_ktype(struct kobject *kobj);

这个函数其实就是从以上提到的这两个地方返回kobj_type指针,源码如下:

static inline struct kobj_type * get_ktype(struct kobject * k){
if (k->kset && k->kset->ktype) return k->kset->ktype; else return k->ktype;}

在新版本的内核中已经在struct kset中去除了 struct kobj_type * ktype;

也就是说只有struct kobject中存在 kobj_type ,所以get_ktype也相应变为了:

static inline struct kobj_type * get_ktype(struct kobject * kobj){
return kobj->ktype;}

kobject层次结构、kset和子系统

内核通常用kobject 结构将各个对象连接起来组成一个分层的结构体系,与模型化的子系统相匹配。有 2 个独立的机制用于连接: parent 指针和 kset。

parent 是指向另外一个kobject 结构(分层结构中上一层的节点)的指针,主要用途是在 sysfs 层次中定位对象。

kset

kset 像 kobj_type 结构的扩展; 一个 kset 是嵌入到相同类型结构的 kobject 的集合。但 struct kobj_type 关注的是对象的类型,而struct kset 关心的是对象的聚合和集合,其主要功能是包容,可认为是kobjects 的顶层容器类。每个 kset 在内部包含自己的 kobject, 并可以用多种处理kobject 的方法处理kset。 ksets 总是在 sysfs 中出现; 一旦设置了 kset 并把它添加到系统中, 将在 sysfs 中创建一个目录;kobjects 不必在 sysfs 中表示, 但kset中的每一个 kobject 成员都要在sysfs中表述。

创建一个对象时,通常增加 kobject 到 kset 中去其过程分为2步:

1.先把kobject的kset成员要指向目的kset;
2.然后将kobject传递到kobject_add函数中。

int kobject_add(struct kobject *kobj); /*函数可能失败(返回一个负错误码),程序应作出相应地反应*/

内核提供了一个组合函数:

extern int kobject_register(struct kobject *kobj); /*仅仅是一个 kobject_init 和 kobject_add 的结合,其他成员的初始化必须在之前手动完成*/

这个函数时kobject_init和kobject_add的简单组合。

当把一个kobject传递给kobject_add时,将会增加它的引用计数。在kset中包含的最重要的内容是对象的引用。在某些时候会需要使用kobject_del函数把kobject从kset中删除,以清楚引用。

void kobject_del(struct kobject *kobj);

还有一个kobject_unregister函数,是kobject_del和kobject_put的组合

void kobject_unregister(struct kobject *kobj); /*是 kobject_del 和 kobject_put 的结合*/

但是在新的内核中已经没有" register"和" unregister "类似的函数了,取而代之的是类似

extern int __must_check kobject_init_and_add(struct kobject *kobj,                     struct kobj_type *ktype,                     struct kobject *parent,                     const char *fmt, ...);

这样的函数,所以请根据你使用的内核版本自己研究了.

kset 在一个标准的内核链表中保存了它的子节点,在大部分情况下, 被包含的 kobjects 在它们的 parent 成员中保存指向 kset内嵌的 kobject的指针,关系如下:

在这里插入图片描述

kset 上的操作

ksets 有类似于kobjects初始化和设置接口:

void kset_init(struct kset *kset);int kset_add(struct kset *kset);int kset_register(struct kset *kset);void kset_unregister(struct kset *kset);/*管理 ksets 的引用计数:*/struct kset *kset_get(struct kset *kset);void kset_put(struct kset *kset);/* kset 也有一个名字,存储于嵌入的 kobject,因此设置它的名字用:*/kobject_set_name(&my_set->kobj, "The name");

ksets 还有一个指针指向 kobj_type 结构来描述它包含的 kobject,这个类型优先于 kobject 自身中的 ktype 。因此在典型的应用中, 在 struct kobject 中的 ktype 成员被设为 NULL, 而 kset 中的ktype是实际被使用的。

在新的内核里,kset不再包含一个子系统指针struct subsystem * subsys, 而且subsystem已经被kset取代。


子系统

子系统是对整个内核中一些高级部分的表述。子系统通常(但不一定)出现在 sysfs分层结构中的顶层,内核子系统包括 block_subsys(/sys/block 块设备)、 devices_subsys(/sys/devices 核心设备层)以及内核已知的用于各种总线的特定子系统。

对于新的内核已经不再有subsystem数据结构了,用kset代替了。每个 kset 必须属于一个子系统,子系统成员帮助内核在分层结构中定位 kset 。

子系统结构其实就是对kset和一个信号量的封装:

struct subsystem{
struct kset kset; struct rw_semaphore rwsem; }
/*子系统通常用以下的宏声明:*/decl_subsys(name, struct kobj_type *type, struct kset_uevent_ops * uevent_ops);/*子系统的操作函数:*/void subsystem_init(struct kset *s);int subsystem_register(struct kset *s);void subsystem_unregister(struct kset *s);struct subsystem *subsys_get(struct kset *s)void subsys_put(struct kset *s);/*这些函数基本上是kset操作函数的包装,以实现子系统的操作*/

底层sysfs操作

kobject 是在 sysfs 虚拟文件系统后的机制。对每个在 sysfs 中的目录, 在内核中都会有一个 kobject 与之对应。每个 kobject 都输出一个或多个属性, 它在 kobject 的 sysfs 目录中以文件的形式出现, 其中的内容由内核产生。<linux/sysfs.h> 包含 sysfs 的工作代码。

在 sysfs 中创建kobject的入口是kobject_add的工作的一部分,只要调用 kobject_add 就会在sysfs 中显示,还有些知识值得记住:
1.kobjects 的 sysfs 入口始终为目录, kobject_add 的调用将在sysfs 中创建一个目录,这个目录包含一个或多个属性(文件);
2.分配给 kobject 的名字( 用 kobject_set_name ) 是 sysfs 中的目录名,出现在 sysfs 层次的相同部分的 kobjects 必须有唯一的名字. 分配给 kobjects 的名字也应当是合法的文件名字: 它们不能包含非法字符(如:斜线)且不推荐使用空白。
3.sysfs 入口位置对应 kobject 的 parent 指针。若 parent 是 NULL ,则它被设置为嵌入到新 kobject 的 kset 中的 kobject;若 parent 和 kset 都是 NULL, 则sysfs 入口目录在顶层,通常不推荐。


默认属性

当创建kobject 时, 每个 kobject 都被给定一系列默认属性。这些属性保存在 kobj_type 结构中:

struct kobj_type {
void (*release)(struct kobject *); struct sysfs_ops *sysfs_ops;/*提供实现以下属性的方法*/ struct attribute **default_attrs; /*用于保存类型属性列表(指针的指针) */};

default_attrs成员保存了属性列表,用于创建该类型的每一个kobject,sysfs_ops提供了实现这些属性的方法。default_attrs指向一个包含attribute结构数组的指针:

struct attribute {
char *name;/*属性的名字( 在 kobject 的 sysfs 目录中显示)*/ struct module *owner;/*指向模块的指针(如果有), 此模块负责实现这个属性*/ mode_t mode; /*属性的保护位,modes 的宏定义在
:例如S_IRUGO 为只读属性;S_IWUSER仅为root提供写权限等等*/}; /*default_attrs 列表中的最后一个元素必须用 0 填充*/

sysfs 读写这些属性是由 kobj_type->sysfs_ops 成员中的函数完成的:

struct sysfs_ops {
ssize_t (*show)(struct kobject *kobj, struct attribute *attr, char *buffer); ssize_t (*store)(struct kobject *kobj, struct attribute *attr, const char *buffer, size_t size);};

当用户空间读取一个属性时,内核会使用指向 kobject 的指针(kobj)和正确的属性结构(*attr)来调用show 方法,该方法将给定属性值编码进缓冲(buffer)(注意不要越界( PAGE_SIZE 字节)), 并返回实际数据长度。sysfs 的约定要求每个属性应当包含一个单个人眼可读值; 若返回大量信息,需将它分为多个属性.

也可对所有 kobject 关联的属性使用同一个 show 方法,用传递到函数的 attr 指针来判断所请求的属性。有的 show 方法包含对属性名字的检查。有的show 方法会将属性结构嵌入另一个结构, 这个结构包含需要返回属性值的信息,这时可用container_of 获得上层结构的指针以返回属性值的信息。

store 方法将存在缓冲(buffer)的数据( size 为数据的长度,不能超过 PAGE_SIZE )解码并保存新值到属性(*attr), 返回实际解码的字节数。store 方法只在拥有属性的写权限时才能被调用。此时注意:接收来自用户空间的数据一定要验证其合法性。如果到数据不匹配, 返回一个负的错误值。


非默认属性

虽然 kobject 类型的 default_attrs 成员描述了所有的 kobject 会拥有的属性,倘若想添加新属性到 kobject 的 sysfs 目录属性只需简单地填充一个attribute结构并传递到以下函数:

int sysfs_create_file(struct kobject *kobj, struct attribute *attr);/*若成功,文件以attribute结构中的名字创建并返回 0; 否则, 返回负错误码*//*注意:内核会调用相同的 show() 和 store() 函数来实现对新属性的操作,所以在添加一个新非默认属性前,应采取必要的步骤确保这些函数知道如何实现这个属性*/

若要删除属性,调用:

int sysfs_remove_file(struct kobject *kobj, struct attribute *attr);/*调用后, 这个属性不再出现在 kobject 的 sysfs 入口。若一个用户空间进程可能有一个打开的那个属性的文件描述符,在这个属性已经被删除后,show 和 store 仍然可能被调用*/

二进制属性

sysfs 通常要求所有属性都只包含一个可读文本格式的值,很少需要创建能够处理大量二进制数据的属性。但当在用户空间和设备间传递不可改变的数据时(如上传固件到设备)就需要这个特性。二进制属性使用一个 bin_attribute 结构来描述:

struct bin_attribute {
struct attribute attr;/*属性结构体*/ size_t size;/*这个二进制属性的最大大小(若无最大值则为0)*/ void *private; ssize_t (*read)(struct kobject *, char *, loff_t, size_t); ssize_t (*write)(struct kobject *, char *, loff_t, size_t);/*read 和 write 方法类似字符驱动的读写方法;,在一次加载中可被多次调用,每次调用最大操作一页数据,且必须能以其他方式判断操作数据的末尾*/ int (*mmap)(struct kobject *, struct bin_attribute *attr, struct vm_area_struct *vma);};/*二进制属性必须显式创建,不能以默认属性被创建,创建一个二进制属性调用:*/int sysfs_create_bin_file(struct kobject *kobj, struct bin_attribute *attr);/*删除二进制属性调用:*/int sysfs_remove_bin_file(struct kobject *kobj, struct bin_attribute *attr);

符号链接

sysfs 文件系统具有树型结构, 反映 kobject之间的组织层次关系。为了表示驱动程序和所管理的设备间的关系,需要额外的指针,其在 sysfs 中通过符号链接实现。

/*在 sysfs 创建一个符号链接:*/int sysfs_create_link(struct kobject *kobj, struct kobject *target, char *name);/*函数创建一个链接(name)指向target的 sysfs 入口作为 kobj 的一个属性,是一个相对连接,与它在sysfs 系统中的位置无关*//*删除符号连接调用:*/void sysfs_remove_link(struct kobject *kobj, char *name);

热插拔事件产生

一个热插拔事件是一个从内核空间发送到用户空间的通知, 表明系统配置已经改变. 无论 kobject 被创建或删除,都会产生这种事件。热插拔事件会导致对 /sbin/hotplug 的调用, 它通过加载驱动程序, 创建设备节点, 挂载分区或其他正确动作响应事件。


热插拔操作

热插拔事件的实际控制是通过一套存储于 kset_uevent_ops (《LDD3》中介绍的struct kset_hotplug_ops * hotplug_ops;在2.6.22.2中已经被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);};

可以在 kset 结构的uevent_ops 成员中找到指向kset_uevent_ops结构的指针。

若在 kobject 中不包含指定的 kset , 内核将通过 parent 指针在分层结构中进行搜索,直到发现一个包含有kset的 kobject ; 接着使用这个 kset 的热插拔操作。

kset_uevent_ops 结构中的三个方法作用如下:
1.filter 函数让 kset 代码决定是否将事件传递给用户空间。如果 filter 返回 0,将不产生事件。以磁盘的 filter 函数为例,它只允许kobject产生磁盘和分区的事件,源码如下:

static int block_hotplug_filter(struct kset *kset, struct kobject *kobj){
struct kobj_type *ktype = get_ktype(kobj); return ((ktype == &ktype_block) || (ktype == &ktype_part));}
  1. 当调用用户空间的热插拔程序时,相关子系统的名字将作为唯一的参数传递给它。name 函数负责返回合适的字符串传递给用户空间的热插拔程序。

3.热插拔脚本想得到的任何其他参数都通过环境变量传递。uevent 函数的作用是在调用热插拔脚本之前将参数添加到环境变量中。函数原型:

int (*uevent)(struct kset *kset, struct kobject *kobj, /*产生事件的目标对象*/ char **envp,/*一个保存其他环境变量定义(通常为 NAME=value 的格式)的数组*/ int num_envp, /*环境变量数组中包含的变量个数(数组大小)*/ char *buffer, int buffer_size/*环境变量被编码后放入的缓冲区的指针和字节数(大小)*//*若需要添加任何环境变量到 envp, 必须在最后的添加项后加一个 NULL 入口,使内核知道数组的结尾*/        );/*返回值正常应当是 0,若返回非零值将终止热插拔事件的产生*/

sysfs文件系统

Sysfs文件系统是一个类似于proc文件系统的特殊文件系统,用于将系统中的设备组织成层次结构,并向用户模式程序提供详细的内核数据结构信息。其顶层目录主要有:

Block目录:包含所有的块设备
Devices目录:包含系统所有的设备,并根据设备挂接的总线类型组织成层次结构
Bus目录:包含系统中所有的总线类型
Drivers目录:包括内核中所有已注册的设备驱动程序
Class目录:系统中的设备类型(如网卡设备,声卡设备等)


内核对象机制关键数据结构

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 2.6内核定义了一系列操作函数,定义于lib/kobject.c文件中。

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); //将kobj对象加入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);//从Linux设备层次(hierarchy)中删除kobj对象。 void kobject_unregister(struct kobject * kobj);//kobject注销函数。与kobject_register()相反,它首先调用kobject_del从设备层次中删除该对象,再调用kobject_put()减少该对象的引用计数,如果引用计数降为0,则释放该kobject对象。

kset相关函数

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

设备模型组件

devices

系统中的任一设备在设备模型中都由一个device对象描述,其对应的数据结构struct device定义为:

struct device {
struct list_head g_list; struct list_head node; struct list_head bus_list; struct list_head driver_list; struct list_head children; struct device *parent; struct kobject kobj; char bus_id[BUS_ID_SIZE]; struct bus_type *bus; struct device_driver *driver; void *driver_data; /* Several fields omitted */ };

g_list 将该device对象挂接到全局设备链表中,所有的device对象都包含在devices_subsys中,并组织成层次结构。Node域将该对象挂接 到其兄弟对象的链表中,而bus_list则用于将连接到相同总线上的设备组织成链表,driver_list则将同一驱动程序管理的所有设备组织为链 表。此外,children域指向该device对象子对象链表头,parent域则指向父对象。Device对象还内嵌一个kobject对象,用于引 用计数管理并通过它实现设备层次结构。Driver域指向管理该设备的驱动程序对象,而driver_data则是提供给驱动程序的数据。Bus域描述设备所连接的总线类型。

内核提供了相应的函数用于操作device对象。其中Device_register()函数将一个新的device对象插入设备模型,并自动在/sys/devices下创建一个对应的目录。Device_unregister()完成相反的操作,注销设备对象。 Get_device()和put_device()分别增加与减少设备对象的引用计数。通常device结构不单独使用,而是包含在更大的结构中作为一 个子结构使用,比如描述PCI设备的struct pci_dev,其中的dev域就是一个device对象。


drivers

系统中的每个驱动程序由一个device_driver对象描述,对应的数据结构定义为:

struct device_driver {
char *name; 设备驱动程序的名称 struct bus_type *bus; 该驱动所管理的设备挂接的总线类型 struct kobject kobj; 内嵌kobject对象 struct list_head devices; 该驱动所管理的设备链表头 int (*probe)(struct device *dev); 指向设备探测函数,用于探测设备是否可以被该驱动程序管理 int (*remove)(struct device *dev); 用于删除设备的函数 /* some fields omitted*/ };

与device 结构类似,device_driver对象依靠内嵌的kobject对象实现引用计数管理和层次结构组织。内核提供类似的函数用于操作 device_driver对象,如get_driver()增加引用计数,driver_register()用于向设备模型插入新的driver对 象,同时在sysfs文件系统中创建对应的目录。Device_driver()结构还包括几个函数,用于处理热拔插、即插即用和电源管理事件。


buses

系统中总线由struct bus_type描述,定义为:

struct bus_type {
char * name; 总线类型的名称 struct subsystem subsys; 与该总线相关的subsystem struct kset drivers; 所有与该总线相关的驱动程序集合 struct kset devices; 所有挂接在该总线上的设备集合 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 (*hotplug) (struct device *dev, char **envp, int num_envp, char *buffer, int buffer_size); int (*suspend)(struct device * dev, u32 state); int (*resume)(struct device * dev); };

每个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结构还包含几个函数 (match()、hotplug()等)处理相应的热插拔、即插即拔和电源管理事件。


classes

系统中的设备类由 struct class描述,表示某一类设备。所有的class对象都属于class_subsys子系统,对应于sysfs文件系统中的/sys/class目录。 每个class对象包括一个class_device链表,每个class_device对象表示一个逻辑设备,并通过struct class_device中的dev域(一个指向struct device的指针)关联一个物理设备。这样,一个逻辑设备总是对应于一个物理设备,但是一个物理设备却可能对应于多个逻辑设备。此外,class结构中 还包括用于处理热插拔、即插即拔和电源管理事件的函数,这与device对象和driver对象相似。

转载地址:http://rezxi.baihongyu.com/

你可能感兴趣的文章
关于VS Code 中文显示乱码
查看>>
mongoose上传文件
查看>>
HTTP协议详解
查看>>
Visual Studio 编译jrtplib
查看>>
wireshark抓取rtp包
查看>>
VS2015编译eXosip2-5.0.0
查看>>
pthread_cond_wait()用法分析
查看>>
Qt半透明对话框
查看>>
QT:QDialog去掉标题栏不显示
查看>>
Qt应用程序开发一:中文编译错误和乱码处理
查看>>
海思音频理解
查看>>
windows 上ffplay 遇到的问题 WASAPI can’t initialize audio client
查看>>
ffmpeg 合并h264 aac 无损
查看>>
linux DRM基本概念与使用示例
查看>>
mp4v2编译出错
查看>>
运行时域和加载时域(运行地址和加载地址)
查看>>
drm 随记
查看>>
LR和pc寄存器
查看>>
rga 格式转换到buffer指定位置
查看>>
ubuntu16.04 安装交叉编译工具aarch64-linux-gnu-gcc/g++,并解决GLIBCXX_3.4.26‘ not found的问题
查看>>