1. 程式人生 > >linux裝置模型二(kobject)

linux裝置模型二(kobject)

1. 前言

Kobject是Linux裝置模型的基礎,也是裝置模型中最難理解的一部分(可參考Documentation/kobject.txt的表述)。因此有必要先把它分析清楚。

2. 基本概念

由“Linux裝置模型(1)_基本概念”可知,Linux裝置模型的核心是使用Bus、Class、Device、Driver四個核心資料結構,將大量的、不同功能的硬體裝置(以及驅動該硬體裝置的方法),以樹狀結構的形式,進行歸納、抽象,從而方便Kernel的統一管理。

而硬體裝置的數量、種類是非常多的,這就決定了Kernel中將會有大量的有關裝置模型的資料結構。這些資料結構一定有一些共同的功能,需要抽象出來統一實現,否則就會不可避免的產生冗餘程式碼。這就是Kobject誕生的背景。

  1. 通過parent指標,可以將所有Kobject以層次結構的形式組合起來。
  2. 使用一個引用計數(reference count),來記錄Kobject被引用的次數,並在引用次數變為0時把它釋放(這是Kobject誕生時的唯一功能)。
  3. 和sysfs虛擬檔案系統配合,將每一個Kobject及其特性,以檔案的形式,開放到使用者空間(有關sysfs,會在其它文章中專門描述,本文不會涉及太多內容)。

注1:在Linux中,Kobject幾乎不會單獨存在。它的主要功能,就是內嵌在一個大型的資料結構中,為這個資料結構提供一些底層的功能實現。 
注2:Linux driver開發者,很少會直接使用Kobject以及它提供的介面,而是使用構建在Kobject之上的裝置模型介面。

3. 程式碼解析

3.1 在Linux Kernel source code中的位置

在Kernel原始碼中,Kobject由如下兩個檔案實現:

  • include/linux/kobject.h
  • lib/kobject.c

其中kobject.h為Kobject的標頭檔案,包含所有的資料結構定義和介面宣告。kobject.c為核心功能的實現。

3.2 主要的資料結構

在描述資料結構之前,有必要說明一下Kobject, Kset和Ktype這三個概念。

Kobject是基本資料型別,每個Kobject都會在"/sys/“檔案系統中以目錄的形式出現。

Ktype代表Kobject(嚴格地講,是包含了Kobject的資料結構

)的屬性操作集合(由於通用性,多個Kobject可能共用同一個屬性操作集,因此把Ktype獨立出來了)。 
注3:在裝置模型中,ktype的命名和解釋,都非常抽象,理解起來非常困難,後面會詳細說明。

