kobject system analysis

1 Overview

To say that kobject has to say sysfs

sysfs is a memory-based file system that was originally based on ramfs.It provides a way to export kernel data structures and their attributes, as well as their association to user space.

sysfs is always closely related to the underlying structure of kobject.

Any kobject registered in the system will have a directory created in sysfs.This directory is created as a subdirectory of the directory where the kobject's parent object resides to accurately pass the object hierarchy of the kernel to user space.The top-level directory in sysfs represents a common ancestor of the core object hierarchy; for example, some objects belong to a subsystem.
The properties of kobject can be exported as a normal file in the file system.Sysfs defines file-oriented I/O operations for attributes to provide read and write access to kernel attributes.

The above excerpt from the kernel document Documentation/zh_CN/filesystems/sysfs.txt mainly demonstrates the role of kobject, which is actually the creation of a directory in sysfs for registering kobject.Adding attributes to the directory is used to create common files in sysfs, and reading and writing these attributes files will invoke the read and write callback functions set in the kernel for the attributes. So the attribute files are used for kernel space and user space communication.Anyone who knows the linux system should know that sysfs files are typically used to dynamically configure the kernel.This document does not explain the concept of attribute groups, which are a set of attributes that create files in the directory of the attribute group they belong to.(sysfs root directory general question/sys).

So the topology of the kobject system is roughly as follows (note that the kobject parent in the diagram is also a kobject, but just the parent of the kobject below):

Both kobject and attribute_group correspond to directory, and attribute corresponds to ordinary file. Under kobject, you can either place attribute group or attribute, which is the sysfs file system tree structure described by kobject.

2 Use

With this background, let's see how to add a kobject to sysfs, which provides two more common interfaces for creating sysfs files

int sysfs_create_dir_ns(struct kobject *kobj, const char *new_name,
                        const void *new_ns)
int sysfs_create_file(struct kobject * kobj, const struct attribute * attr)
int sysfs_create_group(struct kobject *kobj, struct attribute_group *grp)

(kobject uses the sysfs_create_dir_ns function to create the kobject directory, the sysfs_create_file function to create the attribute file, and the sysfs_create_group function to create the attribute group directory and the attribute files underneath the group.

void kobject_init(struct kobject *kobj, struct kobj_type *ktype)
int kobject_add(struct kobject *kobj, struct kobject *parent,
                const char *fmt, ...)
                
struct kobject *kobject_create(void)

struct kobject *kobject_create_and_add(const char *name, struct kobject *parent)  
int kobject_init_and_add(struct kobject *kobj, struct kobj_type *ktype,
                         struct kobject *parent, const char *fmt, ...)
                                    
int kobject_rename(struct kobject *kobj, const char *new_name)

char *kobject_get_path(struct kobject *kobj, gfp_t gfp_mask)
int kobject_set_name(struct kobject *kobj, const char *fmt, ...)  

struct kobject *kobject_get(struct kobject *kobj)
void kobject_put(struct kobject *kobj)
void kobject_del(struct kobject *kobj)                                  

The functions exported by kobject are divided into the above five groups, each separated by blank lines.

  • kobject_init and kobject_add are steps to initially initialize kobject and add kobject to sysfs.
  • kobject_create provides an encapsulation for creating and initializing kobjects and provides some default behavior for creating and initializing kobjects.
  • kobject_create_and_add encapsulates the kobject_create and kobject_add functions and provides some default behavior for creating and initializing kobjects and adding kobjects to sysfs.
  • kobject_init_and_add encapsulates the kobject_init and kobject_add functions and provides some default behavior for kobject initialization and adding kobject to sysfs.
  • kobject_rename is used to modify the kobject directory name, kobject_get_path is used to get the file path corresponding to kobject, and kobject_set_name sets the kobject directory name.
  • kobject_get increases the reference count, kobject_put decreases the reference count, and calls kobject_del when the count is 0. Users can also call kobject_del to force the deletion of kobject.

This shows that the kobject system provides us with both simple creation functions and highly customizable interfaces.The easiest way to add a kobject to sysfs is as follows:

struct kobject *kobject_create_and_add(const char *name, struct kobject *parent)  

The more complex sequences are as follows:

kobj =new a kobject
ktype = new a ktype
void kobject_init(struct kobject *kobj, struct kobj_type *ktype)
int kobject_add(struct kobject *kobj, struct kobject *parent, const char *fmt, ...)

Or:

kobj  = kobject_create(void)
int kobject_add(struct kobject *kobj, struct kobject *parent, const char *fmt, ...)

Or:

kobj =new a kobject
ktype = new a ktype
int kobject_init_and_add(struct kobject *kobj, struct kobj_type *ktype, struct kobject *parent, const char *fmt, ...)

3 Source Analysis

Since kobject_create_and_add encapsulates the whole process of creating and adding a kobject, we analyze it as a scenario.

/**
 * kobject_create_and_add - create a struct kobject dynamically and register it with sysfs
 *
 * @name: the name for the kobject
 * @parent: the parent kobject of this kobject, if any.
 *
 * This function creates a kobject structure dynamically and registers it
 * with sysfs.  When you are finished with this structure, call
 * kobject_put() and the structure will be dynamically freed when
 * it is no longer being used.
 *
 * If the kobject was not able to be created, NULL will be returned.
 */
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;
}

