设备驱动模型之sysfs与kobject

1. kobject, kset和ktype

要分析sysfs,首先就要分析kobjectkset,因为驱动设备的层次结构的构成就是由这两个东东来完成的。

sysfskobject密不可分。

1.1 kobject

kobject是组成设备模型的基本结构,是所有用来描述设备模型的数据结构的基类。
kobject是一个对象的抽象,它用于管理对象。

kobject被用来控制访问一个更大的,具有特定作用域的对象;为了达到这个作用,kobject将会被嵌入到其他结构体中。
如果你用面向对象的角度来看,kobject结构可以被看做顶层的抽象类,其他类都是从这个类派生出来的。

sysfs中,每个kobject对应着一个目录。

/*include/linux/kobject.h*/
struct kobject {
	 /* 对应sysfs的目录名 */
	const char		*name;
	/*用于连接到所属kset的链表中,用于将kobj挂在kset->list中*/
	struct list_head	entry;
	/*指向 父对象,形成层次结构,在sysfs中表现为父子目录的关系*/
	struct kobject		*parent;
	/*
        属于哪个kset
        表征该kobj所属的kset。
        kset可以作为parent的“候补”:当注册时,传入的parent为空时,可以让kset来担当。
    */
	struct kset		*kset;
	/*类型属性,每个kobj或其嵌入的结构对象应该都对应一个kobj_type。 */
	const struct kobj_type	*ktype;
	/*sysfs中与该对象对应的文件节点对象*/
    // 在3.14以后的内核中,sysfs基于kernfs来实现。
	struct kernfs_node	*sd; /* sysfs directory entry */
	/*对象的应用计数*/
	struct kref		kref;
#ifdef CONFIG_DEBUG_KOBJECT_RELEASE
	struct delayed_work	release;
#endif
	/* 记录初始化与否。调用kobject_init()后,会置位。 */
	unsigned int state_initialized:1;
	 /* 记录kobj是否注册到sysfs,在kobject_add_internal()中置位。 */
	unsigned int state_in_sysfs:1;
	/* 当发送KOBJ_ADD消息时,置位。提示已经向用户空间发送ADD消息。*/
	unsigned int state_add_uevent_sent:1;
	/* 当发送KOBJ_REMOVE消息时,置位。提示已经向用户空间发送REMOVE消息。*/
	unsigned int state_remove_uevent_sent:1;
	/* 如果该字段为1,则表示忽略所有上报的uevent事件。 */
	unsigned int uevent_suppress:1;
};

/*include/linux/kref.h*/
struct kref {
	refcount_t refcount;
};

目前为止,Kobject主要提供如下功能:

  1. 构成层次结构:通过parent指针,可以将所有Kobject以层次结构的形式组合起来。
  2. 生命周期管理:使用引用计数(reference count),来记录Kobject被引用的次数,并在引用次数变为0时把它释放。
  3. sysfs虚拟文件系统配合,将每一个Kobject及其特性,以文件的形式,开放到用户空间。

没有一个结构会嵌入多于一个kobject结构,如果这么做了,关于这个对象的引用计数肯定会一团糟,你的code也会充满bug,所以千万不要这么做。

实际上,kobject对自身实现什么功能并不感兴趣,它存在的意义在于把高级的对象链接到设备模型上。因此内核代码很少去创建一个单独的kobject对象,相反,kobject用于控制对大型域相关对象的访问(通过container_of)。

1.2 ktype

每个kobject对象都内嵌有一个ktype,该结构定义了kobject在创建和删除时所采取的行为,可以理解为对象的属性。
实际上,Kobject的生命周期管理就是由关联的ktype来实现的。
注意,每个kobject必须关联一个ktype

由于通用性,多个Kobject可能共用同一个属性操作集,因此把Ktype独立出来了。

/*include/linux/kobject.h*/
struct kobj_type {
	/* 处理对象终结的回调函数。该接口应该由具体对象负责填充。 */
	void (*release)(struct kobject *kobj);
	/* 该类型kobj的sysfs操作接口。 */
	const struct sysfs_ops *sysfs_ops;
	/* 该类型kobj自带的缺省属性(文件),这些属性文件在注册kobj时,直接pop为该目录下的文件。 */
	const struct attribute_group **default_groups;
	const struct kobj_ns_type_operations *(*child_ns_type)(struct kobject *kobj);
	/*child_ns_type/namespace 是 文件系统命名空间相关)略*/
	const void *(*namespace)(struct kobject *kobj);
	void (*get_ownership)(struct kobject *kobj, kuid_t *uid, kgid_t *gid);
};

/*include/linux/sysfs.h*/
struct sysfs_ops {
	ssize_t	(*show)(struct kobject *, struct attribute *, char *);
	ssize_t	(*store)(struct kobject *, struct attribute *, const char *, size_t);
};

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
};

kobject的引用计数为0时,通过release方法来释放相关的资源。
attribute为属性,每个属性在sysfs中都有对应的属性文件。
sysfs_op的两个方法用于实现读取和写入属性文件时应该采取的行为。

1.3 kset

kset是一些kobject的集合,这些kobject可以有相同的ktype(属性),也可以不同。
同时,kset自己也包含一个kobject;因此在sysfs中,kset也是对应这一个目录,但是目录下面包含着其他的kojbect

也有一种说法,把Kset看是一个特殊的Kobject。

每个Kobject不一定出现在sys中,但Kset中的每个Kobject会出现在sys中。

/*include/linux/kobject.h*/

/**
 * struct kset - a set of kobjects of a specific type, belonging to a specific subsystem.
 *
 * A kset defines a group of kobjects.  They can be individually
 * different "types" but overall these kobjects all want to be grouped
 * together and operated on in the same manner.  ksets are used to
 * define the attribute callbacks and other common events that happen to
 * a kobject.
 *
 * @list: the list of all kobjects for this kset
 * @list_lock: a lock for iterating over the kobjects
 * @kobj: the embedded kobject for this kset (recursion, isn't it fun...)
 * @uevent_ops: the set of uevent operations for this kset.  These are
 * called whenever a kobject has something happen to it so that the kset
 * can add new environment variables, or filter out the uevents if so
 * desired.
 */
struct kset {
	/*属于该kset的kobject链表,与kobj->entry对应,用来组织本kset管理的kobj*/
	struct list_head list;
	spinlock_t list_lock;
	/*该kset内嵌的kobj*/
	struct kobject kobj;
	/*
        kset用于发送消息的操作函数集。
        需要指出的是,kset能够发送它所包含的各种子kobj、孙kobj的消息;
        即kobj或其父辈、爷爷辈,都可以发送消息;优先父辈,然后是爷爷辈,以此类推。 
    */
	const struct kset_uevent_ops *uevent_ops;
} __randomize_layout;

kset通过标准的内核链表(struct list_head list)来管理它的所有子节点,kobject通过kset成员变量指向它的kset容器。
大多数情况下,kobject都是它所属kset(或者严格点,kset内嵌的kobject)的子节点。

2. kobject与kset的关系

下面这张图非常经典。最下面的kobj都属于一个kset,同时这些kobj的父对象就是kset内嵌的kobj
通过链表,kset可以获取所有属于它的kobj
sysfs角度而言,kset代表一个文件夹,而下面的kobj就是这个文件夹里面的内容,而内容有可能是文件也有可能是文件夹。
在这里插入图片描述

kobjkset没有严格的层级关系,也并不是完全的父子关系。如何理解这句话?

  • kobject之间可以组成一个树形结构:Kobj.parent = kobj
  • kset之间也可以组成一个树形结构,但这是基于kobject实现的:Kset.kobj.parent = Kset'.kobj

正因为kobjkset并不是完全的父子关系,因此在注册kobj时,将同时对parent及其所属的kset增加引用计数。
parentkset为同一对象,则会对kset增加两次引用计数。

kset算是kobj的“接盘侠”:

  • kobj没有所属的parent时,才让kset来接盘当parent
  • 如果连kset也没有,那该kobj属于顶层对象,其sysfs目录将位于/sys/下。

kset内部本身也包含一个kobj对象,在sysfs中也表现为目录;所不同的是,kset要承担kobj状态变动消息的发送任务。因此,首先kset会将所属的kobj组织在kset.list下,同时,通过uevent_ops在合适时候发送消息。

对于kobject_add()来说,它的输入信息是:kobj-parentkobj-namekobject_add()优先使用传入的parent作为kobj->parent;其次,使用kset作为kobj->parent

kobj状态变动后,必须依靠所关联的kset来向用户空间发送消息;若无关联kset(该kobj向上组成的树中,任何成员都无所属的kset),则kobj无法发送用户消息。

kobject 结构可能的层次结构如图:

在这里插入图片描述

3. 通过kobject创建bus目录

分析如何通过kobject/sys创建bus目录。

/*drivers/base/bus.c*/

static int bus_uevent_filter(struct kobject *kobj)
{
	const struct kobj_type *ktype = get_ktype(kobj);

	if (ktype == &bus_ktype)
		return 1;
	return 0;
}

static const struct kset_uevent_ops bus_uevent_ops = {
	.filter = bus_uevent_filter,
};

int __init buses_init(void)
{
	// 直接调用kset_create_and_add,第一个参数为要创建的目录的名字,而第三个参数指定父对象。
	bus_kset = kset_create_and_add("bus", &bus_uevent_ops, NULL);
	if (!bus_kset)
		return -ENOMEM;

	system_kset = kset_create_and_add("system", NULL, &devices_kset->kobj);
	if (!system_kset)
		return -ENOMEM;

	return 0;
}

3.1 kset_create_and_add

kset_create_and_addkobject_createkobject_add的组合

/*lib/kobject.c*/

/**
 * kset_create_and_add() - Create a struct kset dynamically and add it to sysfs.
 *
 * @name: the name for the kset
 * @uevent_ops: a struct kset_uevent_ops for the kset
 * @parent_kobj: the parent kobject of this kset, if any.
 *
 * This function creates a kset structure dynamically and registers it
 * with sysfs.  When you are finished with this structure, call
 * kset_unregister() and the structure will be dynamically freed when it
 * is no longer being used.
 *
 * If the kset was not able to be created, NULL will be returned.
 */
 /**
 * kobject_create_and_add - 动态创建一个kobject结构并注册到sysfs
 *
 * @name: kobject的名称
 * @parent: kobject的parent kobject of this kobject, 如果有的话
 *
 * 该方法动态创建一个kobject结构并注册到sysfs。当你完成该结构
 * 之后. kobject_del(),这样该结构在不再使用时将会动态的释放。
 *
 * 如果该kobject无法被创建,将会返回NULL。
 */
