Linux存储IO栈(2)-- sysfs与内核对象
来源:互联网 发布:2016年外贸依存度数据 编辑:程序博客网 时间:2024/05/15 05:48
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_rwstatic 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_attributestruct 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存储IO栈(2)-- sysfs与内核对象
- Linux存储IO栈(1)-- 内核对象与对象集
- linux内核sysfs详解
- linux内核sysfs详解
- linux内核sysfs详解
- linux内核sysfs详解
- linux内核sysfs详解
- Linux内核模块和Linux fs 与 sysfs 一
- Linux内核模块和Linux fs 与 sysfs 二
- LINUX内核设计与实现之kobject与sysfs
- LINUX内核设计与实现之kobject与sysfs
- 读书笔记之linux内核设计与实现(16)kobject和sysfs
- linux内核设计与实现之kobject和sysfs
- sysfs文件系统与linux设备模型(5.4.2)
- linux内核sysfs详解-1
- linux内核sysfs详解-1
- linux内核之阻塞 IO(2)
- Linux存储IO栈(0)-- 说明
- TCP/IP协议三次握手与四次握手流程解析
- Leetcode 39 Combination Sum
- Python基础-排序
- shape--layer-list
- session原理及实现共享
- Linux存储IO栈(2)-- sysfs与内核对象
- Java中对象的销毁
- android点击背景变深色
- 车联网开发
- 【娱乐】田馥甄 - 小幸运 ukulele谱 马叔叔版
- 可能是讲解Android事件分发最好的文章
- mvc 入门使用ajax get
- 好久没写博客了,今天来写一个.
- Struts2动作的搜索顺序