This function has two parameters named kobject, which is also the name of the folder to be created in sysfs. Parent is the kobject corresponding to the parent directory. Only the parent directory can be determined in the file system.
This function is simple, just calls the kobject_create and kobject_add functions (kobject_create calls kobject_init).When the kobject_add function fails to add, it is released through kobject_put.

kobject_create is the process of creating and initializing kobject with the following code:

 * 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);
    if (!kobj)
        return NULL;

    kobject_init(kobj, &dynamic_kobj_ktype);
    return kobj;
}

The function first calls kzalloc to request kobject's memory. In calling kobject_init initialization, note that the second parameter of kobject_init is dynamic_kobj_ktype, which is used to set some callback functions of kobject in sysfs operations and some configuration of 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->ktype = ktype;
    return;

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

The state_initialized field of the kobject is used to record whether the kobject has been initialized. If the print warning log is initialized twice, the function only checks for exceptions. The real initialization is in the kobject_init_internal, and the kobject->ktype field is set.
The kobj_type is defined as follows:

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 is a callback function released by kobject and used to clean up work (usually kfree (kobject).
  • sysfs_ops is a callback when reading and writing a property file in the kobject directory.
  • default_attrs kobject default property, which creates the corresponding property file when adding kobject, and additional properties require the user to call sysfs_create_file or sysfs_create_group to add them.
  • The child_ns_type command is spatially related.
  • namespace command space correlation.

Next let's look at the specific process of initialization, kobject_init_internal

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;
}

This function is very simple and will not be explained.Before analyzing kobject_add, let's look at the default kobj_type set in kobject_create, which is dynamic_kobj_ktype.

static struct kobj_type dynamic_kobj_ktype = {
        .release        = dynamic_kobj_release,
        .sysfs_ops      = &kobj_sysfs_ops,
};
const struct sysfs_ops kobj_sysfs_ops = {
        .show   = kobj_attr_show,
        .store  = kobj_attr_store,
};

The default kobject setting has a sysfs read-write callback of kobj_sysfs_ops, a release function of dynamic_kobj_release, and a simple implementation of the dynamic_kobj_release function: kfree releases kobject memory, which is not listed here. The implementation of kobj_sysfs_ops will be explained later.

Earlier, we looked at the process of creating and initializing a kobject, which needs to be added to sysfs to make it work.

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