struct kset *kset_create_and_add(const char *name,
				 const 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到sysfs*/
	error = kset_register(kset);
	if (error) {
		kfree(kset);
		return NULL;
	}
	return kset;
}
EXPORT_SYMBOL_GPL(kset_create_and_add);


/**
 * kobject_add() - The main kobject add function.
 * @kobj: the kobject to add
 * @parent: pointer to the parent of the kobject.
 * @fmt: format to name the kobject with.
 *
 * The kobject name is set and added to the kobject hierarchy in this
 * function.
 *
 * If @parent is set, then the parent of the @kobj will be set to it.
 * If @parent is NULL, then the parent of the @kobj will be set to the
 * kobject associated with the kset assigned to this kobject.  If no kset
 * is assigned to the kobject, then the kobject will be located in the
 * root of the sysfs tree.
 *
 * Note, no "add" uevent will be created with this call, the caller should set
 * up all of the necessary sysfs files for the object and then call
 * kobject_uevent() with the UEVENT_ADD parameter to ensure that
 * userspace is properly notified of this kobject's creation.
 *
 * Return: If this function returns an error, kobject_put() must be
 *         called to properly clean up the memory associated with the
 *         object.  Under no instance should the kobject that is passed
 *         to this function be directly freed with a call to kfree(),
 *         that can leak memory.
 *
 *         If this function returns success, kobject_put() must also be called
 *         in order to properly clean up the memory associated with the object.
 *
 *         In short, once this function is called, kobject_put() MUST be called
 *         when the use of the object is finished in order to properly free
 *         everything.
 */
int kobject_add(struct kobject *kobj, struct kobject *parent,
		const char *fmt, ...)
{
	va_list args;
	int retval;

	if (!kobj)
		return -EINVAL;

	if (!kobj->state_initialized) {
		pr_err("kobject '%s' (%p): tried to add an uninitialized object, something is seriously wrong.\n",
		       kobject_name(kobj), kobj);
		dump_stack();
		return -EINVAL;
	}
	va_start(args, fmt);
	retval = kobject_add_varg(kobj, parent, fmt, args);
	va_end(args);

	return retval;
}
EXPORT_SYMBOL(kobject_add);

这里主要调用了两个函数,接下分别来看下。

3.1.1 kset_create

建立kset,设置名字与父对象。
路径:lib/kobject.c

/**
 * kset_create() - Create a struct kset dynamically.
 *
 * @name: the name for the kset
 * @uevent_ops: a struct kset_uevent_ops for the kset
 * @parent_kobj: the parent kobject of this kset, if any.
 *
 * This function creates a kset structure dynamically.  This structure can
 * then be registered with the system and show up in sysfs with a call to
 * kset_register().  When you are finished with this structure, if
 * kset_register() has been called, call kset_unregister() and the
 * structure will be dynamically freed when it is no longer being used.
 *
 * If the kset was not able to be created, NULL will be returned.
 */
static struct kset *kset_create(const char *name,
				const struct kset_uevent_ops *uevent_ops,
				struct kobject *parent_kobj)
{
	struct kset *kset;
	int retval;

	/*创建 kset 实例*/
	kset = kzalloc(sizeof(*kset), GFP_KERNEL);
	if (!kset)
		return NULL;
	/*设置kobj->name*/
	retval = kobject_set_name(&kset->kobj, "%s", name);
	if (retval) {
		kfree(kset);
		return NULL;
	}
	kset->uevent_ops = uevent_ops;
	/*设置父对象*/
	kset->kobj.parent = parent_kobj;

	/*
	 * The kobject of this kset will have a type of kset_ktype and belong to
	 * no kset itself.  That way we can properly free it when it is
	 * finished being used.
	 */
	kset->kobj.ktype = &kset_ktype;
	/*本keset不属于任何kset*/
	kset->kobj.kset = NULL;

	return kset;
}

这个函数中,动态分配了kset实例,调用kobject_set_name设置kset->kobj->namebus,也就是我们要创建的目录bus
同时这里kset->kobj.parentNULL,也就是没有父对象。

因为要创建的bus目录是在sysfs所在的根目录创建的,自然没有父对象。

随后简要看下由kobject_set_name函数调用引发的一系列调用。

/*lib/kobject.c*/
/**
 * kobject_set_name_vargs() - Set the name of a kobject.
 * @kobj: struct kobject to set the name of
 * @fmt: format string used to build the name
 * @vargs: vargs to format the string.
 */
int kobject_set_name_vargs(struct kobject *kobj, const char *fmt,
				  va_list vargs)
{
	const char *s;

	if (kobj->name && !fmt)
		return 0;

	s = kvasprintf_const(GFP_KERNEL, fmt, vargs);
	if (!s)
		return -ENOMEM;

	/*
	 * ewww... some of these buggers have '/' in the name ... If
	 * that's the case, we need to make sure we have an actual
	 * allocated copy to modify, since kvasprintf_const may have
	 * returned something from .rodata.
	 */
	if (strchr(s, '/')) {
		char *t;

		t = kstrdup(s, GFP_KERNEL);
		kfree_const(s);
		if (!t)
			return -ENOMEM;
		strreplace(t, '/', '!');
		s = t;
	}
	kfree_const(kobj->name);
	kobj->name = s;

	return 0;
}

/*lib/kasprintf.c*/
/* Simplified asprintf. */
char *kvasprintf(gfp_t gfp, const char *fmt, va_list ap)
{
	unsigned int first, second;
	char *p;
	va_list aq;

	va_copy(aq, ap);
	first = vsnprintf(NULL, 0, fmt, aq);
	va_end(aq);

	p = kmalloc_track_caller(first+1, gfp);
	if (!p)
		return NULL;

	second = vsnprintf(p, first+1, fmt, ap);
	WARN(first != second, "different return values (%u and %u) from vsnprintf(\"%s\", ...)",
	     first, second, fmt);

	return p;
}
EXPORT_SYMBOL(kvasprintf);
3.1.2 kset_register
/*lib/kobject.c*/
/**
 * kset_register() - Initialize and add a kset.
 * @k: kset.
 */
int kset_register(struct kset *k)
{
	int err;

	if (!k)
		return -EINVAL;

	/*初始化kset*/
	kset_init(k);
	 /*在sysfs中建立目录*/
	err = kobject_add_internal(&k->kobj);
	if (err)
		return err;
	/* 向用户空间发送 KOBJ_ADD事件。 */
	kobject_uevent(&k->kobj, KOBJ_ADD);
	return 0;
}
EXPORT_SYMBOL(kset_register);

这里面调用了3个函数。这里先介绍前两个函数。kobject_uevent实现的是用户空间事件传递,留到以后再说。

3.1.2.1 kset_init

该函数用于初始化kset

/*lib/kobject.c*/
/**
 * kset_init() - Initialize a kset for use.
 * @k: kset
 */
void kset_init(struct kset *k)
{
    /*初始化kobject的某些字段*/
    kobject_init_internal(&k->kobj);
    /*初始化链表头*/
    INIT_LIST_HEAD(&k->list);
    /*初始化自旋锁*/
    spin_lock_init(&k->list_lock);
}

/* 设置初始化状态 */
static void kobject_init_internal(struct kobject *kobj)
{
    if (!kobj)
        return;
    /*初始化引用基计数*/
    kref_init(&kobj->kref);
    /*初始化链表头*/
    INIT_LIST_HEAD(&kobj->entry);
    // 记录kobj是否注册到sysfs,在kobject_add_internal()中置位。
    kobj->state_in_sysfs = 0;
    // 当发送KOBJ_ADD消息时,置位。提示已经向用户空间发送ADD消息。
    kobj->state_add_uevent_sent = 0;
    // 当发送KOBJ_REMOVE消息时,置位。提示已经向用户空间发送REMOVE消息。
    kobj->state_remove_uevent_sent = 0;
    // 标记已经初始化了。
    kobj->state_initialized = 1;
}
3.1.2.2 kobject_add_internal

该函数将在sysfs中建立目录。
kobject_add_internal()会根据kobj.parentkobj.kset来综合决定父对象(相当于/sysfs中的父级目录):

  • 如果有parent则默认parent为父对象;
  • 如果没有parent作为父对象,但是有当前的kobj位于kset中,则使用kset中的kobj作为父对象
  • 如果没有parent也没有kset作为父对象,那该kobj属于顶层对象:在sysfs中,我们可以看到kobj位于/sys/下。
/*lib/kobject.c*/
static int kobject_add_internal(struct kobject *kobj)
{
    int error = 0;
    struct kobject *parent;

    if (!kobj)
        return -ENOENT;

    /*检查name字段是否存在*/
    if (!kobj->name || !kobj->name[0]) {
        return -EINVAL;
    }

    /*有父对象则增加父对象引用计数*/
    parent = kobject_get(kobj->parent);

    /* join kset if set, use it as parent if we do not already have one */
    /*
        由于在kset_create中有kset->kobj.kset = NULL,
        因此if (kobj->kset)条件不满足。
    */
    if (kobj->kset) {
        if (!parent)
            /*kobj属于某个kset,但是该kobj没有父对象,则以kset的kobj作为父对象*/
            parent = kobject_get(&kobj->kset->kobj);
        /*将kojbect添加到kset结构中的链表当中*/
        kobj_kset_join(kobj);
        /* 根据kobj.parent和kobj.kset来综合决定父对象*/
        kobj->parent = parent;
    }

    pr_debug("kobject: '%s' (%p): %s: parent: '%s', set: '%s'\n",
         kobject_name(kobj), kobj, __func__,
         parent ? kobject_name(parent) : "<NULL>",
         kobj->kset ? kobject_name(&kobj->kset->kobj) : "<NULL>");

    /*根据kobj->name在sys中建立目录*/
    error = create_dir(kobj);
    // 如果出错时,回收资源
    if (error) {
        /* 把kobj从kobj->kset的链表中去除 */
        // 对应于 kobj_kset_join
        kobj_kset_leave(kobj);
        kobject_put(parent);
        kobj->parent = NULL;

        /* be noisy on error issues */
        if (error == -EEXIST)
            WARN(1, "%s failed for %s with "
                 "-EEXIST, don't try to register things with "
                 "the same name in the same directory.\n",
                 __func__, kobject_name(kobj));
        else
            WARN(1, "%s failed for %s (error: %d parent: %s)\n",
                 __func__, kobject_name(kobj), error,
                 parent ? kobject_name(parent) : "'none'");
    } else
        kobj->state_in_sysfs = 1;

    return error;
}

