linux设备模型深探
更新时间:2024-04-18 13:22:01 阅读量: 综合文库 文档下载
一:前言
Linux设备模型是一个极其复杂的结构体系,在编写驱动程序的时候,通常不会用到这方面的东西,但是。理解这部份内容,对于我们理解linux设备驱动的结构是大有裨益的。我们不但可以在编写程序程序的时候知其然,亦知其所以然。又可以学习到一种极其精致的架构设计方法。由于之前已经详细分析了sysfs文件系统。所以本节的讨论主要集中在设备模型的底层实现上。上层的接口,如pci.,usb ,网络设备都可以看成是底层的封装。
二:kobject ,kset和ktype
Kobject,kset,kypte这三个结构是设备模型中的下层架构。模型中的每一个元素都对应一个kobject.kset和ktype可以看成是kobject在层次结构与属性结构方面的扩充。将三者之间的关系用图的方示描述如下:
如上图所示:我们知道。在sysfs中每一个目录都对应一个kobject.这些kobject都有自己的parent。在没有指定parent的情况下,都会指向它所属的kset->object。其次,kset也内嵌了kobject.这个kobject又可以指它上一级的parent。就这样。构成了一个空间上面的层次关系。
其实,每个对象都有属性。例如,电源管理,执插拨事性管理等等。因为大部份的同类设备都有相同的属性,因此将这个属性隔离开来,存放在ktype中。这样就可以灵活的管理了.记得在分析sysfs的时候。对于sysfs中的普通文件读写操作都是由kobject->ktype->sysfs_ops来完成的.
经过上面的分析,我们大概了解了kobject.kset与ktype的大概架设与相互之间的关系。下面我们从linux源代码中的分析来详细研究他们的操作。
三:kobject,kset和ktype的操作
为了说明kobject的操作,先写一个测试模块,代码如下:
#include
#include
#include
#include
#include
#include
#include
MODULE_AUTHOR(\
MODULE_LICENSE(\
void obj_test_release(struct kobject *kobject);
ssize_t eric_test_show(struct kobject *kobject, struct attribute *attr,char *buf);
ssize_t eric_test_store(struct kobject *kobject,struct attribute *attr,const char *buf, size_t count);
struct attribute test_attr = {
.name = \
.mode = S_IRWXUGO, };
static struct attribute *def_attrs[] = {
&test_attr,
NULL, };
struct sysfs_ops obj_test_sysops = {
.show = eric_test_show,
.store = eric_test_store, };
struct kobj_type ktype = {
.release = obj_test_release,
.sysfs_ops=&obj_test_sysops,
.default_attrs=def_attrs, };
void obj_test_release(struct kobject *kobject) {
printk(\ }
ssize_t eric_test_show(struct kobject *kobject, struct attribute *attr,char *buf) {
printk(\
printk(\
sprintf(buf,\
return strlen(attr->name)+2; }
ssize_t eric_test_store(struct kobject *kobject,struct attribute *attr,const char *buf, size_t count) {
printk(\
printk(\
return count; }
struct kobject kobj;
static int kobject_test_init() {
printk(\
kobject_init_and_add(&kobj,&ktype,NULL,\
return 0; }
static int kobject_test_exit() {
printk(\
kobject_del(&kobj);
return 0; }
module_init(kobject_test_init);
module_exit(kobject_test_exit);
加载模块之后,会发现,在/sys下多了一个eric_test目录。该目录下有一个叫eric_xiao的文件。如下所示:
[root@localhost eric_test]# ls
eric_xiao
用cat察看此文件:
[root@localhost eric_test]# cat eric_xiao
eric_xiao
再用echo往里面写点东西;
[root@localhost eric_test]# echo hello > eric_xiao
Dmesg的输出如下:
have show.
attrname:eric_xiao.
havestore
write: hello
如上所示。我们看到了kobject的大概建立过程.我们来看一下kobject_init_and_add()的实现。在这个函数里,包含了对kobject的大部份操作。
int kobject_init_and_add(struct kobject *kobj, struct kobj_type *ktype,
struct kobject *parent, const char *fmt, ...) {
va_list args;
int retval;
//初始化kobject
kobject_init(kobj, ktype);
va_start(args, fmt);
//为kobjcet设置名称,在sysfs中建立相关信息
retval = kobject_add_varg(kobj, parent, fmt, args);
va_end(args);
return retval; }
上面的流程主要分为两部份。一部份是kobject的初始化。在这一部份,它将kobject与给定的ktype关联起来。初始化kobject中的各项结构。另一部份是kobject的名称设置。空间层次关系的设置,具体表现在sysfs文件系统中.
对于第一部份,代码比较简单,这里不再赘述。跟踪第二部份,也就是kobject_add_varg()的实现.
static int kobject_add_varg(struct kobject *kobj, struct kobject *parent,
const char *fmt, va_list vargs) {
va_list aq;
int retval;
va_copy(aq, vargs);
//设置kobject的名字。即kobject的name成员
retval = kobject_set_name_vargs(kobj, fmt, aq);
va_end(aq);
if (retval) {
printk(KERN_ERR \
return retval;
}
//设置kobject的parent。在上面的例子中,我们没有给它指定父结点
kobj->parent = parent;
//在sysfs中添加kobjcet信息
return kobject_add_internal(kobj); }
设置好kobject->name后,转入kobject_add_internal()。在sysfs中创建空间结构.代码如下:
static int kobject_add_internal(struct kobject *kobj) {
int error = 0;
struct kobject *parent;
if (!kobj)
return -ENOENT;
//如果kobject的名字为空.退出
if (!kobj->name || !kobj->name[0]) {
pr_debug(\
\
WARN_ON(1);
return -EINVAL;
}
//取kobject的父结点
parent = kobject_get(kobj->parent);
//如果kobject的父结点没有指定,就将kset->kobject做为它的父结点
/* join kset if set, use it as parent if we do not already have one */
if (kobj->kset) {
if (!parent)
parent = kobject_get(&kobj->kset->kobj);
kobj_kset_join(kobj);
kobj->parent = parent;
}
//调试用
pr_debug(\
kobject_name(kobj), kobj, __FUNCTION__,
parent ? kobject_name(parent) : \
kobj->kset ? kobject_name(&kobj->kset->kobj) : \
//在sysfs中创建kobject的相关元素
error = create_dir(kobj);
if (error) {
//v如果创建失败。减少相关的引用计数
kobj_kset_leave(kobj);
kobject_put(parent);
kobj->parent = NULL;
/* be noisy on error issues */
if (error == -EEXIST)
printk(KERN_ERR \
\
\
__FUNCTION__, kobject_name(kobj));
else
printk(KERN_ERR \
__FUNCTION__, kobject_name(kobj), error);
dump_stack();
} else
//如果创建成功。将state_in_sysfs建为1。表示该object已经在sysfs中了
kobj->state_in_sysfs = 1;
return error; }
这段代码比较简单,它主要完成kobject父结点的判断和选定,然后再调用create_dir()在sysfs创建相关信息。该函数代码如下:
static int create_dir(struct kobject *kobj) {
int error = 0;
if (kobject_name(kobj)) {
//为kobject创建目录
error = sysfs_create_dir(kobj);
if (!error) {
//为kobject->ktype中的属性创建文件
error = populate_dir(kobj);
if (error)
sysfs_remove_dir(kobj);
}
}
return error; }
我们在上面的示例中看到的/sys下的eric_test目录,以及该目录下面的eric_xiao的这个文件就是这里被创建的。我们先看一下kobject所表示的目录创建过程。这是在sysfs_create_dir()中完成的。代码如下:
int sysfs_create_dir(struct kobject * kobj) {
struct sysfs_dirent *parent_sd, *sd;
int error = 0;
BUG_ON(!kobj);
/*如果kobject的parnet存在。就在目录点的目录下创建这个目录。如果没有父结点不存在,就在/sys下面创建结点。在上面的流程中,我们可能并没有为其指定父结点,也没有为其指定kset。 */
if (kobj->parent)
parent_sd = kobj->parent->sd;
else
parent_sd = &sysfs_root;
//在sysfs中创建目录
error = create_dir(kobj, parent_sd, kobject_name(kobj), &sd);
if (!error)
kobj->sd = sd;
return error; }
在这里,我们就要联系之前分析过的sysfs文件系统的研究了。如果不太清楚的,可以在找到那篇文章仔细的研读一下。create_dir()就是在sysfs中创建目录的接口,在之前已经详细分析过了。这里不再讲述。
接着看为kobject->ktype中的属性创建文件。这是在populate_dir()中完成的。代码如下:
static int populate_dir(struct kobject *kobj) {
struct kobj_type *t = get_ktype(kobj);
struct attribute *attr;
int error = 0;
int i;
if (t && t->default_attrs) {
for (i = 0; (attr = t->default_attrs[i]) != NULL; i++) {
error = sysfs_create_file(kobj, attr);
if (error)
break;
}
}
return error; }
这段代码比较简单。它遍历ktype中的属性。然后为其建立文件。请注意:文件的操作最后都会回溯到ktype->sysfs_ops的show和store这两个函数中.
Kobject的创建已经分析完了,接着分析怎么将一个kobject注销掉。注意过程是在kobject_del()中完成的。代码如下:
void kobject_del(struct kobject *kobj) {
if (!kobj)
return;
sysfs_remove_dir(kobj);
kobj->state_in_sysfs = 0;
kobj_kset_leave(kobj);
kobject_put(kobj->parent);
kobj->parent = NULL; }
该函数会将在sysfs中的kobject对应的目录删除。请注意,属性文件是建立在这个目录下面的。只需要将这个目录删除。属性文件也随之删除。
是后,减少相关的引用计数,如果kobject的引用计数为零。则将其所占空间释放.
Kset的操作与kobject类似,因为kset中内嵌了一个kobject结构,所以,大部份操作都是集中在kset->kobject上.具体分析一下kset_create_and_add()这个接口,类似上面分析的kobject接口,这个接口也包括了kset的大部分操作.代码如下:
struct kset *kset_create_and_add(const char *name,
struct kset_uevent_ops *uevent_ops,
struct kobject *parent_kobj) {
struct kset *kset;
int error;
//创建一个kset
kset = kset_create(name, uevent_ops, parent_kobj);
if (!kset)
return NULL;
//注册kset
error = kset_register(kset);
if (error) {
//如果注册失败,释放kset
kfree(kset);
return NULL;
}
return kset; }
Kset_create()用来创建一个struct kset结构.代码如下:
static struct kset *kset_create(const char *name,
struct kset_uevent_ops *uevent_ops,
struct kobject *parent_kobj) {
struct kset *kset;
kset = kzalloc(sizeof(*kset), GFP_KERNEL);
if (!kset)
return NULL;
kobject_set_name(&kset->kobj, name);
kset->uevent_ops = uevent_ops;
kset->kobj.parent = parent_kobj;
kset->kobj.ktype = &kset_ktype;
kset->kobj.kset = NULL;
return kset; }
我们注意,在这里创建kset时.为其内嵌的kobject指定其ktype结构为kset_ktype.这个结构的定义如下:
static struct kobj_type kset_ktype = {
.sysfs_ops = &kobj_sysfs_ops,
.release = kset_release, };
属性文件的读写操作全部都包含在sysfs_ops成员里.kobj_sysfs_ops的定义如下:
struct sysfs_ops kobj_sysfs_ops = {
.show = kobj_attr_show,
.store = kobj_attr_store, };
Show,store成员对应的函数代码如下所示:
static ssize_t kobj_attr_show(struct kobject *kobj, struct attribute *attr,
char *buf) {
struct kobj_attribute *kattr;
ssize_t ret = -EIO;
kattr = container_of(attr, struct kobj_attribute, attr);
if (kattr->show)
ret = kattr->show(kobj, kattr, buf);
return ret; }
static ssize_t kobj_attr_store(struct kobject *kobj, struct attribute *attr,
const char *buf, size_t count) {
struct kobj_attribute *kattr;
ssize_t ret = -EIO;
kattr = container_of(attr, struct kobj_attribute, attr);
if (kattr->store)
ret = kattr->store(kobj, kattr, buf, count);
return ret; }
从上面的代码看以看出.会将struct attribute结构转换为struct kobj_attribte结构.也就是说struct kobj_attribte内嵌了一个struct attribute.实际上,这是和宏__ATTR配合在一起使用的.经常用于group中.在这里并不打算研究group.原理都是一样的.这里列出来只是做个说明而已.
创建好了kset之后,会调用kset_register().这个函数就是kset操作的核心代码了.如下:
int kset_register(struct kset *k) {
int err;
if (!k)
return -EINVAL;
kset_init(k);
err = kobject_add_internal(&k->kobj);
if (err)
return err;
kobject_uevent(&k->kobj, KOBJ_ADD);
return 0; }
在kset_init()里会初始化kset中的其它字段.然后调用kobject_add_internal()为其内嵌的
kobject结构建立空间层次结构.之后因为添加了kset.会产生一个事件.这个事件是通过用户空间的hotplug程序处理的.这就是kset明显不同于kobject的地方.详细研究一下这个函数.这对于我们研究hotplug的深层机理是很有帮助的.它的代码如下;
int kobject_uevent(struct kobject *kobj, enum kobject_action action) {
return kobject_uevent_env(kobj, action, NULL); }
之后,会调用kobject_uevent_env().这个函数中的三个参数含义分别为:引起事件的kobject.事件类型(add,remove,change,move,online,offline等).第三个参数是要添加的环境变量.
代码篇幅较长,我们效仿情景分析上面的做法.分段分析如下:
int kobject_uevent_env(struct kobject *kobj, enum kobject_action action,
char *envp_ext[]) {
struct kobj_uevent_env *env;
const char *action_string = kobject_actions[action];
const char *devpath = NULL;
const char *subsystem;
struct kobject *top_kobj;
struct kset *kset;
struct kset_uevent_ops *uevent_ops;
u64 seq;
int i = 0;
int retval = 0;
pr_debug(\
kobject_name(kobj), kobj, __FUNCTION__);
/* search the kset we belong to */
top_kobj = kobj;
while (!top_kobj->kset && top_kobj->parent)
top_kobj = top_kobj->parent;
if (!top_kobj->kset) {
pr_debug(\
\
__FUNCTION__);
return -EINVAL;
}
因为对事件的处理函数包含在kobject->kset-> uevent_ops中.要处理事件,就必须要找到上层的一个不为空的kset.上面的代码就是顺着kobject->parent找不到一个不为空的kset.如果不存在这样的kset.就退出
kset = top_kobj->kset;
uevent_ops = kset->uevent_ops;
/* skip the event, if the filter returns zero. */
if (uevent_ops && uevent_ops->filter)
if (!uevent_ops->filter(kset, kobj)) {
pr_debug(\
\
kobject_name(kobj), kobj, __FUNCTION__);
return 0;
}
/* originating subsystem */
if (uevent_ops && uevent_ops->name)
subsystem = uevent_ops->name(kset, kobj);
else
subsystem = kobject_name(&kset->kobj);
if (!subsystem) {
pr_debug(\
\
__FUNCTION__);
return 0;
}
找到了不为空的kset.就跟kset-> uevent_ops->filter()匹配.看这个事件是否被过滤.如果没有被过滤掉.就会调用kset-> uevent_ops->name()得到子系统的名称,如果不存在kset-> uevent_ops->name().就会以kobject->name做为子系统名称.
/* environment buffer */
env = kzalloc(sizeof(struct kobj_uevent_env), GFP_KERNEL);
if (!env)
return -ENOMEM;
/* complete object path */
devpath = kobject_get_path(kobj, GFP_KERNEL);
if (!devpath) {
retval = -ENOENT;
goto exit;
}
/* default keys */
retval = add_uevent_var(env, \
if (retval)
goto exit;
retval = add_uevent_var(env, \
if (retval)
goto exit;
retval = add_uevent_var(env, \
if (retval)
goto exit;
/* keys passed in from the caller */
if (envp_ext) {
for (i = 0; envp_ext[i]; i++) {
retval = add_uevent_var(env, envp_ext[i]);
if (retval)
goto exit;
}
}
接下来,就应该设置为调用hotplug设置环境变量了.首先,分配一个struct kobj_uevent_env结构用来存放环境变量的值.然后调用kobject_get_path()用来获得引起事件的kobject在sysfs中的路径.再调用add_uevent_var()将动作代表的字串,kobject路径,子系统名称填充到struct kobj_uevent_env中,如果有指定环境变量,也将其添加进去. kobject_get_path()和add_uevent_var()都比较简单.这里不再详细分析了.请自行查看源代码
/* let the kset specific function add its stuff */
if (uevent_ops && uevent_ops->uevent) {
retval = uevent_ops->uevent(kset, kobj, env);
if (retval) {
pr_debug(\
\
__FUNCTION__, retval);
goto exit;
}
}
/*
* Mark \
* events to userspace during automatic cleanup. If the object did
* send an \
* the core, if not already done by the caller.
*/
if (action == KOBJ_ADD)
kobj->state_add_uevent_sent = 1;
else if (action == KOBJ_REMOVE)
kobj->state_remove_uevent_sent = 1;
/* we will send an event, so request a new sequence number */
spin_lock(&sequence_lock);
seq = ++uevent_seqnum;
spin_unlock(&sequence_lock);
retval = add_uevent_var(env, \
if (retval)
goto exit;
在这里还会调用kobject->kset-> uevent_ops->uevent().让产生事件的kobject添加环境变量.最
后将事件序列添加到环境变量中去.
#if defined(CONFIG_NET)
/* send netlink message */
if (uevent_sock) {
struct sk_buff *skb;
size_t len;
/* allocate message with the maximum possible size */
len = strlen(action_string) + strlen(devpath) + 2;
skb = alloc_skb(len + env->buflen, GFP_KERNEL);
if (skb) {
char *scratch;
/* add header */
scratch = skb_put(skb, len);
sprintf(scratch, \
/* copy keys to our continuous event payload buffer */
for (i = 0; i < env->envp_idx; i++) {
len = strlen(env->envp[i]) + 1;
scratch = skb_put(skb, len);
strcpy(scratch, env->envp[i]);
}
NETLINK_CB(skb).dst_group = 1;
netlink_broadcast(uevent_sock, skb, 0, 1, GFP_KERNEL);
}
}
#endif
/* call uevent_helper, usually only enabled during early boot */
if (uevent_helper[0]) {
char *argv [3];
argv [0] = uevent_helper;
argv [1] = (char *)subsystem;
argv [2] = NULL;
retval = add_uevent_var(env, \
if (retval)
goto exit;
retval = add_uevent_var(env,
\
if (retval)
goto exit;
call_usermodehelper(argv[0], argv, env->envp, UMH_WAIT_EXEC);
} exit:
kfree(devpath);
kfree(env);
return retval; }
忽略一段选择编译的代码.再后就是调用用户空间的hotplug了.添加最后两个环境变量.HOME和PATH.然后调用hotplug.以子系统名称为参数.
现在我们终于知道hotplug处理程序中的参数和环境变量是怎么来的了.^_^
使用完了kset.再调用kset_unregister()将其注销.这个函数很简单,请自行查阅代码.
为了印证一下上面的分析,写一个测试模块。如下:
#include
#include
#include
#include
#include
#include
#include
#include
MODULE_AUTHOR(\
MODULE_LICENSE(\
int kset_filter(struct kset *kset, struct kobject *kobj);
const char *kset_name(struct kset *kset, struct kobject *kobj);
int kset_uevent(struct kset *kset, struct kobject *kobj,
struct kobj_uevent_env *env);
struct kset kset_p;
struct kset kset_c;
struct kset_uevent_ops uevent_ops = {
.filter = kset_filter,
.name = kset_name,
.uevent = kset_uevent, };
int kset_filter(struct kset *kset, struct kobject *kobj) {
printk(\
return 1; }
const char *kset_name(struct kset *kset, struct kobject *kobj) {
static char buf[20];
printk(\
sprintf(buf,\
return buf; }
int kset_uevent(struct kset *kset, struct kobject *kobj,
struct kobj_uevent_env *env) {
int i = 0;
printk(\
while( i< env->envp_idx){
printk(\
i++;
}
return 0; }
int kset_test_init() {
printk(\
kobject_set_name(&kset_p.kobj,\
kset_p.uevent_ops = &uevent_ops;
kset_register(&kset_p);
kobject_set_name(&kset_c.kobj,\
kset_c.kobj.kset = &kset_p;
kset_register(&kset_c);
return 0; }
int kset_test_exit() {
printk(\
kset_unregister(&kset_p);
kset_unregister(&kset_c);
return 0; }
module_init(kset_test_init);
module_exit(kset_test_exit);
在这里,定义并注册了二个kset.第二个kset的kobj->kset域指向第一个kset.这样,当第二个kset注册或者卸载的时候就会调用第一个kset中的uevent_ops的相关操作.
kset_p.uevent_ops->filter函数中,使其返回1.使其匹配成功。
在kset_p.uevent_ops->name中。使其返回的子系统名为引起事件的kobject的名称,即:kset_c.
最后在kset_p.uevent_ops->uevent中将环境变量全部打印出来。
下面是dmesg的输出结果:
kset test init.
UEVENT: filter. kobj kset_c.
UEVENT: name. kobj kset_c.
UEVENT: uevent. kobj kset_c.
ACTION=add.
DEVPATH=/kset_p/kset_c.
SUBSYSTEM=kset_test.
输出结果跟我们的分析是吻合的.
在这里,值得我们注意的是。注册一个kobject不会产生事件,只有注册kset才会.
四:bus,device和device_driver
上面分析了kobject.kset,ktype.这三个结构联合起来一起构成了整个设备模型的基石.而bus.device.device_driver.则是基于kobject.kset.ktype之上的架构.在这里,总线,设备,驱动被有序的组和在一起.
Bus.device.device_driver三者之间的关系如下图所示:
如上图所示.struct bus_type的p->drivers_kset指向注册在上面的驱动程序.它的p->device_kset上挂着注册在上面的设备.每次有一个新的设备注册到上面,都会去匹配右边的驱动,看是否能匹配上.如果匹配成功,则将设备结构的is_registerd域置为0.然后将设备添加到驱动的p->klist_devices域.同理,每注册一个驱动,都会去匹配左边的设备,.如果匹配成功,将则设备加到驱动的p->klist_devices域.再将设备的is_registerd置为0/
这就是linux设备模型用来管理设备和驱动的基本架构. 我们来跟踪一下代码来看下详细的操作.
注册一个总线的接口为bus_register().我们照例分段分析:
int bus_register(struct bus_type *bus) {
int retval;
struct bus_type_private *priv;
priv = kzalloc(sizeof(struct bus_type_private), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->bus = bus;
bus->p = priv;
BLOCKING_INIT_NOTIFIER_HEAD(&priv->bus_notifier);
retval = kobject_set_name(&priv->subsys.kobj, \
if (retval)
goto out;
priv->subsys.kobj.kset = bus_kset;
priv->subsys.kobj.ktype = &bus_ktype;
priv->drivers_autoprobe = 1;
retval = kset_register(&priv->subsys);
if (retval)
goto out;
首先,先为struct bus_type的私有区分配空间,然后将其和struct bus_type关联起来.由于struct bus_type也要在sysfs文件中表示一个节点,因此,它也内嵌也一个kset的结构.这就是priv->subsys.
首先,它为这个kset的名称赋值为bus的名称,然后将priv->subsys.kobj.kset指向bus_kset. priv->subsys.kobj.ktype指向bus_ktype;然后调用kset_reqister()将priv->subsys注册.这里涉及到的接口都在之前分析过.注册过后,应该会在bus_kset所表示的目录下创建一个总线名称的目录.并且用户空间的hotplug应该会检测到一个add事件.我们来看一下bus_kset到底指向的是什么:
bus_kset = kset_create_and_add(\
从此可以看出.这个bus_kset在sysfs中的结点就是/sys/bus.在这里注册的struct bus_types就
会在/sys/bus/下面出现.
retval = bus_create_file(bus, &bus_attr_uevent);
if (retval)
goto bus_uevent_fail;
bus_create_file()就是在priv->subsys.kobj的这个kobject上建立一个普通属性的文件.这个文件的属性对应在bus_attr_uevent.读写操作对应在priv->subsys.ktype中.我们到后面才统一分析bus注册时候的文件创建
priv->devices_kset = kset_create_and_add(\
&priv->subsys.kobj);
if (!priv->devices_kset) {
retval = -ENOMEM;
goto bus_devices_fail;
}
priv->drivers_kset = kset_create_and_add(\
&priv->subsys.kobj);
if (!priv->drivers_kset) {
retval = -ENOMEM;
goto bus_drivers_fail;
}
klist_init(&priv->klist_devices, klist_devices_get, klist_devices_put);
klist_init(&priv->klist_drivers, NULL, NULL);
这段代码会在bus所在的目录下建立两个目录,分别为devices和drivers.并初始化挂载设备和驱动的链表
retval = add_probe_files(bus);
if (retval)
goto bus_probe_files_fail;
retval = bus_add_attrs(bus);
if (retval)
goto bus_attrs_fail;
pr_debug(\
return 0;
在这里,会为bus_attr_drivers_probe, bus_attr_drivers_autoprobe.注册bus_type中的属性建立文件
bus_attrs_fail:
remove_probe_files(bus);
bus_probe_files_fail:
kset_unregister(bus->p->drivers_kset);
bus_drivers_fail:
kset_unregister(bus->p->devices_kset);
bus_devices_fail:
bus_remove_file(bus, &bus_attr_uevent);
bus_uevent_fail:
kset_unregister(&bus->p->subsys);
kfree(bus->p); out:
return retval; }
这段代码为出错处理
这段代码中比较繁锁的就是bus_type对应目录下的属性文件建立,为了直观的说明,将属性文件的建立统一放到一起分析
从上面的代码中可以看,创建属性文件对应的属性分别为:
bus_attr_uevent bus_attr_drivers_probe, bus_attr_drivers_autoprobe
分别定义如下:
static BUS_ATTR(uevent, S_IWUSR, NULL, bus_uevent_store);
static BUS_ATTR(drivers_probe, S_IWUSR, NULL, store_drivers_probe);
static BUS_ATTR(drivers_autoprobe, S_IWUSR | S_IRUGO,
show_drivers_autoprobe, store_drivers_autoprobe);
BUS_ATTR定义如下:
#define BUS_ATTR(_name, _mode, _show, _store) \\
struct bus_attribute bus_attr_##_name = __ATTR(_name, _mode, _show, _store)
#define __ATTR(_name,_mode,_show,_store) { \\
.attr = {.name = __stringify(_name), .mode = _mode }, \\
.show = _show, \\
.store = _store, \\ }
由此可见.上面这三个属性对应的名称为别为uevent, drivers_probe, drivers_autoprobe.也就是说,会在bus_types目录下生成三个文件,分别为uevent,probe,autoprobe.
根据之前的分析,我们知道在sysfs文件系统中,对普通属性文件的读写都会回溯到kobject->ktype->sysfs_ops中.在这里,注意到有:
priv->subsys.kobj.kset = bus_kset;
priv->subsys.kobj.ktype = &bus_ktype;
显然,读写操作就回溯到了bus_ktype中.定义如下:
static struct kobj_type bus_ktype = {
.sysfs_ops = &bus_sysfs_ops, };
static struct sysfs_ops bus_sysfs_ops = {
.show = bus_attr_show,
.store = bus_attr_store, };
Show和store函数对应的代码为:
static ssize_t bus_attr_show(struct kobject *kobj, struct attribute *attr,
char *buf) {
struct bus_attribute *bus_attr = to_bus_attr(attr);
struct bus_type_private *bus_priv = to_bus(kobj);
ssize_t ret = 0;
if (bus_attr->show)
ret = bus_attr->show(bus_priv->bus, buf);
return ret; }
static ssize_t bus_attr_store(struct kobject *kobj, struct attribute *attr,
const char *buf, size_t count) {
struct bus_attribute *bus_attr = to_bus_attr(attr);
struct bus_type_private *bus_priv = to_bus(kobj);
ssize_t ret = 0;
if (bus_attr->store)
ret = bus_attr->store(bus_priv->bus, buf, count);
return ret; }
从代码可以看出.读写操作又会回溯到bus_attribute中的show和store中.在自定义结构里嵌入struct attribute,.然后再操作回溯到自定义结构中,这是一种比较高明的架构设计手法.
闲言少叙.我们对应看一下上面三个文件对应的最终操作:
Uevent对应的读写操作为:NULL, bus_uevent_store.对于这个文件没有读操作,只有写操作.用cat 命令去查看这个文件的时候,可能会返回”设备不存在”的错误.bus_uevent_store()代码如下:
static ssize_t bus_uevent_store(struct bus_type *bus,
const char *buf, size_t count) {
enum kobject_action action;
if (kobject_action_type(buf, count, &action) == 0)
kobject_uevent(&bus->p->subsys.kobj, action);
return count; }
从这里可以看到,可以在用户空间控制事件的发生,如echo add > event就会产生一个add的事件,
Probe文件对应的读写操作为:NULL store_drivers_probe.
store_drivers_probe()这个函数的代码涉及到struct device.等分析完struct device可以自行回过来看下这个函数的实现.实际上,这个函数是将用户输和的设备名称对应的设备与驱动匹配一次.
Autoprobe文件对应的读写操作为show_drivers_autoprobe, store_drivers_autoprobe.对应读的代码为:
static ssize_t show_drivers_autoprobe(struct bus_type *bus, char *buf) {
return sprintf(buf, \
}
它将总线对应的drivers_autoprobe的值输出到用户空间,这个值为1时,自动将驱动与设备进行匹配.否则,反之.
写操作的代码如下:
static ssize_t store_drivers_autoprobe(struct bus_type *bus,
const char *buf, size_t count) {
if (buf[0] == '0')
bus->p->drivers_autoprobe = 0;
else
bus->p->drivers_autoprobe = 1;
return count; }
写操作就会改变bus->p->drivers_autoprobe的值.
就这样,通过sysfs就可以控制总线是否要进行自动匹配了.
从这里也可以看出.内核开发者的思维是何等的灵活.
我们从sysfs中找个例子来印证一下:
Cd / sys/bus/usb
用ls命令查看:
devices drivers drivers_autoprobe drivers_probe uevent
与上面分析的相吻合
设备的注册接口为: device_register().
int device_register(struct device *dev) {
device_initialize(dev);
return device_add(dev); }
Device_initialize()中有几个很重要的操作,如下:
void device_initialize(struct device *dev) {
dev->kobj.kset = devices_kset;
kobject_init(&dev->kobj, &device_ktype);
klist_init(&dev->klist_children, klist_children_get,
klist_children_put);
INIT_LIST_HEAD(&dev->dma_pools);
INIT_LIST_HEAD(&dev->node);
init_MUTEX(&dev->sem);
spin_lock_init(&dev->devres_lock);
INIT_LIST_HEAD(&dev->devres_head);
device_init_wakeup(dev, 0);
set_dev_node(dev, -1); }
在这里,它为device的内嵌kobject指定了ktype和kset.device_kset的值如下:
devices_kset = kset_create_and_add(\
即对应sysfs中的/sys/devices
device_ktype 中对属性的读写操作同bus中的类似,被回溯到了struct device_attribute中的show 和store.
接着往下看device_add()的实现.这个函数比较长,分段分析如下:
int device_add(struct device *dev) {
struct device *parent = NULL;
struct class_interface *class_intf;
int error;
dev = get_device(dev);
if (!dev || !strlen(dev->bus_id)) {
error = -EINVAL;
goto Done;
}
pr_debug(\
parent = get_device(dev->parent);
setup_parent(dev, parent);
/* first, register with generic layer. */
error = kobject_add(&dev->kobj, dev->kobj.parent, \
if (error)
goto Error;
如果注册device的时候,没有指定父结点,在kobject_add将会在/sys/device/下建立相同名称的目录
/* notify platform of device entry */
if (platform_notify)
platform_notify(dev);
/* notify clients of device entry (new way) */
if (dev->bus)
blocking_notifier_call_chain(&dev->bus->p->bus_notifier,
BUS_NOTIFY_ADD_DEVICE, dev);
忽略notify部份,这部份不会影响本函数的流程
error = device_create_file(dev, &uevent_attr);
if (error)
goto attrError;
if (MAJOR(dev->devt)) {
error = device_create_file(dev, &devt_attr);
if (error)
goto ueventattrError;
}
建立属性为uevent_attr的属性文件,如果device中指定了设备号,则建立属性为devt_attr的属性文件
error = device_add_class_symlinks(dev);
if (error)
goto SymlinkError;
error = device_add_attrs(dev);
if (error)
goto AttrsError;
error = dpm_sysfs_add(dev);
if (error)
goto PMError;
device_pm_add(dev);
在这里,不打算讨论class的部份,dpm pm是选择编译部份,不讨论. device_add_attrs中涉及到了group的部分,暂不讨论
error = bus_add_device(dev);
if (error)
goto BusError;
kobject_uevent(&dev->kobj, KOBJ_ADD);
bus_attach_device(dev);
if (parent)
klist_add_tail(&dev->knode_parent, &parent->klist_children);
if (dev->class) {
down(&dev->class->sem);
/* tie the class to the device */
list_add_tail(&dev->node, &dev->class->devices);
/* notify any interfaces that the device is here */
list_for_each_entry(class_intf, &dev->class->interfaces, node)
if (class_intf->add_dev)
class_intf->add_dev(dev, class_intf);
up(&dev->class->sem);
}
bus_add_device()会在对应总线代表目录的device目录下创建几个到device的链接.然后产生一个add事件,再调用bus_attach_device()去匹配已经注册到总线的驱动程序.全部做完之后,将设备挂到父结点的子链表.
Done:
put_device(dev);
return error;
BusError:
device_pm_remove(dev);
PMError:
if (dev->bus)
blocking_notifier_call_chain(&dev->bus->p->bus_notifier,
BUS_NOTIFY_DEL_DEVICE, dev);
device_remove_attrs(dev);
AttrsError:
device_remove_class_symlinks(dev);
SymlinkError:
if (MAJOR(dev->devt))
device_remove_file(dev, &devt_attr);
ueventattrError:
device_remove_file(dev, &uevent_attr);
attrError:
kobject_uevent(&dev->kobj, KOBJ_REMOVE);
kobject_del(&dev->kobj);
Error:
cleanup_device_parent(dev);
if (parent)
put_device(parent);
goto Done; }
出错处理部份.
bus_attach_device()是一个很重要的函数。它将设备自动与挂在总线上面的驱动进行匹配。
代码如下:
void bus_attach_device(struct device *dev) {
struct bus_type *bus = dev->bus;
int ret = 0;
if (bus) {
dev->is_registered = 1;
if (bus->p->drivers_autoprobe)
ret = device_attach(dev);
WARN_ON(ret < 0);
if (ret >= 0)
klist_add_tail(&dev->knode_bus, &bus->p->klist_devices);
else
dev->is_registered = 0;
} }
从上面的代码我们可以看出。只有在bus->p->drivers_autoprobe为1的情况下,才会去自己匹配。这也就是bus目录下的drivers_probe 文件的作用.然后,将设备挂到总线的设备链表。
Device_attach()代码如下:
int device_attach(struct device *dev) {
int ret = 0;
down(&dev->sem);
if (dev->driver) {
ret = device_bind_driver(dev);
if (ret == 0)
ret = 1;
else {
dev->driver = NULL;
ret = 0;
}
} else {
ret = bus_for_each_drv(dev->bus, NULL, dev, __device_attach);
}
up(&dev->sem);
return ret; }
对于设备自己已经指定驱动的情况,只需要将其直接和驱动绑定即可。如果没有指定驱动。就匹配总线之上的驱动。这是在bus_for_each_drv(dev->bus, NULL, dev, __device_attach);完成的。代码如下:
int bus_for_each_drv(struct bus_type *bus, struct device_driver *start,
void *data, int (*fn)(struct device_driver *, void *)) {
struct klist_iter i;
struct device_driver *drv;
int error = 0;
if (!bus)
return -EINVAL;
klist_iter_init_node(&bus->p->klist_drivers, &i,
start ? &start->p->knode_bus : NULL);
while ((drv = next_driver(&i)) && !error)
error = fn(drv, data);
klist_iter_exit(&i);
return error; }
很明显,这个函数就是遍历总线之上的驱动。每遍历一个驱动就调用一次回调函数进行判断。如果回调函数返回不为0。就说明匹配已经成功了。不需要再匹配剩余的。退出。在这里调用的回调函数是__device_attach().在这里。完全了设备与驱动匹配的最核心的动作。代码如下:
static int __device_attach(struct device_driver *drv, void *data) {
struct device *dev = data;
return driver_probe_device(drv, dev); }
转到driver_probe_device():
int driver_probe_device(struct device_driver *drv, struct device *dev)
{
int ret = 0;
if (!device_is_registered(dev))
return -ENODEV;
if (drv->bus->match && !drv->bus->match(dev, drv))
goto done;
pr_debug(\
drv->bus->name, __FUNCTION__, dev->bus_id, drv->name);
ret = really_probe(dev, drv);
done:
return ret; }
如果设备没有注册到总线之上。即dev->is_registered不为1. 就直接返回。
然后,再调用总线的match()函数进行匹配。如果match()函数返回0.说明匹配失败。那退出此函数。如果match函数返回1.说明初步的检查已经通过了。可以进入really_probe()再进行细致的检查。如果匹配成功,这个函数会返回1.此函数比较长而且比较重要,分段列出代码:
static int really_probe(struct device *dev, struct device_driver *drv) {
int ret = 0;
atomic_inc(&probe_count);
pr_debug(\
drv->bus->name, __FUNCTION__, drv->name, dev->bus_id);
WARN_ON(!list_empty(&dev->devres_head));
dev->driver = drv;
if (driver_sysfs_add(dev)) {
printk(KERN_ERR \
__FUNCTION__, dev->bus_id);
goto probe_failed;
}
先假设驱动和设备是匹配的。为设备结构设置驱动成员。使其指向匹配的驱动。然后再调用driver_sysfs_add()建立几个符号链接。这几个链接分别为:
1:在驱动目录下建立一个到设备的同名链接
2:在设备目录下建立一个名为driver。到驱动的链接
if (dev->bus->probe) {
ret = dev->bus->probe(dev);
if (ret)
goto probe_failed;
} else if (drv->probe) {
正在阅读:
linux设备模型深探04-18
中国和平发展战略面临的机遇与挑战09-18
2014年银行出纳年终个人总结03-12
2019年高考英语作文专题指导(推荐)11-06
汽车设计—侧滑量检验台设计10-29
金融学-名词解释09-14
廉政党课:巩固和发展良好政治生态02-23
四年级上册数学提高50题03-22
《最后一头战象》优秀教学设计优秀9篇03-24
- 多层物业服务方案
- (审判实务)习惯法与少数民族地区民间纠纷解决问题(孙 潋)
- 人教版新课标六年级下册语文全册教案
- 词语打卡
- photoshop实习报告
- 钢结构设计原理综合测试2
- 2014年期末练习题
- 高中数学中的逆向思维解题方法探讨
- 名师原创 全国通用2014-2015学年高二寒假作业 政治(一)Word版
- 北航《建筑结构检测鉴定与加固》在线作业三
- XX县卫生监督所工程建设项目可行性研究报告
- 小学四年级观察作文经典评语
- 浅谈110KV变电站电气一次设计-程泉焱(1)
- 安全员考试题库
- 国家电网公司变电运维管理规定(试行)
- 义务教育课程标准稿征求意见提纲
- 教学秘书面试技巧
- 钢结构工程施工组织设计
- 水利工程概论论文
- 09届九年级数学第四次模拟试卷
- 模型
- 设备
- linux
- XX国际大酒店APP及微信公众平台建设可行性方案 - 图文
- 贵州省林业行政处罚自由裁量权实施办法
- 体格检查评分表
- 《创新设计高考总复习》配套学案:定积分与微积分基本定理
- 云南金杉监理事故隐患排查治理和反违章管理实施细则
- 机关效能建设六型机关创建活动经验交流材料
- 实验室安全与卫生管理制度
- 大学计算机基础理论考试模拟练习题
- 白水河上期盼平安桥
- 佛山事业单位考试申论技巧:三大题型的答题方法解析
- 期中考试总结演讲稿
- 德国双元制职业教育对中国的启示
- 高考核心词汇大全--考前必看
- 参加培训会议专家代表名单
- 初中数学教学与现代信息技术整合策略的研究
- 安全文明施工方案201510
- 西政发30号 关于印发北京市西城区规范行政执法若干规定的通知
- 2016中心路小学“全员育人导机制”实施方案
- 口腔局解考试题
- 当前民主监督工作存在的问题与建议