Kset是一個特殊的Kobject(因此它也會在"/sys/“檔案系統中以目錄的形式出現),它用來集合相似的Kobject(這些Kobject可以是相同屬性的,也可以不同屬性的)。

下面就是它們三者在核心中的低位和關係結構圖。

  • 首先看一下Kobject的原型
    struct kobject {
    	const char		*name;
    	struct list_head	entry;
    	struct kobject		*parent;
    	struct kset		*kset;
    	struct kobj_type	*ktype;
    	struct kernfs_node	*sd; /* sysfs directory entry */
    	struct kref		kref;
    #ifdef CONFIG_DEBUG_KOBJECT_RELEASE
    	struct delayed_work	release;
    #endif
    	unsigned int state_initialized:1;
    	unsigned int state_in_sysfs:1;
    	unsigned int state_add_uevent_sent:1;
    	unsigned int state_remove_uevent_sent:1;
    	unsigned int uevent_suppress:1;
    };

    引數作用

  • name,該Kobject的名稱,同時也是sysfs中的目錄名稱。由於Kobject新增到Kernel時,需要根據名字註冊到sysfs中,之後就不能再直接修改該欄位。如果需要修改Kobject的名字,需要呼叫kobject_rename介面,該介面會主動處理sysfs的相關事宜。
    entry,用於將Kobject加入到Kset中的list_head。
    parent,指向parent kobject,以此形成層次結構(在sysfs就表現為目錄結構)。
    kset,該kobject屬於的Kset。可以為NULL。如果存在,且沒有指定parent,則會把Kset作為parent(別忘了Kset是一個特殊的Kobject)。
    ktype,該Kobject屬於的kobj_type。每個Kobject必須有一個ktype,或者Kernel會提示錯誤。
    sd,該Kobject在sysfs中的表示。
    kref,"struct kref”型別(在include/linux/kref.h中定義)的變數,為一個可用於原子操作的引用計數。
    state_initialized,指示該Kobject是否已經初始化,以在Kobject的Init,Put,Add等操作時進行異常校驗。
    state_in_sysfs,指示該Kobject是否已在sysfs中呈現,以便在自動登出時從sysfs中移除。
    state_add_uevent_sent/state_remove_uevent_sent,記錄是否已經向用戶空間傳送ADD uevent,如果有,且沒有傳送remove uevent,則在自動登出時,補發REMOVE uevent,以便讓使用者空間正確處理。
    uevent_suppress,如果該欄位為1,則表示忽略所有上報的uevent事件。
    注4:Uevent提供了“使用者空間通知”的功能實現,通過該功能,當核心中有Kobject的增加、刪除、修改等動作時,會通知使用者空間。有關該功能的具體內容,會在其它文章詳細描述。
  • Kset的原型為

/**
 * 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 {
	struct list_head list;
	spinlock_t list_lock;
	struct kobject kobj;
	const struct kset_uevent_ops *uevent_ops;
};
list/list_lock,用於儲存該kset下所有的kobject的連結串列。
kobj,該kset自己的kobject(kset是一個特殊的kobject,也會在sysfs中以目錄的形式體現)。
uevent_ops,該kset的uevent操作函式集。當任何Kobject需要上報uevent時,都要呼叫它所從屬的kset的uevent_ops,新增環境變數,或者過濾event(kset可以決定哪些event可以上報)。因此,如果一個kobject不屬於任何kset時,是不允許傳送uevent的。
  • Ktype的原型為
struct kobj_type {
	void (*release)(struct kobject *kobj);
	const struct sysfs_ops *sysfs_ops;
	struct attribute **default_attrs;
	const struct kobj_ns_type_operations *(*child_ns_type)(struct kobject *kobj);
	const void *(*namespace)(struct kobject *kobj);
};
release,通過該回調函式,可以將包含該種類型kobject的資料結構的記憶體空間釋放掉。
sysfs_ops,該種類型的Kobject的sysfs檔案系統介面。
default_attrs,該種類型的Kobject的atrribute列表(所謂attribute,就是sysfs檔案系統中的檔案)。將會在Kobject新增到核心時,一併註冊到sysfs中。
child_ns_type/namespace,和檔案系統(sysfs)的名稱空間有關,這裡不再詳細說明。

總結,Ktype以及整個Kobject機制的理解。 
Kobject的核心功能是:保持一個引用計數,當該計數減為0時,自動釋放(由本文所講的kobject模組負責) Kobject所佔用的meomry空間。這就決定了Kobject必須是動態分配的(只有這樣才能動態釋放)。 

而Kobject大多數的使用場景,是內嵌在大型的資料結構中(如Kset、device_driver等),因此這些大型的資料結構,也必須是動態分配、動態釋放的。那麼釋放的時機是什麼呢?是內嵌的Kobject釋放時。但是Kobject的釋放是由Kobject模組自動完成的(在引用計數為0時),那麼怎麼一併釋放包含自己的大型資料結構呢? 

這時Ktype就派上用場了。我們知道,Ktype中的release回撥函式負責釋放Kobject(甚至是包含Kobject的資料結構)的記憶體空間,那麼Ktype及其內部函式,是由誰實現呢?是由上層資料結構所在的模組!因為只有它,才清楚Kobject嵌在哪個資料結構中,並通過Kobject指標以及自身的資料結構型別,找到需要釋放的上層資料結構的指標,然後釋放它。 

講到這裡,就清晰多了。所以,每一個內嵌Kobject的資料結構,例如kset、device、device_driver等等,都要實現一個Ktype,並定義其中的回撥函式。同理,sysfs相關的操作也一樣,必須經過ktype的中轉,因為sysfs看到的是Kobject,而真正的檔案操作的主體,是內嵌Kobject的上層資料結構! 

順便提一下,Kobject是面向物件的思想在Linux kernel中的極致體現,但C語言的優勢卻不在這裡,所以Linux kernel需要用比較巧妙(也很囉嗦)的手段去實現。

3.3 功能分析

3.3.1 Kobject使用流程

Kobject大多數情況下(有一種例外,下面會講)會嵌在其它資料結構中使用,其使用流程如下:

  1. 定義一個struct kset型別的指標,並在初始化時為它分配空間,新增到核心中
  2. 根據實際情況,定義自己所需的資料結構原型,該資料結構中包含有Kobject
  3. 定義一個適合自己的ktype,並實現其中回撥函式
  4. 在需要使用到包含Kobject的資料結構時,動態分配該資料結構,並分配Kobject空間,新增到核心中
  5. 每一次引用資料結構時,呼叫kobject_get介面增加引用計數;引用結束時,呼叫kobject_put介面,減少引用計數
  6. 當引用計數減少為0時,Kobject模組呼叫ktype所提供的release介面,釋放上層資料結構以及Kobject的記憶體空間

上面有提過,有一種例外,Kobject不再嵌在其它資料結構中,可以單獨使用,這個例外就是:開發者只需要在sysfs中建立一個目錄,而不需要其它的kset、ktype的操作。這時可以直接呼叫kobject_create_and_add介面,分配一個kobject結構並把它新增到kernel中。

3.3.2 Kobject的分配和釋放

前面講過,Kobject必須動態分配,而不能靜態定義或者位於堆疊之上,它的分配方法有兩種。

1. 通過kmalloc自行分配(一般是跟隨上層資料結構分配),並在初始化後新增到kernel。這種方法涉及如下介面:

extern void kobject_init(struct kobject *kobj, struct kobj_type *ktype);
extern __printf(3, 4) __must_check
int kobject_add(struct kobject *kobj, struct kobject *parent,
		const char *fmt, ...);
extern __printf(4, 5) __must_check
int kobject_init_and_add(struct kobject *kobj,
			 struct kobj_type *ktype, struct kobject *parent,
			 const char *fmt, ...);

extern void kobject_del(struct kobject *kobj);

kobject_init,初始化通過kmalloc等記憶體分配函式獲得的struct kobject指標。

/**
 * kobject_init - initialize a kobject structure
 * @kobj: pointer to the kobject to initialize
 * @ktype: pointer to the ktype for this kobject.
 *
 * This function will properly initialize a kobject such that it can then
 * be passed to the kobject_add() call.
 *
 * After this function is called, the kobject MUST be cleaned up by a call
 * to kobject_put(), not by a call to kfree directly to ensure that all of
 * the memory is cleaned up properly.
 */
void kobject_init(struct kobject *kobj, struct kobj_type *ktype)
{
	char *err_str;

	if (!kobj) {        /* 必須有效才能初始化 */
		err_str = "invalid kobject pointer!";
		goto error;
	}
	if (!ktype) {         /* 必須有效才能繫結 */
		err_str = "must have a ktype to be initialized properly!\n";
		goto error;
	}
	if (kobj->state_initialized) {    /* 用這個函式初始化,所以正常都是沒初始化過的 */
		/* do not error out as sometimes we can recover */
		printk(KERN_ERR "kobject (%p): tried to init an initialized "
		       "object, something is seriously wrong.\n", kobj);
		dump_stack();
	}

	kobject_init_internal(kobj);    /* 初始化kobj */
	kobj->ktype = ktype;            /* 繫結ktype到該kobj */
	return;

error:
	printk(KERN_ERR "kobject (%p): %s\n", kobj, err_str);
	dump_stack();
}

主要執行邏輯為:

  • 確認kobj和ktype不為空
  • 如果該指標已經初始化過(判斷kobj->state_initialized),列印錯誤提示及堆疊資訊(但不是致命錯誤,所以還可以繼續)
  • 初始化kobj內部的引數,包括引用計數、list、各種標誌等
  • 根據輸入引數,將ktype指標賦予kobj->ktype
    static void kobject_init_internal(struct kobject *kobj)
    {
    	if (!kobj)
    		return;
    	kref_init(&kobj->kref);               /* 原子操作,對kref引用計數加1 */
    	INIT_LIST_HEAD(&kobj->entry);
    	kobj->state_in_sysfs = 0;             /* 標記還沒呈現到sysfs */
    	kobj->state_add_uevent_sent = 0;      /* 還未戶空間傳送ADD uevent */
    	kobj->state_remove_uevent_sent = 0;   /* 沒有傳送remove uevent */
    	kobj->state_initialized = 1;          /* 標記被初始化過 */
    }

kobject_add,將初始化完成的kobject新增到kernel中,引數包括需要新增的kobject、該kobject的parent(用於形成層次結構,可以為空)、用於提供kobject name的格式化字串。主要執行邏輯為:


/**
 * 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.
 *
 * 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.
 *
 * 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.
 */
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) {    /* 只有初始化過的才能add */
		printk(KERN_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;
}
  • 確認kobj不為空,確認kobj已經初始化,否則錯誤退出
  • 呼叫內部介面kobject_add_varg,完成新增操作

kobject_init_and_add,是上面兩個介面的組合,不再說明。

==========================內部介面======================================

kobject_add_varg,解析格式化字串,將結果賦予kobj->name,之後呼叫kobject_add_internal介面,完成真正的新增操作。

static int kobject_add_varg(struct kobject *kobj, struct kobject *parent,
			    const char *fmt, va_list vargs)
{
	int retval;

	retval = kobject_set_name_vargs(kobj, fmt, vargs);
	if (retval) {
		printk(KERN_ERR "kobject: can not set name properly!\n");
		return retval;
	}
	kobj->parent = parent;
	return kobject_add_internal(kobj);
}

kobject_add_internal,將kobject新增到kernel。


static int kobject_add_internal(struct kobject *kobj)
{
	int error = 0;
	struct kobject *parent;

	if (!kobj)            /* 必須有效 */
		return -ENOENT;

	if (!kobj->name || !kobj->name[0]) {    /* 有具體名字,不然沒法顯示資料夾 */
		WARN(1, "kobject: (%p): attempted to be registered with empty "
			 "name!\n", kobj);
		return -EINVAL;
	}

	parent = kobject_get(kobj->parent);    /* 如果有父節點的話,父節的點引用計數加1 */

	/* join kset if set, use it as parent if we do not already have one */
	if (kobj->kset) {        /* kset是kobj的集合 */
		if (!parent)         /* 如果該kobj的父節點不存在,就讓kset裡面的kobj做它的父節點 */
			parent = kobject_get(&kobj->kset->kobj);
		kobj_kset_join(kobj);        /* 把kobj加入到kset list的連結串列中去 */
		kobj->parent = parent;       /* 給kobj新增父節點 */
	}

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

	error = create_dir(kobj);        /* 呼叫sysfs檔案系統介面,建立一個名為kobj->name的資料夾 ,目錄和parent有直接關係*/
	if (error) {
		kobj_kset_leave(kobj);
		kobject_put(parent);
		kobj->parent = NULL;

		/* be noisy on error issues */
		if (error == -EEXIST)
			printk(KERN_ERR "%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
			printk(KERN_ERR "%s failed for %s (%d)\n",
			       __func__, kobject_name(kobj), error);
		dump_stack();
	} else
		kobj->state_in_sysfs = 1;

	return error;
}

主要執行邏輯為:

  • 校驗kobj以及kobj->name的合法性,若不合法列印錯誤資訊並退出
  • 呼叫kobject_get增加該kobject的parent的引用計數(如果存在parent的話)
  • 如果存在kset(即kobj->kset不為空),則呼叫kobj_kset_join介面加入kset。同時,如果該kobject沒有parent,卻存在kset,則將它的parent設為kset(kset是一個特殊的kobject),並增加kset的引用計數
  • 通過create_dir介面,呼叫sysfs的相關介面,在sysfs下建立該kobject對應的目錄
  • 如果建立失敗,執行後續的回滾操作,否則將kobj->state_in_sysfs置為1

kobj_kset_join,負責將kobj加入到對應kset的連結串列中。

/* add the kobject to its kset's list */
static void kobj_kset_join(struct kobject *kobj)
{
	if (!kobj->kset)
		return;

	kset_get(kobj->kset);
	spin_lock(&kobj->kset->list_lock);
	list_add_tail(&kobj->entry, &kobj->kset->list);    /* 將kobj的entey加入到kset連結串列中去 */
	spin_unlock(&kobj->kset->list_lock);
}

這種方式分配的kobject,會在引用計數變為0時,由kobject_put呼叫其ktype的release介面,釋放記憶體空間,具體可參考後面有關kobject_put的講解。

2. 使用kobject_create建立

Kobject模組可以使用kobject_create自行分配空間,並內建了一個ktype(dynamic_kobj_ktype),用於在計數為0是釋放空間。程式碼如下:

extern struct kobject * __must_check kobject_create(void);
extern struct kobject * __must_check kobject_create_and_add(const char *name,
						struct kobject *parent);

每個kobj在其分配的時候就已經為其準備好了釋放函式,繫結在它的kobj_type的回撥函式release中


/**
 * kobject_create - create a struct kobject dynamically
 *
 * This function creates a kobject structure dynamically and sets it up
 * to be a "dynamic" kobject with a default release function set up.
 *
 * If the kobject was not able to be created, NULL will be returned.
 * The kobject structure returned from here must be cleaned up with a
 * call to kobject_put() and not kfree(), as kobject_init() has
 * already been called on this structure.
 */
struct kobject *kobject_create(void)
{
	struct kobject *kobj;

	kobj = kzalloc(sizeof(*kobj), GFP_KERNEL);    /* 建立一個kobj */
	if (!kobj)
		return NULL;

	kobject_init(kobj, &dynamic_kobj_ktype);      /* 初始化並繫結ktype,登出函式 */
	return kobj;
}
static void dynamic_kobj_release(struct kobject *kobj)
{
	pr_debug("kobject: (%p): %s\n", kobj, __func__);
	kfree(kobj);
}

static struct kobj_type dynamic_kobj_ktype = {
	.release	= dynamic_kobj_release,
	.sysfs_ops	= &kobj_sysfs_ops,
};

kobject_create,該介面為kobj分配記憶體空間,並以dynamic_kobj_ktype為引數,呼叫kobject_init介面,完成後續的初始化操作。

kobject_create_and_add,是kobject_create和kobject_add的組合,不再說明。

dynamic_kobj_release,直接呼叫kfree釋放kobj的空間。

3.3.4 Kset的初始化、註冊

Kset是一個特殊的kobject,因此其初始化、註冊等操作也會呼叫kobject的相關介面,除此之外,會有它特有的部分。另外,和Kobject一樣,kset的記憶體分配,可以由上層軟體通過kmalloc自行分配,也可以由Kobject模組負責分配,具體如下。

extern void kset_init(struct kset *kset);
extern int __must_check kset_register(struct kset *kset);
extern void kset_unregister(struct kset *kset);
extern struct kset * __must_check kset_create_and_add(const char *name,
						const struct kset_uevent_ops *u,
						struct kobject *parent_kobj);
/**
 * kset_init - initialize a kset for use
 * @k: kset
 */
void kset_init(struct kset *k)
{
	kobject_init_internal(&k->kobj);         /* 初始化裡面的kobj */
	INIT_LIST_HEAD(&k->list);                /* 初始化kset用於組織下面kobj的連結串列 */
	spin_lock_init(&k->list_lock);
}

kset_init,該介面用於初始化已分配的kset,主要包括呼叫kobject_init_internal初始化其kobject,然後初始化kset的連結串列。需要注意的時,如果使用此介面,上層軟體必須提供該kset中的kobject的ktype。

/**
 * kset_register - initialize and add a kset.
 * @k: kset.
 */
int kset_register(struct kset *k)
{
	int err;

	if (!k)
		return -EINVAL;

	kset_init(k);                            /* 初始化kset */
	err = kobject_add_internal(&k->kobj);    /* 把kset的kobj加入到核心(建立一個資料夾) */
	if (err)
		return err;
	kobject_uevent(&k->kobj, KOBJ_ADD);
	return 0;
}

kset_register,先呼叫kset_init,然後呼叫kobject_add_internal將其kobject新增到kernel。

/**
 * kset_unregister - remove a kset.
 * @k: kset.
 */
void kset_unregister(struct kset *k)
{
	if (!k)
		return;
	kobject_del(&k->kobj);  /* 刪除這個kset */      
	kobject_put(&k->kobj);  /* 引用計數減1,呼叫release */
}

kset_unregister,直接呼叫kobject_put釋放其kobject。當其kobject的引用計數為0時,即呼叫ktype的release介面釋放kset佔用的空間。

/**
 * 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.
 */
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_create(name, uevent_ops, parent_kobj);
	if (!kset)
		return NULL;
	error = kset_register(kset);
	if (error) {
		kfree(kset);
		return NULL;
	}
	return kset;
}

kset_create_and_add,會呼叫內部介面kset_create動態建立一個kset,並呼叫kset_register將其註冊到kernel。

==========================內部介面======================================

static void kset_release(struct kobject *kobj)
{
	struct kset *kset = container_of(kobj, struct kset, kobj);
	pr_debug("kobject: '%s' (%p): %s\n",
		 kobject_name(kobj), kobj, __func__);
	kfree(kset);        /* 釋放kset本身 */
}

static struct kobj_type kset_ktype = {
	.sysfs_ops	= &kobj_sysfs_ops,
	.release = kset_release,        /* 繫結釋放函式 */
};


/**
 * 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 = kzalloc(sizeof(*kset), GFP_KERNEL);    /* 申請一個kset */
	if (!kset)
		return NULL;
	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;    /* 給它裡面的kobj裡面的ktype繫結一個release函式 */
	kset->kobj.kset = NULL;            /* kobj本就屬於kset,這裡不需要連線 */

	return kset;
}

