Linux存储IO栈(2)-- sysfs与内核对象

sysfs与内核对象

本篇文章不是以文件系统的角度来详细描述sysfs,而是从内核对象如何通过sysfs表示整个设备驱动模型为切入点,进一步理解Linux内核对象。

内核对象添加到sysfs

在上文《内核对象与对象集》中,将kobject添加到sysfs中,kobject_add –> kobject_add_varg –> kobject_add_internal,调用create_dir创建sysfs目录和属性文件。

static int create_dir(struct kobject *kobj)
{
    const struct kobj_ns_type_operations *ops;
    int error;
        //调用sysfs接口创建kobject对应的目录
    error = sysfs_create_dir_ns(kobj, kobject_namespace(kobj));
    if (error)
        return error;

    error = populate_dir(kobj);  //在kobject对应的目录中生成默认属性文件
    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;
}

/*
 * populate_dir - populate directory with attributes.
 * @kobj: object we're working on.
 *
 * Most subsystems have a set of default attributes that are associated
 * with an object that registers with them.  This is a helper called during
 * object registration that loops through the default attributes of the
 * subsystem and creates attributes files for them in sysfs.
 */
static int populate_dir(struct kobject *kobj)
{
    struct kobj_type *t = get_ktype(kobj);
    struct attribute *attr;
    int error = 0;
    int i;

    if (t && t->default_attrs) {
        for (i = 0; (attr = t->default_attrs[i]) != NULL; i++) {
            error = sysfs_create_file(kobj, attr); //为每个属性创建对应的文件
            if (error)
                break;
        }
    }
    return error;
}

create_dir通过调用sysfs_create_dir_ns创建sysfs中的目录,调用sysfs_create_file创建属性文件。

sysfs的核心结构

kern_node代表sysfs中每个节点。

/*
 * kernfs_node - the building block of kernfs hierarchy.  Each and every
 * kernfs node is represented by single kernfs_node.  Most fields are
 * private to kernfs and shouldn't be accessed directly by kernfs users.
 *
 * As long as s_count reference is held, the kernfs_node itself is
 * accessible.  Dereferencing elem or any other outer entity requires
 * active reference.
 */
struct kernfs_node {
    atomic_t        count;   //引用计数
    atomic_t        active;  //活动的引用计数
#ifdef CONFIG_DEBUG_LOCK_ALLOC
    struct lockdep_map  dep_map;
#endif
    /*
     * Use kernfs_get_parent() and kernfs_name/path() instead of
     * accessing the following two fields directly.  If the node is
     * never moved to a different parent, it is safe to access the
     * parent directly.
     */
    struct kernfs_node  *parent; //指向父节点
    const char      *name;       //节点名称,在sysfs显示的名字

    struct rb_node      rb;      //接入sysfs红黑树的链接项

    const void      *ns;    /* namespace tag */
    unsigned int        hash;   /* ns + name hash 红黑树key */
    union {
        struct kernfs_elem_dir      dir;     //该kern_node类型为目录
        struct kernfs_elem_symlink  symlink; //该kern_node类型为链接
        struct kernfs_elem_attr     attr;    //该kern_node类型为属性文件
    };

    void            *priv;

    unsigned short      flags; //标记位,目录、链接、属性文件或是否已被删除
    umode_t         mode;      //访问权限,在sysfs中该kern_node的权限
    unsigned int        ino;   //唯一编号
    struct kernfs_iattrs    *iattr;  //用于设置非默认的inode属性,如果没有则置为NULL
};

在sysfs中创建目录sysfs_create_dir_ns

/**
 * sysfs_create_dir_ns - create a directory for an object with a namespace tag
 * @kobj: object we're creating directory for
 * @ns: the namespace tag to use
 */