因此在这个函数中,对name进行了必要的检查之后,调用了create_dirsysfs中创建目录。

create_dir执行完成以后会在sysfs的根目录(/sys/)建立文件夹bus。该函数的详细分析将在后面给出。

至此,对bus目录的建立有了简单而直观的了解。

我们可以看出kset其实就是表示一个文件夹,而kset本身也含有一个kobject,而该kobjectname字段即为该目录的名字,本例中为bus

4. kobj/kset功能特性

我们先大概提一下这些功能特性,在后面的文章中会详细说明:对象生命周期管理以及用户空间事件投递

4.1 对象生命周期管理

在创建一个kobj对象时,kobj中的引用计数管理成员kref被初始化为1;从此kobj可以使用下面的API函数来进行生命周期管理:

/*lib/kobject.c*/
/**
 * kobject_get() - Increment refcount for object.
 * @kobj: object.
 */
struct kobject *kobject_get(struct kobject *kobj)
{
	if (kobj) {
		if (!kobj->state_initialized)
			WARN(1, KERN_WARNING
				"kobject: '%s' (%p): is not initialized, yet kobject_get() is being called.\n",
			     kobject_name(kobj), kobj);
		kref_get(&kobj->kref);
	}
	return kobj;
}
EXPORT_SYMBOL(kobject_get);


/**
 * kobject_put() - Decrement refcount for object.
 * @kobj: object.
 *
 * Decrement the refcount, and if 0, call kobject_cleanup().
 */
void kobject_put(struct kobject *kobj)
{
	if (kobj) {
		if (!kobj->state_initialized)
			WARN(1, KERN_WARNING
				"kobject: '%s' (%p): is not initialized, yet kobject_put() is being called.\n",
			     kobject_name(kobj), kobj);
		kref_put(&kobj->kref, kobject_release);
	}
}
EXPORT_SYMBOL(kobject_put);

static void dynamic_kobj_release(struct kobject *kobj)
{
	pr_debug("kobject: (%p): %s\n", kobj, __func__);
	kfree(kobj);
}

对于kobject_get(),它就是直接使用kref_get()接口来对引用计数进行加1操作;

而对于kobject_put(),它不仅要使用kref_put()接口来对引用计数进行减1操作,还要对生命终结的对象执行release()操作:当引用计数减为0时,回收该对象的资源。

然而kobject是高度抽象的实体,导致kobject不会单独使用,而是嵌在具体对象中。反过来也可以这样理解:凡是需要做对象生命周期管理的对象,都可以通过内嵌kobject来实现需求。

4.1.1 kobject在kobject_put的资源回收是如何实现的?

实际上,kobject_put()通常被具体对象做一个简单包装,如:bus_put(),它直接调用kset_put(),然后调用到kobject_put()

那对于这个bus_type对象而言,仅仅通过kobject_put(),如何来达到释放整个bus_type的目的呢?这里就需要kobject另一个成员struct kobj_type * ktype来完成。

当引用计数为0时,kobject核心会调用kobject_release(),最后会调用kobj_type->release(kobj)来完成对象的释放。可是具体对象的释放,最后却通过kobj->kobj_type->release()来释放,那这个release()函数,就必须得由具体的对象来指定。

还是拿bus_type举例:

在通过bus_register(struct bus_type *bus)进行总线注册时,该API内部会执行priv->subsys.kobj.ktype = &bus_ktype操作;

// driver/base/bus.c
int bus_register(struct bus_type *bus)
{
    // ...
    
    priv->subsys.kobj.ktype = &bus_ktype;
    
    // ...
}

有了该操作,那么前面的bus_put()在执行bus_type->p-> subsys.kobj->ktype->release()时,就会执行上面注册的bus_ktype.release = bus_release函数;

// driver/base/bus.c
static struct kobj_type bus_ktype = {
    .sysfs_ops  = &bus_sysfs_ops,
    .release    = bus_release,
};


static void bus_release(struct kobject *kobj)
{
	// 获取整个 具体的 bus子系统 对象
	struct subsys_private *priv = to_subsys_private(kobj);
	struct bus_type *bus = priv->bus;

	// 释放资源
	kfree(priv);
	bus->p = NULL;
}

由于bus_release()函数由具体的bus子系统提供,它必定知道如何释放包括kobj在内的bus_type对象。

5. sysfs文件系统的层次组织

sysfs向用户空间展示了驱动设备的层次结构。这一个功能比较简单,先完全贴出来。

我们都知道设备和对应的驱动都是由内核管理的,这些对于用户空间是不可见的。现在通过sysfs,可以在用户空间直观的了解设备驱动的层次结构。

实际上,sysfs文件系统的组织功能是基于kobject实现的:该功能依靠kobjparentksetsd等成员来完成。sysfs文件系统能够以友好的界面,将kobj所刻画的对象层次、所属关系、属性值,展现在用户空间。

我们来看看sysfs的文件结构:

uos@uos-PC:~$ uname -r
4.19.0-loongson-3-desktop
uos@uos-PC:~$ ls /sys
block  bus  class  dev  devices  firmware  fs  kernel  module  power
目录意义
block块设备
bus系统中的总线
class设备类型,比如输入设备
dev系统中已注册的设备节点的视图,有两个子目录char和block。
devices系统中所有设备拓扑结构视图
fireware固件
fs文件系统
kernel内核配置选项和状态信息
module模块
power系统的电源管理数据

6. 用户空间事件投递

实际上,当具体对象有事件发生时,相应的操作函数中(如device_add()),会调用事件消息接口kobject_uevent()。在该接口中,首先会添加一些共性的消息(路径、子系统名等),然后会找寻合适的kset->uevent_ops,并调用该ops->uevent(kset, kobj, env)来添加该对象特有的事件消息(如device对象的设备号、设备名、驱动名、DT信息),一切准备完毕,就会通过两种可能的方法向用户空间发送消息:1.netlink广播;2. uevent_helper程序。

因此,上面具体对象的事件消息填充函数,应该由特定对象来填充;对于device对象来说,在初始化的时候,通过下面这行代码:

devices_kset = kset_create_and_add("devices", &device_uevent_ops, NULL);

来完成kset->uevent_ops的指定,今后所有设备注册时,调用device_register()-->device_initialize()后,都将导致:

dev->kobj.kset = devices_kset;

因此通过device_register()注册的设备,在调用kobject_uevent()接口发送事件消息时,就自动会调用devices_kset的device_uevent_ops。该opsuevent()方法定义如下:

static const struct kset_uevent_ops device_uevent_ops = {

         .filter =     dev_uevent_filter,

         .name =             dev_uevent_name,

         .uevent = dev_uevent,

};                ||

                  \/

dev_uevent(struct kset *kset, struct kobject *kobj, struct kobj_uevent_env *env)

         add_uevent_var(env, "xxxxxxx", ....)

         add_uevent_var(env, "xxxxxxx", ....)

         add_uevent_var(env, "xxxxxxx", ....)

         ....

         if (dev->bus && dev->bus->uevent)

                   dev->bus->uevent(dev, env)       //通过总线的uevent()方法,发送设备状态改变的事件消息

         if (dev->class && dev->class->dev_uevent)

                   dev->class->dev_uevent(dev, env)

         if (dev->type && dev->type->uevent)

                   dev->type->uevent(dev, env)

在该opsuevent()方法中,会分别调用busclassdevice typeuevent方法来生成事件消息。

7. kobject总结

kobject可以看成是一个基类,这个基类通过ktype属性实现了资源回收的功能,至于kset,负责记录一组kobject,我们将其看成是一个集合,负责事件管理。

从此,其他对象就可以通过包含kobject来实现上层的功能。

8. sysfs

sysfs 是一个基于内存的虚拟的文件系统,由kernel提供,挂载到 /sys 目录下(用 mount 查看得到 sysfs on /sys type sysfs (rw,nosuid,nodev,noexec,relatime)),负责以设备树的形式向 user space 提供直观的设备和驱动信息。

8.1 对象模型体系

sysfssystem filesystem)是一个处于内存中的虚拟文件系统,为我们提供了kobject对象层次结构的视图。以一个简单的文件系统的方式来观察系统中各种设备的拓扑结构。

sysfs是用于表现设备驱动模型的文件系统,它基于ramfs。要学习linux的设备驱动模型,就要先做好底层工作,总结sysfs提供给外界的API就是其中之一。

sysfs文件系统中提供了四类文件的创建与管理,分别是目录、普通文件、软链接文件、二进制文件。

  • 目录层次往往代表着设备驱动模型的结构
  • 软链接文件则代表着不同部分间的关系。比如某个设备的目录只出现在/sys/devices下,其它地方涉及到它时只好用软链接文件链接过去,保持了设备唯一的实例。
  • 普通文件和二进制文件往往代表了设备的属性,读写这些文件需要调用相应的属性读写。

sysfs是表现设备驱动模型的文件系统,它的目录层次实际反映的是对象的层次。为了配合这种目录,linux专门提供了两个结构作为sysfs的骨架,它们就是struct kobjectstruct kset

我们知道,sysfs是完全虚拟的,它的每个目录其实都对应着一个kobject,要想知道这个目录下有哪些子目录,就要用到kset。从面向对象的角度来讲,kset继承了kobject的功能,既可以表示sysfs中的一个目录,还可以包含下层目录。

dentry结构体表示目录项,通过连接kobject到指定的目录项上,无疑方便的将kobject映射到该目录上。

因此,kobjects其实已经形成了一棵树了——就是对象模型体系。

由于kobject被映射到目录项,同时对象层次结构也已经在内存中形成了一棵树,因此sysfs就诞生了。

