(todo)Linux 核心:裝置驅動模型(0)sysfs與kobject基類
(todo)Linux 核心:裝置驅動模型(0)sysfs與kobject
背景
學習Linux 裝置驅動模型時,對 kobject 不太理解。因此,學習了一下。
現在我知道了:kobj/kset
是如何作為統一裝置模型的基礎,以及到底提供了哪些功能。
以後我們就知道,在具體應用過程中,如device、bus甚至platform_device等是如何使用kobj/kset的。
參考:
- https://blog.csdn.net/yj4231/article/details/7799245
- http://www.wowotech.net/device_model/kobject.html
- http://www.wowotech.net/device_model/421.html
- Documentation\kobject.txt
核心版本:3.14
kobject, kset和ktype
要分析sysfs,首先就要分析kobject和kset,因為驅動裝置的層次結構的構成就是由這兩個東東來完成的。
sysfs與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。 */ struct kobj_type *ktype; /*sysfs中與該物件對應的檔案節點物件*/ // 在3.14以後的核心中,sysfs基於kernfs來實現。 struct kernfs_node *sd; /*物件的應用計數*/ struct kref kref; /* 記錄初始化與否。呼叫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 { atomic_t refcount; };
目前為止,Kobject主要提供如下功能:
- 構成層次結構:通過
parent
指標,可以將所有Kobject
以層次結構的形式組合起來。 - 生命週期管理:使用引用計數(reference count),來記錄Kobject被引用的次數,並在引用次數變為0時把它釋放。
- 和sysfs虛擬檔案系統配合,將每一個Kobject及其特性,以檔案的形式,開放到使用者空間。
沒有一個結構會嵌入多於一個kobject結構,如果這麼做了,關於這個物件的引用計數肯定會一團糟,你的code也會充滿bug,所以千萬不要這麼做。
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為該目錄下的檔案。 */
struct attribute **default_attrs;
/*child_ns_type/namespace 是 檔案系統名稱空間相關)略*/
const struct kobj_ns_type_operations *(*child_ns_type)(struct kobject *kobj);
const void *(*namespace)(struct kobject *kobj);
};
// 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;
};
當kobject的引用計數為0時,通過release
方法來釋放相關的資源。
attribute
為屬性,每個屬性在sysfs中都有對應的屬性檔案。
sysfs_op
的兩個方法用於實現讀取和寫入屬性檔案時應該採取的行為。
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;
};
kset通過標準的核心連結串列(struct list_head list
)來管理它的所有子節點,kobject通過kset成員變數指向它的kset容器。
大多數情況下,kobject都是它所屬kset(或者嚴格點,kset內嵌的kobject)的子節點。
kobject與kset的關係
下面這張圖非常經典。最下面的kobj
都屬於一個kset
,同時這些kobj的父物件就是kset
內嵌的kobj
。
通過連結串列,kset
可以獲取所有屬於它的kobj
。
從sysfs
角度而言,kset
代表一個資料夾,而下面的kobj
就是這個資料夾裡面的內容,而內容有可能是檔案也有可能是資料夾。
kobj
和kset
沒有嚴格的層級關係,也並不是完全的父子關係。如何理解這句話?
-
kobject
之間可以組成一個樹形結構:Kobj.parent = kobj'
-
kset
之間也可以組成一個樹形結構,但這是基於kobject
實現的:Kset.kobj.parent = Kset'.kobj
正因為kobj和kset並不是完全的父子關係,因此在註冊kobj時,將同時對parent及其所屬的kset增加引用計數。
若parent和kset為同一物件,則會對kset增加兩次引用計數。
kset算是kobj的“接盤俠”:
- 當kobj沒有所屬的parent時,才讓kset來接盤當parent;
- 如果連kset也沒有,那該kobj屬於頂層物件,其sysfs目錄將位於/sys/下。
待會我們看
kobject_add_internal
中是如何做的。
kset
內部本身也包含一個kobj
物件,在sysfs
中也表現為目錄;所不同的是,kset
要承擔kobj
狀態變動訊息的傳送任務。因此,首先kset會將所屬的kobj組織在kset.list
下,同時,通過uevent_ops
在合適時候傳送訊息。
對於kobject_add()
來說,它的輸入資訊是:kobj-parent
、kobj-name
,kobject_add()
優先使用傳入的parent
作為kobj->parent
;其次,使用kset
作為kobj->parent
。
kobj狀態變動後,必須依靠所關聯的kset來向用戶空間傳送訊息;若無關聯kset(該kobj
向上組成的樹中,任何成員都無所屬的kset),則kobj無法傳送使用者訊息。
kobject 結構可能的層次結構如圖:
舉例:通過kobject
建立bus
目錄
分析如何通過kobject
在/sys
建立bus
目錄。
// drivers/base/bus.c
static int bus_uevent_filter(struct kset *kset, struct kobject *kobj)
{
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);
// ...
return 0;
}
kset_create_and_add
kset_create_and_add
是kobject_create
和kobject_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.
*/
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.
*
* 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) {
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;
}
EXPORT_SYMBOL(kobject_add);
這裡主要呼叫了兩個函式,接下分別來看下。
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);
/*設定kobj->name*/
retval = kobject_set_name(&kset->kobj, "%s", name);
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;
}
Issues
讀原始碼時有個疑問,當呼叫kset_create()時,為何kset->kobj.kset=NULL;
原始碼中的解釋如下:
/*
* 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.
*/
但是當第三個引數parent_kobj
有值時,既然kset->kobj.parent=parent_kobj;
那麼說明kset->kobj是有上層kset的,而這個上層kset應該可以用container_of由parent_kobj得出。
但為何在原始碼中直接就將kset->kobj.kset置為NULL了呢。是因為kset不會從屬於其它kset麼。謝謝
wowo
@Issues:
首先,kset是一個特殊的kobject,它是kobject的容器,用於將多個kobject收集在一起。那麼kset是否還會從屬於另一個kset?即容器是否會巢狀?從程式碼註釋上可以看出,答案是否定的。從實際邏輯上考慮,這也是合理的。這就是kset->kobj.kset為NULL的原因。
其次,雖然kset->kobj.kset為NULL,但為了kset的自動free,它還是需要一個ktype的,這就是為什麼kset->kobj.ktype不為NULL。
最後,您提到的kset->kobj.parent=parent_kobj,也就是說該kset可能會有一個上層的parent(也是一個kset),這就是另外一個含義了,kset之間可以組成一個樹形結構,這不是kset要描述的事情,而是kobject要描述的。
loren
2017-05-10 20:09
@wowo:“kset->kobj.parent=parent_kobj,能說明該kset可能會有一個上層的parent(也是一個kset)”
為什麼這個上層的parent必須是一個kset呢?不能這個parent不屬於任何一個kset嗎?
wowo
2017-05-11 08:56
@loren:是的,你說的對,kernel沒有規定這個parent kobj必須是什麼,它只是為了讓kset有個樹形結構,因而可以是另一個kset,也有可能是一個普通的資料結構。
不過看kernel程式碼,大部分時候,都是一個kset。因為kset的上級是kset,結構上比較清晰。
這個函式中,動態分配了kset例項,呼叫kobject_set_name
設定kset->kobj->name為bus
,也就是我們要建立的目錄bus
。
同時這裡kset->kobj.parent
為NULL
,也就是沒有父物件。
因為要建立的bus目錄是在sysfs所在的根目錄建立的,自然沒有父物件。
隨後簡要看下由kobject_set_name函式呼叫引發的一系列呼叫。
// lib/kobject.c
/**
* kobject_set_name_vargs - Set the name of an 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 *old_name = kobj->name;
char *s;
if (kobj->name && !fmt)
return 0;
kobj->name = kvasprintf(GFP_KERNEL, fmt, vargs);
if (!kobj->name) {
kobj->name = old_name;
return -ENOMEM;
}
/* ewww... some of these buggers have '/' in the name ... */
while ((s = strchr(kobj->name, '/')))
s[0] = '!';
kfree(old_name);
return 0;
}
// lib/kasprintf.c
/* Simplified asprintf. */
char *kvasprintf(gfp_t gfp, const char *fmt, va_list ap)
{
unsigned int len;
char *p;
va_list aq;
va_copy(aq, ap);
len = vsnprintf(NULL, 0, fmt, aq);
va_end(aq);
p = kmalloc_track_caller(len+1, gfp);
if (!p)
return NULL;
vsnprintf(p, len+1, fmt, ap);
return p;
}
EXPORT_SYMBOL(kvasprintf);
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;
}
這裡面呼叫了3個函式。這裡先介紹前兩個函式。kobject_uevent
實現的是使用者空間事件傳遞,留到以後再說。
kset_init
該函式用於初始化kset。
/**
* 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->state_in_sysfs = 0;
kobj->state_add_uevent_sent = 0;
kobj->state_remove_uevent_sent = 0;
kobj->state_initialized = 1;
}
kobject_add_internal
該函式將在sysfs中建立目錄。
kobject_add_internal()
會根據kobj.parent
和kobj.kset
來綜合決定父物件(相當於/sysfs
中的父級目錄):
- 如果有
parent
則預設parent
為父物件; - 如果沒有
parent
作為父物件,但是有當前的kobj
位於kset
中,則使用kset
中的kobj
作為父物件 - 如果沒有
parent
也沒有kset
作為父物件,那該kobj
屬於頂層物件:在sysfs
中,我們可以看到kobj
位於/sys/
下。
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_dir
在sysfs中建立目錄。
在create_dir執
行完成以後會在sysfs的根目錄(/sys/)建立資料夾bus。該函式的詳細分析將在後面給出。
至此,對bus目錄的建立有了簡單而直觀的瞭解。
我們可以看出kset其實就是表示一個資料夾,而kset本身也含有一個kobject,而該kobject的name欄位即為該目錄的名字,本例中為bus。
kobj/kset功能特性
我們先大概提一下這些功能特性,在後面的文章中會詳細說明:物件生命週期管理
以及使用者空間事件投遞
。
物件生命週期管理
在建立一個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)
kref_get(&kobj->kref);
return 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) {
kref_put(&kobj->kref, kobject_release);
}
}
static void kobject_release(struct kref *kref)
{
struct kobject *kobj = container_of(kref, struct kobject, kref);
kobject_cleanup(kobj);
}
對於kobject_get()
,它就是直接使用kref_get()
介面來對引用計數進行加1操作;
而對於kobject_put()
,它不僅要使用kref_put()
介面來對引用計數進行減1操作,還要對生命終結的物件執行release()
操作:當引用計數減為0時,回收該物件的資源。
然而kobject是高度抽象的實體,導致kobject不會單獨使用,而是嵌在具體物件中。反過來也可以這樣理解:凡是需要做物件生命週期管理的物件,都可以通過內嵌kobject來實現需求。
下列的東西寫入 bus 驅動中
回到`kobject_put()`,它通常被具體物件做一個簡單包裝,如bus_put(),它直接呼叫kset_put(),然後呼叫到kobject_put()。那對於這個bus_type物件而言,僅僅通過kobject_put(),如何來達到釋放整個bus_type的目的呢?這裡就需要kobject另一個成員`struct kobj_type * ktype`來完成。
回到kobject_put()的release()操作。當引用計數為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`操作;
有了該操作,那麼前面的`bus_put()`在執行`bus_type->p-> subsys.kobj->ktype->release()`時,就會執行上面註冊的`bus_ktype.release = bus_releas`e函式,由於bus_release()函式由具體的bus子系統提供,它必定知道如何釋放包括kobj在內的bus_type物件。
sysfs檔案系統的層次組織
sysfs向用戶空間展示了驅動裝置的層次結構。這一個功能比較簡單,先完全貼出來。
我們都知道裝置和對應的驅動都是由核心管理的,這些對於使用者空間是不可見的。現在通過sysfs,可以在使用者空間直觀的瞭解裝置驅動的層次結構。
實際上,sysfs檔案系統的組織功能是基於kobject實現的:該功能依靠kobj的parent、kset、sd等成員來完成。sysfs檔案系統能夠以友好的介面,將kobj所刻畫的物件層次、所屬關係、屬性值,展現在使用者空間。
我們來看看sysfs的檔案結構:
$ uname -r
4.15.0-142-generic
$ ls /sys
block bus class dev devices firmware fs hypervisor kernel module power
目錄 | 意義 |
---|---|
block | 塊裝置 |
bus | 系統中的匯流排 |
class | 裝置型別,比如輸入裝置 |
dev | 系統中已註冊的裝置節點的檢視,有兩個子目錄char和block。 |
devices | 系統中所有裝置拓撲結構檢視 |
fireware | 韌體 |
fs | 檔案系統 |
kernel | 核心配置選項和狀態資訊 |
module | 模組 |
power | 系統的電源管理資料 |
使用者空間事件投遞
這方面的內容可參考《http://www.wowotech.net/device_model/uevent.html》,該博文已經詳細的說明了使用者空間事件投遞。
具體物件有事件發生時,相應的操作函式中(如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。該ops的uevent()方法定義如下:
> 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)
在該ops的uevent()方法中,會分別呼叫bus、class、device type的uevent方法來生成事件訊息。
總結
kobject
可以看成是一個基類,這個基類通過ktype
屬性實現了資源回收的功能,至於kset
,負責記錄一組kobject
,我們將其看成是一個集合,負責事件管理。
從此,其他物件就可以通過包含kobject
來實現上層的功能。
kobject_create_and_add解析
// lib/kobject.c
/**
* kobject_create_and_add - 動態建立一個kobject結構並註冊到sysfs
*
* @name: kobject的名稱
* @parent: kobject的parent kobject of this kobject, 如果有的話
*
* 該方法動態建立一個kobject結構並註冊到sysfs。當你完成該結構
* 之後. 呼叫kobject_put(),這樣該結構在不再使用時將會動態的釋放。
*
* 如果該kobject無法被建立,將會返回NULL。
*/
struct kobject *kobject_create_and_add(const char *name, struct kobject *parent)
{
struct kobject *kobj;
int retval;
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;
}
return kobj;
}
kobject_create
/**
* kobject_create - 動態建立一個kobject結構
*
* 動態建立一個kobject結構,並將其設定為一個帶預設釋放方法的
* 動態的kobject。
*
* 如果無法建立kobject,將會返回NULL。從這裡返回的kobject 結構釋放
* 必須呼叫kobject_put() 方法而不是kfree(),因為kobject_init()已經被呼叫
* 過了。
*/
struct kobject *kobject_create(void)
{
struct kobject *kobj;
kobj = kzalloc(sizeof(*kobj), GFP_KERNEL);
if (!kobj)
return NULL;
kobject_init(kobj, &dynamic_kobj_ktype);
return kobj;
}
kobject_init
/**
* kobject_init - 初始化一個kobject結構
* @kobj: 指向要初始化的kobject的指標
* @ktype: 指向該kobject 的ktype的指標
*
* 該方法會正確的初始化一個kobject來保證它可以被傳遞給kobject_add()
* 呼叫.
*
* 該功能被呼叫後,kobject必須通過呼叫kobject_put()來清理,而不是直接
* 呼叫kfree,來保證所有的記憶體都可以被正確的清理。
*/
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->ktype = ktype;
return;
error:
printk(KERN_ERR "kobject (%p): %s\n", kobj, err_str);
dump_stack();
}
kobject_init_internal
/* 設定初始化狀態 */
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;
}
/**
* 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, ...)
{
va_list args;
int retval;
if (!kobj)
return -EINVAL;
if (!kobj->state_initialized) {
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;
}
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_set_name_vargs - 設定一個kobject的名稱
* @kobj: 要設定名稱的kobject
* @fmt: 用來構建名稱的字串格式
* @vargs: 構建字串的引數
*/
int kobject_set_name_vargs(struct kobject *kobj, const char *fmt,
va_list vargs)
{
const char *old_name = kobj->name;
char *s;
if (kobj->name && !fmt)
return 0;
kobj->name = kvasprintf(GFP_KERNEL, fmt, vargs);
if (!kobj->name) {
kobj->name = old_name;
return -ENOMEM;
}
/* ewww... some of these buggers have '/' in the name ... */
while ((s = strchr(kobj->name, '/')))
s[0] = '!';
kfree(old_name);
return 0;
}
API與使用
由於我們現在關心的是裝置驅動模型,因此暫不瞭解。等到具體的程式碼中,我們再進行解析。
例子:https://blog.csdn.net/yldfree/article/details/84393321
https://www.cnblogs.com/helloahui/p/3674933.html
## Kobject的使用
### 使用流程
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中。
### kobject初始化
建立kobject當然必須初始化kobject物件,kobject的一些內部成員需要(強制)通過kobject_init()初始化:
\```c
void kobject_init(struct kobject *kobj, struct kobj_type *ktype);
\```
在呼叫kobject_init將kobject註冊到sysfs之後,必須呼叫kobject_add()新增:
\```c
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改名,則呼叫kobject_rename()函式實現:
\```c
int kobject_rename(struct kobject *kobj, const char *new_name);
\```
kobject_rename內部並不執行任何鎖定操作,也沒用何時名稱是有效的概念。因此呼叫者必須自己實現完整性檢查和保證序列性。
> 有一個函式叫kobject_set_name(),但是它遺留了一些缺陷,目前正在被移除。如果你的程式碼需要呼叫這個函式,它是不正確的,需要被修正。
可以通過kobject_name()函式來正確獲取kobject的名稱:
\```c
const char *kobject_name(const struct kobject * kobj);
\```
輔助函式`kobject_init_and_add`用來在同時初始化和新增kobject,它的引數和前面介紹的單獨使用kobject_init()、kobject_add()這兩個函式時一樣
\```c
int kobject_init_and_add(struct kobject *kobj, struct kobj_type *ktype,
struct kobject *parent, const char *fmt, ...);
\```
**建立簡單的kobject**
有些時候,開發者希望的只是在sysfs中建立一個目錄,並且不會被kset、show、store函式等細節的複雜概念所迷惑。
這裡有一個可以單獨建立kobject的例外,為了建立這樣一個入口,可以通過如下的函式來實現:
\```c
struct kobject *kobject_create_and_add(char *name, struct kobject *parent);
\```
這個函式會建立一個kobject,並把它的目錄放到指定父節點在sysfs中目錄裡面。
可以通過如下的函式簡單建立kobject相關聯的屬性:
\```c
int sysfs_create_file(struct kobject *kobj, struct attribute *attr);
\```
或者
\```c
int sysfs_create_group(struct kobject *kobj, struct attribute_group *grp);
\```
這兩種方法都包含了屬性型別,和通過`kobject_create_and_add()`建立的kobject型別;屬性型別可以直接使用kobj_attribute型別,所以不需要特別建立自定義屬性。
### 分配和釋放