sysfs就是linux中/sys/下的所有內(nèi)容,官方文檔中sysfs的定義如下:
sysfs 是一個(gè)最初基于 ramfs 且位于內(nèi)存的文件系統(tǒng)。它提供導(dǎo)出內(nèi)核
數(shù)據(jù)結(jié)構(gòu)及其屬性,以及它們之間的關(guān)聯(lián)到用戶空間的方法。只要內(nèi)核配置中定義了 CONFIG_SYSFS ,sysfs 總是被編譯進(jìn)內(nèi)核。你可
通過(guò)以下命令掛載它:
mount -t sysfs sysfs /sys
本文的代碼是基于centos7,linux內(nèi)核3.10
一、sysfs在linux中是什么樣的
在linux中的/sys/目錄下,可以看到很多子目錄:
- /block 所有的塊設(shè)備
- /bus 系統(tǒng)中所有的總線
- /class 設(shè)備類(lèi)型(比如scsi_class)
- /device 系統(tǒng)中的所有設(shè)備
- /driver 內(nèi)核注冊(cè)的驅(qū)動(dòng)程序
之后可以看到,sysfs在linux的中的文件夾、文件全都是“虛擬出來(lái)的”,對(duì)文件的讀寫(xiě),不是真的讀寫(xiě),而是對(duì)函數(shù)的調(diào)用
二、sysfs的骨骼:kobject, kset, subsystem
sysfs在linux中的目錄結(jié)構(gòu)是怎么構(gòu)造出來(lái)的,秘密就kobject、kset和subsystem中:
- kobject sysfs最基本的結(jié)構(gòu)體,對(duì)應(yīng)sysfs中的一個(gè)目錄,提供引用計(jì)數(shù)等重要功能(kobject.h中定義)
- kset 一系列的kobject的集合,kobject的頂層類(lèi)(kobject.h中定義)
- subsystem 一系列的kset的集合,在3.10內(nèi)核中已經(jīng)沒(méi)有subsystem的結(jié)構(gòu)體了,變成了在drivers/base.h中定義的device_private和subsys_private結(jié)構(gòu)體,不過(guò)這個(gè)概念還是存在于內(nèi)核代碼中,只是本質(zhì)上也是管理幾個(gè)kset。
kobject和kset的結(jié)構(gòu)體如下:
struct kobject {
const char *name; //名稱(chēng)
struct list_head entry;
struct kobject *parent;
struct kset *kset; //屬于哪個(gè)keset
struct kobj_type *ktype; //relase\store\show函數(shù),用于sysfs調(diào)用
struct sysfs_dirent *sd; //kobject的基礎(chǔ):目錄
struct kref kref; //引用計(jì)數(shù)
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;
};
需要說(shuō)明的幾點(diǎn):
- *sd 是kobject的基石,最終sysfs_add_file(kobj->sd, attr, SYSFS_KOBJ_ATTR); 目錄就是這樣創(chuàng)建出來(lái)的(file.c中定義)
- *kset 指向一個(gè)kset
- kref 調(diào)用kobject_get()會(huì)增加引用計(jì)數(shù),調(diào)用kobject_put()會(huì)減少引用計(jì)數(shù),減少到0會(huì)release這個(gè)kobj
kset:
struct kset {
struct list_head list; //保存了一些列的kobject的列表
spinlock_t list_lock;
struct kobject kobj; //keset繼承了kobj
const struct kset_uevent_ops *uevent_ops;
};
需要說(shuō)明的幾點(diǎn):
- kset這個(gè)結(jié)構(gòu)體本身比較簡(jiǎn)單,而且其本身也繼承了一個(gè)kobject,所以kset自己也是一個(gè)目錄
- subsystem也是一個(gè)kset,sys/目錄下的bus、class的目錄,就是一個(gè)subsystem
關(guān)于kset和kobject的關(guān)系,有一個(gè)經(jīng)典的圖,如下:

三、kobject、kset的處理函數(shù)
關(guān)于kobject的幾個(gè)處理函數(shù)如下(kobject.c中定義):
初始化一個(gè)kobject函數(shù)
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;//賦值ktype
return;
error:
printk(KERN_ERR "kobject (%p): %s\n", kobj, err_str);
dump_stack();
}
static void kobject_init_internal(struct kobject *kobj)
{
if (!kobj)
return;
kref_init(&kobj->kref); //初始化引用計(jì)數(shù)
//初始化幾個(gè)狀態(tài)變量
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:將一個(gè)kobject鏈接到一個(gè)父節(jié)點(diǎn)上(比如說(shuō)kset的kobject節(jié)點(diǎn))
并創(chuàng)建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);//調(diào)用kobject_add_varg
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; //鏈接上父節(jié)點(diǎn)
return kobject_add_internal(kobj);//調(diào)用kobject_add_internal
}
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);
/* join kset if set, use it as parent if we do not already have one */
//如果kobj有kset,parent指向kset的kobject
if (kobj->kset) {
if (!parent)
parent = kobject_get(&kobj->kset->kobj);
kobj_kset_join(kobj);
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>");
//調(diào)用kobj中的sd去創(chuàng)建一個(gè)目錄
error = create_dir(kobj);
if (error) {
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;
}
kset的初始化
void kset_init(struct kset *k)
{
kobject_init_internal(&k->kobj);//初始化kset自己的kobject,創(chuàng)建kset的目錄
INIT_LIST_HEAD(&k->list); //初始化一個(gè)列表保存鏈接上來(lái)的kobject
spin_lock_init(&k->list_lock);
}
kset注冊(cè)函數(shù),調(diào)用kset_init,kobject_add_internal后再調(diào)用kobject_uvent通知用戶空間
kobject_uevent_env函數(shù)比較難懂,具體來(lái)說(shuō)就是觸發(fā)某個(gè)事件,把事件寫(xiě)在env里面,然后通知用戶空間。
int kset_register(struct kset *k)
{
int err;
if (!k)
return -EINVAL;
kset_init(k); //初始化kobject
err = kobject_add_internal(&k->kobj);//將keset自己的kobject鏈接到自己的kset
if (err)
return err;
kobject_uevent(&k->kobj, KOBJ_ADD);//通知用戶空間
return 0;
}
int kobject_uevent(struct kobject *kobj, enum kobject_action action)
{
return kobject_uevent_env(kobj, action, NULL);
}
int kobject_uevent_env(struct kobject *kobj, enum kobject_action action, char *envp_ext[])
{
struct kobj_uevent_env *env;
const char *action_string = kobject_actions[action];
const char *devpath = NULL;
const char *subsystem;
struct kobject *top_kobj;
struct kset *kset;
const struct kset_uevent_ops *uevent_ops;
int i = 0;
int retval = 0;
/* search the kset we belong to */
top_kobj = kobj;
kset = top_kobj->kset;
uevent_ops = kset->uevent_ops; //kset結(jié)構(gòu)體中的uevent_ops函數(shù),熱插拔函數(shù)!!!
//如果是原始的subsystem
/* originating subsystem */
if (uevent_ops && uevent_ops->name)
subsystem = uevent_ops->name(kset, kobj);
else
subsystem = kobject_name(&kset->kobj);
if (!subsystem) {
pr_debug("kobject: '%s' (%p): %s: unset subsystem caused the "
"event to drop!\n", kobject_name(kobj), kobj,
__func__);
return 0;
}
/* environment buffer */
env = kzalloc(sizeof(struct kobj_uevent_env), GFP_KERNEL);
if (!env)
return -ENOMEM;
/* complete object path */
devpath = kobject_get_path(kobj, GFP_KERNEL);
if (!devpath) {
retval = -ENOENT;
goto exit;
}
/* default keys */
retval = add_uevent_var(env, "ACTION=%s", action_string); //action_string='add'
if (retval)
goto exit;
retval = add_uevent_var(env, "DEVPATH=%s", devpath);
if (retval)
goto exit;
retval = add_uevent_var(env, "SUBSYSTEM=%s", subsystem);
if (retval)
goto exit;
........省略
retval = call_usermodehelper(argv[0], argv, env->envp, UMH_WAIT_EXEC);
}
三、sysfs的例子
源碼在https://github.com/martinezjavier/ldd3/tree/master/lddbus
可以看一個(gè)sysfs的例子,來(lái)自于ldd3中的lddbus示例,目標(biāo)是在/sys/bus中創(chuàng)建一個(gè)自定義的bus,以及在/sys/device/下創(chuàng)建ldd0的塊設(shè)備
1.創(chuàng)建一個(gè)device
//device結(jié)構(gòu)體中包含一個(gè)struct kobject kobj;
struct device ldd_bus = {
.init_name = "ldd0",
.release = ldd_bus_release
};
//注冊(cè)這個(gè)device
ret = device_register(&ldd_bus);
//實(shí)際上這個(gè)函數(shù)和kobject_init和kobject_add是完全對(duì)應(yīng)的,在device下創(chuàng)建ldd0塊設(shè)備
int device_register(struct device *dev)
{
device_initialize(dev);
return device_add(dev);
}
2.創(chuàng)建一個(gè)bus_type
struct bus_type ldd_bus_type = {
.name = "ldd",
.match = ldd_match, //兩個(gè)自定義函數(shù)
.uevent = ldd_uevent,
};
//bus_type的完整結(jié)構(gòu)體,在Device.h中定義
struct bus_type {
const char *name;
const char *dev_name;
struct device *dev_root; //這里有一個(gè)device的結(jié)構(gòu)體
struct bus_attribute *bus_attrs;
struct device_attribute *dev_attrs;
struct driver_attribute *drv_attrs;
int (*match)(struct device *dev, struct device_driver *drv);
int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
int (*probe)(struct device *dev);
int (*remove)(struct device *dev);
void (*shutdown)(struct device *dev);
int (*suspend)(struct device *dev, pm_message_t state);
int (*resume)(struct device *dev);
const struct dev_pm_ops *pm;
struct iommu_ops *iommu_ops;
struct subsys_private *p;
struct lock_class_key lock_key;
};
3.在bus下,會(huì)創(chuàng)建一個(gè)ldd的文件夾,這里會(huì)有subsystem的概念,在ldd內(nèi)通過(guò)subsys創(chuàng)建driver和device的目錄,
static int __init ldd_bus_init(void)
{
int ret;
//注冊(cè)bus_type
ret = bus_register(&ldd_bus_type);
if (ret)
return ret;
//創(chuàng)建屬性
if (bus_create_file(&ldd_bus_type, &bus_attr_version))
printk(KERN_NOTICE "Unable to create version attribute\n");
//創(chuàng)建了ldd0的塊設(shè)備
ret = device_register(&ldd_bus);
if (ret)
printk(KERN_NOTICE "Unable to register ldd0\n");
return ret;
}
//注冊(cè)bus_type
int bus_register(struct bus_type *bus)
{
int retval;
struct subsys_private *priv; //subsystem的概念在這里體現(xiàn)的,bus_type中繼承了subsys_private
struct lock_class_key *key = &bus->lock_key;
priv = kzalloc(sizeof(struct subsys_private), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->bus = bus;
bus->p = priv;
BLOCKING_INIT_NOTIFIER_HEAD(&priv->bus_notifier);
retval = kobject_set_name(&priv->subsys.kobj, "%s", bus->name); //用bus的name“l(fā)dd”來(lái)命令priv中的kset:subsys
if (retval)
goto out;
priv->subsys.kobj.kset = bus_kset;
priv->subsys.kobj.ktype = &bus_ktype;
priv->drivers_autoprobe = 1;
retval = kset_register(&priv->subsys);//注冊(cè)subsystem的kset,創(chuàng)建了ldd的文件夾
if (retval)
goto out;
retval = bus_create_file(bus, &bus_attr_uevent);
if (retval)
goto bus_uevent_fail;
priv->devices_kset = kset_create_and_add("devices", NULL,
&priv->subsys.kobj);//創(chuàng)建device文件夾
if (!priv->devices_kset) {
retval = -ENOMEM;
goto bus_devices_fail;
}
priv->drivers_kset = kset_create_and_add("drivers", NULL,
&priv->subsys.kobj);//創(chuàng)建driver文件夾
if (!priv->drivers_kset) {
retval = -ENOMEM;
goto bus_drivers_fail;
}
INIT_LIST_HEAD(&priv->interfaces);
__mutex_init(&priv->mutex, "subsys mutex", key);
klist_init(&priv->klist_devices, klist_devices_get, klist_devices_put);
klist_init(&priv->klist_drivers, NULL, NULL);
retval = add_probe_files(bus);//在bus下,創(chuàng)建ldd文件夾
if (retval)
goto bus_probe_files_fail;
retval = bus_add_attrs(bus);
if (retval)
goto bus_attrs_fail;
pr_debug("bus: '%s': registered\n", bus->name);
return 0;
}
四、sysfs的葉子 —— attribute
之前說(shuō)到一個(gè)kobject就是sysfs中的一個(gè)目錄,一個(gè)kset是一組目錄,現(xiàn)在看看sysfs中的一個(gè)目錄/sys/bus/cpu/
/devices/ 目錄
/drivers/ 目錄
drivers_autoprobe 文件
drivers_probe 文件
uevent 文件
可以看到除了兩個(gè)目錄(在結(jié)構(gòu)體subsys_private中定義的kset)之外,還有三個(gè)文件,這三個(gè)文件在sysfs中是如何實(shí)現(xiàn)的?實(shí)際上這是sysfs中的屬性的概念。
//sysfs.h中定義
struct attribute {
const char *name; //定義了文件名稱(chēng)
umode_t mode;
}
在lddbus的實(shí)例代碼中,需要在ldd文件夾下創(chuàng)建一個(gè)文件,需要定義一個(gè)bus_attribute的結(jié)構(gòu)體,其原型如下:
struct bus_attribute {
struct attribute attr; //繼承了attribute結(jié)構(gòu)體
ssize_t (*show)(struct bus_type *bus, char *buf);
ssize_t (*store)(struct bus_type *bus, const char *buf, size_t count);
};
這個(gè)結(jié)構(gòu)體定義了兩個(gè)函數(shù),show和store,分別代表了對(duì)這個(gè)文件的讀和寫(xiě)操作,在實(shí)例中定義了一個(gè)show函數(shù),顯示一個(gè)version的字符串:
static ssize_t show_bus_version(struct bus_type *bus, char *buf)
{
return snprintf(buf, PAGE_SIZE, "%s\n", Version);
}
最后創(chuàng)建一個(gè)bus_attribute的結(jié)構(gòu)體:
//這里有個(gè)技巧,利用BUS_ATTR的宏定義,生成一個(gè)名叫bus_attr_version的bus_attribute結(jié)構(gòu)體,store函數(shù)為空,傳入show函數(shù)
static BUS_ATTR(version, S_IRUGO, show_bus_version, NULL);
將這個(gè)結(jié)構(gòu)體注冊(cè)在bus_type中:
bus_create_file(&ldd_bus_type, &bus_attr_version)
就可以看到/sys/bus/ldd/version這個(gè)文件了,cat這個(gè)文件得的正是show_bus_version函數(shù)返回的內(nèi)容。