uos@uos-PC:~$ tree /sys | head -n 300
/sys
├── block
│   └── nvme0n1 -> ../devices/pci0000:00/0000:00:13.0/0000:05:00.0/nvme/nvme0/nvme0n1
├── bus
│   ├── acpi
│   │   ├── devices
│   │   │   ├── ACPI0003:00 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:00/PNP0C09:00/ACPI0003:00
│   │   │   ├── ACPI0007:00 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/ACPI0007:00
│   │   │   ├── ACPI0007:01 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/ACPI0007:01
│   │   │   ├── ACPI0007:02 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/ACPI0007:02
│   │   │   ├── ACPI0007:03 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/ACPI0007:03
│   │   │   ├── device:00 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:00
│   │   │   ├── device:01 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:01
│   │   │   ├── device:02 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:01/device:02
│   │   │   ├── device:03 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:03
│   │   │   ├── device:04 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:03/device:04
│   │   │   ├── device:05 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:03/device:04/device:05
│   │   │   ├── device:06 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:03/device:04/device:06
│   │   │   ├── device:07 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:03/device:04/device:07
│   │   │   ├── device:08 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:08
│   │   │   ├── device:09 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:08/device:09
│   │   │   ├── device:0a -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:08/device:09/device:0a
│   │   │   ├── device:0b -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:08/device:09/device:0b
│   │   │   ├── device:0c -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:08/device:09/device:0c
│   │   │   ├── device:0d -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:0d
│   │   │   ├── device:0e -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:0d/device:0e
│   │   │   ├── device:0f -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:0d/device:0e/device:0f
│   │   │   ├── device:10 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:0d/device:0e/device:10
│   │   │   ├── device:11 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:0d/device:0e/device:11
│   │   │   ├── device:12 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:12
│   │   │   ├── device:13 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:12/device:13
│   │   │   ├── device:14 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:12/device:13/device:14
│   │   │   ├── device:15 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:12/device:13/device:15
│   │   │   ├── device:16 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:12/device:13/device:16
│   │   │   ├── device:17 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:17
│   │   │   ├── device:18 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:18
│   │   │   ├── device:19 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:19
│   │   │   ├── device:1a -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:19/device:1a
│   │   │   ├── device:1b -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:1b
│   │   │   ├── device:1c -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:1b/device:1c
│   │   │   ├── device:1d -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:1d
│   │   │   ├── device:1e -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:1d/device:1e
│   │   │   ├── IPI0001:00 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/IPI0001:00
│   │   │   ├── LEN2020:00 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:00/LEN2020:00
│   │   │   ├── LNXPOWER:00 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:00/PNP0C09:00/LNXPOWER:00
│   │   │   ├── LNXPOWER:01 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:01/LNXPOWER:01
│   │   │   ├── LNXPWRBN:00 -> ../../../devices/LNXSYSTM:00/LNXPWRBN:00
│   │   │   ├── LNXSYBUS:00 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00
│   │   │   ├── LNXSYBUS:01 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:01
│   │   │   ├── LNXSYSTM:00 -> ../../../devices/LNXSYSTM:00
│   │   │   ├── LNXTHERM:00 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:01/LNXTHERM:00
│   │   │   ├── LOON0000:00 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:00/PNP0C09:00/LOON0000:00
│   │   │   ├── LOON0001:00 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/LOON0001:00
│   │   │   ├── LOON0002:00 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/LOON0002:00
│   │   │   ├── LOON0004:00 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/LOON0004:00
│   │   │   ├── LOON0004:01 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/LOON0004:01
│   │   │   ├── LOON0004:02 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/LOON0004:02
│   │   │   ├── LOON0004:03 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/LOON0004:03
│   │   │   ├── LOON0004:04 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/LOON0004:04
│   │   │   ├── LOON0006:00 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/LOON0006:00
│   │   │   ├── LOON0006:01 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/LOON0006:01
│   │   │   ├── LOON0006:02 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/LOON0006:02
│   │   │   ├── LOON0006:03 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/LOON0006:03
│   │   │   ├── PNP0303:00 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:00/PNP0303:00
│   │   │   ├── PNP0501:00 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0501:00
│   │   │   ├── PNP0501:01 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0501:01
│   │   │   ├── PNP0501:02 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0501:02
│   │   │   ├── PNP0501:03 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0501:03
│   │   │   ├── PNP0501:04 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0501:04
│   │   │   ├── PNP0A08:00 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00
│   │   │   ├── PNP0C09:00 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:00/PNP0C09:00
│   │   │   └── PNP0C0A:00 -> ../../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:00/PNP0C09:00/PNP0C0A:00
│   │   ├── drivers
│   │   │   ├── ac
│   │   │   │   ├── ACPI0003:00 -> ../../../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:00/PNP0C09:00/ACPI0003:00
│   │   │   │   ├── bind
│   │   │   │   ├── uevent
│   │   │   │   └── unbind
│   │   │   ├── battery
│   │   │   │   ├── bind
│   │   │   │   ├── PNP0C0A:00 -> ../../../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:00/PNP0C09:00/PNP0C0A:00
│   │   │   │   ├── uevent
│   │   │   │   └── unbind
│   │   │   ├── button
│   │   │   │   ├── bind
│   │   │   │   ├── LNXPWRBN:00 -> ../../../../devices/LNXSYSTM:00/LNXPWRBN:00
│   │   │   │   ├── uevent
│   │   │   │   └── unbind
│   │   │   ├── ec
│   │   │   │   ├── bind
│   │   │   │   ├── PNP0C09:00 -> ../../../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:00/PNP0C09:00
│   │   │   │   ├── uevent
│   │   │   │   └── unbind
│   │   │   ├── loongson-laptop_EC Event
│   │   │   │   ├── bind
│   │   │   │   ├── LOON0000:00 -> ../../../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:00/PNP0C09:00/LOON0000:00
│   │   │   │   ├── uevent
│   │   │   │   └── unbind
│   │   │   ├── thermal
│   │   │   │   ├── bind
│   │   │   │   ├── LNXTHERM:00 -> ../../../../devices/LNXSYSTM:00/LNXSYBUS:01/LNXTHERM:00
│   │   │   │   ├── uevent
│   │   │   │   └── unbind
│   │   │   └── tpm_crb
│   │   │       ├── bind
│   │   │       ├── uevent
│   │   │       └── unbind
│   │   ├── drivers_autoprobe
│   │   ├── drivers_probe
│   │   └── uevent
│   ├── clockevents
│   │   ├── devices
│   │   │   ├── clockevent0 -> ../../../devices/system/clockevents/clockevent0
│   │   │   ├── clockevent1 -> ../../../devices/system/clockevents/clockevent1
│   │   │   ├── clockevent2 -> ../../../devices/system/clockevents/clockevent2
│   │   │   └── clockevent3 -> ../../../devices/system/clockevents/clockevent3
│   │   ├── drivers
│   │   ├── drivers_autoprobe
│   │   ├── drivers_probe
│   │   └── uevent
│   ├── clocksource
│   │   ├── devices
│   │   │   └── clocksource0 -> ../../../devices/system/clocksource/clocksource0
│   │   ├── drivers
│   │   ├── drivers_autoprobe
│   │   ├── drivers_probe
│   │   └── uevent
│   ├── container
│   │   ├── devices
│   │   ├── drivers
│   │   ├── drivers_autoprobe
│   │   ├── drivers_probe
│   │   └── uevent
│   ├── cpu
│   │   ├── devices
│   │   │   ├── cpu0 -> ../../../devices/system/cpu/cpu0
│   │   │   ├── cpu1 -> ../../../devices/system/cpu/cpu1
│   │   │   ├── cpu2 -> ../../../devices/system/cpu/cpu2
│   │   │   └── cpu3 -> ../../../devices/system/cpu/cpu3
│   │   ├── drivers
│   │   │   └── processor
│   │   │       ├── bind
│   │   │       ├── cpu0 -> ../../../../devices/system/cpu/cpu0
│   │   │       ├── cpu1 -> ../../../../devices/system/cpu/cpu1
│   │   │       ├── cpu2 -> ../../../../devices/system/cpu/cpu2
│   │   │       ├── cpu3 -> ../../../../devices/system/cpu/cpu3
│   │   │       ├── uevent
│   │   │       └── unbind
│   │   ├── drivers_autoprobe
│   │   ├── drivers_probe
│   │   └── uevent
│   ├── event_source
│   │   ├── devices
│   │   │   ├── cpu -> ../../../devices/cpu
│   │   │   ├── kprobe -> ../../../devices/kprobe
│   │   │   ├── software -> ../../../devices/software
│   │   │   ├── tracepoint -> ../../../devices/tracepoint
│   │   │   └── uprobe -> ../../../devices/uprobe
│   │   ├── drivers
│   │   ├── drivers_autoprobe
│   │   ├── drivers_probe
│   │   └── uevent
│   ├── genpd
│   │   ├── devices
│   │   ├── drivers
│   │   ├── drivers_autoprobe
│   │   ├── drivers_probe
│   │   └── uevent
│   ├── gpio
│   │   ├── devices
│   │   │   └── gpiochip0 -> ../../../devices/platform/LOON0002:00/gpiochip0
│   │   ├── drivers
│   │   ├── drivers_autoprobe
│   │   ├── drivers_probe
│   │   └── uevent
│   ├── hdaudio
│   │   ├── devices
│   │   │   ├── hdaudioC0D0 -> ../../../devices/pci0000:00/0000:00:06.2/hdaudioC0D0
│   │   │   ├── hdaudioC0D1 -> ../../../devices/pci0000:00/0000:00:06.2/hdaudioC0D1
│   │   │   └── hdaudioC1D0 -> ../../../devices/pci0000:00/0000:00:07.0/hdaudioC1D0
│   │   ├── drivers
│   │   │   ├── snd_hda_codec_analog
│   │   │   │   ├── bind
│   │   │   │   ├── uevent
│   │   │   │   └── unbind
│   │   │   ├── snd_hda_codec_ca0110
│   │   │   │   ├── bind
│   │   │   │   ├── uevent
│   │   │   │   └── unbind
│   │   │   ├── snd_hda_codec_ca0132
│   │   │   │   ├── bind
│   │   │   │   ├── uevent
│   │   │   │   └── unbind
│   │   │   ├── snd_hda_codec_cirrus
│   │   │   │   ├── bind
│   │   │   │   ├── uevent
│   │   │   │   └── unbind
│   │   │   ├── snd_hda_codec_cmedia
│   │   │   │   ├── bind
│   │   │   │   ├── uevent
│   │   │   │   └── unbind
│   │   │   ├── snd_hda_codec_conexant
│   │   │   │   ├── bind
│   │   │   │   ├── hdaudioC1D0 -> ../../../../devices/pci0000:00/0000:00:07.0/hdaudioC1D0
│   │   │   │   ├── uevent
│   │   │   │   └── unbind
│   │   │   ├── snd_hda_codec_generic
│   │   │   │   ├── bind
│   │   │   │   ├── uevent
│   │   │   │   └── unbind
│   │   │   ├── snd_hda_codec_hdmi
│   │   │   │   ├── bind
│   │   │   │   ├── hdaudioC0D0 -> ../../../../devices/pci0000:00/0000:00:06.2/hdaudioC0D0
│   │   │   │   ├── hdaudioC0D1 -> ../../../../devices/pci0000:00/0000:00:06.2/hdaudioC0D1
│   │   │   │   ├── uevent
│   │   │   │   └── unbind
│   │   │   ├── snd_hda_codec_idt
│   │   │   │   ├── bind
│   │   │   │   ├── uevent
│   │   │   │   └── unbind
│   │   │   ├── snd_hda_codec_realtek
│   │   │   │   ├── bind
│   │   │   │   ├── uevent
│   │   │   │   └── unbind
│   │   │   ├── snd_hda_codec_si3054
│   │   │   │   ├── bind
│   │   │   │   ├── uevent
│   │   │   │   └── unbind
│   │   │   └── snd_hda_codec_via
│   │   │       ├── bind
│   │   │       ├── uevent
│   │   │       └── unbind
│   │   ├── drivers_autoprobe
│   │   ├── drivers_probe
│   │   └── uevent
│   ├── hid
│   │   ├── devices
│   │   │   └── 0003:1C4F:0048.0001 -> ../../../devices/pci0000:00/0000:00:05.0/usb6/6-1/6-1:1.0/0003:1C4F:0048.0001
│   │   ├── drivers
│   │   │   └── hid-generic
│   │   │       ├── 0003:1C4F:0048.0001 -> ../../../../devices/pci0000:00/0000:00:05.0/usb6/6-1/6-1:1.0/0003:1C4F:0048.0001
│   │   │       ├── bind
│   │   │       ├── module -> ../../../../module/hid_generic
│   │   │       ├── new_id
│   │   │       ├── uevent
│   │   │       └── unbind
│   │   ├── drivers_autoprobe
│   │   ├── drivers_probe
│   │   └── uevent
│   ├── i2c
│   │   ├── devices
│   │   │   ├── 0-0050 -> ../../../devices/pci0000:00/0000:00:06.1/i2c-0/0-0050
│   │   │   ├── 6-0050 -> ../../../devices/pci0000:00/0000:00:06.1/i2c-6/6-0050
│   │   │   ├── i2c-0 -> ../../../devices/pci0000:00/0000:00:06.1/i2c-0
│   │   │   ├── i2c-1 -> ../../../devices/platform/LOON0004:00/i2c-1
│   │   │   ├── i2c-2 -> ../../../devices/platform/LOON0004:01/i2c-2
│   │   │   ├── i2c-3 -> ../../../devices/platform/LOON0004:02/i2c-3
│   │   │   ├── i2c-4 -> ../../../devices/platform/LOON0004:03/i2c-4
│   │   │   ├── i2c-5 -> ../../../devices/platform/LOON0004:04/i2c-5
│   │   │   └── i2c-6 -> ../../../devices/pci0000:00/0000:00:06.1/i2c-6
│   │   ├── drivers
│   │   │   ├── dummy
│   │   │   │   ├── bind
│   │   │   │   ├── uevent
│   │   │   │   └── unbind
│   │   │   ├── ipmi_ssif
│   │   │   │   ├── bind
│   │   │   │   ├── module -> ../../../../module/ipmi_ssif
│   │   │   │   ├── uevent
│   │   │   │   └── unbind
│   │   │   └── pca953x
│   │   │       ├── bind
│   │   │       ├── uevent
│   │   │       └── unbind
│   │   ├── drivers_autoprobe
│   │   ├── drivers_probe
│   │   └── uevent
│   ├── isa
│   │   ├── devices
│   │   ├── drivers
│   │   ├── drivers_autoprobe
│   │   ├── drivers_probe
│   │   └── uevent
│   ├── mdio_bus
│   │   ├── devices
│   │   ├── drivers
│   │   │   ├── Generic 10G PHY
│   │   │   │   ├── bind
│   │   │   │   ├── uevent
│   │   │   │   └── unbind
│   │   │   └── Generic PHY
│   │   │       ├── bind
│   │   │       ├── uevent
│   │   │       └── unbind
│   │   ├── drivers_autoprobe
│   │   ├── drivers_probe
│   │   └── uevent
│   ├── memory
│   │   ├── devices
│   │   │   ├── memory0 -> ../../../devices/system/memory/memory0

