sysfs源码笔记

sysfs就是linux中/sys/下的所有内容,官方文档中sysfs的定义如下:

sysfs 是一个最初基于 ramfs 且位于内存的文件系统。它提供导出内核
数据结构及其属性,以及它们之间的关联到用户空间的方法。只要内核配置中定义了 CONFIG_SYSFS ,sysfs 总是被编译进内核。你可
通过以下命令挂载它:

mount -t sysfs sysfs /sys

本文的代码是基于centos7,linux内核3.10

一、sysfs在linux中是什么样的

在linux中的/sys/目录下,可以看到很多子目录:

  • /block 所有的块设备
  • /bus 系统中所有的总线
  • /class 设备类型(比如scsi_class)
  • /device 系统中的所有设备
  • /driver 内核注册的驱动程序

之后可以看到,sysfs在linux的中的文件夹、文件全都是“虚拟出来的”,对文件的读写,不是真的读写,而是对函数的调用

二、sysfs的骨骼:kobject, kset, subsystem

sysfs在linux中的目录结构是怎么构造出来的,秘密就kobject、kset和subsystem中:

  • kobject sysfs最基本的结构体,对应sysfs中的一个目录,提供引用计数等重要功能(kobject.h中定义)
  • kset 一系列的kobject的集合,kobject的顶层类(kobject.h中定义)
  • subsystem 一系列的kset的集合,在3.10内核中已经没有subsystem的结构体了,变成了在drivers/base.h中定义的device_private和subsys_private结构体,不过这个概念还是存在于内核代码中,只是本质上也是管理几个kset。

kobject和kset的结构体如下:

struct kobject {
const char        *name;      //名称
struct list_head    entry;
struct kobject        *parent;
struct kset        *kset;      //属于哪个keset
struct kobj_type    *ktype; //relase\store\show函数,用于sysfs调用
struct sysfs_dirent    *sd;    //kobject的基础:目录
struct kref        kref;       //引用计数
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;
};

需要说明的几点:

  1. *sd 是kobject的基石,最终sysfs_add_file(kobj->sd, attr, SYSFS_KOBJ_ATTR); 目录就是这样创建出来的(file.c中定义)
  2. *kset 指向一个kset
  3. kref 调用kobject_get()会增加引用计数,调用kobject_put()会减少引用计数,减少到0会release这个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;
};

需要说明的几点:

  1. kset这个结构体本身比较简单,而且其本身也继承了一个kobject,所以kset自己也是一个目录
  2. subsystem也是一个kset,sys/目录下的bus、class的目录,就是一个subsystem

关于kset和kobject的关系,有一个经典的图,如下:

Paste_Image.png

三、kobject、kset的处理函数

关于kobject的几个处理函数如下(kobject.c中定义):

初始化一个kobject函数

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);  //初始化引用计数
    //初始化几个状态变量
    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:将一个kobject链接到一个父节点上(比如说kset的kobject节点)
并创建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);//调用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; //链接上父节点
    return kobject_add_internal(kobj);//调用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>");

    //调用kobj中的sd去创建一个目录
    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,创建kset的目录
    INIT_LIST_HEAD(&k->list); //初始化一个列表保存链接上来的kobject
    spin_lock_init(&k->list_lock);
}

kset注册函数,调用kset_init,kobject_add_internal后再调用kobject_uvent通知用户空间

kobject_uevent_env函数比较难懂,具体来说就是触发某个事件,把事件写在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结构体中的uevent_ops函数,热插拔函数!!!


    //如果是原始的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
可以看一个sysfs的例子,来自于ldd3中的lddbus示例,目标是在/sys/bus中创建一个自定义的bus,以及在/sys/device/下创建ldd0的块设备

1.创建一个device

//device结构体中包含一个struct kobject kobj;
struct device ldd_bus = { 
    .init_name = "ldd0",
    .release  = ldd_bus_release
};    

//注册这个device
ret = device_register(&ldd_bus);

//实际上这个函数和kobject_init和kobject_add是完全对应的,在device下创建ldd0块设备
int device_register(struct device *dev)
{
    device_initialize(dev);
    return device_add(dev);
}

2.创建一个bus_type