The function first guarantees that the call execution first initializes the kobject, otherwise the error log is printed and returned.The kobject_add_varg function is then called to add the kobject to the sysfs system.Variable parameters after kobject_add_varg are used to assemble the name of the kobject.

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

The function sets the name and parent kobject, then calls the kobject_add_internal function to add kobject to the sysfs system.

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

        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;
}

The kset section is not analyzed, and the most important timing for this function is to call create_dir(kobj) to create a directory of sysfs.

static int create_dir(struct kobject *kobj)
{
        const struct kobj_ns_type_operations *ops;
        int error;

        error = sysfs_create_dir_ns(kobj, kobject_namespace(kobj));
        if (error)
                return error;

        error = populate_dir(kobj);
        if (error) {
                sysfs_remove_dir(kobj);
                return error;
        }

        /*
         * @kobj->sd may be deleted by an ancestor going away.  Hold an
         * extra reference so that it stays until @kobj is gone.
         */
        sysfs_get(kobj->sd);

        /*
         * If @kobj has ns_ops, its children need to be filtered based on
         * their namespace tags.  Enable namespace support on @kobj->sd.
         */
        ops = kobj_child_ns_ops(kobj);
        if (ops) {
                BUG_ON(ops->type <= KOBJ_NS_TYPE_NONE);
                BUG_ON(ops->type >= KOBJ_NS_TYPES);
                BUG_ON(!kobj_ns_type_registered(ops->type));

                sysfs_enable_ns(kobj->sd);
        }

        return 0;
}

This section deals with sysfs and ns and is not analysed in depth.

Finally, let's look at the data structure of the kobject attribute and a macro that defines it

struct kobj_attribute {
        struct attribute attr;
        ssize_t (*show)(struct kobject *kobj, struct kobj_attribute *attr,
                        char *buf);
        ssize_t (*store)(struct kobject *kobj, struct kobj_attribute *attr,
                         const char *buf, size_t count);
};

/**
 * Use these macros to make defining attributes easier. See include/linux/device.h
 * for examples..
 */
#define __ATTR(_name, _mode, _show, _store) {                           \
        .attr = {.name = __stringify(_name),                            \
                 .mode = VERIFY_OCTAL_PERMISSIONS(_mode) },             \
        .show   = _show,                                                \
        .store  = _store,                                               \
}

You can use the u ATTR macro to define kobj_attribute, similar to the following

__ATTR(foo, 0664, foo_show, foo_store)

Here foo is the property name, 0664 is the file permission, foo_show is the property file read callback, and foo_store is the property file write callback.

Finally, let's see how sysfs calls back the read-write function of the property file, remembering that kobj_sysfs_op in dynamic_kobj_ktype is a default implementation

const struct sysfs_ops kobj_sysfs_ops = {
        .show   = kobj_attr_show,
        .store  = kobj_attr_store,
};
static ssize_t kobj_attr_show(struct kobject *kobj, struct attribute *attr,
                              char *buf)
{
        struct kobj_attribute *kattr;
        ssize_t ret = -EIO;

        kattr = container_of(attr, struct kobj_attribute, attr);
        if (kattr->show)
                ret = kattr->show(kobj, kattr, buf);
        return ret;
}

static ssize_t kobj_attr_store(struct kobject *kobj, struct attribute *attr,
                               const char *buf, size_t count)
{
        struct kobj_attribute *kattr;
        ssize_t ret = -EIO;

        kattr = container_of(attr, struct kobj_attribute, attr);
        if (kattr->store)
                ret = kattr->store(kobj, kattr, buf, count);
        return ret;
}

