本篇是关于linux的设备模型,将会覆盖相关的绝大部分知识,以及实践操作。希望这篇材料让大家基本掌握linux驱动设备模型,从而写出更加优秀的linux驱动。
linux统一的设备模型,提供了对电源管理和系统关机 、用户空间的通讯、可热插拔设备 、设备类别 、对象生命期 的广泛支持。
Linux 设备模型代码负责所有这些方面, 驱动代码作者只需要充分信任这些代码即可,但是, 理解设备模型绝对是一个好事情.
Linux2.6内核引入了sysfs文件系统,是与proc同类别的文件系统。sysfs把连接在系统上的设备和总线组织成分级的文件,使其从用户空间可以访问到。
有疑问联系:perftrace@gmail.com
1Â kobject
Kobject 是基础的结构, 它保持设备模型在一起,实现了基本的面向对象管理机制,是设备模型的核心结构。它与sysfs文件系统紧密相连,在内核中注册的每个kobject对象对应sysfs文件系统中的一个目录。bus,devices,drivers都是典型的容器。这些容器通过kobject连接起来,形成了一个树状结构。Kobject通常是嵌入到其他结构中的,单独意义不大。
结构体定义如下,位于文件include/linux/kobject.h":
struct kobject {
const char *name;//函数容器名称的字符串
struct list_head entry;//用于kobject所插入的链表指针
struct kobject *parent;//指向父kobject
struct kset *kset;//指向包含的kset
struct kobj_type *ktype;//指向kobject类型描述符
struct kernfs_node *sd; /* sysfs directory entry */
struct kref kref;//容器引用计数器
#ifdef CONFIG_DEBUG_KOBJECT_RELEASE
struct delayed_work release;
#endif
unsigned int state_initialized:1;
unsigned int state_in_sysfs:1;
unsigned int state_add_uevent_sent:1;
unsigned int state_remove_uevent_sent:1;
unsigned int uevent_suppress:1;
};
kobject 结构常常用来连接对象到一个层级的结构中, parent 成员代表在层次中之上一级. 如果一个 kobject 表示一个 USB 设备, 它的 parent 指针可能指示这 个设备被插入的 hub. parent 指针的主要用途是在 sysfs 层次中定位对象.
这里不得不先将一个宏,因为kobject会嵌入在其他设备机构中,那么如果有一个 struct kobject 指针, 那如何得到包含它的上级结构的指针呢?
这个就是通过container_of 来实现的,通过向后反转来得到包含kobject的结构的指针。
/**
* container_of - cast a member of a structure out to the containing structure
* @ptr: the pointer to the member.
* @type: the type of the container struct this is embedded in.
* @member: the name of the member within the struct.
*
*/
#define container_of(ptr, type, member)
1.1 kobject初始化
kobject初始化需要调用kobject_init 设置 kobject 的引用计数为 1 ,kobject_set_name设置kobject名字。分配给 kobject 的名字( 用 kobject_set_name ) 是给 sysfs 目录使用的名字.
int kobject_set_name(struct kobject *kobj, const char *fmt, ...)
kobject的其中一个关键函数是引用计数器操作,只要对这个对象的引用存在, 这个对象(和支持它的代码) 必须继续存在.操作一个 kobject 的引用计数的低级函数是
struct kobject *kobject_get(struct kobject *kobj)
void kobject_put(struct kobject *kobj)
1.2 kobject释放
每个 kobject 必须有一个释放函数, 并且这个 kobject 必须持续 ( 以一致的状态 ) 直到这个方法被调用. 当引用为0的时候就会调用release函数。
释放的具体release函数被关联到包含 kobject 的结构类型中。 这个类型用kobj_type 结构类型, 我们可以简单称为ktype,如下节所示。
2 kobj_type
kobj_type描述kobject类型,这样就不需要每个kobject分别定义自己特性结构体了,结构体如下:
struct kobj_type {
void (*release)(struct kobject *kobj);//析构行为函数
const struct sysfs_ops *sysfs_ops;//sysfs行为
struct attribute **default_attrs;
const struct kobj_ns_type_operations *(*child_ns_type)(struct kobject *kobj);
const void *(*namespace)(struct kobject *kobj);
};
每一个 kobject 需要有一个关联的 kobj_type 结构。如果这个 kobject 是一个 kset 的成员, kobj_type 指针由 kset 提供. 可以通过函数get_ktype来获取kobject的kobj_type指针。
static inline struct kobj_type *get_ktype(struct kobject *kobj)
release函数是kobject的释放函数,当引用为0的时候会调用。
Sysfs_ops负责描述如何使用它们。结构体如下,就两个函数非常简单:
struct sysfs_ops {
ssize_t (*show)(struct kobject *, struct attribute *, char *);
ssize_t (*store)(struct kobject *, struct attribute *, const char *, size_t);
};
用户空间读取sysfs时调用show方法,在写操作时候调用store方法。
默认属性default_attrs负责将内核数据映射成sysfs中的文件,其中模式可以是S_IRUGO。
struct attribute {
const char *name;
umode_t mode;
#ifdef CONFIG_DEBUG_LOCK_ALLOC
bool ignore_lockdep:1;
struct lock_class_key *key;
struct lock_class_key skey;
#endif
};
name 是属性的名字( 会出现在kobject的sysfs目录中)。
如果你想添加一 个新属性到一个 kobject 的 sysfs 目录, 简单地填充一个属性结构并且传递它到:
sysfs_create_file(struct kobject *kobj,const struct attribute *attr)
去除属性使用函数sysfs_remove_file。
sysfs_remove_file(struct kobject *kobj, const struct attribute *attr)
上面这些事文本属性文件,还可以创建和删除二进制属性:
sysfs_create_bin_file(struct kobject *kobj, const struct bin_attribute *attr)
sysfs_remove_bin_file(struct kobject *kobj, const struct bin_attribute *attr)
这里的属性是使用bin_attribute结构体,里面分装了attribute。
3 kset
通过kset数据结构图可将kobjects组织成一颗层次树。Kset是同类kobjects的一个集合体,也就是说相关的kobjects包含在同类型的容器中。
其数据结构如下:
struct kset {
struct list_head list; //包含在kset的kobject链表
spinlock_t list_lock;
struct kobject kobj;
const struct kset_uevent_ops *uevent_ops;
}
Kset是把kobject集中到一个集合(相同ktype的kobject可以分组到不同的kset),而ktype描述类型所共有的特性。一旦一个 kset 已被建立并且加入到系统, 会有一个 sysfs 目录给它.
可以通过kobject_add来将kobject添加到kset中,并在sysfs中出现,会创建一个目录。当一个 kobject 被传递给 kobject_add, 它的引用计数被递增,移出时清除这个引用。移除的函数是kobject_del。sysfs 层级常常匹配使用 kset 创建的内部层次,如果 parent 和 kset 都 是 NULL, sysfs 目录在顶级被创建。
一个 kset有名字, 存储于嵌入的 kobject.
如前面所说,kset 中的kojbect指向 kobject_type 结构优先于在 kobject 自身中的 ktype 成员.
最后我们需要知道的是,一个kset属于子系统subsystem。
我们先来看下上述这些的代码实战。
3.1 kset示例代码
摘自网上,编译执行后称为模块。因为没有指定parent对象,所以插入到内核后,会在/sys目录下生成一个文件夹叫做kobject_test ,该目录下有一个文件是kobject_config,可以往该文件读写会触发相关函数,具体见代码:
#include <linux/device.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/string.h>
#include <linux/sysfs.h>
#include <linux/stat.h>
MODULE_LICENSE("Dual BSD/GPL");
/*声明release、show、store函数*/
void obj_test_release(struct kobject *kobject);
ssize_t kobj_test_show(struct kobject *kobject, struct attribute *attr,char *buf);
ssize_t kobj_test_store(struct kobject *kobject,struct attribute *attr,const char *buf, size_t count);
/*对应于kobject的目录下的一个文件,Name成员就是文件名*/
struct attribute test_attr = {
.name = "kobj_config",
.mode = S_IRWXUGO,
};
static struct attribute *def_attrs[] = {
&test_attr,
NULL,
};
//kobject对象的操作
struct sysfs_ops obj_test_sysops =
{
.show = kobj_test_show,
.store = kobj_test_store,
};
/*定义kobject对象的一些属性及对应的操作*/
struct kobj_type ktype =
{
.release = obj_test_release,
.sysfs_ops=&obj_test_sysops,
.default_attrs=def_attrs,
};
/*release方法释放该kobject对象*/
void obj_test_release(struct kobject *kobject)
{
printk("object_test: release .\n");
}
/*当读文件时执行的操作*/
ssize_t kobj_test_show(struct kobject *kobject, struct attribute *attr,char *buf)
{
printk("have show.\n");
printk("attrname:%s.\n", attr->name);
sprintf(buf,"%s\n",attr->name);
return strlen(attr->name)+2;
}
/*当写文件时执行的操作*/
ssize_t kobj_test_store(struct kobject *kobject,struct attribute *attr,const char *buf, size_t count)
{
printk("havestore\n");
printk("write: %s\n",buf);
return count;
}
struct kobject kobj;//声明kobject对象
static int kobj_test_init(void)
{
printk("kboject test init.\n");
//初始化kobject对象kobj,并将其注册到linux系统
kobject_init_and_add(&kobj,&ktype,NULL,"kobject_test");
return 0;
}
static void kobj_test_exit(void)
{
printk("kobject test exit.\n");
kobject_del(&kobj);
}
module_init(kobj_test_init);
module_exit(kobj_test_exit);
4 符号链接
sysfs文件系统的树结构,反映它代表的kobjects的层次组织. 一个sysfs子树(/sys/devices )代表所有的系统已知的设备,而其他的子树( 在 /sys/bus 之下 )表示设备驱动.但是,不代表驱动和它们所管理的设备间的关系.
展示这些附加关系需要额外的指针, 指针在 sysfs 中通过符号连接实现。
/**
* sysfs_create_link - create symlink between two objects.
* @kobj: object whose directory we're creating the link in.
* @target: object we're pointing to.
* @name: name of the symlink.
*/
int sysfs_create_link(struct kobject *kobj, struct kobject *target,
const char *name)
移除符号:
int sysfs_create_link_nowarn(struct kobject *kobj, struct kobject *target, const char *name)
5 总线
总线是处理器和一个或多个设备之间的通道,所有的设备都通过一个总线连接. 总线可以插入另一个,例如一个 USB 控制器常常是一个 PCI 设备,总线由 bus_type 结构代表.
struct bus_type {
const char *name;//总线类型名字,用于在sysfs文件系统标识总线
const char *dev_name;
struct device *dev_root;
const struct attribute_group **bus_groups;
const struct attribute_group **dev_groups;
const struct attribute_group **drv_groups;
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 (*online)(struct device *dev);
int (*offline)(struct device *dev);
int (*suspend)(struct device *dev, pm_message_t state);//进入低功耗的调用方法
int (*resume)(struct device *dev);//改变供电状态和恢复硬件上下文的方法
int (*num_vf)(struct device *dev);
const struct dev_pm_ops *pm;
const struct iommu_ops *iommu_ops;
struct subsys_private *p;
struct lock_class_key lock_key;
bool force_dma;
};
name 成员是总线的名字,一个总线包含 2 个 ksets, 代表已知总线的驱动和所有插入总线的设备, 每个总线是它自己的子系统,在成员subsys_private中。
注册总线通过函数bus_register,会在/sys/bus中出现:
/**
* bus_register - register a driver-core subsystem
* @bus: bus to register
*
* Once we have that, we register the bus with the kobject
* infrastructure, then register the children subsystems it has:
* the devices and drivers that belong to the subsystem.
*/
int bus_register(struct bus_type *bus)
注销使用函数bus_unregister
void bus_unregister(struct bus_type *bus)
在bus_type 结构定义的方法,允许总线代码作为一个设备核心和单独驱动之 间的中介,例如match匹配设备和驱动。
如果遍历总线上的设备和驱动呢?内核提供了很好的API函数,
/**
* bus_for_each_dev - device iterator.
* @bus: bus type.
* @start: device to start iterating from.
* @data: data for the callback.
* @fn: function to be called for each device.
*
* Iterate over @bus's list of devices, and call @fn for each,
* passing it @data. If @start is not NULL, we use that device to
* begin iterating from.
*
* We check the return of @fn each time. If it returns anything
* other than 0, we break out and return that value.
*
* NOTE: The device that returns a non-zero value is not retained
* in any way, nor is its refcount incremented. If the caller needs
* to retain this data, it should do so, and increment the reference
* count in the supplied callback.
*/
int bus_for_each_dev(struct bus_type *bus, struct device *start,
void *data, int (*fn)(struct device *, void *))
这个函数列举总线上的每个设备, 传递关联的设备结构给 fn, 连同作为 data 来传递的 值. 如果 start 是 NULL, 列举从总线的第一个设备开始; 否则列举从 start 之后的第 一个设备开始. 如果 fn 返回一个非零值, 列举停止并且那个值从 bus_for_each_dev 返 回.
遍历总线上的驱动使用函数bus_for_each_drv。功能基本同bus_for_each_dev,只不过遍历的是驱动。
5.1 总线属性
Linux 驱动模型中的每一层都提供一个添加属性的接口, 并且总线层不例外.
struct bus_attribute {
struct attribute attr;
ssize_t (*show)(struct bus_type *bus, char *buf);
ssize_t (*store)(struct bus_type *bus, const char *buf, size_t count);
};
可以在编译时创建和初始化 bus_attribute 结构,使用BUS_ATTR(_name, _mode, _show, _store)
也可以使用类似kobject创建属性的方法
int bus_create_file(struct bus_type *bus, struct bus_attribute *attr)
void bus_remove_file(struct bus_type *bus, struct bus_attribute *attr)
6 设备
Linux 系统中的每个设备由一个 struct device 代表。
struct device {
struct device *parent;
struct device_private *p;
struct kobject kobj;
const char *init_name; /* initial name of the device */
const struct device_type *type;
struct mutex mutex; /* mutex 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 *platform_data; /* Platform specific data, device
core doesn't touch it */
void *driver_data; /* Driver data, set and get with
dev_set/get_drvdata */
struct dev_links_info links;
struct dev_pm_info power;
struct dev_pm_domain *pm_domain;
#ifdef CONFIG_GENERIC_MSI_IRQ_DOMAIN
struct irq_domain *msi_domain;
#endif
#ifdef CONFIG_PINCTRL
struct dev_pin_info *pins;
#endif
#ifdef CONFIG_GENERIC_MSI_IRQ
struct list_head msi_list;
#endif
#ifdef CONFIG_NUMA
int numa_node; /* NUMA node this device is close to */
#endif
const struct dma_map_ops *dma_ops;
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. */
unsigned long dma_pfn_offset;
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 */
#ifdef CONFIG_DMA_CMA
struct cma *cma_area; /* contiguous memory area for dma
allocations */
#endif
/* arch specific additions */
struct dev_archdata archdata;
struct device_node *of_node; /* associated device tree node */
struct fwnode_handle *fwnode; /* firmware device node */
dev_t devt; /* dev_t, creates the sysfs "dev" */
u32 id; /* device instance */
spinlock_t devres_lock;
struct list_head devres_head;
struct klist_node knode_class;
struct class *class;
const struct attribute_group **groups; /* optional groups */
void (*release)(struct device *dev);
struct iommu_group *iommu_group;
struct iommu_fwspec *iommu_fwspec;
bool offline_disabled:1;
bool offline:1;
bool of_node_reused:1;
};
内核提供标准函数device_register将设备添加到内核数据结构中。
其中有成员
struct device *parent;
parent父设备是某种总线或者主控制器. 如果 parent 是 NULL, 设备是一个顶层设备.
struct kobject kobj;
kobj代表这个设备并且连接它到层次中的 kobject.
设备注册使用函数device_register,注销使用device_unregister。
/**
* device_register - register a device with the system.
* @dev: pointer to the device structure
*
* This happens in two clean steps - initialize the device
* and add it to the system. The two steps can be called
* separately, but this is the easiest and most common.
* I.e. you should only call the two helpers separately if
* have a clearly defined need to use and refcount the device
* before it is added to the hierarchy.
*
* For more information, see the kerneldoc for device_initialize()
* and device_add().
*
* NOTE: _Never_ directly free @dev after calling this function, even
* if it returned an error! Always use put_device() to give up the
* reference initialized in this function instead.
*/
int device_register(struct device *dev)
注册完毕后可以在/dev/devices目录下看到设备,如果是总线设备则在/dev/devices/[bus_id]/目录下,注销函数:
void device_unregister(struct device *dev)
6.1 设备属性
sysfs 中的设备入口可以有属性
struct device_attribute {
struct attribute attr;
ssize_t (*show)(struct device *dev, struct device_attribute *attr,
char *buf);
ssize_t (*store)(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count);
};
可以通过device_create_file和device_remove_file函数创建和删除。
int device_create_file(struct device *dev,
const struct device_attribute *attr)
void device_remove_file(struct device *dev,
const struct device_attribute *attr)
驱动和设备定义,甚至包括函数都是非常的类似。
7 驱动
struct device_driver {
const char *name;//驱动程序名字
struct bus_type *bus;//指向总线描述符的指针,总线链接所支持的设备
struct module *owner;//表示设备驱动程序模块
const char *mod_name; /* used for built-in modules */
bool suppress_bind_attrs; /* disables bind/unbind via sysfs */
enum probe_type probe_type;
const struct of_device_id *of_match_table;
const struct acpi_device_id *acpi_match_table;
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); //恢复方法
const struct attribute_group **groups;
const struct dev_pm_ops *pm;
void (*coredump) (struct device *dev);
struct driver_private *p;
};
设备驱动程序数据结构体
通过driver_register注册设备驱动程序。
通常被静态嵌入到更大的描述符中,例如pci_driver.
属性文件创建和删除如下:
/**
* driver_create_file - create sysfs file for driver.
* @drv: driver.
* @attr: driver attribute descriptor.
*/
int driver_create_file(struct device_driver *drv, const struct driver_attribute *attr)
void driver_remove_file(struct device_driver *drv, const struct driver_attribute *attr)
8 类
类是设备的高级视图, 它抽象出低级的实现细节。 驱动可以见到一个 SCSI 磁盘或者一个 ATA 磁盘, 但是在类的级别, 它们都 是磁盘.
所有的类都在 sysfs 中在 /sys/class 下出现. 因此, 例如, 所有的网络接口可在 /sys/class/net 下发现, 不管接口类型. 输入设备可在 /sys/class/input 下, 以及串 行设备在 /sys/class/tty. 一个例外是块设备,在 /sys/block和/sys/class/block中。
设备驱动模型中每个类是由一个class对象描述的。结构体:
struct class {
const char *name;
struct module *owner;
const struct attribute_group **class_groups;
const struct attribute_group **dev_groups;
struct kobject *dev_kobj;
int (*dev_uevent)(struct device *dev, struct kobj_uevent_env *env);
char *(*devnode)(struct device *dev, umode_t *mode);
void (*class_release)(struct class *class);
void (*dev_release)(struct device *dev);
int (*shutdown_pre)(struct device *dev);
const struct kobj_ns_type_operations *ns_type;
const void *(*namespace)(struct device *dev);
const struct dev_pm_ops *pm;
struct subsys_private *p;
};
每个类需要一个唯一的名字,是这个类在 /sys/class 中出现.
创建class使用class_create(owner, name) ,该函数会调用__class_create(owner, name, &__key);,
最后由__class_register函数来进行注册。
8.1 类属性
类的属性结构如下:
struct class_attribute {
struct attribute attr;
ssize_t (*show)(struct class *class, struct class_attribute *attr,
char *buf);
ssize_t (*store)(struct class *class, struct class_attribute *attr,
const char *buf, size_t count);
};
创建类属性文件:
class_create_file(struct class *class, const struct class_attribute *attr)
删除类属性文件:
class_remove_file(struct class *class, const struct class_attribute *attr)
类的真正目的是作为一个是该类成员的设备的容器
8.2 类接口
类子系统有一个在 Linux 设备模型其他部分找不到的概念. 这个机制称为一个接口称为class_interface.
struct class_interface {
struct list_head node;
struct class *class;
int (*add_dev) (struct device *, struct class_interface *);
void (*remove_dev) (struct device *, struct class_interface *);
};
接口通过函数注册和注销。
int class_interface_register(struct class_interface *class_intf)
void class_interface_unregister(struct class_interface *);
一个类设备被加入到class_interface 结构中指定的类时, 接口的 add 函数被调用. 这个函数可进行任何额外的设 置; 当设备被从类中去 除, remove 方法被调用来进行任何需要的清理.
9 udev
内核中创建统一驱动模型的主要原因是允许用户空间动态管理 /dev 树.
随着 Linux 内核开始安装到大型服务器, 许多用户遇到如何管理大量设备的问题.