int sysfs_create_dir_ns(struct kobject *kobj, const void *ns)
{
    struct kernfs_node *parent, *kn;

    BUG_ON(!kobj);

    if (kobj->parent)
        parent = kobj->parent->sd; //如果kobject设置parent,则使用之
    else
        parent = sysfs_root_kn;  //否则parent就设置为sysfs根目录

    if (!parent)
        return -ENOENT;
    //创建目录
    kn = kernfs_create_dir_ns(parent, kobject_name(kobj),
                  S_IRWXU | S_IRUGO | S_IXUGO, kobj, ns);
    if (IS_ERR(kn)) {
        if (PTR_ERR(kn) == -EEXIST)
            sysfs_warn_dup(parent, kobject_name(kobj));
        return PTR_ERR(kn);
    }

    kobj->sd = kn;
    return 0;
}

/**
 * kernfs_create_dir_ns - create a directory
 * @parent: parent in which to create a new directory
 * @name: name of the new directory
 * @mode: mode of the new directory
 * @priv: opaque data associated with the new directory
 * @ns: optional namespace tag of the directory
 *
 * Returns the created node on success, ERR_PTR() value on failure.
 */
struct kernfs_node *kernfs_create_dir_ns(struct kernfs_node *parent,
                     const char *name, umode_t mode,
                     void *priv, const void *ns)
{
    struct kernfs_node *kn;
    int rc;

    /* allocate 分配空间并初始化, KERNFS_DIR指定创建目录 */
    kn = kernfs_new_node(parent, name, mode | S_IFDIR, KERNFS_DIR);
    if (!kn)
        return ERR_PTR(-ENOMEM);

    kn->dir.root = parent->dir.root; //指向根目录kern_node
    kn->ns = ns;  //指定命名空间
    kn->priv = priv;

    /* link in */
    rc = kernfs_add_one(kn); //将kern_node加入父目录的红黑树中
    if (!rc)
        return kn;

    kernfs_put(kn);
    return ERR_PTR(rc);
}

kernfs_create_dir_ns函数中的两个主要函数kernfs_new_node和kernfs_add_one,在创建文件和创建符号链接同样使用,仅是参数不同。

为kern_node结构分配空间,并初始化

struct kernfs_node *kernfs_new_node(struct kernfs_node *parent,
                    const char *name, umode_t mode,
                    unsigned flags)
{
    struct kernfs_node *kn;
    //分配kern_node空间,并初始化
    kn = __kernfs_new_node(kernfs_root(parent), name, mode, flags);
    if (kn) {
        kernfs_get(parent);
        kn->parent = parent;
    }
    return kn;
}

static struct kernfs_node *__kernfs_new_node(struct kernfs_root *root,
                         const char *name, umode_t mode,
                         unsigned flags)
{
    struct kernfs_node *kn;
    int ret;

    name = kstrdup_const(name, GFP_KERNEL); //复制常量字符串
    if (!name)
        return NULL;

    kn = kmem_cache_zalloc(kernfs_node_cache, GFP_KERNEL); //在缓存空间分配kernfs_node
    if (!kn)
        goto err_out1;

    /*
     * If the ino of the sysfs entry created for a kmem cache gets
     * allocated from an ida layer, which is accounted to the memcg that
     * owns the cache, the memcg will get pinned forever. So do not account
     * ino ida allocations.
     */
    ret = ida_simple_get(&root->ino_ida, 1, 0,  //获取唯一标号,用于唯一标示kern_node
                 GFP_KERNEL | __GFP_NOACCOUNT);
    if (ret < 0)
        goto err_out2;
    kn->ino = ret;

    atomic_set(&kn->count, 1);  //更新引用计数
    atomic_set(&kn->active, KN_DEACTIVATED_BIAS);
    RB_CLEAR_NODE(&kn->rb);
    //设置kern_node相关域
    kn->name = name;
    kn->mode = mode;
    kn->flags = flags;

    return kn;

 err_out2:
    kmem_cache_free(kernfs_node_cache, kn);
 err_out1:
    kfree_const(name);
    return NULL;
}