kset_create,該介面使用kzalloc分配一個kset空間,並定義一個kset_ktype型別的ktype,用於釋放所有由它分配的kset空間。

static void kset_release(struct kobject *kobj)
{
	struct kset *kset = container_of(kobj, struct kset, kobj);
	pr_debug("kobject: '%s' (%p): %s\n",
		 kobject_name(kobj), kobj, __func__);
	kfree(kset);
}

static struct kobj_type kset_ktype = {
	.sysfs_ops	= &kobj_sysfs_ops,
	.release = kset_release,
};

最後我們分析一下釋放kset的方法。

/**  該函式的使用通常和kset_register配對
 * kset_unregister - remove a kset.
 * @k: kset.
 */
void kset_unregister(struct kset *k)
{
	if (!k)
		return;
        /* 此函式只是將kobject從sysfs和kset中去掉(初始化kobject的list_head),並不是刪除kobject,刪除kobject得靠kobject->ktype->release才行。kobject_del在把kobject從kset中去掉後,會把kset的引用值減1。*/
        kobject_del(&k->kobj);   
        /* 此函式為kobject的引用值減1,若引用值減為0,則銷燬此kobject(呼叫kobject_release呼叫kobject_cleanup,最終呼叫kobject_del、ktype的release),最後一步釋放的name是在kobject_add中呼叫的kobject_set_name_vargs中分配的。 */   
	kobject_put(&k->kobj);       
}

