Linux驱动模型

Linux设备驱动模型


kobject 概述

kobject是Linux中通用的对象模型,为内核中的对象模型的管理提供统一的视角。在内核中应用最多的是驱动模型,即总线、设备、驱动、类的管理,都使用了kobject。kobject一般都不会单独使用,这样没有意义,它总是内嵌到其他结构体中。

kobject只是通用对象的表示,其自身不包含任何特定于某个模块的属性,因此我们更关心的是kobject的外围结构。每个外围结构体所在的模块都应该定义一个ktype,这个ktype就完成了由通用向特殊的过渡。因此kobject可看做其他所有外围结构的基类,它抽象出一些通用的属性(如引用计数)以及通用接口,而每个外围结构各自完成这些接口的内部实现,这里使用内嵌的方式来体现这种继承关系。

使用内嵌而不用指针也是为了方便通过kobject来反向定位其外围结构的实例,通过我们熟知的container_of()宏便可以做到。由于kobject是系统统一管理的,因此先找到kobject对象进而跟踪到其代表的具体对象是很常见的做法。

kset是一个基本的容器类,它是一组kobject的集合。当我们想统一管理某些有类似属性的kobjects时,可以将它们加入到一个集合中,这个集合的作用是,当一个事件发生时,可以同时通知到集合中的所有kobjects。


kobject 的具体功能

对象引用计数:

  • 一个内核对象创建时,用来跟踪对象的生命周期,当内核中没有代码持有该对象时,该对象结束自己的生命周期,可以被删除。

sysfs表述

  • 在sysfs中显示的每一个对象,对应一个kobject,它被用来和内核交互,在sys/目录下创建对应的文件。

数据结构关联

  • kobject 把大量的数据结构体连接成一个多层次的体系结构。

热插拔事件处理

  • kobject 控制系统中的热插拔事件,并且把产生的事件通知用户空间。

kobject 结构体

kobject 定义在<lncluce/linux/kobject.h>中。在这个头文件中,还定义了操作kobject对象的函数,与kobject相关的结构体声明等。

struct kobject 
{
	const char	*name;
	struct  list_head	entry;
	struct  kobject  *parent; 
	struct  kset  *kset;
	struct  kobj_type	*ktype;
	struct  sysfs_dirent	*sd;
	struct  kref  kref;
	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;
};



sysfs

sysfs是存在于内存中的一种虚拟文件系统。它是为kobject对象的层次结构提供了视图形式,使用户能够以一个文件系统的方式查看系统中的设备结构,读写这些设备文件。它代替了/proc下的相关设备文件。


sysfs目录介绍

– block
  block目录下的每个目录,对应一个系统中注册的块设备。

– bus
   bus目录提供一个系统总线的视图。

– class
  提供一个以高层视角观察系统设备功能的视图。

– dev
  dev目录是设备节点的视图。

– drivers
  drivers目录是系统中设备拓扑结构视图,它直接映射了设备结构体的组织结构。

– fs
  fs是注册文件系统的的视图

– kernel
  kernel目录显示内核配置和状态信息。

– power
  power目录显示了电源管理数据信息。

driver目录是内核将设备模型导出到用户空间的目录,目录结构就是设备实际的结构。而其它目录是将drivers目录下的数据经过加工而得到的,以符号链接的形式出现。


kobject和sysfs

在 Linux内核将所有的kobjects与虚拟文件系统sysfs紧密结合起来,这样就让所有的kobjects可视化和层次化。kobject描述了sysfs虚拟文件系统中的层级结构,一个kobject对象就对应了sysfs中的一个目录,而sysfs中的目录结构也体现在各个kobjects之间的父子关系。

使用 kobject_add()可以把kobject导入到sysfs中,删除使用kobject_del()。

int kobject_add(struct kobject *kobj, struct kobject *parent,const char *fmt, ...)
void kobject_del(struct kobject *kobj)

kobject 包含注释举例:

#include <linux/kobject.h>
struct kobject 
{
    const char *name;  /* kobject对象的名字,对应sysfs中的目录名 */
    struct list_head  entry;  /* 在kset中的链表节点 */
    struct kobject  *parent;  /* 用于构建sysfs中kobjects的层次结构,指向父目录 */
    struct kset  *kset;  /* 所属kset */
    struct kobj_type  *ktype;  /* 特定对象类型相关,用于跟踪object及其属性 */
    struct sysfs_dirent  *sd;  /* 指向该目录的dentry私有数据 */
    struct kref  kref;  /* kobject的引用计数,初始值为1 */
    unsigned int state_initialized:1;  /* kobject是否初始化,由kobject_init()设置 */
    unsigned int state_in_sysfs:1;  /* 是否已添加到sysfs层次结构中 */
    unsigned int state_add_uevent_sent:1;
    unsigned int state_remove_uevent_sent:1;
    unsigned int uevent_suppress:1;  /* 是否忽略uevent事件 */
};

######################################################################


新建一个kobject对象

初始化kobject对象函数:

void kobject_init(struct kobject *kobj, struct kobj_type *ktype);

这个函数需要传入两个参数,kobj和ktype,他们必须非NULL,由于kobj都是嵌入到其他结构体,所以一般传kobj参数的方式形如&pcdev->kobj。

初始化kobj->kref引用计数为初始值1;
初始化kobj->entry空链表头;
kobj->ktype = ktype;
kobj->state_initialized置为1,表示该kobject已初始化。


初始化之后,通过kobject_add()将kobj添加到系统中。

int kobject_add(struct kobject *kobj, struct kobject *parent, const char *fmt, ...);

这个函数给kobj指定一个名字,这个名字也就是其在sysfs中的目录名,parent用来指明kobj的父节点,即指定了kobj的目录在sysfs中创建的位置。如果这个kobj要加入到一个特定的kset中,则在kobject_add()必须给kobj->kset赋值,此时parent可以设置为NULL,这样kobj会自动将kobj->kset对应的对象作为自己的parent。如果parent设置为NULL,且没有加入到一个kset中,kobject会被创建到/sys顶层目录下。


设置对象的名字是通过下面的接口完成的:

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

这个函数给kobj的name成员赋值。注意这里面是通过kmalloc给name分配内存的。相应的获取一个kobject对象的名字的接口为:

const char *kobject_name(const struct kobject * kobj);

ktype

ktype是由struct kobj_type来定义的

#include <linux/kobject.h>
struct kobj_type 
{
  void (*release)(struct kobject *kobj);
  const struct sysfs_ops *sysfs_ops;
  struct attribute **default_attrs;
  const struct kobj_ns_type_operations *(*child_ns_type)(struct kobject *kobj);
  const void *(*namespace)(struct kobject *kobj);
};

kobj_type都必须实现release方法,该方法由kobject的外层结构来定义,用来释放特定模块相关的kobject资源。


default_attrs定义了一系列默认属性,default_attrs是一个二级指针,可以对每个kobject设置多个默认属性(最后一个属性用NULL填充)。

struct attribute 
{
    const char      *name; /* 属性名字 */
    umode_t         mode; /* 用户访问模式,在<linux/stat.h>中定义 */
};

kobj_type是由具体模块定义的,每一个属性都对应着kobject目录下的一个文件,这样可以在用户态通过读写属性文件,来完成对该属性值的读取和更改。


sysfs_ops定义了属性的操作:

struct sysfs_ops
 {
    ssize_t (*show)(struct kobject *, struct attribute *,char *);
    ssize_t (*store)(struct kobject *,struct attribute *,const char *, size_t);
    const void *(*namespace)(struct kobject *, const struct attribute *);
};

也就是说,default_attrs数组中所有属性的操作都是由sysfs_ops来完成的。
在kobject的容器(外围结构)中,一般会定义xxx_add_attrs()和xxx_remove_attrs()提供增加和删除额外属性的接口。例如对于bus_ktype,默认属性是空的,因此我们看到/sys/bus/目录下是没有属性文件的,而每个子目录的属性可能不同,因此都需要动态地去定义属性列表并据此创建属性文件。动态属性一般通过__ATTR()宏来定义。


