本篇是关于linux的设备模型,将会覆盖相关的绝大部分知识,以及实践操作。希望这篇材料让大家基本掌握linux驱动设备模型,从而写出更加优秀的linux驱动。
linux统一的设备模型,提供了对电源管理和系统关机 、用户空间的通讯、可热插拔设备 、设备类别 、对象生命期 的广泛支持。
Linux 设备模型代码负责所有这些方面, 驱动代码作者只需要充分信任这些代码即可,但是, 理解设备模型绝对是一个好事情.
Linux2.6内核引入了sysfs文件系统,是与proc同类别的文件系统。sysfs把连接在系统上的设备和总线组织成分级的文件,使其从用户空间可以访问到。
有疑问联系:[email protected]
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;
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
这里的属性是使用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
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;
#ifdef CONFIG_PINCTRL
struct dev_pin_info *pins;
#ifdef CONFIG_GENERIC_MSI_IRQ
struct list_head msi_list;
#ifdef CONFIG_NUMA
int numa_node; /* NUMA node this device is close to */
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 */
/* 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, 设备是一个顶层设备.
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函数创建和删除。
device_create_file(struct device *dev,
const struct device_attribute *attr)
device_remove_file(struct device *dev,
const struct device_attribute *attr)
驱动和设备定义,甚至包括函数都是非常的类似。
7
驱动
struct device_driver {
*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;
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 {
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);
每个类需要一个唯一的名字,是这个类在 /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;
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 内核开始安装到大型服务器, 许多用户遇到如何管理大量设备的问题.