sysfs的根目录下包含了七个目录:blockbusclassdevicesfirmwaremodulepower

  • block目录下的每个子目录都对应着系统的一个块设备。
  • bus目录提供了一个系统总线视图。
  • class目录包含了以高层功能逻辑组织起来的系统设备视图。
  • devices目录是系统中设备拓扑结构视图,它直接映射除了内核中设备结构体的组织层次。
  • firmware目录包含了一些诸如ACPI、EDD、EFI等低层子系统的特殊树。
  • power目录包含了系统范围的电源管理数据。
  • 最重要的目录是devices,该目录将设备模型导出到用户空间。目录结构就是系统中实际的设备拓扑。

    kernel 包括了一些内核的可调参数等信息,和devices目录关联性没那么强。

注意,其他目录中的很多数据都是将devices目录下的数据加以转换加工而得;

因此,在class章节中,我们会看到一些软链接的创建,其实就是为了优雅地归对设备进行归类。

8.2 sysfs中添加和删除kobject

8.2.1 导入kobject到sysfs

仅仅初始化kobjcet是不能自动将其导出到sysfs中的,想要把kobject导入sysfs,需要用到函数kobject_add()

需要注意的是,并不是说每一个kobject对象都需要在sysfs中表示,但是每一个被注册到系统中的kset都会被添加到sysfs文件系统中。
一个kset对象就对应一个/sys中的一个目录,kset中的每一个kobject成员,都对应sysfs中一个文件或者一个目录。

#include <linux/kobject.h> 
#if 0 // 下面两种操作等价
void kobject_init(struct kobject *kobj, struct kobj_type *ktype);
int kobject_add(struct kobject *kobj);
#else
struct kobject *kobject_create_and_add(const char *name, struct kobject *parent);
// 再也没有 kobject_register 这个函数了
#endif

kobjectsysfs中的位置取决于kobject在对象层次结构中的位置。如果kobject的父指针被设置,那么在sysfs中的kobject将被映射为其父目录下的子目录。如果parent没有设置,那么kobject将被映射为kset->kobj中的子目录。

如果给定的kobjectparentkset字段都没有设置,那么就认为kobject没有父对象,所以就会被映射成sysfs下的根级目录。但这往往不是你所需要的,所以在调用kobject_add()parentkset字段应该显示的设置。

不管怎样,sysfs中代表kobject的目录名字是由kobj->k_name指定的。

不过不需要调用kobject_init()kobject_add(),因为系统提供了函数kobject_create_and_add()

该函数既初始化了给定的kobject对象,同时又将其加入到对象层次结构中。

8.2.1.1 kobj/kset的引用计数示例

下图展示了一个顶层kobj/kset,通过不断的添加子节点,导致的引用计数变化情况。
在这里插入图片描述
首先在(I)中建立了顶层对象A,其kref初始化为1;在第(II)步中,建立了A的子对象B,此时A对象的kref引用计数加1变为2;在第(III)步中,继续在A下建立子对象C,此时A对象的kref引用计数加1变为3;在第(IV)步中,建立B对象的子对象D,此时,只会对D的父对象进行引用计数加1;而对更上层的父对象,则不进行引用加1操作。

8.2.1.2 platform_bus_type的注册示例

内核启动时,会在初始化阶段进行platform_bus_type总线的注册:bus_register(&platform_bus_type)。在该函数里,会设置platform_bus_type->p->subsys.kobj.kset = bus_ksetplatform_bus_type->p->subsys.kobj.ktype = &bus_ktype,因此按照前面的总结,由于platform_bus_typekset、无parent,导致该bus注册进系统后,其kset引用计数将被增加两次,同时,在bus_kset对应的目录(/sys/bus/)下建立platform_bus_type对应的子目录(/sys/bus/platform/)。同理,当该总线的引用计数为0时,将导致该对象生命的终结,会触发release()操作,显然该操作将导致bus_kset的引用计数减少两次。

8.2.2 从sysfs删除kobject
/*lib/kobject.c*/
/**
 * kobject_del() - Unlink kobject from hierarchy.
 * @kobj: object.
 *
 * This is the function that should be called to delete an object
 * successfully added via kobject_add().
 */
void kobject_del(struct kobject *kobj)
{
	struct kobject *parent;

	if (!kobj)
		return;

	parent = kobj->parent;
	__kobject_del(kobj);
	kobject_put(parent);
}
EXPORT_SYMBOL(kobject_del);

sysfs中删除一个kobject对应文件目录。需使用函数kobject_del(),内部会自动使用put来让引用减一。

以前的版本需要再调用一次kobject_put;但现在不需要了。

8.2.3 向sysfs中添加文件

kobject已经被映射为文件目录。所有的对象层次一个不少的映射成sys下的目标结构。

sysfs仅仅是一个漂亮的树,但是没有提供实际数据的文件。

因此,需要kobj_type来进行支持,有关sysfs的成员如下:

/*include/linux/kobject.h*/
struct kobj_type {
	void (*release)(struct kobject *kobj);
	/* 该类型kobj的sysfs操作接口。 */
	const struct sysfs_ops *sysfs_ops;
	/* 该类型kobj自带的缺省属性(文件),这些属性文件在注册kobj时,直接pop为该目录下的文件。 */
	const struct attribute_group **default_groups;
	const struct kobj_ns_type_operations *(*child_ns_type)(struct kobject *kobj);
	const void *(*namespace)(struct kobject *kobj);
	void (*get_ownership)(struct kobject *kobj, kuid_t *uid, kgid_t *gid);
};
8.2.3.1 默认属性attribute

默认的文件集合是通过Kobjectksetktype字段提供的。因此所有具有相同类型的kobject在它们对应的sysfs目录下都拥有相同的默认文件集合。

kobj_type字段含有一个字段——default_attrs,它是一个attribute结构体数组。这些属性负责将内核数据映射成sysfs中的文件。

8.2.3.1.1 attribute原型
/*include/linux/sysfs.h*/

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
};


/**
 * struct attribute_group - data structure used to declare an attribute group.
 * @name:	Optional: Attribute group name
 *		If specified, the attribute group will be created in
 *		a new subdirectory with this name.
 * @is_visible:	Optional: Function to return permissions associated with an
 *		attribute of the group. Will be called repeatedly for each
 *		non-binary attribute in the group. Only read/write
 *		permissions as well as SYSFS_PREALLOC are accepted. Must
 *		return 0 if an attribute is not visible. The returned value
 *		will replace static permissions defined in struct attribute.
 * @is_bin_visible:
 *		Optional: Function to return permissions associated with a
 *		binary attribute of the group. Will be called repeatedly
 *		for each binary attribute in the group. Only read/write
 *		permissions as well as SYSFS_PREALLOC are accepted. Must
 *		return 0 if a binary attribute is not visible. The returned
 *		value will replace static permissions defined in
 *		struct bin_attribute.
 * @attrs:	Pointer to NULL terminated list of attributes.
 * @bin_attrs:	Pointer to NULL terminated list of binary attributes.
 *		Either attrs or bin_attrs or both must be provided.
 */
struct attribute_group {
	/* 属性名称 */
	const char		*name;
	umode_t			(*is_visible)(struct kobject *,
					      struct attribute *, int);
	umode_t			(*is_bin_visible)(struct kobject *,
						  struct bin_attribute *, int);
	struct attribute	**attrs;
	struct bin_attribute	**bin_attrs;
};

其中,name:提供了该属性的名称,最终出现在sysfs中的文件名就是它。
mode字段类型为mode_t,它表示了sysfs中该文件的权限。

  • 对于只读属性而言,如果是所有人都可读它,那么该字段设为S_IRUGO;
  • 如果只限于所有者可读,则该字段设置为S_IRUSR
  • 同样对于可写属性,可能会设置该字段为S_IRUGO|S_IWUSR
  • sysfs中的所有文件和目录的uidgid标志均为零。
8.2.3.1.2 如何理解属性?
uos@uos-PC:~$ ls /sys/class/leds/input0::capslock/
brightness  device  max_brightness  power  subsystem  trigger  uevent

其中,brightnesstrigger这些文件就是属性,以文件的形式提供。

8.2.3.1.3 属性读写方法sysfs_ops

虽然default_attrs列出了默认的属性,sysfs_ops则描述了如何使用它们。

#include <linux/sysfs.h>
struct sysfs_ops {
    /* 读取该 sysfs 文件时该方法被调用 */
    ssize_t (*show)(struct kobject *, struct attribute *, char *);
    /* 写入该 sysfs 文件时该方法被调用 */
    ssize_t (*store)(struct kobject *, struct attribute *, const char *, size_t);
};

struct sysfs_ops中包含showstore两个函数指针,它们分别在sysfs文件读和文件写时调用。

8.2.3.1.4 创建/删除新属性

一些特别情况下会碰到特殊的kobject实例,它希望(甚至必须)有自己的属性——也许是通用属性没包含那些需要的数据或者函数。

因此使用sysfs_create_file()接口在默认集合上添加新属性:

#include <linux/sysfs.h>
int sysfs_create_file(struct kobject *kobj,
                      const struct attribute *attr);
int sysfs_create_files(struct kobject *kobj,
                       const struct attribute **attr);

void sysfs_remove_file(struct kobject *kobj,
                       const struct attribute *attr)

这个接口通过attr参数指向相应的attribute结构体,而参数kobj则指定了属性所在的kobject对象。

在该函数被调用之前,给定的属性将被赋值,如果成功,该函数返回零。否则返回负的错误码。

kobjectktype.sysfs_ops操作将负责处理新属性。现有的show()store()方法必须能够处理新属性。

删除一个属性通过函数sysfs_remove_file()完成:一旦调用sysfs_remove_file,给定的属性将不复存在于给定的kobject目录中。

8.2.3.1.5 创建/删除软链接

除了添加文件外,可能还要创建符号连接。在sysfs中创建一个符号连接方式。

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);

该函数创建的符号连接名由name指定,连接则由kobj对应的目录映射到target指定的目录。如果成功,则返回零,如果失败,返回负的错误码。

而由sysfs_create_link()创建的符号连接可通过函数sysfs_remove_link()删除:

void sysfs_remove_link(struct kobject *kobj, const char *name);
// 一旦调用返回,给定的连接将不复存在于给定的kobject目录中。
8.2.3.2 sysfs约定

首先sysfs属性应该保证每个属性文件只导出一个值,该值应该是文本形式而且被映射为简单C类型。其次,在sysfs中要以一个清晰的层次组织数据。最后,记住sysfs提供内核到用户空间的服务,这多少有些用户空间的ABI(应用程序二进制接口)的作用。

9. 附录:kobject创建

9.1 简单创建kobj以及添加

#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/kobject.h>

struct kobject *g_obj = NULL;
struct kobject *g_parobj = NULL;

static int __init  test_init(void)
{    
    g_parobj  = kobject_create_and_add("dog",NULL);
    if(NULL == g_parobj)
    {
        printk("create kobj error\n");
        return -1;
    }

    g_obj  = kobject_create_and_add("whitedog",g_parobj);
    if(NULL == g_obj)
    {
        if(g_parobj)
        {     
            kobject_del(g_parobj); 
            kobject_put(g_parobj);
        }
    }
    return 0;
}

static void __exit test_exit(void)
{
    if(g_obj)
    {
        kobject_del(g_obj); 
        kobject_put(g_obj);
    }
    if(g_parobj)
    {
        kobject_del(g_parobj); 
        kobject_put(g_parobj);
    }

    return;
}

module_init(test_init);
module_exit(test_exit); 
MODULE_LICENSE("GPL");

9.2 内核kobj例子

代码来自:samples/kobject/kobject-example.c

可能会遇到error: negative width in bit-field ‘’这个错误,需要将__ATTR中的666权限改为664。

// SPDX-License-Identifier: GPL-2.0
/*
 * Sample kobject implementation
 *
 * Copyright (C) 2004-2007 Greg Kroah-Hartman <greg@kroah.com>
 * Copyright (C) 2007 Novell Inc.
 */
#include <linux/kobject.h>
#include <linux/string.h>
#include <linux/sysfs.h>
#include <linux/module.h>
#include <linux/init.h>

/*
 * This module shows how to create a simple subdirectory in sysfs called
 * /sys/kernel/kobject-example  In that directory, 3 files are created:
 * "foo", "baz", and "bar".  If an integer is written to these files, it can be
 * later read out of it.
 */

static int foo;
static int baz;
static int bar;

/*
 * The "foo" file where a static variable is read from and written to.
 */
static ssize_t foo_show(struct kobject *kobj, struct kobj_attribute *attr,
			char *buf)
{
	return sysfs_emit(buf, "%d\n", foo);
}

static ssize_t foo_store(struct kobject *kobj, struct kobj_attribute *attr,
			 const char *buf, size_t count)
{
	int ret;

	ret = kstrtoint(buf, 10, &foo);
	if (ret < 0)
		return ret;

	return count;
}

/* Sysfs attributes cannot be world-writable. */
static struct kobj_attribute foo_attribute =
	__ATTR(foo, 0664, foo_show, foo_store);

/*
 * More complex function where we determine which variable is being accessed by
 * looking at the attribute for the "baz" and "bar" files.
 */
static ssize_t b_show(struct kobject *kobj, struct kobj_attribute *attr,
		      char *buf)
{
	int var;

	if (strcmp(attr->attr.name, "baz") == 0)
		var = baz;
	else
		var = bar;
	return sysfs_emit(buf, "%d\n", var);
}

static ssize_t b_store(struct kobject *kobj, struct kobj_attribute *attr,
		       const char *buf, size_t count)
{
	int var, ret;

	ret = kstrtoint(buf, 10, &var);
	if (ret < 0)
		return ret;

	if (strcmp(attr->attr.name, "baz") == 0)
		baz = var;
	else
		bar = var;
	return count;
}

static struct kobj_attribute baz_attribute =
	__ATTR(baz, 0664, b_show, b_store);
static struct kobj_attribute bar_attribute =
	__ATTR(bar, 0664, b_show, b_store);


/*
 * Create a group of attributes so that we can create and destroy them all
 * at once.
 */
static struct attribute *attrs[] = {
	&foo_attribute.attr,
	&baz_attribute.attr,
	&bar_attribute.attr,
	NULL,	/* need to NULL terminate the list of attributes */
};

/*
 * An unnamed attribute group will put all of the attributes directly in
 * the kobject directory.  If we specify a name, a subdirectory will be
 * created for the attributes with the directory being the name of the
 * attribute group.
 */
static struct attribute_group attr_group = {
	.attrs = attrs,
};

static struct kobject *example_kobj;

static int __init example_init(void)
{
	int retval;

	/*
	 * Create a simple kobject with the name of "kobject_example",
	 * located under /sys/kernel/
	 *
	 * As this is a simple directory, no uevent will be sent to
	 * userspace.  That is why this function should not be used for
	 * any type of dynamic kobjects, where the name and number are
	 * not known ahead of time.
	 */
	example_kobj = kobject_create_and_add("kobject_example", kernel_kobj);
	if (!example_kobj)
		return -ENOMEM;

	/* Create the files associated with this kobject */
	retval = sysfs_create_group(example_kobj, &attr_group);
	if (retval)
		kobject_put(example_kobj);

	return retval;
}