创建属性文件最终都是通过sysfs_create_file()或sysfs_create_group()来完成的。其中sysfs_create_group(kobj, grp)用来创建一组属性文件,需要定义struct attribute_group结构的属性集,这里需要说明的是,其中name成员如果为NULL,则直接在kobj目录下创建各个属性文件,如果name不为NULL,则会创建一个名为name的目录,然后在该目录下创建各个属性文件。

struct attribute_group 
{
    const char      *name;
    umode_t         (*is_visible)(struct kobject *,
                          struct attribute *, int);
    struct attribute    **attrs;
};

sysfs关联

读写sysfs中的文件(即每个attribute)的操作函数集为sysfs_file_operations:

const struct file_operations sysfs_file_operations = 
{
    .read       = sysfs_read_file,
    .write      = sysfs_write_file,
    .llseek     = generic_file_llseek,
    .open       = sysfs_open_file,
    .release    = sysfs_release,
    .poll       = sysfs_poll,
};

在读写sysfs的文件时,是通过文件的dentry找到和sysfs关联的struct sysfs_dirent结构,sysfs中每一个目录或文件都有其对应的一个struct sysfs_dirent结构的实例,而sysfs_dirent的s_parent成员用于构建sysfs中的层次结构。
那么,对于一个属性文件的sysfs_dirent结构,可通过其s_parent找到其所在目录的kobject实例,进而找到文件操作对应的kobj->ktype->sysfs_ops方法集合。相应的,kobject的sd成员即指向该目录对应的sysfs_dirent实例。

在读写一个sysfs里的属性文件时,其中read操作会去调用其所在目录对应的kobject的kobj->ktype->sysfs_ops的show方法完成属性读取,write操作会去调用kobj->ktype->sysfs_ops的store方法完成属性写入并使其生效。open/release方法用于打开/关闭文件。poll用来探测文件内容是否改变(如果相应kobject实现了poll/select的事件通知),poll方法返回POLLERR|POLLPRI事件,如果发现文件内容有变则需要重新打开文件或seek到文件头并重新读取才能读到新的内容。
实际上,上述sysfs_ops的show和store方法也是简单的包裹函数,实际调用了具体模块的xxx_attribute的show和store方法(在实现一个属性的操作时,如果是很简单的属性读取和配置,可以复用struct kobj_attribute,这种属性一般只是一个整型或字符串,如果是比较复杂的模块和属性,则可自定义形如xxx_attribute的结构及其show/store方法)。


Uevents

在一个kobject的状态变化时(新注册、注销、重命名等),都会广播出一个对应的事件通知(通常用户态会去接收并处理),通知事件的接口为:

int kobject_uevent(struct kobject *kobj, enum kobject_action action);

int kobject_uevent_env(struct kobject *kobj, enum kobject_action action, char *envp_ext[]);

第二个接口可以传递额外的环境变量(“额外”的意思是说,即使envp_ext为NULL,也会传递基本的”ACTION=%s”、”DEVPATH=%s”、”SUBSYSTEM=%s”、”SEQNUM=%llu”)。action的可取值如下定义:

enum kobject_action 
{
    KOBJ_ADD,
    KOBJ_REMOVE,
    KOBJ_CHANGE,
    KOBJ_MOVE,
    KOBJ_ONLINE,
    KOBJ_OFFLINE,
    KOBJ_MAX
};

这些动作在发送到用户态时是通过字符串来表达的,其对应关系为:

static const char *kobject_actions[] = 
{
    [KOBJ_ADD] =        "add",
    [KOBJ_REMOVE] =     "remove",
    [KOBJ_CHANGE] =     "change",
    [KOBJ_MOVE] =       "move",
    [KOBJ_ONLINE] =     "online",
    [KOBJ_OFFLINE] =    "offline",
};

设备容器

struct kset 
{
	struct list_head list;
	spinlock_t list_lock;
	struct kobject kobj;
	const struct kset_uevent_ops *uevent_ops;
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值