之前我们分析了引用计数kref,总结了sysfs提供的API,并翻译了介绍kobject原理及用法的文档。应该说准备工作做得足够多,kobject的实现怎么都可以看懂了,甚至只需要总结下API就行了。可我还是决定把kobject的实现代码从头分析一遍。一是因为kobject的代码很重要,会在设备驱动模型代码中无数次被用到,如果不熟悉的话可以说是举步维艰。二是为了熟悉linux的编码风格,为以后分析更大规模的代码奠定基础。
kobject的头文件在include/linux/kobject.h,实现在lib/kobject.c。闲话少说,上代码。
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;
};
在struct kobject中,name是名字,entry是用于kobject所属kset下的子kobject链表,parent指向kobject的父节点,kset指向kobject所属的kset,ktype定义了kobject所属的类型,sd指向kobject对应的sysfs目录,kref记录kobject的引用计数,之后是一系列标志。
struct kobj_type {
void (*release)(struct kobject *kobj);
struct sysfs_ops *sysfs_ops;
struct attribute **default_attrs;
};
struct kobj_type就是定义了kobject的公共类型,其中既有操作的函数,也有公共的属性。其中release()是在kobject释放时调用的,sysfs_ops中定义了读写属性文件时调用的函数。default_attrs中定义了这类kobject公共的属性。
struct kset {
struct list_head list;
spinlock_t list_lock;
struct kobject kobj;
struct kset_uevent_ops *uevent_ops;
};
struct kset可以看成在kobject上的扩展,它包含一个kobject的链表,可以方便地表示sysfs中目录与子目录的关系。其中,list是所属kobject的链表头,list_lock用于在访问链表时加锁,kobj是kset的内部kobject,要表现为sysfs中的目录就必须拥有kobject的功能,最后的kset_uevent_ops定义了对发往用户空间的uevent的处理。我对uevent不了解,会尽量忽略。
struct kobj_attribute {
struct attribute attr;
ssize_t (*show)(struct kobject *kobj, struct kobj_attribute *attr,
char *buf);
ssize_t (*store)(struct kobject *kobj, struct kobj_attribute *attr,
const char *buf, size_t count);
};
struct kobj_attribute是kobject在attribute上做出的扩展,添加了两个专门读写kobject属性的函数。无论是kobject,还是kset(说到底是kset内部的kobject),都提供了使用kobj_attribute的快速创建方法。
结构差不多介绍完了,下面看看实现。我所知道的代码分析风格,喜欢自顶向下的方式,从一个函数开始,介绍出一个函数调用树。在代码量很大,涉及调用层次很深的时候,确实要采用这种打洞的方式来寻找突破口。但这种自顶向下的方式有两个问题:一是很容易迷失,二是代码分析的难度会逐渐增大而不是减小。在茫茫的代码中,你一头下去,周围都是你不认识的函数,一个函数里调用了三个陌生的函数,其中一个陌生的函数又调用了五个更陌生的函数...不久你就会产生很强的挫败感。这就像走在沙漠上,你不知道终点在哪,也许翻过一个沙丘就到了,也许还有无数个沙丘。而且在这种分析时,人是逐渐走向细节,容易被细节所困扰,忽略了整体的印象与代码的层次感。所以,我觉得在分析代码时,也可以采用自底向上的方式,从细小的、内部使用的函数,到比较宏观的、供外部调用的函数。而且按照这种顺序来看代码,基本就是文件从头读到尾的顺序,也比较符合写代码的流程。linux代码喜欢在文件开始处攒内部静态函数,攒到一定程度爆发,突然实现几个外部API,然后再攒,再实现。而且之前的内部静态函数会反复调用到。linux代码写得很有层次感,除了内外有别,还把意思相近的,或者功能刚好相反的,或者使用时顺序调用的函数放在一起,很便于阅读。闲话少说,等你看完kobject的实现自然就清楚了。
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;
}
static int create_dir(struct kobject *kobj)
{
int error = 0;
if (kobject_name(kobj)) {
error = sysfs_create_dir(kobj);
if (!error) {
error = populate_dir(kobj);
if (error)
sysfs_remove_dir(kobj);
}
}
return error;
}
create_dir()在sysfs中创建kobj对应的目录,populate_dir()创建kobj中默认属性对应的文件。create_dir()正是调用populate_dir()实现的。
static int get_kobj_path_length(struct kobject *kobj)
{
int length = 1;
struct kobject *parent = kobj;
/* walk up the ancestors until we hit the one pointing to the
* root.
* Add 1 to strlen for leading '/' of each level.
*/
do {
if (kobject_name(parent) == NULL)
return 0;
length += strlen(kobject_name(parent)) + 1;
parent = parent->parent;
} while (parent);
return length;
}
static void fill_kobj_path(struct kobject *kobj, char *path, int length)
{
struct kobject *parent;
--length;
for (parent = kobj; parent; parent = parent->parent) {
int cur = strlen(kobject_name(parent));
/* back up enough to print this name with '/' */
length -= cur;
strncpy(path + length, kobject_name(parent), cur);
*(path + --length) = '/';
}
pr_debug("kobject: '%s' (%p): %s: path = '%s'\n", kobject_name(kobj),
kobj, __func__, path);
}
/**
* kobject_get_path - generate and return the path associated with a given kobj and kset pair.
*
* @kobj: kobject in question, with which to build the path
* @gfp_mask: the allocation type used to allocate the path
*
* The result must be freed by the caller with kfree().
*/
char *kobject_get_path(struct kobject *kobj, gfp_t gfp_mask)
{
char *path;
int len;
len = get_kobj_path_length(kobj);
if (len == 0)
return NULL;
path = kzalloc(len, gfp_mask);
if (!path)
return NULL;
fill_kobj_path(kobj, path, len);