将kern_node添加到parent的红黑树中:

/**
 *  kernfs_add_one - add kernfs_node to parent without warning
 *  @kn: kernfs_node to be added
 *
 *  The caller must already have initialized @kn->parent.  This
 *  function increments nlink of the parent's inode if @kn is a
 *  directory and link into the children list of the parent.
 *
 *  RETURNS:
 *  0 on success, -EEXIST if entry with the given name already
 *  exists.
 */
int kernfs_add_one(struct kernfs_node *kn)
{
    struct kernfs_node *parent = kn->parent;
    struct kernfs_iattrs *ps_iattr;
    bool has_ns;
    int ret;

    mutex_lock(&kernfs_mutex);

    ret = -EINVAL;
    has_ns = kernfs_ns_enabled(parent);
    if (WARN(has_ns != (bool)kn->ns, KERN_WARNING "kernfs: ns %s in '%s' for '%s'\n",
         has_ns ? "required" : "invalid", parent->name, kn->name))
        goto out_unlock;

    if (kernfs_type(parent) != KERNFS_DIR) //检查parent是否为目录
        goto out_unlock;

    ret = -ENOENT;
    if (parent->flags & KERNFS_EMPTY_DIR)  //检查parent是否为空目录
        goto out_unlock;
    //检查parent是否是active状态
    if ((parent->flags & KERNFS_ACTIVATED) && !kernfs_active(parent))
        goto out_unlock;

    kn->hash = kernfs_name_hash(kn->name, kn->ns); //作为红黑树比较的key

    ret = kernfs_link_sibling(kn); //kern_node链入parent节点红黑树中
    if (ret)
        goto out_unlock;

    /* Update timestamps on the parent */
    ps_iattr = parent->iattr;
    if (ps_iattr) {
        struct iattr *ps_iattrs = &ps_iattr->ia_iattr;
        ps_iattrs->ia_ctime = ps_iattrs->ia_mtime = CURRENT_TIME;
    }

    mutex_unlock(&kernfs_mutex);

    /*
     * Activate the new node unless CREATE_DEACTIVATED is requested.
     * If not activated here, the kernfs user is responsible for
     * activating the node with kernfs_activate().  A node which hasn't
     * been activated is not visible to userland and its removal won't
     * trigger deactivation.
     */
    if (!(kernfs_root(kn)->flags & KERNFS_ROOT_CREATE_DEACTIVATED))
        kernfs_activate(kn);
    return 0;

out_unlock:
    mutex_unlock(&kernfs_mutex);
    return ret;
}

sysfs红黑树中的key:

/**
 *  kernfs_name_hash
 *  @name: Null terminated string to hash
 *  @ns:   Namespace tag to hash
 *
 *  Returns 31 bit hash of ns + name (so it fits in an off_t )
 */
static unsigned int kernfs_name_hash(const char *name, const void *ns)
{
    unsigned long hash = init_name_hash();
    unsigned int len = strlen(name);
    while (len--)
        hash = partial_name_hash(*name++, hash);
    hash = (end_name_hash(hash) ^ hash_ptr((void *)ns, 31));
    hash &= 0x7fffffffU;
    /* Reserve hash numbers 0, 1 and INT_MAX for magic directory entries */
    if (hash < 2)
        hash += 2;
    if (hash >= INT_MAX)
        hash = INT_MAX - 1;
    return hash;
}

static int kernfs_name_compare(unsigned int hash, const char *name,
                   const void *ns, const struct kernfs_node *kn)
{
    if (hash < kn->hash)
        return -1;
    if (hash > kn->hash)
        return 1;
    if (ns < kn->ns)
        return -1;
    if (ns > kn->ns)
        return 1;
    return strcmp(name, kn->name);
}
  • kernfs_name_hash: 根据name和ns计算kern_node的hash值,保存在kern_node.hash域中。
  • kernfs_name_compare: sysfs红黑树key的比较函数, 比较优先级是: hash > ns > name