對比上下兩個可以發現在kset_unregister裡面其實呼叫兩次konject_put函式,一次是其parent的,一次是自己的。

/**
 * kobject_del - unlink kobject from hierarchy.
 * @kobj: object.
 */
void kobject_del(struct kobject *kobj)
{
	struct kernfs_node *sd;

	if (!kobj)
		return;

	sd = kobj->sd;
	sysfs_remove_dir(kobj);      /* 刪除sysfs裡面的資料夾 */
	sysfs_put(sd);               /* 清理sysfs裡面的引用計數,釋放計數為0的節點等 */

	kobj->state_in_sysfs = 0;    /* 標明不再sysfs中了 */
	kobj_kset_leave(kobj);        
	kobject_put(kobj->parent);   /* 父節點引用數減1,如果減值0,則釋放父節點kset */
	kobj->parent = NULL;         /* 它的parent之前是kset的kobj,現在置位NULL */
}

/**
 * kref_sub - subtract a number of refcounts for object.
 * @kref: object.
 * @count: Number of recounts to subtract.
 * @release: pointer to the function that will clean up the object when the
 *	     last reference to the object is released.
 *	     This pointer is required, and it is not acceptable to pass kfree
 *	     in as this function.  If the caller does pass kfree to this
 *	     function, you will be publicly mocked mercilessly by the kref
 *	     maintainer, and anyone else who happens to notice it.  You have
 *	     been warned.
 *
 * Subtract @count from the refcount, and if 0, call release().
 * Return 1 if the object was removed, otherwise return 0.  Beware, if this
 * function returns 0, you still can not count on the kref from remaining in
 * memory.  Only use the return value if you want to see if the kref is now
 * gone, not present.
 */
