LDD3读书笔记-----Linux 设备模型

2.6 设备模型

2.6 设备模型提供了这个抽象. 现在它用在内核来支持广泛的任务, 包括:

电源管理和系统关机
这些需要一个对系统的结构的理解. 例如, 一个 USB 宿主适配器不可能被关闭, 在处理所有的连接到这个适配器的设备之前. 这个设备模型使能了一个按照正确顺序的系统硬件的遍历.

与用户空间的通讯
sysfs 虚拟文件系统的实现被紧密地捆绑进设备模型, 并且暴露它所代表的结构. 关于系统到用户空间的信息提供和改变操作参数的旋纽正越来越多地通过 sysfs 和 通过设备模型来完成.

可热插拔设备
计算机硬件正更多地动态变化; 外设可因用户的一时念头而进出. 在内核中使用的来处理和(特别的)与用户空间关于设备插入和拔出的通讯, 是由设备模型来管理.

设备类别
系统的许多部分对设备如何连接没有兴趣, 但是它们需要知道什么类型的设备可用. 设备模型包括一个机制来分配设备给类别, 它在一个更高的功能性的级别描述了这些设备, 并且允许它们从用户空间被发现.

对象生命期
许多上面描述的功能, 包括热插拔支持和 sysfs, 使在内核中创建和操作对象复杂了. 设备模型的实现要求创建一套机制来处理对象生命期, 它们之间的关系, 和它们在用户空间的表示.

Kobjects

Kobject 是基础的结构, 它保持设备模型在一起. 初始地它被作为一个简单的引用计数, 但是它的责任已随时间增长, 并且因此有了它自己的战场. struct kobject 所处理的任务和它的支持代码现在包括:

对象的引用计数

常常, 当一个内核对象被创建, 没有方法知道它会存在多长时间. 一种跟踪这种对象生命周期的方法是通过引用计数. 当没有内核代码持有对给定对象的引用, 那个对象已经完成了它的有用寿命并且可以被删除.

sysfs 表示

在 sysfs 中出现的每个对象在它的下面都有一个 kobject, 它和内核交互来创建它的可见表示.

数据结构粘和

设备模型是, 整体来看, 一个极端复杂的由多级组成的数据结构, 各级之间有许多连接. kobject 实现这个结构并且保持它在一起.

热插拔事件处理

kobject 子系统处理事件的产生, 事件通知用户空间关于系统中硬件的来去.

你可能从前面的列表总结出 kobject 是一个复杂的结构. 这可能是对的. 通过一次看一部分, 但是, 是有可能理解这个结构和它如何工作的.

一个 kobject 有类型 struct kobject; 它在 <linux/kobject.h> 中定义.

kobject 可被看作一个顶级的, 抽象类, 其他的类自它而来. 一个 kobject 实现一系列功能, 这些功能对自己不是特别有用而对其他对象是好的.(基类)

kobject 初始化

1.设置整个 kobject 为 0, 常常使用一个对 memset 的调用

2.设立一些内部成员, 使用对 kobject_init() 的调用:

void kobject_init(struct kobject *kobj); 

在其他事情中, kobject_init 设置 kobject 的引用计数为 1. 调用 kobject_init 不够, 但是. kobject 用户必须,

3.设置 kobject 的名子. 这是用在 sysfs 入口的名子. 如果你深入内核代码, 你可以发现直接拷贝一个字符串到 kobject 的名子成员的代码, 但是应当避免这个方法. 相反, 使用:

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

这个函数采用一个 printk 风格的变量参数列表.

引用计数的操作

来操作一个 kobject 的引用计数的低级函数是:

struct kobject *kobject_get(struct kobject *kobj);
void kobject_put(struct kobject *kobj);

一个对 kobject_get 的成功调用递增 kobject 的 引用计数并且返回一个指向 kobject 的指针

当一个引用被释放, 对 kobject_put 的调用递减引用计数, 并且可能地, 释放这个对象. 记住 kobject _init 设置这个引用计数为 1;

意, 在许多情况下, 在 kobject 自身中的引用计数可能不足以阻止竞争情况. 一个 kobject 的存在( 以及它的包含结构 ) 可能非常, 例如, 需要创建这个 kobject 的模块的继续存在. 在这个 kobject 仍然在被传送时不能卸载那个模块. 这是为什么我们看到的 cdev 结构包含一个 struct module 指针.

释放函数

kobject 的引用计数到 0,

这个通知由 kobject 的一个释放函数来完成. 常常地, 这个方法有一个形式如下:

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 必须有一个释放函数, 并且这个 kobject 必须持续( 以一致的状态 ) 直到这个方法被调用.

释放方法没有存储在 kobject 自身里面; 相反, 它被关联到包含 kobject 的结构类型中. 这个类型被跟踪, 用一个 struct kobj_type 结构类型, 常常简单地称为一个 "ktype". 这个结构看来如下:

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