kern_node链入parent节点红黑树中:

/**
 *  kernfs_link_sibling - link kernfs_node into sibling rbtree
 *  @kn: kernfs_node of interest
 *
 *  Link @kn into its sibling rbtree which starts from
 *  @kn->parent->dir.children.
 *
 *  Locking:
 *  mutex_lock(kernfs_mutex)
 *
 *  RETURNS:
 *  0 on susccess -EEXIST on failure.
 */
static int kernfs_link_sibling(struct kernfs_node *kn)
{
    struct rb_node **node = &kn->parent->dir.children.rb_node; //parent目录的红黑树
    struct rb_node *parent = NULL;

    while (*node) {  //在parent的目录中,寻找合适的位置将kn插入parent的红黑树中
        struct kernfs_node *pos;
        int result;

        pos = rb_to_kn(*node);
        parent = *node;
        result = kernfs_sd_compare(kn, pos); //优先顺序: hash > ns > name
        if (result < 0)
            node = &pos->rb.rb_left;
        else if (result > 0)
            node = &pos->rb.rb_right;
        else
            return -EEXIST;
    }

    /* add new node and rebalance the tree */
    rb_link_node(&kn->rb, parent, node);
    rb_insert_color(&kn->rb, &kn->parent->dir.children);

    /* successfully added, account subdir number */
    if (kernfs_type(kn) == KERNFS_DIR)
        kn->parent->dir.subdirs++;

    return 0;
}

在sysfs中创建文件

static inline int __must_check sysfs_create_file(struct kobject *kobj,
                         const struct attribute *attr)
{
    return sysfs_create_file_ns(kobj, attr, NULL);
}

/**
 * sysfs_create_file_ns - create an attribute file for an object with custom ns
 * @kobj: object we're creating for
 * @attr: attribute descriptor
 * @ns: namespace the new file should belong to
 */
int sysfs_create_file_ns(struct kobject *kobj, const struct attribute *attr,
             const void *ns)
{
    BUG_ON(!kobj || !kobj->sd || !attr);

    return sysfs_add_file_mode_ns(kobj->sd, attr, false, attr->mode, ns);

}
EXPORT_SYMBOL_GPL(sysfs_create_file_ns);

int sysfs_add_file_mode_ns(struct kernfs_node *parent,
               const struct attribute *attr, bool is_bin,
               umode_t mode, const void *ns)
{
    struct lock_class_key *key = NULL;
    const struct kernfs_ops *ops;
    struct kernfs_node *kn;
    loff_t size;

    if (!is_bin) {
        struct kobject *kobj = parent->priv;
        const struct sysfs_ops *sysfs_ops = kobj->ktype->sysfs_ops;

        /* every kobject with an attribute needs a ktype assigned */
        if (WARN(!sysfs_ops, KERN_ERR
             "missing sysfs attribute operations for kobject: %s\n",
             kobject_name(kobj)))
            return -EINVAL;
        //确定读写的操作函数
        if (sysfs_ops->show && sysfs_ops->store) {
            if (mode & SYSFS_PREALLOC)
                ops = &sysfs_prealloc_kfops_rw;
            else
                ops = &sysfs_file_kfops_rw;
        } else if (sysfs_ops->show) {
            if (mode & SYSFS_PREALLOC)
                ops = &sysfs_prealloc_kfops_ro;
            else
                ops = &sysfs_file_kfops_ro;
        } else if (sysfs_ops->store) {
            if (mode & SYSFS_PREALLOC)
                ops = &sysfs_prealloc_kfops_wo;
            else
                ops = &sysfs_file_kfops_wo;
        } else
            ops = &sysfs_file_kfops_empty;

        size = PAGE_SIZE;
    } else {
        struct bin_attribute *battr = (void *)attr;

        if (battr->mmap)
            ops = &sysfs_bin_kfops_mmap;
        else if (battr->read && battr->write)
            ops = &sysfs_bin_kfops_rw;
        else if (battr->read)
            ops = &sysfs_bin_kfops_ro;
        else if (battr->write)
            ops = &sysfs_bin_kfops_wo;
        else
            ops = &sysfs_file_kfops_empty;

        size = battr->size;
    }

#ifdef CONFIG_DEBUG_LOCK_ALLOC
    if (!attr->ignore_lockdep)
        key = attr->key ?: (struct lock_class_key *)&attr->skey;
#endif
    kn = __kernfs_create_file(parent, attr->name, mode & 0777, size, ops,
                  (void *)attr, ns, key); //创建属性文件
    if (IS_ERR(kn)) {
        if (PTR_ERR(kn) == -EEXIST)
            sysfs_warn_dup(parent, attr->name);
        return PTR_ERR(kn);
    }
    return 0;
}