static void __exit example_exit(void)
{
	kobject_put(example_kobj);
}

module_init(example_init);
module_exit(example_exit);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Greg Kroah-Hartman <greg@kroah.com>");

9.2.1 对应的Makefile
EXTRA_CFLAGS += $(DEBFLAGS)
#EXTRA_CFLAGS += -I$(INCDIR)

########## change your module name here
MODULE   = myKobj

########## change your obj file(s) here
$(MODULE)-objs:= kobject-example.o
#CROSS_COMPILE ?= arm-linux-gnueabihf-
#ARCH 		  ?= arm

ifneq ($(KERNELRELEASE), )
	obj-m := $(MODULE).o

else
	KERNELDIR ?= /lib/modules/$(shell uname -r)/build
	PWD := $(shell pwd)

all:
	$(MAKE_BEGIN)
	@echo
	@if \
	$(MAKE) INCDIR=$(PWD)/configs -C $(KERNELDIR) M=$(PWD) modules; \
	then $(MAKE_DONE);\
	else \
	$(MAKE_ERR);\
	exit 1; \
	fi

endif

show:
	@echo "ARCH     :    ${ARCH}"
	@echo "CC       :    ${CROSS_COMPILE}gcc"
	@echo "KDIR     :    ${KERNELDIR}"
	@echo "$(MODULE):    $(ALLOBJS)"
clean:
	$(CLEAN_BEGIN)
	rm -rf *.cmd *.o *.ko *.mod.c *.symvers *.order *.markers .tmp_versions .*.cmd *~ .*.d
	$(CLEAN_END)

