## 14.5. 类
我们在本章中要考察最后的设备模型概念是类.一个类是一个设备的高级视图, 它抽象出低级的实现细节. 驱动可以见到一个SCSI 磁盘或者一个 ATA 磁盘, 在类的级别, 它们都是磁盘. 类允许用户空间基于它们做什么来使用设备, 而不是它们如何被连接或者它们如何工作.
几乎所有的类都在 sysfs 中在 /sys/class 下出现. 因此, 例如, 所有的网络接口可在 /sys/class/net 下发现, 不管接口类型. 输入设备可在 /sys/class/input 下, 以及串行设备在 /sys/class/tty. 一个例外是块设备, 由于历史的原因在 /sys/block.
类成员关系常常由高级的代码处理, 不必要驱动的明确的支持. 当 sbull 驱动( 见 16 章) 创建一个虚拟磁盘设备, 它自动出现在 /sys/block. snull 网络驱动(见 17 章)没有做任何特殊事情给它的接口在 /sys/class/net 中出现. 将有多次, 但是, 当驱动结束直接处理类.
在许多情况, 类子系统是最好的输出信息到用户空间的方法. 当一个子系统创建一个类, 它完全拥有这个类, 因此没有必要担心哪个模块拥有那里发现的属性. 它也用极少的时间徘徊于更加面向硬件的 sysfs 部分来了解, 它不是一个直接浏览的好地方. 用户会更加高兴地在 /sys/class/some-widget 中发现信息, 而不是, /sys/device/pci0000:00/0000:00:10.0/usb2/2-0:1.0.
驱动核心输出 2 个清晰的接口来管理类. class_simple 函数设计来尽可能容易地添加新类到系统. 它们的主要目的, 常常, 是暴露包含设备号的属性来使能设备节点的自动创建. 常用的类接口更加复杂但是同时提供更多特性. 我们从简单版本开始.
### 14.5.1. class_simple 接口
class_simple 接口意图是易于使用, 以至于没人会抱怨没有暴露至少一个包含设备的被分配的号的属性. 使用这个接口只不过是一对函数调用, 没有通常的和 Linux 设备模型关联的样板.
第一步是创建类自身. 使用一个对 class_simple_create 的调用来完成:
~~~
struct class_simple *class_simple_create(struct module *owner, char *name);
~~~
这个函数使用给定的名子创建一个类. 这个操作可能失败, 当然, 因此在继续之前返回值应当一直被检查( 使用 IS_ERR, 在第 1 章的"指针和错误值"一节中描述过).
一个简单的类可被销毁, 使用:
~~~
void class_simple_destroy(struct class_simple *cs);
~~~
创建一个简单类的真实目的是添加设备给它; 这个任务使用:
~~~
struct class_device *class_simple_device_add(struct class_simple *cs, dev_t devnum, struct device *device, const char *fmt, ...);
~~~
这里, cs 是之前创建的简单类, devnum 是分配的设备号, device 是代表这个设备的 struct device, 其他的参数是一个 printk-风格 的格式串和参数来创建设备名子. 这个调用添加一项到类, 包含一个属性, dev, 含有设备号. 如果设备参数是非 NULL, 一个符号连接( 称为 device )指向在 /sys/devices 下的设备的入口.
可能添加其他的属性到设备入口. 它只是使用 class_device_create_file, 我们在下一节和完整类子系统所剩下的内容讨论.
当设备进出时类产生热插拔事件. 如果你的驱动需要添加变量到环境中给用户空间事件处理者, 可以建立一个热插拔回调, 使用:
~~~
int class_simple_set_hotplug(struct class_simple *cs,
int (*hotplug)(struct class_device *dev,
char **envp, int num_envp,
char *buffer, int buffer_size));
~~~
当你的设备离开时, 类入口应当被去除, 使用:
~~~
void class_simple_device_remove(dev_t dev);
~~~
注意, 由 class_simple_device_add 返回的 class_device 结构这里不需要; 设备号(它当然应当是唯一的)足够了.
### 14.5.2. 完整的类接口
class_simple 接口满足许多需要, 但是有时需要更多灵活性. 下面的讨论描述如何使用完整的类机制, class_simple 正是基于此. 它是简短的: 类函数和结构遵循设备模型其他部分相同的模式, 因此这里没有什么真正是新的.
#### 14.5.2.1. 管理类
一个类由一个 struct class 的实例来定义:
~~~
struct class {
char *name;
struct class_attribute *class_attrs;
struct class_device_attribute *class_dev_attrs;
int (*hotplug)(struct class_device *dev, char **envp,
int num_envp, char *buffer, int buffer_size);
void (*release)(struct class_device *dev);
void (*class_release)(struct class *class);
/* Some fields omitted */
};
~~~
每个类需要一个唯一的名子, 它是这个类如何在 /sys/class 中出现. 当这个类被注册, 由 class_attrs 所指向的数组中列出的所有属性被创建. 还有一套缺省属性给每个添加到类中的设备; class_dev_attrs 指向它们. 有通常的热插拔函数来添加变量到环境中, 当事件产生时. 还有 2 个释放方法: release 在无论何时从类中去除一个设备时被调用, 而 class_release 在类自己被释放时调用.
注册函数是:
~~~
int class_register(struct class *cls);
void class_unregister(struct class *cls);
~~~
使用属性的接口不应当在这点吓人:
~~~
struct class_attribute {
struct attribute attr;
ssize_t (*show)(struct class *cls, char *buf);
ssize_t (*store)(struct class *cls, const char *buf, size_t count);
};
CLASS_ATTR(name, mode, show, store);
int class_create_file(struct class *cls, const struct class_attribute *attr);
void class_remove_file(struct class *cls, const struct class_attribute *attr);
~~~
#### 14.5.2.2. 类设备
一个类的真正目的是作为一个是该类成员的设备的容器. 一个成员由 struct class_device 来表示:
~~~
struct class_device {
struct kobject kobj;
struct class *class;
struct device *dev;
void *class_data;
char class_id[BUS_ID_SIZE];
};
~~~
class_id 成员持有设备名子, 如同它在 sysfs 中的一样. class 指针应当指向持有这个设备的类, 并且 dev 应当指向关联的设备结构. 设置 dev 是可选的; 如果它是非 NULL, 它用来创建一个符号连接从类入口到对应的在 /sys/devices 下的入口, 使得易于在用户空间找到设备入口. 类可以使用 class_data 来持有一个私有指针.
通常的注册函数已经被提供:
~~~
int class_device_register(struct class_device *cd);
void class_device_unregister(struct class_device *cd);
~~~
类设备接口也允许重命名一个已经注册的入口:
~~~
int class_device_rename(struct class_device *cd, char *new_name);
~~~
类设备入口有属性:
~~~
struct class_device_attribute {
struct attribute attr;
ssize_t (*show)(struct class_device *cls, char *buf);
ssize_t (*store)(struct class_device *cls, const char *buf,
size_t count);
};
CLASS_DEVICE_ATTR(name, mode, show, store);
int class_device_create_file(struct class_device *cls, const struct class_device_attribute *attr);
void class_device_remove_file(struct class_device *cls, const struct class_device_attribute *attr);
~~~
一个缺省的属性集合, 在类的 class_dev_attrs 成员, 被创建当类设备被注册时; class_device_create_file 可用来创建额外的属性. 属性还可以被加入到由 class_simple 接口创建的类设备.
#### 14.5.2.3. 类接口
类子系统有一个额外的在 Linux 设备模型其他部分找不到的概念. 这个机制称为一个接口, 但是它是, 也许, 最好作为一种触发机制可用来在设备进入或离开类时得到通知.
一个接口被表示, 使用:
~~~
struct class_interface {
struct class *class;
int (*add) (struct class_device *cd);
void (*remove) (struct class_device *cd);
};
~~~
接口可被注册或注销, 使用:
~~~
int class_interface_register(struct class_interface *intf);
void class_interface_unregister(struct class_interface *intf);
~~~
一个接口的功能是简单明了的. 无论何时一个类设备被加入到在 class_interface 结构中指定的类时, 接口的 add 函数被调用. 这个函数可进行任何额外的这个设备需要的设置; 这个设置常常采取增加更多属性的形式, 但是其他的应用都可能. 当设备被从类中去除, remove 方法被调用来进行任何需要的清理.
可注册多个接口给一个类.
- Linux设备驱动第三版
- 第 1 章 设备驱动简介
- 1.1. 驱动程序的角色
- 1.2. 划分内核
- 1.3. 设备和模块的分类
- 1.4. 安全问题
- 1.5. 版本编号
- 1.6. 版权条款
- 1.7. 加入内核开发社团
- 1.8. 本书的内容
- 第 2 章 建立和运行模块
- 2.1. 设置你的测试系统
- 2.2. Hello World 模块
- 2.3. 内核模块相比于应用程序
- 2.4. 编译和加载
- 2.5. 内核符号表
- 2.6. 预备知识
- 2.7. 初始化和关停
- 2.8. 模块参数
- 2.9. 在用户空间做
- 2.10. 快速参考
- 第 3 章 字符驱动
- 3.1. scull 的设计
- 3.2. 主次编号
- 3.3. 一些重要数据结构
- 3.4. 字符设备注册
- 3.5. open 和 release
- 3.6. scull 的内存使用
- 3.7. 读和写
- 3.8. 使用新设备
- 3.9. 快速参考
- 第 4 章 调试技术
- 4.1. 内核中的调试支持
- 4.2. 用打印调试
- 4.3. 用查询来调试
- 4.4. 使用观察来调试
- 4.5. 调试系统故障
- 4.6. 调试器和相关工具
- 第 5 章 并发和竞争情况
- 5.1. scull 中的缺陷
- 5.2. 并发和它的管理
- 5.3. 旗标和互斥体
- 5.4. Completions 机制
- 5.5. 自旋锁
- 5.6. 锁陷阱
- 5.7. 加锁的各种选择
- 5.8. 快速参考
- 第 6 章 高级字符驱动操作
- 6.1. ioctl 接口
- 6.2. 阻塞 I/O
- 6.3. poll 和 select
- 6.4. 异步通知
- 6.5. 移位一个设备
- 6.6. 在一个设备文件上的存取控制
- 6.7. 快速参考
- 第 7 章 时间, 延时, 和延后工作
- 7.1. 测量时间流失
- 7.2. 获知当前时间
- 7.3. 延后执行
- 7.4. 内核定时器
- 7.5. Tasklets 机制
- 7.6. 工作队列
- 7.7. 快速参考
- 第 8 章 分配内存
- 8.1. kmalloc 的真实故事
- 8.2. 后备缓存
- 8.3. get_free_page 和其友
- 8.4. 每-CPU 的变量
- 8.5. 获得大量缓冲
- 8.6. 快速参考
- 第 9 章 与硬件通讯
- 9.1. I/O 端口和 I/O 内存
- 9.2. 使用 I/O 端口
- 9.3. 一个 I/O 端口例子
- 9.4. 使用 I/O 内存
- 9.5. 快速参考
- 第 10 章 中断处理
- 10.1. 准备并口
- 10.2. 安装一个中断处理
- 10.3. 前和后半部
- 10.4. 中断共享
- 10.5. 中断驱动 I/O
- 10.6. 快速参考
- 第 11 章 内核中的数据类型
- 11.1. 标准 C 类型的使用
- 11.2. 安排一个明确大小给数据项
- 11.3. 接口特定的类型
- 11.4. 其他移植性问题
- 11.5. 链表
- 11.6. 快速参考
- 第 12 章 PCI 驱动
- 12.1. PCI 接口
- 12.2. 回顾: ISA
- 12.3. PC/104 和 PC/104+
- 12.4. 其他的 PC 总线
- 12.5. SBus
- 12.6. NuBus 总线
- 12.7. 外部总线
- 12.8. 快速参考
- 第 13 章 USB 驱动
- 13.1. USB 设备基础知识
- 13.2. USB 和 sysfs
- 13.3. USB 的 Urbs
- 13.4. 编写一个 USB 驱动
- 13.5. 无 urb 的 USB 传送
- 13.6. 快速参考
- 第 14 章 Linux 设备模型
- 14.1. Kobjects, Ksets 和 Subsystems
- 14.2. 低级 sysfs 操作
- 14.3. 热插拔事件产生
- 14.4. 总线, 设备, 和驱动
- 14.5. 类
- 14.6. 集成起来
- 14.7. 热插拔
- 14.8. 处理固件
- 14.9. 快速参考
- 第 15 章 内存映射和 DMA
- 15.1. Linux 中的内存管理
- 15.2. mmap 设备操作
- 15.3. 进行直接 I/O
- 15.4. 直接内存存取
- 15.5. 快速参考
- 第 16 章 块驱动
- 16.1. 注册
- 16.2. 块设备操作
- 16.3. 请求处理
- 16.4. 一些其他的细节
- 16.5. 快速参考
- 第 17 章 网络驱动
- 17.1. snull 是如何设计的
- 17.2. 连接到内核
- 17.3. net_device 结构的详情
- 17.4. 打开与关闭
- 17.5. 报文传送
- 17.6. 报文接收
- 17.7. 中断处理
- 17.8. 接收中断缓解
- 17.9. 连接状态的改变
- 17.10. Socket 缓存
- 17.11. MAC 地址解析
- 17.12. 定制 ioctl 命令
- 17.13. 统计信息
- 17.14. 多播
- 17.15. 几个其他细节
- 17.16. 快速参考
- 第 18 章 TTY 驱动
- 18.1. 一个小 TTY 驱动
- 18.2. tty_driver 函数指针
- 18.3. TTY 线路设置
- 18.4. ioctls 函数
- 18.5. TTY 设备的 proc 和 sysfs 处理
- 18.6. tty_driver 结构的细节
- 18.7. tty_operaions 结构的细节
- 18.8. tty_struct 结构的细节
- 18.9. 快速参考