通过上面的代码跟踪,创建属性文件由__kernfs_create_file实现,最终仍然是调用kernfs_new_node和kernfs_add_one。

/**
 * __kernfs_create_file - kernfs internal function to create a file
 * @parent: directory to create the file in
 * @name: name of the file
 * @mode: mode of the file
 * @size: size of the file
 * @ops: kernfs operations for the file
 * @priv: private data for the file
 * @ns: optional namespace tag of the file
 * @key: lockdep key for the file's active_ref, %NULL to disable lockdep
 *
 * Returns the created node on success, ERR_PTR() value on error.
 */
struct kernfs_node *__kernfs_create_file(struct kernfs_node *parent,
                     const char *name,
                     umode_t mode, loff_t size,
                     const struct kernfs_ops *ops,
                     void *priv, const void *ns,
                     struct lock_class_key *key)
{
    struct kernfs_node *kn;
    unsigned flags;
    int rc;

    flags = KERNFS_FILE; //创建的kern_node类型为file
    //分配空间并初始化
    kn = kernfs_new_node(parent, name, (mode & S_IALLUGO) | S_IFREG, flags);
    if (!kn)
        return ERR_PTR(-ENOMEM);

    kn->attr.ops = ops;
    kn->attr.size = size;
    kn->ns = ns;
    kn->priv = priv;

#ifdef CONFIG_DEBUG_LOCK_ALLOC
    if (key) {
        lockdep_init_map(&kn->dep_map, "s_active", key, 0);
        kn->flags |= KERNFS_LOCKDEP;
    }
#endif

    /*
     * kn->attr.ops is accesible only while holding active ref.  We
     * need to know whether some ops are implemented outside active
     * ref.  Cache their existence in flags.
     */
    if (ops->seq_show)
        kn->flags |= KERNFS_HAS_SEQ_SHOW;
    if (ops->mmap)
        kn->flags |= KERNFS_HAS_MMAP;

    rc = kernfs_add_one(kn); //将kern_node添加到parent的红黑树中
    if (rc) {
        kernfs_put(kn);
        return ERR_PTR(rc);
    }
    return kn;
}

在sysfs_add_file_mode_ns函数中根据flags的不同,注册不同的读写回调函数,下面以sysfs_prealloc_kfops_rw函数为例,其他结构类似,不赘述。

//常规文件--sysfs_prealloc_kfops_rw
static const struct kernfs_ops sysfs_prealloc_kfops_rw = {
    .read       = sysfs_kf_read,
    .write      = sysfs_kf_write,
    .prealloc   = true,
};

/* kernfs read callback for regular sysfs files with pre-alloc */
static ssize_t sysfs_kf_read(struct kernfs_open_file *of, char *buf,
                 size_t count, loff_t pos)
{
    const struct sysfs_ops *ops = sysfs_file_ops(of->kn); //获取kobject中的sysfs_ops操作表
    struct kobject *kobj = of->kn->parent->priv;
    size_t len;

    /*
     * If buf != of->prealloc_buf, we don't know how
     * large it is, so cannot safely pass it to ->show
     */
    if (pos || WARN_ON_ONCE(buf != of->prealloc_buf))
        return 0;
    len = ops->show(kobj, of->kn->priv, buf); //kobject中sd域的sysfs_ops操作表中的show
    return min(count, len);
}