.PHONY:all clean show
#xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
### nothing
#OFFSET=\e[21G    # 21 col
COLOR1=\e[32m  # all --> bule
COLOR2=\e[33m  # clean --> brown
COLOR3=\e[31m  # error --> red
RESET=\e[0m

CLEAN_BEGIN=@echo -e "$(OFFSET)$(COLOR2)Cleaning up...$(RESET)"
CLEAN_END=@echo -e "$(OFFSET)$(COLOR2)Cleaned.$(RESET)"

MAKE_BEGIN=@echo -ne "$(OFFSET)$(COLOR1)Compiling...$(RESET)"
### I do not forget "@", but it DOES NOT need "@"
MAKE_DONE=echo -e "$(OFFSET)$(COLOR1)Compilied.$(RESET)"
MAKE_ERR=echo -e "$(OFFSET)$(COLOR3)[Oops! Error occurred]$(RESET)"
9.2.2 测试

编译以后,加载模块:

sudo insmod  myKobj.ko

发现创建了/sys/kernel/kobject_example/目录,而且目录中有3个属性文件。

uos@uos-PC:$ ls /sys/kernel/kobject_example/
bar  baz  foo

读写这些属性

root@uos-PC:/home/uos/Desktop/samples/kobject# cd
root@uos-PC:~# 
root@uos-PC:~# ls /sys/kernel/kobject_example/
bar  baz  foo
root@uos-PC:~# cat /sys/kernel/kobject_example/bar
0
root@uos-PC:~# cat /sys/kernel/kobject_example/baz
0
root@uos-PC:~# cat /sys/kernel/kobject_example/foo
0
root@uos-PC:~# echo "2021" > /sys/kernel/kobject_example/foo
root@uos-PC:~# cat /sys/kernel/kobject_example/foo
2021
root@uos-PC:~# 

9.3 内核kset例子

代码来自:samples/kobject/kset-example.c

可能会遇到error: negative width in bit-field ‘’这个错误,需要将__ATTR中的666权限改为664。

// SPDX-License-Identifier: GPL-2.0
/*
 * Sample kset and ktype implementation
 *
 * Copyright (C) 2004-2007 Greg Kroah-Hartman <greg@kroah.com>
 * Copyright (C) 2007 Novell Inc.
 */
#include <linux/kobject.h>
#include <linux/string.h>
#include <linux/sysfs.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/init.h>

/*
 * This module shows how to create a kset in sysfs called
 * /sys/kernel/kset-example
 * Then tree kobjects are created and assigned to this kset, "foo", "baz",
 * and "bar".  In those kobjects, attributes of the same name are also
 * created and if an integer is written to these files, it can be later
 * read out of it.
 */


/*
 * This is our "object" that we will create a few of and register them with
 * sysfs.
 */
struct foo_obj {
	struct kobject kobj;
	int foo;
	int baz;
	int bar;
};
#define to_foo_obj(x) container_of(x, struct foo_obj, kobj)

/* a custom attribute that works just for a struct foo_obj. */
struct foo_attribute {
	struct attribute attr;
	ssize_t (*show)(struct foo_obj *foo, struct foo_attribute *attr, char *buf);
	ssize_t (*store)(struct foo_obj *foo, struct foo_attribute *attr, const char *buf, size_t count);
};
#define to_foo_attr(x) container_of(x, struct foo_attribute, attr)

/*
 * The default show function that must be passed to sysfs.  This will be
 * called by sysfs for whenever a show function is called by the user on a
 * sysfs file associated with the kobjects we have registered.  We need to
 * transpose back from a "default" kobject to our custom struct foo_obj and
 * then call the show function for that specific object.
 */
static ssize_t foo_attr_show(struct kobject *kobj,
			     struct attribute *attr,
			     char *buf)
{
	struct foo_attribute *attribute;
	struct foo_obj *foo;

	attribute = to_foo_attr(attr);
	foo = to_foo_obj(kobj);

	if (!attribute->show)
		return -EIO;

	return attribute->show(foo, attribute, buf);
}

/*
 * Just like the default show function above, but this one is for when the
 * sysfs "store" is requested (when a value is written to a file.)
 */
static ssize_t foo_attr_store(struct kobject *kobj,
			      struct attribute *attr,
			      const char *buf, size_t len)
{
	struct foo_attribute *attribute;
	struct foo_obj *foo;

	attribute = to_foo_attr(attr);
	foo = to_foo_obj(kobj);

	if (!attribute->store)
		return -EIO;

	return attribute->store(foo, attribute, buf, len);
}

/* Our custom sysfs_ops that we will associate with our ktype later on */
static const struct sysfs_ops foo_sysfs_ops = {
	.show = foo_attr_show,
	.store = foo_attr_store,
};

/*
 * The release function for our object.  This is REQUIRED by the kernel to
 * have.  We free the memory held in our object here.
 *
 * NEVER try to get away with just a "blank" release function to try to be
 * smarter than the kernel.  Turns out, no one ever is...
 */
static void foo_release(struct kobject *kobj)
{
	struct foo_obj *foo;

	foo = to_foo_obj(kobj);
	kfree(foo);
}

/*
 * The "foo" file where the .foo variable is read from and written to.
 */
static ssize_t foo_show(struct foo_obj *foo_obj, struct foo_attribute *attr,
			char *buf)
{
	return sysfs_emit(buf, "%d\n", foo_obj->foo);
}

static ssize_t foo_store(struct foo_obj *foo_obj, struct foo_attribute *attr,
			 const char *buf, size_t count)
{
	int ret;

	ret = kstrtoint(buf, 10, &foo_obj->foo);
	if (ret < 0)
		return ret;

	return count;
}

/* Sysfs attributes cannot be world-writable. */
static struct foo_attribute foo_attribute =
	__ATTR(foo, 0664, foo_show, foo_store);

/*
 * More complex function where we determine which variable is being accessed by
 * looking at the attribute for the "baz" and "bar" files.
 */
static ssize_t b_show(struct foo_obj *foo_obj, struct foo_attribute *attr,
		      char *buf)
{
	int var;

	if (strcmp(attr->attr.name, "baz") == 0)
		var = foo_obj->baz;
	else
		var = foo_obj->bar;
	return sysfs_emit(buf, "%d\n", var);
}

static ssize_t b_store(struct foo_obj *foo_obj, struct foo_attribute *attr,
		       const char *buf, size_t count)
{
	int var, ret;

	ret = kstrtoint(buf, 10, &var);
	if (ret < 0)
		return ret;

	if (strcmp(attr->attr.name, "baz") == 0)
		foo_obj->baz = var;
	else
		foo_obj->bar = var;
	return count;
}

static struct foo_attribute baz_attribute =
	__ATTR(baz, 0664, b_show, b_store);
static struct foo_attribute bar_attribute =
	__ATTR(bar, 0664, b_show, b_store);

/*
 * Create a group of attributes so that we can create and destroy them all
 * at once.
 */
static struct attribute *foo_default_attrs[] = {
	&foo_attribute.attr,
	&baz_attribute.attr,
	&bar_attribute.attr,
	NULL,	/* need to NULL terminate the list of attributes */
};
ATTRIBUTE_GROUPS(foo_default);

/*
 * Our own ktype for our kobjects.  Here we specify our sysfs ops, the
 * release function, and the set of default attributes we want created
 * whenever a kobject of this type is registered with the kernel.
 */
static struct kobj_type foo_ktype = {
	.sysfs_ops = &foo_sysfs_ops,
	.release = foo_release,
	.default_groups = foo_default_groups,
};

static struct kset *example_kset;
static struct foo_obj *foo_obj;
static struct foo_obj *bar_obj;
static struct foo_obj *baz_obj;

static struct foo_obj *create_foo_obj(const char *name)
{
	struct foo_obj *foo;
	int retval;

	/* allocate the memory for the whole object */
	foo = kzalloc(sizeof(*foo), GFP_KERNEL);
	if (!foo)
		return NULL;

	/*
	 * As we have a kset for this kobject, we need to set it before calling
	 * the kobject core.
	 */
	foo->kobj.kset = example_kset;

	/*
	 * Initialize and add the kobject to the kernel.  All the default files
	 * will be created here.  As we have already specified a kset for this
	 * kobject, we don't have to set a parent for the kobject, the kobject
	 * will be placed beneath that kset automatically.
	 */
	retval = kobject_init_and_add(&foo->kobj, &foo_ktype, NULL, "%s", name);
	if (retval) {
		kobject_put(&foo->kobj);
		return NULL;
	}

	/*
	 * We are always responsible for sending the uevent that the kobject
	 * was added to the system.
	 */
	kobject_uevent(&foo->kobj, KOBJ_ADD);

	return foo;
}

static void destroy_foo_obj(struct foo_obj *foo)
{
	kobject_put(&foo->kobj);
}

static int __init example_init(void)
{
	/*
	 * Create a kset with the name of "kset_example",
	 * located under /sys/kernel/
	 */
	example_kset = kset_create_and_add("kset_example", NULL, kernel_kobj);
	if (!example_kset)
		return -ENOMEM;

	/*
	 * Create three objects and register them with our kset
	 */
	foo_obj = create_foo_obj("foo");
	if (!foo_obj)
		goto foo_error;

	bar_obj = create_foo_obj("bar");
	if (!bar_obj)
		goto bar_error;

	baz_obj = create_foo_obj("baz");
	if (!baz_obj)
		goto baz_error;

	return 0;

baz_error:
	destroy_foo_obj(bar_obj);
bar_error:
	destroy_foo_obj(foo_obj);
foo_error:
	kset_unregister(example_kset);
	return -EINVAL;
}

static void __exit example_exit(void)
{
	destroy_foo_obj(baz_obj);
	destroy_foo_obj(bar_obj);
	destroy_foo_obj(foo_obj);
	kset_unregister(example_kset);
}

module_init(example_init);
module_exit(example_exit);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Greg Kroah-Hartman <greg@kroah.com>");

9.3.1 对应的Makefile
EXTRA_CFLAGS += $(DEBFLAGS)
#EXTRA_CFLAGS += -I$(INCDIR)

########## change your module name here
MODULE   = myKset

########## change your obj file(s) here
#$(MODULE)-objs:= kobject-example.o
$(MODULE)-objs:= kset-example.o
#CROSS_COMPILE ?= arm-linux-gnueabihf-
#ARCH 		  ?= arm

ifneq ($(KERNELRELEASE), )
	obj-m := $(MODULE).o

else
	KERNELDIR ?= /lib/modules/$(shell uname -r)/build
	PWD := $(shell pwd)

all:
	$(MAKE_BEGIN)
	@echo
	@if \
	$(MAKE) INCDIR=$(PWD)/configs -C $(KERNELDIR) M=$(PWD) modules; \
	then $(MAKE_DONE);\
	else \
	$(MAKE_ERR);\
	exit 1; \
	fi

endif

show:
	@echo "ARCH     :    ${ARCH}"
	@echo "CC       :    ${CROSS_COMPILE}gcc"
	@echo "KDIR     :    ${KERNELDIR}"
	@echo "$(MODULE):    $(ALLOBJS)"
clean:
	$(CLEAN_BEGIN)
	rm -rf *.cmd *.o *.ko *.mod.c *.symvers *.order *.markers .tmp_versions .*.cmd *~ .*.d
	$(CLEAN_END)

.PHONY:all clean show
#xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
### nothing
#OFFSET=\e[21G    # 21 col
COLOR1=\e[32m  # all --> bule
COLOR2=\e[33m  # clean --> brown
COLOR3=\e[31m  # error --> red
RESET=\e[0m

CLEAN_BEGIN=@echo -e "$(OFFSET)$(COLOR2)Cleaning up...$(RESET)"
CLEAN_END=@echo -e "$(OFFSET)$(COLOR2)Cleaned.$(RESET)"

MAKE_BEGIN=@echo -ne "$(OFFSET)$(COLOR1)Compiling...$(RESET)"
### I do not forget "@", but it DOES NOT need "@"
MAKE_DONE=echo -e "$(OFFSET)$(COLOR1)Compilied.$(RESET)"
MAKE_ERR=echo -e "$(OFFSET)$(COLOR3)[Oops! Error occurred]$(RESET)"
9.3.2 测试

编译以后,加载模块:

sudo insmod  myKset.ko

发现创建了/sys/kernel/kset_example/目录,而且目录中有3个子目录,每一个子目录中都有3个属性文件。

root@uos-PC:~# tree /sys/kernel/kset_example/
/sys/kernel/kset_example/
├── bar
│   ├── bar
│   ├── baz
│   └── foo
├── baz
│   ├── bar
│   ├── baz
│   └── foo
└── foo
    ├── bar
    ├── baz
    └── foo

3 directories, 9 files

10. 附录:一部分关于kobject的API

10.1 动态创建

struct kobject *kobject_create(void);

动态创建一个kobject结构,并将其设置为一个带默认释放方法(dynamic_kobj_ktype)的动态的kobject

如果无法创建kobject,将会返回NULL。从这里返回的kobject 结构释放必须调用kobject_put() 方法而不是kfree(),因为kobject_init()已经被调用过了。

10.2 初始化

创建kobject当然必须初始化kobject对象,kobject的一些内部成员需要(强制)通过kobject_init()初始化:

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

该方法会正确的初始化一个kobject来保证它可以被传递给kobject_add()

该功能被调用后,kobject必须通过调用kobject_put()来清理,而不是直接调用kfree,来保证所有的内存都可以被正确的清理。

辅助函数kobject_init_and_add用来在同时初始化和添加kobject,它的参数和前面介绍的单独使用kobject_init()kobject_add()这两个函数时一样

int kobject_init_and_add(struct kobject *kobj, struct kobj_type *ktype,
                         struct kobject *parent, const char *fmt, ...);

10.3 添加到sysfs

在调用kobject_initkobject注册到sysfs之后,必须调用kobject_add()添加:

/**
 * kobject_add - kobject添加主方法
 * @kobj: 要添加的kobject
 * @parent: 指向父kobject的指针
 * @fmt: kobject带的名称格式
 *
 * 本方法中会设置kobject名称并添加到kobject阶层。
 *
 * 如果设置了@parent, 那么@kobj的parent将会被设置为它.
 * 如果 @parent为空, 那么该@kobj的 parent会被设置为与该kobject
 * 相关联的.  如果没有kset分配给这个kobject,那么该kobject会放在
 * sysfs的根目录。
 *
 * 如果该方法返回错误,必须调用 kobject_put()来正确的清理该object
 * 相关联的内存。
 * 在任何情况下都不要直接通过调用to kfree()来直接释放传递给该方法
 * 的kobject,那样会导致内存泄露。
 *
 * 注意,该调用不会创建"add" uevent, 调用方需要为该object设置好所有
 * 必要的sysfs文件,然后再调用带UEVENT_ADD 参数的kobject_uevent() 
 * 来保证用户控件可以正确的收到该kobject创建的通知。
 */

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

它正确设置了kobject的名称和父节点,如果这个kobject和一个特定的kset关联,则kobj->kset必须在调用kobject_add之前被指定。

kobject已经跟一个kset关联,在调用kobject_add时可以将这个kobject的父节点设置为NULL,这样它的父节点就会被设置为这个kset本身。

在将kobject添加到kernel时,它的名称就已经被设定好了,代码中不应该直接操作kobject的名称。

不过不需要调用kobject_init()kobject_add(),因为系统提供了函数kobject_create_and_add()

当kobj,无parent、无kset时,将在sysfs的根目录(即/sys/)下创建目录;

当kobj,无parent、有kset时,kobject_add()会设置kobj->parent为kset->kobj;因此会在该kset下创建目录;该kobj会加入kset.list;同时,会对kobj->kset->kobj增加两次引用:1.增加对kobj->parent的引用;2.增加对kobj->kset的引用。

当kobj,有parent、无kset时,kobject_add()会在该parent下创建目录;同时,会对parent增加引用。

当kobj,有parent、有kset时,优先在parent下创建目录;该kobj会加入kset.list;同时,会分别对parent、kset增加引用计数。

#include <linux/kobject.h> 
#if 0 // 下面两种操作等价
struct kobject *kobject_create(void);
int kobject_add(struct kobject *kobj);
#else
struct kobject *kobject_create_and_add(const char *name, struct kobject *parent);
// 再也没有 kobject_register 这个函数了
#endif

10.4 创建简单的kobject

有些时候,开发者希望的只是在sysfs中创建一个目录,并且不会被ksetshowstore函数等细节的复杂概念所迷惑。

这里有一个可以单独创建kobject的例外,为了创建这样一个入口,可以通过如下的函数来实现:

struct kobject *kobject_create_and_add(char *name, struct kobject *parent);

这个函数会创建一个kobject,并把它的目录放到指定父节点在sysfs中目录里面。

10.5 重命名

如果你必须为kobject改名,则调用kobject_rename()函数实现:

int kobject_rename(struct kobject *kobj, const char *new_name);

// 如果 kobject 还没被 add 进 sysfs,
// 那么还可以使用下列的函数,否则必须使用kobject_rename
int kobject_set_name(struct kobject *kobj, const char *fmt, ...);
int kobject_set_name_vargs(struct kobject *kobj, const char *fmt,
                           va_list vargs);

kobject_rename内部并不执行任何锁定操作,也没用何时名称是有效的概念。因此调用者必须自己实现完整性检查和保证序列性。

可以通过kobject_name()函数来正确获取kobject的名称:

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

11. 小结

  • kobj对应一个目录;
  • kobj实现对象的生命周期管理(计数为0即清除);
  • kset包含一个kobj,相当于也是一个目录;
  • kset是一个容器,可以包容不同类型的kobj(甚至父子关系的两个kobj都可以属于一个kset);
  • 注册kobj(如kobj_add()函数)时,优先以kobj->parent作为自身的parent目录;
    其次,以kset作为parent目录;若都没有,则是sysfs的顶层目录;
    同时,若设置了kset,还会将kobj加入kset.list。举例:
    1、无parent、无kset,则将在sysfs的根目录(即/sys/)下创建目录;
    2、无parent、有kset,则将在kset下创建目录;并将kobj加入kset.list
    3、有parent、无kset,则将在parent下创建目录;
    4、有parent、有kset,则将在parent下创建目录,并将kobj加入kset.list
  • 注册kset时,如果它的kobj->parent为空,则设置它所属的ksetkset.kobj->kset.kobj)为parent,即优先使用kobj所属的parent;然后再使用kset作为parent。(如platform_bus_type的注册,如某些input设备的注册)
  • 注册device时,dev->kobj.kset = devices_kset;然而kobj->parent的选取有优先次序:

在这里插入图片描述

refer to

  1. Linux 内核:设备驱动模型(1)sysfs与kobject基类
  2. 统一设备模型:kobj、kset分析
  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值