struct bus_type ldd_bus_type = {
    .name = "ldd",
    .match = ldd_match, //两个自定义函数
    .uevent  = ldd_uevent,
};

//bus_type的完整结构体,在Device.h中定义
struct bus_type {
    const char        *name;
    const char        *dev_name;
    struct device        *dev_root;   //这里有一个device的结构体
    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下,会创建一个ldd的文件夹,这里会有subsystem的概念,在ldd内通过subsys创建driver和device的目录,

static int __init ldd_bus_init(void)
{
    int ret;
    //注册bus_type
    ret = bus_register(&ldd_bus_type);
    if (ret)
        return ret;
    //创建属性
    if (bus_create_file(&ldd_bus_type, &bus_attr_version))
        printk(KERN_NOTICE "Unable to create version attribute\n");
    //创建了ldd0的块设备
    ret = device_register(&ldd_bus);
    if (ret)
        printk(KERN_NOTICE "Unable to register ldd0\n");
    return ret;
}    

//注册bus_type
int bus_register(struct bus_type *bus)
{
    int retval;
    struct subsys_private *priv;  //subsystem的概念在这里体现的,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“ldd”来命令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);//注册subsystem的kset,创建了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);//创建device文件夹
    if (!priv->devices_kset) {
        retval = -ENOMEM;
        goto bus_devices_fail;
    }

    priv->drivers_kset = kset_create_and_add("drivers", NULL,
                         &priv->subsys.kobj);//创建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下,创建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

之前说到一个kobject就是sysfs中的一个目录,一个kset是一组目录,现在看看sysfs中的一个目录/sys/bus/cpu/

/devices/ 目录

/drivers/ 目录

drivers_autoprobe 文件

drivers_probe 文件

uevent 文件

可以看到除了两个目录(在结构体subsys_private中定义的kset)之外,还有三个文件,这三个文件在sysfs中是如何实现的?实际上这是sysfs中的属性的概念。

//sysfs.h中定义
struct attribute {
    const char        *name; //定义了文件名称
    umode_t            mode;
}

在lddbus的实例代码中,需要在ldd文件夹下创建一个文件,需要定义一个bus_attribute的结构体,其原型如下:

struct bus_attribute {
    struct attribute    attr;  //继承了attribute结构体
    ssize_t (*show)(struct bus_type *bus, char *buf);
    ssize_t (*store)(struct bus_type *bus, const char *buf, size_t count);
};

这个结构体定义了两个函数,show和store,分别代表了对这个文件的读和写操作,在实例中定义了一个show函数,显示一个version的字符串:

static ssize_t show_bus_version(struct bus_type *bus, char *buf)
{
    return snprintf(buf, PAGE_SIZE, "%s\n", Version);
}

最后创建一个bus_attribute的结构体:

//这里有个技巧,利用BUS_ATTR的宏定义,生成一个名叫bus_attr_version的bus_attribute结构体,store函数为空,传入show函数
static BUS_ATTR(version, S_IRUGO, show_bus_version, NULL);

将这个结构体注册在bus_type中:

bus_create_file(&ldd_bus_type, &bus_attr_version)

就可以看到/sys/bus/ldd/version这个文件了,cat这个文件得的正是show_bus_version函数返回的内容。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,928评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,192评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,468评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,186评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,295评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,374评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,403评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,186评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,610评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,906评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,075评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,755评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,393评论 3 320
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,079评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,313评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,934评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,963评论 2 351

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,644评论 18 139
  • linux设备模型bus,device,driver作者 codercjg 在 10 十一月 2015, 2:43...
    codercjg阅读 567评论 0 1
  • linux设备模型bus,device,driver作者 codercjg 在 10 十一月 2015, 2:43...
    codercjg阅读 414评论 0 1
  • 转自,格式做了调整。 如果你使用Linux比较长时间了,那你就知道,在对待设备文件这块,Linux改变了几次策略。...
    mikeliuy阅读 7,780评论 0 1
  • linux资料总章2.1 1.0写的不好抱歉 但是2.0已经改了很多 但是错误还是无法避免 以后资料会慢慢更新 大...
    数据革命阅读 12,151评论 2 33