/* kernfs write callback for regular sysfs files */
static ssize_t sysfs_kf_write(struct kernfs_open_file *of, char *buf,
                  size_t count, loff_t pos)
{   //获取kobject中的sysfs_ops操作表
    const struct sysfs_ops *ops = sysfs_file_ops(of->kn);
    struct kobject *kobj = of->kn->parent->priv;

    if (!count)
        return 0;

    return ops->store(kobj, of->kn->priv, buf, count); //kobject中sd域的sysfs_ops操作表中的store
}

关于属性文件的读写操作,最终都回调到kobject中的sd域的sysfs_ops操作表,这个操作表示在kobject_init函数中设置。回顾kobject_create函数:

struct kobject *kobject_create(void)
{
    struct kobject *kobj;

    kobj = kzalloc(sizeof(*kobj), GFP_KERNEL); //分配空间
    if (!kobj)
        return NULL;

    kobject_init(kobj, &dynamic_kobj_ktype);  //初始化, kobj_type类型为dynamic_kobj_ktype
    return kobj;
}

//注册如下结构
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,
};
EXPORT_SYMBOL_GPL(kobj_sysfs_ops);

kobject的sysfs的show和store方法为:kobj_attr_show和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)  //如果业务子系统设置了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)  //如果业务子系统设置了store函数,则调用
        ret = kattr->store(kobj, kattr, buf, count);
    return ret;
}

真正的对属性文件进行读写的回调由业务子系统实现。

在sysfs中创建符号链接

/**
 *  sysfs_create_link - create symlink between two objects.
 *  @kobj:  object whose directory we're creating the link in.
 *  @target:    object we're pointing to.
 *  @name:      name of the symlink.
 */
int sysfs_create_link(struct kobject *kobj, struct kobject *target,
              const char *name)
{
    return sysfs_do_create_link(kobj, target, name, 1);
}
EXPORT_SYMBOL_GPL(sysfs_create_link);

static int sysfs_do_create_link(struct kobject *kobj, struct kobject *target,
                const char *name, int warn)
{
    struct kernfs_node *parent = NULL;

    if (!kobj)
        parent = sysfs_root_kn;
    else
        parent = kobj->sd;

    if (!parent)
        return -EFAULT;

    return sysfs_do_create_link_sd(parent, target, name, warn);
}

static int sysfs_do_create_link_sd(struct kernfs_node *parent,
                   struct kobject *target_kobj,
                   const char *name, int warn)
{
    struct kernfs_node *kn, *target = NULL;

    BUG_ON(!name || !parent);

    /*
     * We don't own @target_kobj and it may be removed at any time.
     * Synchronize using sysfs_symlink_target_lock.  See
     * sysfs_remove_dir() for details.
     */
    spin_lock(&sysfs_symlink_target_lock);
    if (target_kobj->sd) {
        target = target_kobj->sd;
        kernfs_get(target);
    }
    spin_unlock(&sysfs_symlink_target_lock);

    if (!target)
        return -ENOENT;

    kn = kernfs_create_link(parent, name, target); //创建sysfs符号链接
    kernfs_put(target);

    if (!IS_ERR(kn))
        return 0;

    if (warn && PTR_ERR(kn) == -EEXIST)
        sysfs_warn_dup(parent, name);
    return PTR_ERR(kn);
}

由上面的代码追踪,创建符号链接由kernfs_create_link函数上。

/**
 * kernfs_create_link - create a symlink
 * @parent: directory to create the symlink in
 * @name: name of the symlink
 * @target: target node for the symlink to point to
 *
 * Returns the created node on success, ERR_PTR() value on error.
 */
