mirror of
https://github.com/AuxXxilium/linux_dsm_epyc7002.git
synced 2024-11-25 19:51:01 +07:00
7063fbf226
Configfs, a file system for userspace-driven kernel object configuration. The OCFS2 stack makes extensive use of this for propagation of cluster configuration information into kernel. Signed-off-by: Joel Becker <joel.becker@oracle.com>
435 lines
18 KiB
Plaintext
435 lines
18 KiB
Plaintext
|
|
configfs - Userspace-driven kernel object configuation.
|
|
|
|
Joel Becker <joel.becker@oracle.com>
|
|
|
|
Updated: 31 March 2005
|
|
|
|
Copyright (c) 2005 Oracle Corporation,
|
|
Joel Becker <joel.becker@oracle.com>
|
|
|
|
|
|
[What is configfs?]
|
|
|
|
configfs is a ram-based filesystem that provides the converse of
|
|
sysfs's functionality. Where sysfs is a filesystem-based view of
|
|
kernel objects, configfs is a filesystem-based manager of kernel
|
|
objects, or config_items.
|
|
|
|
With sysfs, an object is created in kernel (for example, when a device
|
|
is discovered) and it is registered with sysfs. Its attributes then
|
|
appear in sysfs, allowing userspace to read the attributes via
|
|
readdir(3)/read(2). It may allow some attributes to be modified via
|
|
write(2). The important point is that the object is created and
|
|
destroyed in kernel, the kernel controls the lifecycle of the sysfs
|
|
representation, and sysfs is merely a window on all this.
|
|
|
|
A configfs config_item is created via an explicit userspace operation:
|
|
mkdir(2). It is destroyed via rmdir(2). The attributes appear at
|
|
mkdir(2) time, and can be read or modified via read(2) and write(2).
|
|
As with sysfs, readdir(3) queries the list of items and/or attributes.
|
|
symlink(2) can be used to group items together. Unlike sysfs, the
|
|
lifetime of the representation is completely driven by userspace. The
|
|
kernel modules backing the items must respond to this.
|
|
|
|
Both sysfs and configfs can and should exist together on the same
|
|
system. One is not a replacement for the other.
|
|
|
|
[Using configfs]
|
|
|
|
configfs can be compiled as a module or into the kernel. You can access
|
|
it by doing
|
|
|
|
mount -t configfs none /config
|
|
|
|
The configfs tree will be empty unless client modules are also loaded.
|
|
These are modules that register their item types with configfs as
|
|
subsystems. Once a client subsystem is loaded, it will appear as a
|
|
subdirectory (or more than one) under /config. Like sysfs, the
|
|
configfs tree is always there, whether mounted on /config or not.
|
|
|
|
An item is created via mkdir(2). The item's attributes will also
|
|
appear at this time. readdir(3) can determine what the attributes are,
|
|
read(2) can query their default values, and write(2) can store new
|
|
values. Like sysfs, attributes should be ASCII text files, preferably
|
|
with only one value per file. The same efficiency caveats from sysfs
|
|
apply. Don't mix more than one attribute in one attribute file.
|
|
|
|
Like sysfs, configfs expects write(2) to store the entire buffer at
|
|
once. When writing to configfs attributes, userspace processes should
|
|
first read the entire file, modify the portions they wish to change, and
|
|
then write the entire buffer back. Attribute files have a maximum size
|
|
of one page (PAGE_SIZE, 4096 on i386).
|
|
|
|
When an item needs to be destroyed, remove it with rmdir(2). An
|
|
item cannot be destroyed if any other item has a link to it (via
|
|
symlink(2)). Links can be removed via unlink(2).
|
|
|
|
[Configuring FakeNBD: an Example]
|
|
|
|
Imagine there's a Network Block Device (NBD) driver that allows you to
|
|
access remote block devices. Call it FakeNBD. FakeNBD uses configfs
|
|
for its configuration. Obviously, there will be a nice program that
|
|
sysadmins use to configure FakeNBD, but somehow that program has to tell
|
|
the driver about it. Here's where configfs comes in.
|
|
|
|
When the FakeNBD driver is loaded, it registers itself with configfs.
|
|
readdir(3) sees this just fine:
|
|
|
|
# ls /config
|
|
fakenbd
|
|
|
|
A fakenbd connection can be created with mkdir(2). The name is
|
|
arbitrary, but likely the tool will make some use of the name. Perhaps
|
|
it is a uuid or a disk name:
|
|
|
|
# mkdir /config/fakenbd/disk1
|
|
# ls /config/fakenbd/disk1
|
|
target device rw
|
|
|
|
The target attribute contains the IP address of the server FakeNBD will
|
|
connect to. The device attribute is the device on the server.
|
|
Predictably, the rw attribute determines whether the connection is
|
|
read-only or read-write.
|
|
|
|
# echo 10.0.0.1 > /config/fakenbd/disk1/target
|
|
# echo /dev/sda1 > /config/fakenbd/disk1/device
|
|
# echo 1 > /config/fakenbd/disk1/rw
|
|
|
|
That's it. That's all there is. Now the device is configured, via the
|
|
shell no less.
|
|
|
|
[Coding With configfs]
|
|
|
|
Every object in configfs is a config_item. A config_item reflects an
|
|
object in the subsystem. It has attributes that match values on that
|
|
object. configfs handles the filesystem representation of that object
|
|
and its attributes, allowing the subsystem to ignore all but the
|
|
basic show/store interaction.
|
|
|
|
Items are created and destroyed inside a config_group. A group is a
|
|
collection of items that share the same attributes and operations.
|
|
Items are created by mkdir(2) and removed by rmdir(2), but configfs
|
|
handles that. The group has a set of operations to perform these tasks
|
|
|
|
A subsystem is the top level of a client module. During initialization,
|
|
the client module registers the subsystem with configfs, the subsystem
|
|
appears as a directory at the top of the configfs filesystem. A
|
|
subsystem is also a config_group, and can do everything a config_group
|
|
can.
|
|
|
|
[struct config_item]
|
|
|
|
struct config_item {
|
|
char *ci_name;
|
|
char ci_namebuf[UOBJ_NAME_LEN];
|
|
struct kref ci_kref;
|
|
struct list_head ci_entry;
|
|
struct config_item *ci_parent;
|
|
struct config_group *ci_group;
|
|
struct config_item_type *ci_type;
|
|
struct dentry *ci_dentry;
|
|
};
|
|
|
|
void config_item_init(struct config_item *);
|
|
void config_item_init_type_name(struct config_item *,
|
|
const char *name,
|
|
struct config_item_type *type);
|
|
struct config_item *config_item_get(struct config_item *);
|
|
void config_item_put(struct config_item *);
|
|
|
|
Generally, struct config_item is embedded in a container structure, a
|
|
structure that actually represents what the subsystem is doing. The
|
|
config_item portion of that structure is how the object interacts with
|
|
configfs.
|
|
|
|
Whether statically defined in a source file or created by a parent
|
|
config_group, a config_item must have one of the _init() functions
|
|
called on it. This initializes the reference count and sets up the
|
|
appropriate fields.
|
|
|
|
All users of a config_item should have a reference on it via
|
|
config_item_get(), and drop the reference when they are done via
|
|
config_item_put().
|
|
|
|
By itself, a config_item cannot do much more than appear in configfs.
|
|
Usually a subsystem wants the item to display and/or store attributes,
|
|
among other things. For that, it needs a type.
|
|
|
|
[struct config_item_type]
|
|
|
|
struct configfs_item_operations {
|
|
void (*release)(struct config_item *);
|
|
ssize_t (*show_attribute)(struct config_item *,
|
|
struct configfs_attribute *,
|
|
char *);
|
|
ssize_t (*store_attribute)(struct config_item *,
|
|
struct configfs_attribute *,
|
|
const char *, size_t);
|
|
int (*allow_link)(struct config_item *src,
|
|
struct config_item *target);
|
|
int (*drop_link)(struct config_item *src,
|
|
struct config_item *target);
|
|
};
|
|
|
|
struct config_item_type {
|
|
struct module *ct_owner;
|
|
struct configfs_item_operations *ct_item_ops;
|
|
struct configfs_group_operations *ct_group_ops;
|
|
struct configfs_attribute **ct_attrs;
|
|
};
|
|
|
|
The most basic function of a config_item_type is to define what
|
|
operations can be performed on a config_item. All items that have been
|
|
allocated dynamically will need to provide the ct_item_ops->release()
|
|
method. This method is called when the config_item's reference count
|
|
reaches zero. Items that wish to display an attribute need to provide
|
|
the ct_item_ops->show_attribute() method. Similarly, storing a new
|
|
attribute value uses the store_attribute() method.
|
|
|
|
[struct configfs_attribute]
|
|
|
|
struct configfs_attribute {
|
|
char *ca_name;
|
|
struct module *ca_owner;
|
|
mode_t ca_mode;
|
|
};
|
|
|
|
When a config_item wants an attribute to appear as a file in the item's
|
|
configfs directory, it must define a configfs_attribute describing it.
|
|
It then adds the attribute to the NULL-terminated array
|
|
config_item_type->ct_attrs. When the item appears in configfs, the
|
|
attribute file will appear with the configfs_attribute->ca_name
|
|
filename. configfs_attribute->ca_mode specifies the file permissions.
|
|
|
|
If an attribute is readable and the config_item provides a
|
|
ct_item_ops->show_attribute() method, that method will be called
|
|
whenever userspace asks for a read(2) on the attribute. The converse
|
|
will happen for write(2).
|
|
|
|
[struct config_group]
|
|
|
|
A config_item cannot live in a vaccum. The only way one can be created
|
|
is via mkdir(2) on a config_group. This will trigger creation of a
|
|
child item.
|
|
|
|
struct config_group {
|
|
struct config_item cg_item;
|
|
struct list_head cg_children;
|
|
struct configfs_subsystem *cg_subsys;
|
|
struct config_group **default_groups;
|
|
};
|
|
|
|
void config_group_init(struct config_group *group);
|
|
void config_group_init_type_name(struct config_group *group,
|
|
const char *name,
|
|
struct config_item_type *type);
|
|
|
|
|
|
The config_group structure contains a config_item. Properly configuring
|
|
that item means that a group can behave as an item in its own right.
|
|
However, it can do more: it can create child items or groups. This is
|
|
accomplished via the group operations specified on the group's
|
|
config_item_type.
|
|
|
|
struct configfs_group_operations {
|
|
struct config_item *(*make_item)(struct config_group *group,
|
|
const char *name);
|
|
struct config_group *(*make_group)(struct config_group *group,
|
|
const char *name);
|
|
int (*commit_item)(struct config_item *item);
|
|
void (*drop_item)(struct config_group *group,
|
|
struct config_item *item);
|
|
};
|
|
|
|
A group creates child items by providing the
|
|
ct_group_ops->make_item() method. If provided, this method is called from mkdir(2) in the group's directory. The subsystem allocates a new
|
|
config_item (or more likely, its container structure), initializes it,
|
|
and returns it to configfs. Configfs will then populate the filesystem
|
|
tree to reflect the new item.
|
|
|
|
If the subsystem wants the child to be a group itself, the subsystem
|
|
provides ct_group_ops->make_group(). Everything else behaves the same,
|
|
using the group _init() functions on the group.
|
|
|
|
Finally, when userspace calls rmdir(2) on the item or group,
|
|
ct_group_ops->drop_item() is called. As a config_group is also a
|
|
config_item, it is not necessary for a seperate drop_group() method.
|
|
The subsystem must config_item_put() the reference that was initialized
|
|
upon item allocation. If a subsystem has no work to do, it may omit
|
|
the ct_group_ops->drop_item() method, and configfs will call
|
|
config_item_put() on the item on behalf of the subsystem.
|
|
|
|
IMPORTANT: drop_item() is void, and as such cannot fail. When rmdir(2)
|
|
is called, configfs WILL remove the item from the filesystem tree
|
|
(assuming that it has no children to keep it busy). The subsystem is
|
|
responsible for responding to this. If the subsystem has references to
|
|
the item in other threads, the memory is safe. It may take some time
|
|
for the item to actually disappear from the subsystem's usage. But it
|
|
is gone from configfs.
|
|
|
|
A config_group cannot be removed while it still has child items. This
|
|
is implemented in the configfs rmdir(2) code. ->drop_item() will not be
|
|
called, as the item has not been dropped. rmdir(2) will fail, as the
|
|
directory is not empty.
|
|
|
|
[struct configfs_subsystem]
|
|
|
|
A subsystem must register itself, ususally at module_init time. This
|
|
tells configfs to make the subsystem appear in the file tree.
|
|
|
|
struct configfs_subsystem {
|
|
struct config_group su_group;
|
|
struct semaphore su_sem;
|
|
};
|
|
|
|
int configfs_register_subsystem(struct configfs_subsystem *subsys);
|
|
void configfs_unregister_subsystem(struct configfs_subsystem *subsys);
|
|
|
|
A subsystem consists of a toplevel config_group and a semaphore.
|
|
The group is where child config_items are created. For a subsystem,
|
|
this group is usually defined statically. Before calling
|
|
configfs_register_subsystem(), the subsystem must have initialized the
|
|
group via the usual group _init() functions, and it must also have
|
|
initialized the semaphore.
|
|
When the register call returns, the subsystem is live, and it
|
|
will be visible via configfs. At that point, mkdir(2) can be called and
|
|
the subsystem must be ready for it.
|
|
|
|
[An Example]
|
|
|
|
The best example of these basic concepts is the simple_children
|
|
subsystem/group and the simple_child item in configfs_example.c It
|
|
shows a trivial object displaying and storing an attribute, and a simple
|
|
group creating and destroying these children.
|
|
|
|
[Hierarchy Navigation and the Subsystem Semaphore]
|
|
|
|
There is an extra bonus that configfs provides. The config_groups and
|
|
config_items are arranged in a hierarchy due to the fact that they
|
|
appear in a filesystem. A subsystem is NEVER to touch the filesystem
|
|
parts, but the subsystem might be interested in this hierarchy. For
|
|
this reason, the hierarchy is mirrored via the config_group->cg_children
|
|
and config_item->ci_parent structure members.
|
|
|
|
A subsystem can navigate the cg_children list and the ci_parent pointer
|
|
to see the tree created by the subsystem. This can race with configfs'
|
|
management of the hierarchy, so configfs uses the subsystem semaphore to
|
|
protect modifications. Whenever a subsystem wants to navigate the
|
|
hierarchy, it must do so under the protection of the subsystem
|
|
semaphore.
|
|
|
|
A subsystem will be prevented from acquiring the semaphore while a newly
|
|
allocated item has not been linked into this hierarchy. Similarly, it
|
|
will not be able to acquire the semaphore while a dropping item has not
|
|
yet been unlinked. This means that an item's ci_parent pointer will
|
|
never be NULL while the item is in configfs, and that an item will only
|
|
be in its parent's cg_children list for the same duration. This allows
|
|
a subsystem to trust ci_parent and cg_children while they hold the
|
|
semaphore.
|
|
|
|
[Item Aggregation Via symlink(2)]
|
|
|
|
configfs provides a simple group via the group->item parent/child
|
|
relationship. Often, however, a larger environment requires aggregation
|
|
outside of the parent/child connection. This is implemented via
|
|
symlink(2).
|
|
|
|
A config_item may provide the ct_item_ops->allow_link() and
|
|
ct_item_ops->drop_link() methods. If the ->allow_link() method exists,
|
|
symlink(2) may be called with the config_item as the source of the link.
|
|
These links are only allowed between configfs config_items. Any
|
|
symlink(2) attempt outside the configfs filesystem will be denied.
|
|
|
|
When symlink(2) is called, the source config_item's ->allow_link()
|
|
method is called with itself and a target item. If the source item
|
|
allows linking to target item, it returns 0. A source item may wish to
|
|
reject a link if it only wants links to a certain type of object (say,
|
|
in its own subsystem).
|
|
|
|
When unlink(2) is called on the symbolic link, the source item is
|
|
notified via the ->drop_link() method. Like the ->drop_item() method,
|
|
this is a void function and cannot return failure. The subsystem is
|
|
responsible for responding to the change.
|
|
|
|
A config_item cannot be removed while it links to any other item, nor
|
|
can it be removed while an item links to it. Dangling symlinks are not
|
|
allowed in configfs.
|
|
|
|
[Automatically Created Subgroups]
|
|
|
|
A new config_group may want to have two types of child config_items.
|
|
While this could be codified by magic names in ->make_item(), it is much
|
|
more explicit to have a method whereby userspace sees this divergence.
|
|
|
|
Rather than have a group where some items behave differently than
|
|
others, configfs provides a method whereby one or many subgroups are
|
|
automatically created inside the parent at its creation. Thus,
|
|
mkdir("parent) results in "parent", "parent/subgroup1", up through
|
|
"parent/subgroupN". Items of type 1 can now be created in
|
|
"parent/subgroup1", and items of type N can be created in
|
|
"parent/subgroupN".
|
|
|
|
These automatic subgroups, or default groups, do not preclude other
|
|
children of the parent group. If ct_group_ops->make_group() exists,
|
|
other child groups can be created on the parent group directly.
|
|
|
|
A configfs subsystem specifies default groups by filling in the
|
|
NULL-terminated array default_groups on the config_group structure.
|
|
Each group in that array is populated in the configfs tree at the same
|
|
time as the parent group. Similarly, they are removed at the same time
|
|
as the parent. No extra notification is provided. When a ->drop_item()
|
|
method call notifies the subsystem the parent group is going away, it
|
|
also means every default group child associated with that parent group.
|
|
|
|
As a consequence of this, default_groups cannot be removed directly via
|
|
rmdir(2). They also are not considered when rmdir(2) on the parent
|
|
group is checking for children.
|
|
|
|
[Committable Items]
|
|
|
|
NOTE: Committable items are currently unimplemented.
|
|
|
|
Some config_items cannot have a valid initial state. That is, no
|
|
default values can be specified for the item's attributes such that the
|
|
item can do its work. Userspace must configure one or more attributes,
|
|
after which the subsystem can start whatever entity this item
|
|
represents.
|
|
|
|
Consider the FakeNBD device from above. Without a target address *and*
|
|
a target device, the subsystem has no idea what block device to import.
|
|
The simple example assumes that the subsystem merely waits until all the
|
|
appropriate attributes are configured, and then connects. This will,
|
|
indeed, work, but now every attribute store must check if the attributes
|
|
are initialized. Every attribute store must fire off the connection if
|
|
that condition is met.
|
|
|
|
Far better would be an explicit action notifying the subsystem that the
|
|
config_item is ready to go. More importantly, an explicit action allows
|
|
the subsystem to provide feedback as to whether the attibutes are
|
|
initialized in a way that makes sense. configfs provides this as
|
|
committable items.
|
|
|
|
configfs still uses only normal filesystem operations. An item is
|
|
committed via rename(2). The item is moved from a directory where it
|
|
can be modified to a directory where it cannot.
|
|
|
|
Any group that provides the ct_group_ops->commit_item() method has
|
|
committable items. When this group appears in configfs, mkdir(2) will
|
|
not work directly in the group. Instead, the group will have two
|
|
subdirectories: "live" and "pending". The "live" directory does not
|
|
support mkdir(2) or rmdir(2) either. It only allows rename(2). The
|
|
"pending" directory does allow mkdir(2) and rmdir(2). An item is
|
|
created in the "pending" directory. Its attributes can be modified at
|
|
will. Userspace commits the item by renaming it into the "live"
|
|
directory. At this point, the subsystem recieves the ->commit_item()
|
|
callback. If all required attributes are filled to satisfaction, the
|
|
method returns zero and the item is moved to the "live" directory.
|
|
|
|
As rmdir(2) does not work in the "live" directory, an item must be
|
|
shutdown, or "uncommitted". Again, this is done via rename(2), this
|
|
time from the "live" directory back to the "pending" one. The subsystem
|
|
is notified by the ct_group_ops->uncommit_object() method.
|
|
|
|
|