在 struct kobj_type 中的 release 成员是, 当然, 一个指向这个 kobject 类型的 release 方法的指针.

每一个 kobject 需要有一个关联的 kobj_type 结构. 易混淆地, 指向这个结构的指针能在 2 个不同的地方找到. kobject 结构自身包含一个成员(称为 ktype)包含这个指针. 但是, 如果这个 kobject 是一个 kset 的成员, kobj_type 指针由 kset 提供. 其间, 这个宏定义:

struct kobj_type *get_ktype(struct kobject *kobj); finds the kobj_type pointer for a given kobject.

kobject 结构常常用来连接对象到一个层级的结构中, 匹配正被建模的子系统的结构. 有 2 个分开的机制对于这个连接: parent 指针和 ksets.

在结构 kobject 中的 parent 成员是一个指向其他对象的指针 -- 代表在层次中之上的下一级. 如果, 例如, 一个 kobject 表示一个 USB 设备, 它的 parent 指针可能指示这个设备被插入的 hub.parent 指针的主要用途是在 sysfs 层次中定位对象.

 Ksets 对象

很多情况, 一个 kset 看来象一个 kobj_type 结构的扩展; 一个 kset 是一个嵌入到相同类型结构的 kobject 的集合. 但是, 虽然 struct kobj_type 关注的是一个对象的类型, struct kset 被聚合和集合所关注. 这 2 个概念已被分开以至于一致类型的对象可以出现在不同的集合中.

因此, 一个 kset 的主要功能是容纳; 它可被当作顶层的给 kobjects 的容器类. 实际上, 每个 kset 在内部容纳它自己的 kobject, 并且它可以, 在许多情况下, 如同一个 kobject 相同的方式被对待. 值得注意的是 ksets 一直在 sysfs 中出现; 一旦一个 kset 已被建立并且加入到系统, 会有一个 sysfs 目录给它. kobjects 没有必要在 sysfs 中出现, 但是每个是 kset 成员的 kobject 都出现在那里.

Ksets是在sysfs 中是目录,kobject是在那个目录下的文件

增加一个 kobject 到一个 kset 常常在一个对象创建时完成

kobject_init

int kobject_add(struct kobject *kobj);

或者使用

extern int kobject_register(struct kobject *kobj);

这个函数仅仅是一个 kobject_init 和 kobject_add 的结合.

当一个 kobject 被传递给 kobject_add, 它的引用计数被递增. kset 中容纳的, 毕竟, 是一个对这个对象的引用. 某种意义上, kobject 可能要必须从 kset 中移出来清除这个引用; 完成这个使用:

void kobject_del(struct kobject *kobj);

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


ksets 之上的操作

对于初始化和设置, 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);

对大部分, 这些函数只是在 kset 的嵌入对象上调用类似的 kobject_ 函数.

为管理 ksets 的引用计数, 情况大概相同:

struct kset *kset_get(struct kset *kset);
void kset_put(struct kset *kset);

一个 kset 还有一个名子, 存储于嵌入的 kobject. 因此, 如果你有一个 kset 称为 my_set, 你将设置它的名子用:

kobject_set_name(&my_set->kobj, "The name");

ksets 还有一个指针( 在 ktye 成员 )指向 kobject_type 结构来描述它包含的 kobject. 这个类型优先于在 kobject 自身中的 ktype 成员. 结果, 在典型的应用中, 在 struct kobject 中的 ktype 成员被留为 NULL, 因为 kset 中的相同成员是实际使用的那个.

最后, 一个 kset 包含一个子系统指针(称为 subsys).

子系统

一个子系统是作为一个整体对内核一个高级部分的代表. 子系统常常(但是不是一直)出现在 sysfs 层次的顶级. 一些内核中的例子子系统包括 block_subsys(/sys/block, 给块设备), devices_subsys(/sys/devices, 核心设备层次), 以及一个特殊子系统给每个内核已知的总线类型.

一个子系统由一个简单结构代表:

struct subsystem {
 struct kset kset;
 struct rw_semaphore rwsem; 
}; 

一个子系统, 因此, 其实只是一个对 kset 的包装, 有一个旗标丢在里面.

 

每个 kset 必须属于一个子系统. 子系统成员关系帮助建立 kset 的位置在层次中, 但是, 更重要的, 子系统的 rwsem 旗标用来串行化对 kset 的内部链表的存取. 这个成员关系由在 struct kset 中的 subsys 指针所表示. 因此, 可以从 kset 的结构找到每个 kset 的包含子系统, 但是却无法直接从子系统结构发现多个包含在子系统中的 kset.

子系统常常用一个特殊的宏声明:

decl_subsys(name, struct kobj_type *type, struct kset_hotplug_ops *hotplug_ops);

