写的很乱,还需要整理思路。 这几天看的文件系统,先从底下的内核对象kobject,kset看起的,这是所有device对应的linux文件。了解到这是sysfs文件系统的东西,而sysfs文件系统不会一开始就挂上,在系统调用开始最先经过的是vfs,所以看到了vfsmount部分,那里讲述了vfs文件系统以及其他系统的挂载,而这一切都有一个inode的结构做桥梁,所有文件都离不开inode,在inode之下是dentry的概念,inode代表了实际物理意义的文件,dentry代表的目录项。在inode之上是super_block,每一个文件系统对应一个super_block实例,mount一个文件系统,需要分注册和装载两步,实际是申请一个super_block,然后填充。那么sysfs作为一个在vfs之下的fs,是如何挂载的,这个可以参考vfs挂载子系统来看以及sys_mount还有本篇开头部分。在sysfs中又是如何装载系统的这是本文前面所讲的,也涉及到了inode和kobject的联系,学习inode时有一个dentry结构,有个dentry->d_fsdata成员指向了sysfs下的sysfs_dirent结构。还有dirent结构中的属性集,symlink, bin, dir等,这些sysfs中的文件在内核执行添加内核对象时是如何设置的,它经过各种属性,如:attribute, kobj_attribute, attribte_group,sysfs_ops, kobj_type等一切联系到一起。这些东西还是要配合图和实操会更好,后面还是要从新理一遍加深理解。看完这些内容,起码知道了挂载系统,挂载总线,挂载设备的原理,对内核的分层思想也有所体会,虽然整篇文章我都没有特别留意结构体中出现的list_head链表,但是它每出现一次就基本分了个层,环环相扣,还有到底什么是文件,文件也只是数据而已,只有用ls命令时,才会解析出来东西。
kobject是内核对象(sysfs子系统), inode是物理意义上的文件,他俩之间通过何种方式取得联系?(这个问题我没有做最后总结,通篇文章给出了所有过程要调用的函数以及对应的结构体,它们确实是相关的)
装载sysfs文件系统,mount系统调用最后将填充超级块的工作委托给sysfs_fill_super。先了解基本的数据结构如何相关联。
值得记忆的是:要使一个对象在sysfs文件系统中可见,需要调用kobject_add。但如果kobject是某个
0,装载文件系统
在kobject中有:
struct kobject{
...
struct sysfs_dirent *sd;
...
};
sys_dirent是sysfs的主要数据结构,表示sysfs的目录项。每个sysfs结点都表示为sysfs_dirent的一个实例。
两者之间的联系通过dentry->d_fsdata建立。该成员指向与dentry实例相关的sysfs_dirent实例。
sysfs_dirent结构:
struct sysfs_dirent {
atomic_t s_count;
atomic_t s_active;
#ifdef CONFIG_DEBUG_LOCK_ALLOC
struct lockdep_map dep_map;
#endif
struct sysfs_dirent *s_parent;
const char *s_name;
struct rb_node s_rb;
union {
struct completion *completion;
struct sysfs_dirent *removed_list;
} u;
const void *s_ns; /* namespace tag */
unsigned int s_hash; /* ns + name hash */
union {匿名联合, 根据数据项类型来匹配设置
struct sysfs_elem_dir s_dir;
struct sysfs_elem_symlink s_symlink;
struct sysfs_elem_attr s_attr;
struct sysfs_elem_bin_attr s_bin_attr;
};
unsigned short s_flags;
umode_t s_mode;
unsigned int s_ino; //子节点链表中的所有节点按照s_ino以递减次序排列
struct sysfs_inode_attrs *s_iattr;
};
根据sysfs数据项的类型不同,与之关联的数据类型也不同。一个数据项一次只能表示一种类型。
sysfs数据项也由struct dentry实例表示。
sysfs_get_inode用于创建一个新的struct inode实例,作为整个sysfs树的起点。不仅用于获得sysfs根目录inode,它也是一个通用函数,可用于任何sysfs数据项。
struct inode * sysfs_get_inode(struct super_block *sb, struct sysfs_dirent *sd)
{
struct inode *inode;
inode = iget_locked(sb, sd->s_ino);
if (inode && (inode->i_state & I_NEW))
sysfs_init_inode(sd, inode);
return inode;
}
sysfs_init_inode从头开始构造一个新的inode实例,整个函数与上一个函数应用的场景不同,如果inode已经存在,则上边的函数会检查失败退出,取而代之的是用sysfs_init_inode函数。
static void sysfs_init_inode(struct sysfs_dirent *sd, struct inode *inode)
{
struct bin_attribute *bin_attr;
//设置inode操作,地址空间操作,文件操作
inode->i_private = sysfs_get(sd);
inode->i_mapping->a_ops = &sysfs_aops;
inode->i_mapping->backing_dev_info = &sysfs_backing_dev_info;
inode->i_op = &sysfs_inode_operations;
set_default_inode_attr(inode, sd->s_mode); //设置默认属性gid, uid等等
sysfs_refresh_inode(sd, inode);
//根据类型初始化成员, 软链接、目录、属性、bin属性
/* initialize inode according to type */
switch (sysfs_type(sd)) {
case SYSFS_DIR:
inode->i_op = &sysfs_dir_inode_operations;
inode->i_fop = &sysfs_dir_operations;
break;
case SYSFS_KOBJ_ATTR:
inode->i_size = PAGE_SIZE;
inode->i_fop = &sysfs_file_operations;
break;
case SYSFS_KOBJ_BIN_ATTR:
bin_attr = sd->s_bin_attr.bin_attr;
inode->i_size = bin_attr->size;
inode->i_fop = &bin_fops;
break;
case SYSFS_KOBJ_LINK:
inode->i_op = &sysfs_symlink_inode_operations;
break;
default:
BUG();
}
unlock_new_inode(inode);
}
函数最终在sysfs_fill_super调用来填充一个超级块,d_make_root为根目录分配一个dentry实例之后,将建立sysfs数据和文件系统项之间的关联。
static int sysfs_fill_super(struct super_block *sb, void *data, int silent)
{
struct inode *inode;
struct dentry *root;
sb->s_blocksize = PAGE_CACHE_SIZE;
sb->s_blocksize_bits = PAGE_CACHE_SHIFT;
sb->s_magic = SYSFS_MAGIC;
sb->s_op = &sysfs_ops;
sb->s_time_gran = 1;
/* get root inode, initialize and unlock it */
mutex_lock(&sysfs_mutex);
inode = sysfs_get_inode(sb, &sysfs_root);
mutex_unlock(&sysfs_mutex);
if (!inode) {
pr_debug("sysfs: could not get root inode\n");
return -ENOMEM;
}
/* instantiate and link root dentry */
root = d_make_root(inode);
if (!root) {
pr_debug("%s: could not get root dentry!\n",__func__);
return -ENOMEM;
}
root->d_fsdata = &sysfs_root;
//由文件系统内部使用,这样sysfs能够在sysfs_dirent和dentry实例之间建立一个关联。
//sysfs_root是一个静态的struct sysfs_dirent实例,表示根目录对应的数据项。
//d_fsdata总是指向相关的struct sysfs_dirent实例。
//在sysfs中,不仅根数据项如此,
//所有其他的数据项也是一样。这种关联使得内核能够从通用的VFS数据结构得到特定于sysfs的数据
sb->s_root = root;
return 0;
}
struct sysfs_dirent sysfs_root = {
.s_name = "",
.s_count = ATOMIC_INIT(1),
.s_flags = SYSFS_DIR,
.s_mode = S_IFDIR | S_IRWXU | S_IRUGO | S_IXUGO,
.s_ino = 1,
};
struct dentry *d_make_root(struct inode *root_inode)
{
struct dentry *res = NULL;
if (root_inode) {
static const struct qstr name = { .name = "/", .len = 1 };
res = __d_alloc(root_inode->i_sb, &name);
if (res)
d_instantiate(res, root_inode);
else
iput(root_inode);
}
return res;
}
2,向sysfs添加内容
注册子系统:
属性,特定于子系统的底层工作
edac_device_sysfs.c 设备子系统属性
struct ctl_info_attribute {
struct attribute attr;
ssize_t(*show) (struct edac_device_ctl_info *, char *);
ssize_t(*store) (struct edac_device_ctl_info *, const char *, size_t);
};
如何将默认属性集关联到该子系统的所有kobject,为此,需要使用一个kobj_type,举例子:
static const struct sysfs_ops device_ctl_info_ops = {
.show = edac_dev_ctl_info_show,
.store = edac_dev_ctl_info_store
};
//属性集
#define CTL_INFO_ATTR(_name,_mode,_show,_store) \
static struct ctl_info_attribute attr_ctl_info_##_name = { \
.attr = {.name = __stringify(_name), .mode = _mode }, \
.show = _show, \
.store = _store, \
};
CTL_INFO_ATTR(log_ue, S_IRUGO | S_IWUSR,
edac_device_ctl_log_ue_show, edac_device_ctl_log_ue_store);
CTL_INFO_ATTR(log_ce, S_IRUGO | S_IWUSR,
edac_device_ctl_log_ce_show, edac_device_ctl_log_ce_store);
CTL_INFO_ATTR(panic_on_ue, S_IRUGO | S_IWUSR,
edac_device_ctl_panic_on_ue_show,
edac_device_ctl_panic_on_ue_store);
CTL_INFO_ATTR(poll_msec, S_IRUGO | S_IWUSR,
edac_device_ctl_poll_msec_show, edac_device_ctl_poll_msec_store);
/* Base Attributes of the EDAC_DEVICE ECC object */
static struct ctl_info_attribute *device_ctrl_attr[] = {
&attr_ctl_info_panic_on_ue,
&attr_ctl_info_log_ue,
&attr_ctl_info_log_ce,
&attr_ctl_info_poll_msec,
NULL,
};
static struct kobj_type ktype_device_ctrl = {
.release = edac_device_ctrl_master_release,
.sysfs_ops = &device_ctl_info_ops,
.default_attrs = (struct attribute **)device_ctrl_attr,
};
在edac_device_register_sysfs_main_kobj() 注册子系统main函数中主要是调用三个函数,先看一下源码:
int edac_device_register_sysfs_main_kobj(struct edac_device_ctl_info *edac_dev)
{
struct bus_type *edac_subsys;
int err;
debugf1("%s()\n", __func__);
/* get the /sys/devices/system/edac reference */
edac_subsys = edac_get_sysfs_subsys();
if (edac_subsys == NULL) {
debugf1("%s() no edac_subsys error\n", __func__);
err = -ENODEV;
goto err_out;
}
/* Point to the 'edac_subsys' this instance 'reports' to */
edac_dev->edac_subsys = edac_subsys;
/* Init the devices's kobject */
memset(&edac_dev->kobj, 0, sizeof(struct kobject));
/* Record which module 'owns' this control structure
* and bump the ref count of the module
*/
edac_dev->owner = THIS_MODULE;
if (!try_module_get(edac_dev->owner)) {
err = -ENODEV;
goto err_mod_get;
}
/* register */
err = kobject_init_and_add(&edac_dev->kobj, &ktype_device_ctrl,
&edac_subsys->dev_root->kobj,
"%s", edac_dev->name);
if (err) {
debugf1("%s()Failed to register '.../edac/%s'\n",
__func__, edac_dev->name);
goto err_kobj_reg;
}
kobject_uevent(&edac_dev->kobj, KOBJ_ADD);
/* At this point, to 'free' the control struct,
* edac_device_unregister_sysfs_main_kobj() must be used
*/
debugf4("%s() Registered '.../edac/%s' kobject\n",
__func__, edac_dev->name);
return 0;
/* Error exit stack */
err_kobj_reg:
module_put(edac_dev->owner);
err_mod_get:
edac_put_sysfs_subsys();
err_out:
return err;
}
目的是创建一个对应于kobj_type的kset的过程,在上面的函数中最终调用以下三个函数,注册了总线。
edac_get_sysfs_subsys()调用的subsys_system_register();
kobject_init_and_add();
kobject_uevent();
可以看到kset是从最开始调用的函数一层一层往下,越底层的目录越高,这不难理解,因为要先创建/sys/device/,才能在这个目录的基础上创建/sys/device/system,device_initialize的作用就是初始化设备,来准备设备供其他层使用。然后通过第二个函数device_add把设备添加到kobject的层次结构中,再将其添加到驱动程序模型的其它相关子系统中。在device_add函数中调用了kobject_add实现添加到kobject结构的功能。最后device_create_file进行创建文件,在sysfs中创建文件最终都会调用sysfs_create_file来执行。kobject_set_name和set_dev_name的函数成分一样,一样的函数体,两个名称而已,实现方式是用va_start的方式,在最终ls时通过系统调用打印出来具体的名称。
1,subsys_system_register()负责子系统的注册过程,所有“system”子系统都有一个/sys/devices/system/<name>根设备和子系统的名称。根设备可以携带子系统范围的属性。所有注册的设备都位于这个根设备的下面,并以附加了简单枚举号的子系统命名。注册的设备没有明确的名称;
1.1 bus_register, 总线注册,负责向kobject基础注册总线,设置名称,然后再注册这个总线拥有子系统。kset是目录,kobject是对应的设备文件。里面调用了kset_create_and_add创建了两个目录。
int __bus_register(struct bus_type *bus, struct lock_class_key *key)
{
int retval;
struct subsys_private *priv;
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);
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);
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);
if (!priv->devices_kset) {
retval = -ENOMEM;
goto bus_devices_fail;
}
priv->drivers_kset = kset_create_and_add("drivers", NULL,
&priv->subsys.kobj);
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);
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;
bus_attrs_fail:
remove_probe_files(bus);
bus_probe_files_fail:
kset_unregister(bus->p->drivers_kset);
bus_drivers_fail:
kset_unregister(bus->p->devices_kset);
bus_devices_fail:
bus_remove_file(bus, &bus_attr_uevent);
bus_uevent_fail:
kset_unregister(&bus->p->subsys);
out:
kfree(bus->p);
bus->p = NULL;
return retval;
}
EXPORT_SYMBOL_GPL(__bus_register);
1.2 device_register,设备注册,函数体里只有两行,分两步完成注册过程,初始化设备and添加设备。
1.2.1 device_initialize()上面图片已经包含了函数体,为了方便阅读,再贴一遍:
void device_initialize(struct device *dev)
{
dev->kobj.kset = devices_kset;
kobject_init(&dev->kobj, &device_ktype);
INIT_LIST_HEAD(&dev->dma_pools);
mutex_init(&dev->mutex);
lockdep_set_novalidate_class(&dev->mutex);
spin_lock_init(&dev->devres_lock);
INIT_LIST_HEAD(&dev->devres_head);
device_pm_init(dev);
set_dev_node(dev, -1);
}
device_kset是一个全局定义的kset类型的指针,也就是一个设备目录。device_ktype是一个kobj_type的实例。
函数传入一个实参device,把device中的kobject按照提前设置好的kobj_type类型进行初始化。
1.2.2 device_add()添加设备,创建文件,并申请主设备号。
int device_add(struct device *dev)
{
struct device *parent = NULL;
struct kobject *kobj;
struct class_interface *class_intf;
int error = -EINVAL;
dev = get_device(dev);
if (!dev)
goto done;
if (!dev->p) {
error = device_private_init(dev);
if (error)
goto done;
}
/*
* for statically allocated devices, which should all be converted
* some day, we need to initialize the name. We prevent reading back
* the name, and force the use of dev_name()
*/
if (dev->init_name) {
dev_set_name(dev, "%s", dev->init_name);
dev->init_name = NULL;
}
/* subsystems can specify simple device enumeration */
if (!dev_name(dev) && dev->bus && dev->bus->dev_name)
dev_set_name(dev, "%s%u", dev->bus->dev_name, dev->id);
if (!dev_name(dev)) {
error = -EINVAL;
goto name_error;
}
pr_debug("device: '%s': %s\n", dev_name(dev), __func__);
parent = get_device(dev->parent);
kobj = get_device_parent(dev, parent);
if (kobj)
dev->kobj.parent = kobj;
/* use parent numa_node */
if (parent)
set_dev_node(dev, dev_to_node(parent));
/* first, register with generic layer. */
/* we require the name to be set before, and pass NULL */
error = kobject_add(&dev->kobj, dev->kobj.parent, NULL);
if (error)
goto Error;
/* notify platform of device entry */
if (platform_notify)
platform_notify(dev);
error = device_create_file(dev, &uevent_attr);
if (error)
goto attrError;
if (MAJOR(dev->devt)) {
error = device_create_file(dev, &devt_attr);
if (error)
goto ueventattrError;
error = device_create_sys_dev_entry(dev);
if (error)
goto devtattrError;
devtmpfs_create_node(dev);
}
error = device_add_class_symlinks(dev);
if (error)
goto SymlinkError;
error = device_add_attrs(dev);
if (error)
goto AttrsError;
error = bus_add_device(dev);
if (error)
goto BusError;
error = dpm_sysfs_add(dev);
if (error)
goto DPMError;
device_pm_add(dev);
/* Notify clients of device addition. This call must come
* after dpm_sysfs_add() and before kobject_uevent().
*/
if (dev->bus)
blocking_notifier_call_chain(&dev->bus->p->bus_notifier,
BUS_NOTIFY_ADD_DEVICE, dev);
kobject_uevent(&dev->kobj, KOBJ_ADD);
bus_probe_device(dev);
if (parent)
klist_add_tail(&dev->p->knode_parent,
&parent->p->klist_children);
if (dev->class) {
mutex_lock(&dev->class->p->mutex);
/* tie the class to the device */
klist_add_tail(&dev->knode_class,
&dev->class->p->klist_devices);
/* notify any interfaces that the device is here */
list_for_each_entry(class_intf,
&dev->class->p->interfaces, node)
if (class_intf->add_dev)
class_intf->add_dev(dev, class_intf);
mutex_unlock(&dev->class->p->mutex);
}
done:
put_device(dev);
return error;
DPMError:
bus_remove_device(dev);
BusError:
device_remove_attrs(dev);
AttrsError:
device_remove_class_symlinks(dev);
SymlinkError:
if (MAJOR(dev->devt))
devtmpfs_delete_node(dev);
if (MAJOR(dev->devt))
device_remove_sys_dev_entry(dev);
devtattrError:
if (MAJOR(dev->devt))
device_remove_file(dev, &devt_attr);
ueventattrError:
device_remove_file(dev, &uevent_attr);
attrError:
kobject_uevent(&dev->kobj, KOBJ_REMOVE);
kobject_del(&dev->kobj);
Error:
cleanup_device_parent(dev);
if (parent)
put_device(parent);
name_error:
kfree(dev->p);
dev->p = NULL;
goto done;
}
注册设备需要调3个函数: kobject_init_and_add(), kset_create_and_add(); 其中kset_create_and_add调用了kset_register(),该函数最终调用了kobject_add_init(),kobject_add_internal(),和上述的kobject_uevent()。还有一个sysfs_create_group()。
int kset_register(struct kset *k)
{
int err;
if (!k)
return -EINVAL;
kset_init(k);
err = kobject_add_internal(&k->kobj);
if (err)
return err;
kobject_uevent(&k->kobj, KOBJ_ADD);
return 0;
}
注册一个sysfs子系统的步骤就是, 先设置属性集,为了相关联到kobject,需要用一个kobj_type的类型进行封装,然后设置一个对应当前kobj_type的kset, kset是一个容器类,关于kset和kobject的关系:kset,kobject分析
自己添加读写属性接口
在sysfs接口框架,按照这种模式可以为自己的驱动在sysfs中添加属性读写接口。
/*
* Interface for Dynamic Logical Partitioning of I/O Slots on
* RPA-compliant PPC64 platform.
*
* John Rose <johnrose@austin.ibm.com>
* October 2003
*
* Copyright (C) 2003 IBM.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*/
#include <linux/kobject.h>
#include <linux/string.h>
#include <linux/pci.h>
#include <linux/pci_hotplug.h>
#include "rpadlpar.h"
#include "../pci.h"
#define DLPAR_KOBJ_NAME "control"
/* Those two have no quotes because they are passed to __ATTR() which
* stringifies the argument (yuck !)
*/
#define ADD_SLOT_ATTR_NAME add_slot
#define REMOVE_SLOT_ATTR_NAME remove_slot
#define MAX_DRC_NAME_LEN 64
static ssize_t add_slot_store(struct kobject *kobj, struct kobj_attribute *attr,
const char *buf, size_t nbytes)
{
char drc_name[MAX_DRC_NAME_LEN];
char *end;
int rc;
if (nbytes >= MAX_DRC_NAME_LEN)
return 0;
memcpy(drc_name, buf, nbytes);
end = strchr(drc_name, '\n');
if (!end)
end = &drc_name[nbytes];
*end = '\0';
rc = dlpar_add_slot(drc_name);
if (rc)
return rc;
return nbytes;
}
static ssize_t add_slot_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
return sprintf(buf, "0\n");
}
static ssize_t remove_slot_store(struct kobject *kobj,
struct kobj_attribute *attr,
const char *buf, size_t nbytes)
{
char drc_name[MAX_DRC_NAME_LEN];
int rc;
char *end;
if (nbytes >= MAX_DRC_NAME_LEN)
return 0;
memcpy(drc_name, buf, nbytes);
end = strchr(drc_name, '\n');
if (!end)
end = &drc_name[nbytes];
*end = '\0';
rc = dlpar_remove_slot(drc_name);
if (rc)
return rc;
return nbytes;
}
static ssize_t remove_slot_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
return sprintf(buf, "0\n");
}
static struct kobj_attribute add_slot_attr =
__ATTR(ADD_SLOT_ATTR_NAME, 0644, add_slot_show, add_slot_store);
static struct kobj_attribute remove_slot_attr =
__ATTR(REMOVE_SLOT_ATTR_NAME, 0644, remove_slot_show, remove_slot_store);
static struct attribute *default_attrs[] = {
&add_slot_attr.attr,
&remove_slot_attr.attr,
NULL,
};
static struct attribute_group dlpar_attr_group = {
.attrs = default_attrs,
};
static struct kobject *dlpar_kobj;
int dlpar_sysfs_init(void)
{
int error;
dlpar_kobj = kobject_create_and_add(DLPAR_KOBJ_NAME,
&pci_slots_kset->kobj);
if (!dlpar_kobj)
return -EINVAL;
error = sysfs_create_group(dlpar_kobj, &dlpar_attr_group);
if (error)
kobject_put(dlpar_kobj);
return error;
}
void dlpar_sysfs_exit(void)
{
sysfs_remove_group(dlpar_kobj, &dlpar_attr_group);
kobject_put(dlpar_kobj);
}
装载sysfs系统:dentry(有inode,有一个d_fsdata指针指向sysfs_dirent)和sysfs_dirent的关系, super_block,get_inode 。
注册sysfs子系统, 属性集关联到kobject, kobj_type, kset.
注册bus总线过程, kobject_create_and_add, kset_create_and_add.
添加device在sysfs中的读写属性kobj_attribute, attribute_group, store, show, 。