struct kernfs_node *kernfs_create_link(struct kernfs_node *parent,
                       const char *name,
                       struct kernfs_node *target)
{
    struct kernfs_node *kn;
    int error;
    //指定创建符号链接
    kn = kernfs_new_node(parent, name, S_IFLNK|S_IRWXUGO, KERNFS_LINK);
    if (!kn)
        return ERR_PTR(-ENOMEM);

    if (kernfs_ns_enabled(parent))
        kn->ns = target->ns;
    kn->symlink.target_kn = target;
    kernfs_get(target); /* ref owned by symlink */

    error = kernfs_add_one(kn); //将kern_node添加到parent的红黑树中
    if (!error)
        return kn;

    kernfs_put(kn);
    return ERR_PTR(error);
}

与创建目录和文件类似,最终仍然是调用kernfs_new_node和kernfs_add_one实现。

基于内核对象编程套路

目标:在sysfs中创建一个目录/sys/kernel/storage/,在该目录下,还创建了一个文件value。value可以写入整型数据,随后可以读出。
* 定义内核对象

struct storage_obj {
    struct kobject kobj;
    int val;  //用于保存写入的数据
};
  • 定义属性类型
struct storage_attribute {
    struct attribute *attr;
    ssize_t (*show)(struct kobject *, struct attribute *, char *);
    ssize_t (*store)(struct kobject *, struct attribute *, const char *, size_t);
}
  • 声明属性
    定义属性的show和store方法,如下:
//定义并初始化storage_attribute
struct storage_attribute *sattr = &struct storage_attribute {
    .attr = {.name = "value", .mode = 0666},
    .show = storage_show,
    .store = storage_store,
};
  • 实现sysfs操作
ssize_t storage_show(struct kobject *kobj, struct attribute *attr, char *buf) 
{
    struct storage *stor = container_of(kobj, struct storage_obj, kobj);
    stor->val = atoi(buf);
}

ssize_t storage_store(struct kobject *kobj, struct attribute *attr, const char *buf, size_t s) {
    struct storage *stor = container_of(kobj, struct storage_obj, kobj);
    memcpy(buf, s, itoa(stor->val));
}
  • 定义内核对象release方法
    release方法设置在kobj_type结构中
void storage_release(struct kobject *kobj)
{
    ......
}
  • 声明内核对象类型
struct storage_ktype {
    struct kobj_type *ktype;
}
  • 封装对象属性添加和删除方法
    需要将value属性添加到内核对象,或者从内核对象删除,可以直接调用sysfs_create_file和sysfs_remove_file。但大多数情况下,会对这两个方法做一层封装:storage_create_file和storage_remove_file。
int storage_create_file(struct storage_obj *sobj, const struct storage_attribute *attr)
{
    int error = 0;
    if (sobj) {
        error = sysfs_create_file(&sobj->kobj, &attr->attr);
    }
    return error;
}

void storage_remove_file(struct storage_obj *sobj, const struct storage_attribute *attr)
{
    if (sobj) {
        sysfs_remove_file(&sobj->kobj, &attr->attr);
    }
}
  • 定义对象的创建和销毁方法
struct storage_obj * create_storage_obj() 
{
    struct storage_obj *sobj = (struct storage_obj *)malloc(struct storage_obj);
    struct storage_ktype *stype = (struct storage_ktype *)malloc(struct storage_ktype);
    sobj->parent = kernel_kobj;
    kobject_init_and_add(&sobj->kobj, &stype->ktype);

    return sobj
}

void destroy_storage_obj(struct kobject *kobj) {
    struct storage_obj *sobj = container_of(kobj, struct storage_obj, kobj);

    kobject_del(kboj);
    free(sobj);
    free(stype);
}
  • 实现模块加载和卸载方法
    加载时调用create_storage_obj, 卸载时调用destroy_storage_obj

你可能感兴趣的:(存储,Linux内核)