static inline int kref_sub(struct kref *kref, unsigned int count,
	     void (*release)(struct kref *kref))
{
	WARN_ON(release == NULL);

	if (atomic_sub_and_test((int) count, &kref->refcount)) {
		release(kref);        /* 如果減至1,則呼叫kobj所在的ktype裡面的release */
		return 1;
	}
	return 0;
}
/**
 * kref_put - decrement refcount for object.
 * @kref: object.
 * @release: pointer to the function that will clean up the object when the
 *	     last reference to the object is released.
 *	     This pointer is required, and it is not acceptable to pass kfree
 *	     in as this function.  If the caller does pass kfree to this
 *	     function, you will be publicly mocked mercilessly by the kref
 *	     maintainer, and anyone else who happens to notice it.  You have
 *	     been warned.
 *
 * Decrement the refcount, and if 0, call release().
 * Return 1 if the object was removed, otherwise return 0.  Beware, if this
 * function returns 0, you still can not count on the kref from remaining in
 * memory.  Only use the return value if you want to see if the kref is now
 * gone, not present.
 */
static inline int kref_put(struct kref *kref, void (*release)(struct kref *kref))
{
	return kref_sub(kref, 1, release);    /* 減1 */
}

/**
 * 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);
	}
}


static inline void kset_put(struct kset *k)
{
	kobject_put(&k->kobj);
}


/**
 * 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);    /* 如果引用計數為0,則呼叫kobject_release清理 */
	}
}