这个宏创建一个 struct subsystem 使用一个给这个宏的名子并后缀以 _subsys 而形成的名子. 这个宏还初始化内部的 kset 使用给定的 type 和 hotplug_ops. ( 我们在本章后面讨论热插拔操作).

子系统有通常的建立和拆卸函数:

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);

大部分这些操作只是作用在子系统的 kset上.

Kobjects, Ksets 和 Subsystems的属性

kobject 是在 sysfs 虚拟文件系统之后的机制. 对每个在 sysfs 中发现的目录, 有一个 kobject 潜伏在内核某处. 每个感兴趣的 kobject 也输出一个或多个属性, 它出现在 kobject 的 sysfs 目录, 作为包含内核产生的信息的文件.

使用 sysfs 的代码应当包含 <linux/sysfs.h>.

使一个 kobject 在 sysfs 出现仅仅是调用 kobject_add 的事情. 我们已经见到这个函数作为添加一个 kobject 到一个 kset 的方式; 在 sysfs 中创建入口也是它的工作的一部分. 有一些事情值得知道, 关于 sysfs 入口如何创建:

  • kobjects 的 sysfs 入口一直为目录, 因此一个对 kobject_add 的调用导致在sysfs 中创建一个目录. 常常地, 这个目录包含一个或多个属性; 我们稍后见到属性如何指定.

  • 分配给 kobject 的名子( 用 kobject_set_name ) 是给 sysfs 目录使用的名子. 因此, 出现在 sysfs 层次的相同部分的 kobjects 必须有独特的名子. 分配给 kobjects 的名子也应当是合理的文件名子: 它们不能包含斜线字符, 并且空白的使用强烈不推荐.

  • sysfs 入口位于对应 kobject 的 parent 指针的目录中. 如果 parent 是 NULL 当 kobject_add 被调用时, 它被设置为嵌在新 kobject 的 kset 中的 kobject; 因此, sysfs 层级常常匹配使用 kset 创建的内部层次. 如果 parent 和 kset 都是 NULL, sysfs 目录在顶级被创建, 这几乎当然不是你所要的.

使用我们至今所描述的, 我们可以使用一个 kobject 来在 sysfs 中创建一个空目录. 常常地, 你想做比这更有趣的事情, 因此是时间看属性的实现.

缺省属性

default_attr 成员列举了对每个这样类型的 kobject 被创建的属性, 并且 sysfs_ops 提供方法来实现这些属性. 我们从 default_attrs 开始, 它指向一个指向属性结构的指针数组:

struct attribute {
 char *name;
 struct module *owner;
 mode_t mode; 
}; 

在这个结构中, name 是属性的名子( 如同它出现在 kobject 的 sysfs 目录中), owner 是一个指向模块的指针(如果有一个), 模块负责这个属性的实现, 并且 mode 是应用到这个属性的保护位. mode 常常是 S_IRUGO 对于只读属性; 如果这个属性是可写的, 你可以扔出 S_IWUSR 来只给 root 写权限( modes 的宏定义在 <linux/stat.h> 中). default_attrs 列表中的最后一个入口必须用 0 填充.

default_attr 数组说明这些属性是什么, 但是没有告诉 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);
};

无论何时一个属性从用户空间读取, show 方法被用一个指向 kobject 的指针和适当的属性结构来调用. 这个方法应当将给定属性值编码进缓冲, 要确定没有覆盖它( 它是 PAGE_SIZE 字节), 并且返回实际的被返回数据的长度. sysfs 的惯例表明每个属性应当包含一个单个的, 人可读的值; 如果你有许多消息返回, 你可要考虑将它分为多个属性.

同样的 show 方法用在所有的和给定 kobject 关联的属性. 传递到函数的 attr 指针可用来决定需要哪个属性. 一些 show 方法包含对属性名子的一系列测试. 其他的实现将属性结构嵌入另一个结构, 来包含需要返回属性值的信息; 在这种情况下, container_of 可能用在 show 方法中来获得一个指向嵌入结构的指针.

store 方法类似; 它应当将存在缓冲的数据编码( size 包含数据的长度, 这不能超过 PAGE_SIZE ), 存储和以任何有意义的的方式响应新数据, 并且返回实际编码的字节数. store 方法只在属性的许可允许写才被调用. 当编写一个 store 方法时, 不要忘记你在接收来自用户空间的任意信息; 你应当在采取对应动作之前非常小心地验证它. 如果到数据不匹配期望, 返回一个负的错误值, 而不是可能地做一些不想要的和无法恢复的事情. 如果你的设备输出一个自销毁的属性, 你应当要求一个特定的字符串写到那里来引发这个功能; 一个偶然的, 随机写应当只产生一个错误.

非缺省属性

如果你想添加一个新属性到一个 kobject 的 sysfs 目录, 简单地填充一个属性结构并且传递它到:

