一、Linux设备驱动模型基本数据结构
Linux设备驱动模型有两个基本数据结构:kobject,ksetkobject是组成设备模型的基本结构,类似于C++中的基类,它嵌入于更大的对象中用来描述设备模型的组件,如bus,devices,drivers,device_driver,bus_type等,这相当于面向对象程序设计语言中的继承机制。每个kobject对象都对应于sysfs(将在后面介绍)中的一个目录。Kobject结构中部分成员如下:
struct kobject
{
const char *name; //显示在sysfs中的名称
struct list_head entry; //下一个kobject结构
struct kobject *parent; //指向父kobject结构体,如果存在
……
};
每个kobject都会有一个属性kobj_type,定义了某种类型的kobejct的公共的属性和操作:
struct kobj_type
{
struct sysfs_ops *sysfs_ops; //操作属性的方法
struct attribute **default_attrs; //属性数组
……
};
kset是一个kobject集合(或容器),包含了一系列的kobject。kset内部也嵌入了kobject,这表明kset本身也是一个kobject。Kset结构如下:
struct kset
{
struct list_head list; //连接所包含的kobject对象的链表首地址
struct kobject kobj; //内嵌kobject,说明kset本身也是一个目录
……
};
Kset结构体对象和kobject结构体对象是有密切关联的。kset集合包含了属于它的kobject结构体,kset.list链表用来连接第一个和最后一个kobject对象;第一个kobject使用entry连接kset集合和第二个kobject对象;第二个kobject对象使用entry连接第一个kobject对象和第三个kobject对象,依次类推,最终形成了一个kobject对象的链表;所有的kobject结构的parent指针指向kset包含的kobject对象,构成一个父子层次关系;kobject的所有kset指针指向包含它的kset集合,所以通过kobject对象很容易就能找到kset集合;kobject的kobj_type指针指向自身的kobj_type,每一个kobject都有一个单独的kobj_type结构;另外在kset集合中也有一个kobject结构体,该结构体也指向一个kobj_type结构体;kobj_type中定义了一组属性和操作属性的方法。
二、Linux设备驱动模型高层数据结构
device_driver,device分别表示驱动和设备,且两者需要依附在一种总线上,因此两者的结构体中都包含struct bus_type指针;在Linux中,驱动和设备是可以分开注册的,不需要在对方存在的条件下进行注册;两者通过struct bus_type的成员函数match()进行配对[13]。
总线、驱动和设备三者的结构中部分成员如下:
总线:
struct bus_type
{
const char *name;
struct bus_attribute *bus_attrs;
struct device_attribute *dev_attrs;
struct driver_attribute *drv_attrs;
int (*probe)(struct device *dev);
int (*remove)(struct device *dev);
void (*shutdown)(struct device *dev);
int (*suspend)(struct device *dev,pm_message_t state);
……
};
驱动:
struct device_driver
{
const char *name; //设备驱动名字
struct bus_type *bus; ///指向驱动属于的总线,总线上有很多设备
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 driver_private
{
struct kobject kobj; //内嵌kobject结构,用来构建设备驱动程序模型
struct klist_node knode_bus; //该驱动所属总线
struct module_kobject *mkobj; //驱动的模块
struct device_driver *driver; //指向驱动本身
……
};
设备
struct device
{
struct klist klist_children; //连接子设备的链表
struct device *parent; //指向父设备的指针
const char *init_name; //设备初始名
struct kobject kobj; //内嵌的kobject
struct bus_type *bus; //指向连接的总线指针
struct device_driver *driver; //指向该设备的驱动程序
dev_t devt; //设备号
struct class *class; //指向设备所属类
struct attribute_group **groups; //设备的组属性
……
};
三、Sysfs文件系统
Sysfs 下的文件可分为三种:目录,普通文件,符号连接;通过命令tree -a /sys可查看sysfs的目录结构,部分结果如下:
|--block
|--bus
| |--pci
| | |--device
| | | |--0000:00:00.0 -> ../../../device/pci0000:00/ 0000:00:00.0
| | | |--0000:00:01.0 -> ../../../device/pci0000:00/ 0000:00:01.0
| | | `--0000:00:07.0 -> ../../../device/pci0000:00/ 0000:00:07.0
| | `--drivers
| `--usb
| |--devices
| `--drivers
| `--usb-storage
|--class
| |--pci_bus
| | |--0000:00
| | | `--bridge -> ../../../devices/pci0000:00
| | `--0000:01
| | `-- bridge -> ../../../devices/pci0000:00/0000:00:01.0
| `--…
|--devices
| |--pci0000:00
| | |--0000:00:00.0
| | `--0000:00:01.0
|--kernel
| `--hotplug_seqnum
|--module
| |--usb_storage
| |--usbcore
| | |--parameters
| | `--…
| `--…
`--power
block目录包含所有的块设备;devices目录包含系统所有的设备,并根据设备挂接的总线类型组织成层次结构;drivers目录包含内核中所有已注册的设备驱动程序;bus目录包含系统在所有的总线类型,如PCI、USB总线,而每个总线类型子目录下又有drivers和devices目录,devices目录中的额外那就是对/sys/devices目录中文件的符号连接。
特别需要注意class目录。Linux中设备根据不同的用途被分为不同的类(class),同一个类下面有不同的接口(interface),如输入(input)类中有键盘接口。/sys/class目录中最终也是一个个的文件,而这些文件依然是对/sys/devices目录中相应文件的符号链接。
Sysfs文件系统中,总线、设备、驱动和类的关系如图2.2所示:
总线、驱动和设备最终会落实成为sysfs中的一个目录,查看它们的声明结构体可知,每个类型都直接或间接包含了kobject对象,一个kobject对象对应sysfs中的一个目录,kobject的每个属性(attribute)对应sysfs文件系统中的文件。属性伴随着show()和store()两个函数,这两个函数用来读和写属性对应的文件。
Sysfs文件系统提供了一套方法建立文件与对应的show()和store()函数的映射。首先是一系列宏定义,主要的有对设备的使用DEVICE_ATTR(),对总线使用的BUS_ATTR(),对驱动使用的DRIVER_ATTR(),对类别(class)使用的CLASS_ATTR()。以DEVICE_ATTR()为例,其定义如下:
#define DEVICE_ATTR(_name, _mode, _show, _store) \
struct device_attribute dev_attr_##_name = __ATTR(_name, _mode, _show, _store)
在宏定义中填入如下内容:
static DEVICE_ATTR(polling, S_IRUGO | S_IWUSR, show_polling, set_polling);
会得到如下格式的结构体对象:
static struct device_attribute device_attribute_ polling {
.attr = {.name = polling,.mode = S_IRUGO | S_IWUSR.owner = THIS_MODULE },
.show = show_ polling,.store = store_ polling }
在创建、移除文件时分别需要调用如下两个函数:
int device_create_file(struct device *device, struct device_attribute * entry);
void device_remove_file(struct device * dev, struct device_attribute * attr);
这两个函数的第二个参数为struct device_attribute类型的结构体,这样便建立了设备和对其进行读写操作的函数之间的关联。
(本文是将相关材料整理后写出的,来源:宋宝华.Linux设备驱动开发详解[M].北京:人民邮电出版社,2010)