/* remove the kobject from its kset's list */
static void kobj_kset_leave(struct kobject *kobj)
{
	if (!kobj->kset)
		return;

	spin_lock(&kobj->kset->list_lock);
	list_del_init(&kobj->entry);            /* 刪除kobj在kset中的節點 */
	spin_unlock(&kobj->kset->list_lock);
	kset_put(kobj->kset);                   /* kobj的kset引用計數減1,減為0則呼叫它上一級的kset為其繫結的kobj_type裡面的release釋放kset本身 */
}


/**
 * 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)    /* 0表示已經釋放過了 */
			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);    /* 這個函式在好多地方都時候用了,好好分析一下 */
	}
}

其中kobject_release是kobject.c檔案定義的。

static void kobject_release(struct kref *kref)
{
    /* 先根據kref找到kobject的地址,再把這個地址傳給kobject_cleanup */
	kobject_cleanup(container_of(kref, struct kobject, kref));
}

/*
 * kobject_cleanup - free kobject resources.
 * @kobj: object to cleanup
 */
static void kobject_cleanup(struct kobject *kobj)
{
	struct kobj_type *t = get_ktype(kobj);    /* 得到kobj裡面的ktype指標 */
	const char *name = kobj->name;

	pr_debug("kobject: '%s' (%p): %s\n",
		 kobject_name(kobj), kobj, __func__);

	if (t && !t->release)        /* 如果ktype存在,但ktype裡的release不存在,則ktype沒法釋放,打印出告警資訊 */
		pr_debug("kobject: '%s' (%p): does not have a release() "
			 "function, it is broken and must be fixed.\n",
			 kobject_name(kobj), kobj);

	/* send "remove" if the caller did not do it but sent "add" */
        /* 如果沒發remove卻發了add的話,則向用戶層傳送remove, */
	if (kobj->state_add_uevent_sent && !kobj->state_remove_uevent_sent) {
		pr_debug("kobject: '%s' (%p): auto cleanup 'remove' event\n",
			 kobject_name(kobj), kobj);
		kobject_uevent(kobj, KOBJ_REMOVE);
	}

	/* remove from sysfs if the caller did not do it */
	if (kobj->state_in_sysfs) {    /* 如果換處在sysfs中,則要先呼叫kobjcat_del,刪除資料夾操作等 */
		pr_debug("kobject: '%s' (%p): auto cleanup kobject_del\n",
			 kobject_name(kobj), kobj);
		kobject_del(kobj);
	}

	if (t && t->release) {    /* kobj_type和其釋放函式都在的話,則呼叫release釋放記憶體 */
		pr_debug("kobject: '%s' (%p): calling ktype release\n",
			 kobject_name(kobj), kobj);
		t->release(kobj);
	}

	/* free name if we allocated it */
	if (name) {
		pr_debug("kobject: '%s': free name\n", name);
		kfree(name);            /* 釋放name佔用的位元組數 */
	}
}

總結後我們發現:

extern void kset_init(struct kset *kset);                        /* 被kset_register呼叫 */
extern int __must_check kset_register(struct kset *kset);
extern void kset_unregister(struct kset *kset);
extern struct kset * __must_check kset_create_and_add(const char *name,
						const struct kset_uevent_ops *u,
						struct kobject *parent_kobj);    /* 呼叫kset_register */

如果使用kset_create_and_add函式在kset_creat的時候,它裡面kobject裡面的kobj_type已經被繫結,它裡面的kobject裡面的kset則沒有,即沒有而直接包含在某個裝置中的kset,則直接呼叫kset_register註冊,註冊前要自己初始化好裡面kobject的kobj_type和kset指標,這樣會讓使用變得更加靈活。

看一下kset和kobject的關係

 我們前面說了,每一個 kobj 對應 檔案系統 /sys 裡的一個 目錄,那麼每一個 kset 都包含了一個 kobj,那樣的話,kset也對應於 /sys 裡的一個 目錄
    簡單來說,kset 與 kobj 都是目錄,既然是目錄,那麼在就是一個樹狀結構,每一個目錄都將有一個 父節點,

        在kset中使用kset.kobj->parent 指定 
        在kboject中使用  kobj->parent 指定
    顯然,整個樹狀目錄結構,都是通過kobj來構建的,只不過有些kobj嵌在Kset裡,分析目錄結構時把kset當成一個普通的kobj會好理解很多。

父目錄kset和其子目錄的konject和kset之間,子目錄的kobject(或kset裡面的kobject)使用裡面的parent指向父目錄kset裡面的kobject,kset用其裡的list連結串列作為head負責連線這個資料夾裡面的所有的kobject,用其裡面kobject的lsit指標作為更高一層目錄kset連結串列的node接入上層。

而kobject因為裡面只有list,只能作為node計入同一層目錄下的連結串列中。

