linux kset subsystem 3.10内核,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;

};

需要说明的几点:

*sd 是kobject的基石,最终sysfs_add_file(kobj->sd, attr, SYSFS_KOBJ_ATTR); 目录就是这样创建出来的(file.c中定义)

*kset 指向一个kset

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;

};

需要说明的几点:

kset这个结构体本身比较简单,而且其本身也继承了一个kobject,所以kset自己也是一个目录

subsystem也是一个kset,sys/目录下的bus、class的目录,就是一个subsystem

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

01e6ba4be0aa

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) : "",

kobj->kset ? kobject_name(&kobj->kset->kobj) : "");

//调用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函数返回的内容。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值