(sysfs calls the show or store function of sysfs_ops when reading and writing attributes. This function has a parameter struct attribute *attr, which is actually a structure of kobj_attributes. In fact, the implementation of this function only calls the show or store function of kobj_attributes.

The total call stack for show and store is as follows:
show

vfs_read->__vfs_read->kernfs_fop_read->seq_read->kernfs_seq_show->sysfs_kf_seq_show->kobj_attr_show

[18774.453811] Call Trace:
[18774.453823]  [<c16f8aa3>] dump_stack+0x41/0x52
[18774.453830]  [<c1062fbe>] warn_slowpath_common+0x8e/0xd0
[18774.453835]  [<f9bdd10d>] ? foo_show+0x1d/0x3c [kobject_example]
[18774.453838]  [<f9bdd10d>] ? foo_show+0x1d/0x3c [kobject_example]
[18774.453848]  [<c1339700>] ? current_is_single_threaded+0xb0/0xb0
[18774.453852]  [<c1063022>] warn_slowpath_null+0x22/0x30
[18774.453855]  [<f9bdd10d>] foo_show+0x1d/0x3c [kobject_example]
[18774.453863]  [<c11982bc>] ? __kmalloc+0xac/0x230
[18774.453866]  [<f9bdd0f0>] ? foo_store+0x40/0x40 [kobject_example]
[18774.453872]  [<c133970d>] kobj_attr_show+0xd/0x20
[18774.453880]  [<c1214a12>] sysfs_kf_seq_show+0xd2/0x170
[18774.453885]  [<c12132f4>] kernfs_seq_show+0x24/0x30
[18774.453891]  [<c11cd5f6>] seq_read+0xe6/0x360
[18774.453896]  [<c1213945>] kernfs_fop_read+0x45/0x60
[18774.453901]  [<c1213900>] ? kernfs_file_direct_read+0x110/0x110
[18774.453908]  [<c11ae4d6>] __vfs_read+0x26/0x80
[18774.453912]  [<c11ae5a7>] vfs_read+0x77/0x120

store

vfs_write->kernfs_fop_write->sysfs_kf_write->kobj_attr_store

[19066.385778] Call Trace:
[19066.385786]  [<c16f8aa3>] dump_stack+0x41/0x52
[19066.385791]  [<c1062fbe>] warn_slowpath_common+0x8e/0xd0
[19066.385794]  [<f9bdd0cd>] ? foo_store+0x1d/0x40 [kobject_example]
[19066.385796]  [<f9bdd0cd>] ? foo_store+0x1d/0x40 [kobject_example]
[19066.385798]  [<c1063022>] warn_slowpath_null+0x22/0x30
[19066.385801]  [<f9bdd0cd>] foo_store+0x1d/0x40 [kobject_example]
[19066.385805]  [<c11982bc>] ? __kmalloc+0xac/0x230
[19066.385808]  [<f9bdd0b0>] ? b_show+0x50/0x50 [kobject_example]
[19066.385814]  [<c133973b>] kobj_attr_store+0x1b/0x30
[19066.385819]  [<c121467d>] sysfs_kf_write+0x3d/0x50
[19066.385821]  [<c1214640>] ? sysfs_kf_bin_read+0xe0/0xe0
[19066.385824]  [<c12137ab>] kernfs_fop_write+0xfb/0x140
[19066.385827]  [<c12136b0>] ? kernfs_vma_page_mkwrite+0x80/0x80
[19066.385831]  [<c11ae386>] vfs_write+0xa6/0x1d0
[19066.385834]  [<c11ae765>] SyS_write+0x55/0xc0
[19066.385837]  [<c11c7fc5>] ? __close_fd+0x75/0xa0
[19066.385841]  [<c170395f>] sysenter_do_call+0x12/0x12
[19066.385843] ---[ end trace 9ea3de6ceabd4dc1 ]---

Last

You can also refer to the kobject kernel documentation, the kernel directory Documentation/kobject.txt, or the example samples/kobject/kobject-example.c provided by the kernel.

Forecast

The role of kset will be analyzed later.

113 original articles were published, 22 were praised, 90,000 visits+
Private letter follow

Tags: Attribute Linux

Posted on Fri, 10 Jan 2020 16:58:59 -0800 by Mr Camouflage