那麼kset 有何特殊之處呢?
    我們可以看到 kset 結構中有一個連結串列,它目錄下的 一些相同型別的kobj會在建立時鏈入連結串列,也就是說Kset是一些kobj的集合。    
    幹說還是比較抽象的,那麼我們就來看看 /sys 目錄底下的這些東西,哪些是kset 哪些是kobj 結構又是怎樣的。
    看過上面程式碼的應該知道,想要在/sys 目錄下建立 目錄,那就要 構造 kset 或者 kobj 設定並註冊。

可以簡單點這樣理解,kset是以資料夾形式組織核心中的樹形結構,它通過list連結串列連線下面所有的kobject或kset。而下面的kobject和kset則用他們裡面的kobject裡面的parent指向kset裡面的kobject,同時所有kobject裡面的kset指標也會指向kset結構體(主要實現引用計數,只有在kset資料夾下面的所有kobject和kset都沒有的話,它的下面的最後一個kobject登出的時候,會對它的kset引用計數減1,發現該kset的引用計數為0,才可以呼叫kset自身繫結的ktype裡面的release釋放kset,否則下面還有檔案肯定不能釋放)。除了sysfs的根目錄,其它目錄下的kset最掌管下面的kobject和kset,對上通過裡面的kobject作為更上一層的子目錄。這樣就能通過kset的多級連線以及kobject實現核心sysfs中的樹形目錄的基礎。

再看一下三者總體的關係。

上面這個圖是超級經典的,指的反覆學和思考樹形目錄的實現原理。

kobject是隱藏在sysfs虛擬檔案系統後的機制,對於sysfs中的每一個目錄,核心中都會存在一個對應的kobject。每一個kobject都輸出一個或者多個屬性,它們在kobject的sysfs目錄中表現為檔案,其中的內容由核心生成。

kobject在sysfs中始終是一個目錄,這個目錄包含一個或者多個屬性。

分配給kobject的名字,是sysfs中的目錄名字。

sysfs的入口目錄的位置對應於kobject的parent指標。呼叫kobject_add的時候,如果parent為NULL,它將被設定為嵌入到心得kobject的kset中的kobject,這樣,sysfs 分層結構通常與kset建立的內部結構相匹配。如果parent和kset都是null,則會在最高層建立目錄。

 原本我以為,較高層次的目錄會是kset,因為它是個集合嘛,然而並不全是。

對kobject
kobject_init_and_add
kobject_create_and_add
對kset
kset_create_and_add

我在這3個函式中增加了prink列印語句,列印核心建立的每一個 kobj 或者 kset 的名字,以及父節點的名字,甚至它指向的kset的kobj的名字。

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

    kobject_init(kobj, ktype);

    va_start(args, fmt);
    retval = kobject_add_varg(kobj, parent, fmt, args);
    va_end(args);
    /*********************************************************/
    printk(KERN_INFO"the kobject is %s\n" ,kobj->name);
    if(parent)  
        printk(KERN_INFO"the kobject parent is %s\n", kobj->parent->name);
    else 
        printk(KERN_INFO"the kobject no parent\n");
    
    if(kobj->kset)  
        printk(KERN_INFO"the kobject kset is %s\n", kobj->kset->kobj.name);
    else 
        printk(KERN_INFO"the kobject no kset\n");
    /*********************************************************/
    
    return retval;
}
int kobject_init_and_add(struct kobject *kobj, struct kobj_type *ktype,
             struct kobject *parent, const char *fmt, ...) 
{
    va_list args;
    int retval;

    kobject_init(kobj, ktype);
    kobj = kobject_create();
    if (!kobj)
        return NULL;

    retval = kobject_add(kobj, parent, "%s", name);
    if (retval) {
        printk(KERN_WARNING "%s: kobject_add error: %d\n",
               __func__, retval);
        kobject_put(kobj);
        kobj = NULL;
    }

    /********************************************************/
    printk(KERN_ERR"the kobject is %s\n",kobj->name);
    if(parent)
        printk(KERN_INFO"the kobject parent is %s\n", kobj->parent->name);
    else
        printk(KERN_INFO"the kobject no parent\n");

    if(kobj->kset)
        printk(KERN_INFO"the kobject kset is %s\n" ,kobj->kset->kobj.name)
    else
        printk(KERN_INFO"the kobject no kset\n");
    /********************************************************/

    return kobj;
}
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_create(name, uevent_ops, parent_kobj);
    if (!kset)
        return NULL;
    error = kset_register(kset);
    if (error) {
        kfree(kset);
        return NULL;
    }
    /*************************************************/
    printk(KERN_ERR"the kset is %s\n", kset->kobj.name);
    if(kset->kobj.parent)
        printk(KERN_INFO"the kset parent is %s\n",kset->kobj.parent->name );
    else
        printk(KERN_INFO"the kset no parent\n");

    if(kset->kobj.kset)
        printk(KERN_INFO"the kset of kset is %s\n", kset->kobj.kset->kobj.name);
    else
        printk(KERN_INFO"the kset no kset\n");
    /**************************************************/

    return kset;
}

為此我截圖找到幾個重要的目錄進行分析kset和kobject的在目錄中的組織。

可以發現

1.頂層的一些目錄也可以是kobject,如fs,dev目錄等

2.kobject下面可以放kobject   

3.kset下面可以放kset

4.kset下可以放kobject

5.kobject下可以放kset