int sysfs_create_file(struct kobject *kobj, struct attribute *attr);

如果所有都进行顺利, 文件被使用在属性结构中给定的名子创建, 并且返回值是 0; 否则, 返回通常的负错误码.

注意, 相同的 show() 和 store() 函数被调用来实现对新属性的操作. 在你添加一个新的, 非缺省属性到 kobject, 你应当任何必要的步骤来确保这些函数知道如何实现这个属性.

为去除一个属性, 调用:

int sysfs_remove_file(struct kobject *kobj, struct attribute *attr); 

在调用后, 这个属性不再出现在 kobject 的 sysfs 入口. 要小心, 但是, 一个用户空间进程可能有一个打开的那个属性的文件描述符, 并且在这个属性已经被去除后 show 和 store 调用仍然可能.

二进制属性

二进制属性使用一个 bin+attribute 结构来描述:

struct bin_attribute {
struct attribute attr;
size_t size;
ssize_t (*read)(struct kobject *kobj, char *buffer, loff_t pos, size_t size);
ssize_t (*write)(struct kobject *kobj, char *buffer, loff_t pos, size_t size);
};

这里, attr 是一个属性结构, 给出名子, 拥有者, 和这个二进制属性的权限, 并且 size 是这个二进制属性的最大大小(或者 0 , 如果没有最大值). read 和 write 方法类似于正常的字符驱动对应物; 它们一次加载可被多次调用, 每次调用最大一页数据. 对于 sysfs 没有办法来指示最后一个写操作, 因此实现二进制属性的代码必须能够以其他方式决定数据的结束.

二进制属性必须明确创建; 它们不能建立为缺省属性. 为创建一个二进制属性, 调用:

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 是容易的:

int sysfs_create_link(struct kobject *kobj, struct kobject *target, char *name);

这个函数创建一个连接(称为 name)指向目标的 sysfs 入口作为一个 kobj 的属性. 它是一个相对连接, 因此它不管 sysfs 在任何特殊的系统中安装在哪里都可用.

这个连接甚至当目标被从系统中移走也持续. 如果你在创建对其他 kobjects 的符号连接, 你应当可能有一个方法知道对这个 kobjects 的改变, 或者某种保证目标 kobjects 不会消失. 结果( 在 sysfs 中的死的符号连接 )不是特别严重, 但是它们不代表最好的编程风格并且可能导致在用户空间的混乱.

去除符号连接可使用:

void sysfs_remove_link(struct kobject *kobj, char *name);


 

热插拔事件产生

一个热插拔事件是一个从内核到用户空间的通知, 在系统配置中有事情已经改变.热插拔事件转变为一个对 /sbin/hotplug 的调用

热插拔操作

热插拔事件的实际控制是通过一套存储于 kset_hotplug_ops 结构的方法完成.

struct kset_hotplug_ops {
 int (*filter)(struct kset *kset, struct kobject *kobj);
 char *(*name)(struct kset *kset, struct kobject *kobj);
 int (*hotplug)(struct kset *kset, struct kobject *kobj,
 char **envp, int num_envp, char *buffer,
 int buffer_size);
};

一个指向这个结构的指针在 kset 结构的 hotplug_ops 成员中. 如果一个给定的 kobject 不包含在一个 kset 中, 内核搜索整个层次( 通过 parent 指针) 直到它发现一个 kobject 确实有一个 kset; 接着使用这个 kset 的热插拔操作.

filter 热插拔操作被调用无论何时内核在考虑为给定 kobject 产生一个事件. 如果 filter 返回 0, 事件没有创建. 这个方法, 因此, 给 kset 代码一个机会来决定哪个事件应当被传递给用户空间以及哪个不.

当用户空间热插拔程序被调用, 它被传递给相关子系统的 name 作为它唯一的一个参数. name 热插拔方法负责提供这个名子. 它应当返回一个简单的适合传递给用户空间的字串.

热插拔脚本的可能想知道的其他所有东东都在环境中传递. 最终的热插拔方法( hotplug )给了一个机会来在调用这个脚本之前添加有用的环境变量. 再次, 这个方法的原型是:

int (*hotplug)(struct kset *kset, struct kobject *kobj,
 char **envp, int num_envp, char *buffer,
 int buffer_size); 

如常, kset 和 kobject 描述事件产生给的对象. envp 数组是一个地方来存储额外的环境变量定义(以通常的 NAME=值 的格式); 它有 num_envp 个入口变量. 这些变量自身应当被编码入缓冲, 缓冲是 buffer_size 字节长. 如果你添加任何变量到 envp, 确信添加一个 NULL 入口在你最后的添加项后面, 这样内核知道结尾在哪里. 返回值正常应当是 0; 任何非零返回都终止热插拔事件的产生.

热插拔事件的产生(象在设备模型中大部分工作)常常是由在总线驱动级的逻辑处理.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值