Linux 內核:設備驅動模型(1)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
- https://blog.csdn.net/qb_2008/article/details/6846412
- Documentation\kobject.txt
內核版本:3.14
kobject, kset和ktype
要分析sysfs,首先就要分析kobject和kset,因為驅動設備的層次結構的構成就是由這兩個東東來完成的。
sysfs與kobject密不可分。
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,所以千萬不要這么做。
實際上,kobject對自身實現什么功能並不感興趣,它存在的意義在於把高級的對象鏈接到設備模型上。因此內核代碼很少去創建一個單獨的kobject對象,相反,kobject用於控制對大型域相關對象的訪問(通過container_of)。
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.
*/
/**
* 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.
*
* 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;
}
這個函數中,動態分配了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是否注冊到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_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來實現需求。
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_releas
e函數;
// 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 =
container_of(kobj, typeof(*priv), subsys.kobj);
struct bus_type *bus = priv->bus;
// 釋放資源
kfree(priv);
bus->p = NULL;
}
由於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 | 系統的電源管理數據 |
用戶空間事件投遞
請參考:uevent與熱插拔
實際上,當具體對象有事件發生時,相應的操作函數中(如device_add()
),會調用事件消息接口kobject_uevent()
。
在該接口中,首先會添加一些共性的消息(路徑、子系統名等),然后會找尋合適的kset->uevent_ops
,並調用該ops->uevent(kset, kobj, env)
來添加該對象特有的事件消息(如device對象的設備號、設備名、驅動名、DT信息);
kobject總結
kobject
可以看成是一個基類,這個基類通過ktype
屬性實現了資源回收的功能,至於kset
,負責記錄一組kobject
,我們將其看成是一個集合,負責事件管理。
從此,其他對象就可以通過包含kobject
來實現上層的功能。
sysfs
對象模型體系
sysfs(system filesystem)是一個處於內存中的虛擬文件系統,為我們提供了kobject對象層次結構的視圖。以一個簡單的文件系統的方式來觀察系統中各種設備的拓撲結構。
sysfs是用於表現設備驅動模型的文件系統,它基於ramfs。要學習linux的設備驅動模型,就要先做好底層工作,總結sysfs提供給外界的API就是其中之一。
sysfs文件系統中提供了四類文件的創建與管理,分別是目錄、普通文件、軟鏈接文件、二進制文件。
- 目錄層次往往代表着設備驅動模型的結構
- 軟鏈接文件則代表着不同部分間的關系。比如某個設備的目錄只出現在/sys/devices下,其它地方涉及到它時只好用軟鏈接文件鏈接過去,保持了設備唯一的實例。
- 普通文件和二進制文件往往代表了設備的屬性,讀寫這些文件需要調用相應的屬性讀寫。
sysfs是表現設備驅動模型的文件系統,它的目錄層次實際反映的是對象的層次。為了配合這種目錄,linux專門提供了兩個結構作為sysfs的骨架,它們就是struct kobject和struct kset。
我們知道,sysfs是完全虛擬的,它的每個目錄其實都對應着一個kobject,要想知道這個目錄下有哪些子目錄,就要用到kset。從面向對象的角度來講,kset繼承了kobject的功能,既可以表示sysfs中的一個目錄,還可以包含下層目錄。
dentry結構體表示目錄項,通過連接kobject到指定的目錄項上,無疑方便的將kobject映射到該目錄上。
因此,kobjects其實已經形成了一棵樹了——就是對象模型體系。
由於kobject被映射到目錄項,同時對象層次結構也已經在內存中形成了一棵樹,因此sysfs就誕生了。
$ tree /sys | head -n 300
/sys
├── block
...
├── bus
│ ├── cpu
│ │ ├── devices
│ │ │ └── cpu0 -> ../../../devices/system/cpu/cpu0
│ │ ├── drivers
│ │ │ └── processor
│ │ │ ├── bind
│ │ │ ├── cpu0 -> ../../../../devices/system/cpu/cpu0
│ │ │ ├── uevent
│ │ │ └── unbind
│ │ ├── drivers_autoprobe
│ │ ├── drivers_probe
│ │ └── uevent
...
$ ls /sys
block bus class dev devices firmware fs kernel module power
sysfs的根目錄下包含了七個目錄:block、bus、class、devices、firmware、module和power。
- block目錄下的每個子目錄都對應着系統的一個塊設備。
- bus目錄提供了一個系統總線視圖。
- class目錄包含了以高層功能邏輯組織起來的系統設備視圖。
- devices目錄是系統中設備拓撲結構視圖,它直接映射除了內核中設備結構體的組織層次。
- firmware目錄包含了一些諸如ACPI、EDD、EFI等低層子系統的特殊樹。
- power目錄包含了系統范圍的電源管理數據。
- 最重要的目錄是devices,該目錄將設備模型導出到用戶空間。目錄結構就是系統中實際的設備拓撲。
kernel 包括了一些內核的可調參數等信息,和devices目錄關聯性沒那么強。
注意,其他目錄中的很多數據都是將devices
目錄下的數據加以轉換加工而得;
因此,在class章節中,我們會看到一些軟鏈接的創建,其實就是為了優雅地歸對設備進行歸類。
sysfs中添加和刪除kobject
導入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
kobject在sysfs中的位置取決於kobject在對象層次結構中的位置。如果kobject的父指針被設置,那么在sysfs中的kobject將被映射為其父目錄下的子目錄。如果parent沒有設置,那么kobject將被映射為kset->kobj中的子目錄。
如果給定的kobject中parent或kset字段都沒有設置,那么就認為kobject沒有父對象,所以就會被映射成sysfs下的根級目錄。但這往往不是你所需要的,所以在調用kobject_add()前parent或kset字段應該顯示的設置。
不管怎樣,sysfs中代表kobject的目錄名字是由kobj->k_name
指定的。
不過不需要調用kobject_init()
和kobject_add()
,因為系統提供了函數kobject_create_and_add()
。
該函數既初始化了給定的kobject對象,同時又將其加入到對象層次結構中。
從sysfs刪除kobject
void kobject_del(struct kobject *kobj);
從sysfs中刪除一個kobject對應文件目錄。需使用函數kobject_del(),內部會自動使用put來讓引用減一。
以前的版本需要再調用一次
kobject_put
;但現在不需要了。
向sysfs中添加文件
kobject已經被映射為文件目錄。所有的對象層次一個不少的映射成sys下的目標結構。
sysfs僅僅是一個漂亮的樹,但是沒有提供實際數據的文件。
因此,需要kobj_type
來進行支持,有關sysfs的成員如下:
// include/linux/kobject.h
struct kobj_type {
// ...
/* 該類型kobj的sysfs操作接口。 */
const struct sysfs_ops *sysfs_ops;
/* 該類型kobj自帶的缺省屬性(文件),這些屬性文件在注冊kobj時,直接pop為該目錄下的文件。 */
struct attribute **default_attrs;
};
默認屬性attribute
默認的文件集合是通過Kobject和kset的ktype字段提供的。因此所有具有相同類型的kobject在它們對應的sysfs目錄下都擁有相同的默認文件集合。
kobj_type字段含有一個字段——default_attrs,它是一個attribute結構體數組。這些屬性負責將內核數據映射成sysfs中的文件。
attribute原型
// include/linux/sysfs.h
#include <linux/sysfs.h>
struct attribute {
/* 屬性名稱 */
const char *name;
/* 訪問權限 */
umode_t mode;
// ...
};
struct attribute_group {
const char *name;
umode_t (*is_visible)(struct kobject *,
struct attribute *, int);
struct attribute **attrs;
struct bin_attribute **bin_attrs;
};
struct bin_attribute {
struct attribute attr;
size_t size;
void *private;
ssize_t (*read)(struct file *, struct kobject *, struct bin_attribute *,
char *, loff_t, size_t);
ssize_t (*write)(struct file *, struct kobject *, struct bin_attribute *,
char *, loff_t, size_t);
int (*mmap)(struct file *, struct kobject *, struct bin_attribute *attr,
struct vm_area_struct *vma);
};
其中,name:提供了該屬性的名稱,最終出現在sysfs中的文件名就是它。
owner(todo):在存在所屬模塊的情況下指向其所屬module結構體。如果一個模塊沒有該屬性,那么該字段為NULL。
mode字段類型為mode_t,它表示了sysfs中該文件的權限。
- 對於只讀屬性而言,如果是所有人都可讀它,那么該字段設為S_IRUGO;
- 如果只限於所有者可讀,則該字段設置為S_IRUSR。
- 同樣對於可寫屬性,可能會設置該字段為S_IRUGO|S_IWUSR。
- sysfs中的所有文件和目錄的uid與gid標志均為零。
如何理解屬性?
$ ls /sys/class/leds/input1::capslock
brightness device max_brightness power subsystem trigger uevent
其中,brightness
、trigger
這些文件就是屬性,以文件的形式提供。
屬性讀寫方法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中包含show和store兩個函數指針,它們分別在sysfs文件讀和文件寫時調用。
創建/刪除新屬性
一些特別情況下會碰到特殊的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對象。
在該函數被調用之前,給定的屬性將被賦值,如果成功,該函數返回零。否則返回負的錯誤碼。
kobject中ktype.sysfs_ops
操作將負責處理新屬性。現有的show()
和store()
方法必須能夠處理新屬性。
刪除一個屬性通過函數sysfs_remove_file()完成:一旦調用sysfs_remove_file
,給定的屬性將不復存在於給定的kobject目錄中。
創建/刪除軟鏈接
除了添加文件外,可能還要創建符號連接。在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目錄中。
sysfs約定
首先sysfs屬性應該保證每個屬性文件只導出一個值,該值應該是文本形式而且被映射為簡單C類型。其次,在sysfs中要以一個清晰的層次組織數據。最后,記住sysfs提供內核到用戶空間的服務,這多少有些用戶空間的ABI(應用程序二進制接口)的作用。
附錄:kobject創建
簡單創建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");
內核kobj例子
kobject-example.c
代碼來自:samples/kobject/kobject-example.c
。
可能會遇到
error: negative width in bit-field ‘<anonymous>’
這個錯誤,需要將__ATTR
中的666
權限改為664
。
/*
* Sample kobject implementation
*
* Copyright (C) 2004-2007 Greg Kroah-Hartman <greg@kroah.com>
* Copyright (C) 2007 Novell Inc.
*
* Released under the GPL version 2 only.
*
*/
#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 sprintf(buf, "%d\n", foo);
}
static ssize_t foo_store(struct kobject *kobj, struct kobj_attribute *attr,
const char *buf, size_t count)
{
sscanf(buf, "%du", &foo);
return count;
}
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 sprintf(buf, "%d\n", var);
}
static ssize_t b_store(struct kobject *kobj, struct kobj_attribute *attr,
const char *buf, size_t count)
{
int var;
sscanf(buf, "%du", &var);
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");
MODULE_AUTHOR("Greg Kroah-Hartman <greg@kroah.com>");
對應的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)"
測試
編譯以后,加載模塊:
$ sudo insmod myKobj.ko
發現創建了/sys/kernel/kobject_example/
目錄,而且目錄中有3個屬性文件。
$ ls /sys/kernel/kobject_example/
bar baz foo
讀寫這些屬性
$ cat /sys/kernel/kobject_example/bar
0
$ cat /sys/kernel/kobject_example/baz
0
$ cat /sys/kernel/kobject_example/foo
0
$ sudo su
# echo "2021" > /sys/kernel/kobject_example/foo
# cat /sys/kernel/kobject_example/foo
2021
內核kset例子
kset-example.c
代碼來自:samples/kobject/kset-example.c
。
可能會遇到
error: negative width in bit-field ‘<anonymous>’
這個錯誤,需要將__ATTR
中的666
權限改為664
。
/*
* Sample kset and ktype implementation
*
* Copyright (C) 2004-2007 Greg Kroah-Hartman <greg@kroah.com>
* Copyright (C) 2007 Novell Inc.
*
* Released under the GPL version 2 only.
*
*/
#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 sprintf(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)
{
sscanf(buf, "%du", &foo_obj->foo);
return count;
}
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 sprintf(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;
sscanf(buf, "%du", &var);
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 */
};
/*
* 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_attrs = foo_default_attrs,
};
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");
MODULE_AUTHOR("Greg Kroah-Hartman <greg@kroah.com>");
對應的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)"
測試
編譯以后,加載模塊:
$ sudo insmod myKset.ko
發現創建了/sys/kernel/kset_example/
目錄,而且目錄中有3個子目錄,每一個子目錄中都有3個屬性文件。
$ tree /sys/kernel/kset_example/
/sys/kernel/kset_example/
├── bar
│ ├── bar
│ ├── baz
│ └── foo
├── baz
│ ├── bar
│ ├── baz
│ └── foo
└── foo
├── bar
├── baz
└── foo
讀寫就不再啰嗦了,是一樣的。
附錄:一部分關於kobject的API
由於我們現在關心的是設備驅動模型,因此暫不了解。等到具體的代碼中,我們再進行解析。
動態創建
struct kobject *kobject_create(void);
動態創建一個kobject結構,並將其設置為一個帶默認釋放方法(dynamic_kobj_ktype)的動態的kobject。
如果無法創建kobject,將會返回NULL。從這里返回的kobject 結構釋放必須調用kobject_put() 方法而不是kfree(),因為kobject_init()已經被調用過了。
初始化
創建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, ...);
添加到sysfs
在調用kobject_init將kobject注冊到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()
。
#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
創建簡單的kobject
有些時候,開發者希望的只是在sysfs中創建一個目錄,並且不會被kset、show、store函數等細節的復雜概念所迷惑。
這里有一個可以單獨創建kobject的例外,為了創建這樣一個入口,可以通過如下的函數來實現:
struct kobject *kobject_create_and_add(char *name, struct kobject *parent);
這個函數會創建一個kobject,並把它的目錄放到指定父節點在sysfs中目錄里面。
重命名
如果你必須為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);