kobject裡面可以放kobject(2),但上層的kobject不能聯絡下層,只能下層聯絡上層。

kset下可以放kset(3),上層可以通過kset裡面的list連結串列連線下層kset裡kobject裡的list連線

kset下可以放kobj(4),上層可以通過kset裡面的list連結串列連線下層kobject裡的list連線

kobject裡的kset,但上層的kobject不能聯絡下層,只能下層聯絡上層。


總結:從下層到上層很簡單,只要當前kobject(或kset裡面的kobject)的parent指標操作就可以

 1、sys 目錄下的層次結構依賴於 kobject.parent ,未指定parent時,預設使用 kobject.kset.kobject 作為 parent,如果都沒有,就出現在 /sys 目錄下。

 2、該 kobject 目錄下的屬性檔案依賴於 kobject.ktype

3.kset之間的下級到上級(子目錄到父目錄)關係是依靠,kset裡面kobject裡面的kset指標(只能子指向父)。而同一目錄下的則是用kset裡面kobject裡面的list指標,和父節點彼此組成雙向連結串列。(這樣想來是不是由子目錄返回上一級目錄直接一個指標操作就可以,而父目錄進子目錄需要連結串列遍歷然後對比名字才能進去    【想一想linux中的shell中,子目錄返回父目錄 [..]就可以的實現原理】

4.父目錄kset和其子目錄的konject和kset之間,子目錄的kobject(或kset裡面的kobject)使用裡面的parent指向父目錄kset裡面的kobject,kset用其裡的list連結串列負責連線這個資料夾裡面的所有的kobject.

5.父子關係必須是同類型的,所以資料夾之間的父子是用,子資料夾kset裡面的kobject裡的kset指標指向父kset。檔案和資料夾的父子關係是用檔案kobject裡的parent指標指向資料夾kset裡的kobject.

6.用ABC分表描述三個等級的資料夾,其中A是根目錄,B是A的子資料夾,C是B的子資料夾或裡面的檔案。B資料夾連線下層C檔案或資料夾是使用kset裡的list連結串列(此時B中kset的list是作為連結串列head),而B被A連線使用它裡面的kobject裡的list(此時B中kobject裡的list是作為連結串列node)

同時在驅動層次註冊device或driver的時候會為其kobj(因為kobject單獨存在無意義,通常都是和dev結合的)繫結kobj_type(大多時候都只是show和store介面)。大多數情況下kobject結構體都是作為基類被放置在某個device或driver裡面的,所有釋放device或driver的時候會整體釋放。只有在單獨呼叫下面兩個函式,建立資料夾的時候,才需要調前面分析的建立資料夾時繫結的動態release函式。

extern struct kobject * __must_check kobject_create(void);
extern struct kobject * __must_check kobject_create_and_add(const char *name,
						struct kobject *parent);

最後,這裡借用國嵌的程式碼歷程,對上面的驅動模型的核心框架說明。

#include <linux/device.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/string.h>
#include <linux/sysfs.h>
#include <linux/stat.h>
 

MODULE_LICENSE("Dual BSD/GPL");
 
void obj_test_release(struct kobject *kobject);
ssize_t kobj_test_show(struct kobject *kobject, struct attribute *attr,char *buf);
ssize_t kobj_test_store(struct kobject *kobject,struct attribute *attr,const char *buf, size_t count);
 

/* 檔名和許可權 */
struct attribute test_attr = {
        .name = "kobj_config",
        .mode = S_IRWXUGO,
};

/* 這裡是陣列,即目錄下可以有多個檔案 */
static struct attribute *def_attrs[] = {
        &test_attr,                /* 目錄下的一個檔案 */
        NULL,
};
 
 
struct sysfs_ops obj_test_sysops =
{
        /* 檔案的操作方法 */
        .show = kobj_test_show,
        .store = kobj_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("eric_test: release .\n");
}
 
ssize_t kobj_test_show(struct kobject *kobject, struct attribute *attr,char *buf)
{
        printk("have show.\n");
        printk("attrname:%s.\n", attr->name);
        sprintf(buf,"%s\n",attr->name);
        return strlen(attr->name)+2;
}
 
ssize_t kobj_test_store(struct kobject *kobject,struct attribute *attr,const char *buf, size_t count)
{
        printk("havestore\n");
        printk("write: %s\n",buf);    
        return count;
}
 
struct kobject kobj;
static int kobj_test_init()
{
        /* parent為NULL,即在跟目錄下 */
        printk("kboject test init.\n");
        kobject_init_and_add(&kobj,&ktype,NULL,"kobject_test");        /* 建立一個目錄 */
        return 0;
}
 
static int kobj_test_exit()
{
        printk("kobject test exit.\n");
        kobject_del(&kobj);                        /* 刪除一個目錄 */
        return 0;
}
 
module_init(kobj_test_init);
module_exit(kobj_test_exit);

可以看到kobject就是在sys的根目錄下建立了一個目錄,其中目錄下有一個檔案,該檔案就是由sttribute來管理的。

讀取該檔案的資訊,就會呼叫到相關的show函式。紅框則是驅動讓系統展示的內容。

給該檔案寫資訊,也可以原樣打印出來。

該檔案的許可權也和註冊時的是一樣的。

最後留一個問題release函式什麼時候執行?該如何讓操作?

kset涉及到熱插拔,放到下一節。

參考文件: