asterinas
asterinas copied to clipboard
[RFC] Add basic SysFS
Motivation
In the development of the Asterinas Kernel, implementing the sysfs file system is essential before realizing the cgroup file system, which also benefits #703 . SysFs
, as a critical kernel component, provides the necessary infrastructure and mechanisms for managing devices. Below is a detailed description of the functionality and key design aspects. Thanks @Plucky923 for providing most of the detailed design!
SysFS Design
SysFS File System Functions
-
Kernel Object Mapping:
SysFs
maps kernel objects (such as devices, drivers, buses, etc.) into files and directories within the file system, allowing user-space programs to access and control these kernel objects through file operations. -
Attribute Export:
SysFs
allows kernel objects to export their attributes, which exist as files in the sysfs file system. Users can read or write these files to get or modify the state of the kernel objects. -
Hierarchy Representation:
SysFs
presents a hierarchical file system structure that shows the component hierarchy in the device driver model, enabling users to understand the system composition and configuration intuitively. -
Dynamic Updates:
SysFs
supports dynamically adding, deleting, and modifying kernel objects. These changes are reflected in real-time in the file system, providing a flexible system management approach.
Data Structures of SysFS
Below are the core data structures in the sysfs file system, maintaining interaction and hierarchy between kernel objects and user space:
KObject
KObject: The basic unit in sysfs, representing a kernel object. Through the parent
and children
fields, KObjects can form a hierarchical structure, allowing kernel objects to be organized in a tree-like form. In sysfs, each KObject
corresponds to a file, enabling users to access or modify the state of kernel objects by operating on these files.
struct KObject {
/// Corresponding directory name in sysfs
name: String,
/// Pointer to the parent object, forming a hierarchical structure represented as parent-child directories in sysfs
parent: Option<Weak<Mutex<KObject>>>,
/// Collection of child objects
children: HashMap<String, Arc<Mutex<KObject>>>,
/// Represents the kset this kobject belongs to
kset: Option<Weak<Mutex<KSet>>>,
/// Represents the type attributes of the kernel object
ktype: Option<Arc<KObjectType>>,
/// File node object in sysfs
sd: Option<Arc<Mutex<KernfsNode>>>,
}
Attribute
Attribute: Represents a file in sysfs, acting as the medium for information exchange between the kernel space and user space. By exposing kernel variables or states as Attributes to user space, user space programs can read or modify these variables, thereby controlling the behavior of kernel objects.
struct Attribute {
/// Attribute name
name: String,
/// Permission mode
mode: u32,
}
KObjectType
KObjectType: Embedded into KObjects, KObjectType defines how to operate on the attribute files of a KObject, including default attribute file groups. Each KObject requires a corresponding KObjectType to implement its attribute operation methods.
struct KObjectType {
/// Default attribute groups
default_groups: Vec<Arc<AttributeGroup>>,
}
impl KObjectType {
/// Implement the behavior when reading and writing attribute files
}
KSet
KSet: A collection of KObjects, organizing KObjects with similar or different attributes. A KSet itself is also a KObject, thus appearing as a directory in sysfs, under which other KObjects can be included as children. KSets define common attributes and behaviors for a group of KObjects.
struct KSet {
/// Linked list of kobjects belonging to this kset
kobjects: BTreeMap<String, Arc<Mutex<KObject>>>,
/// Embedded kobject
kobj: Arc<Mutex<KObject>>,
}
KernfsNode
KernfsNode: The underlying data structure of the sysfs file system, serving as both directory entry objects and index node objects. KernfsNode maintains the hierarchical structure of sysfs through its internal fields and provides access control and attribute management for each node.
struct KernfsNode {
/// Parent node
parent: Option<Weak<Mutex<KernfsNode>>>,
/// Node name
name: String,
/// Child nodes
children: HashMap<String, Arc<Mutex<KernfsNode>>>,
}
/// Type of kernel file system node
enum KernfsNodeType {
Dir(KernfsElemDir),
Symlink(KernfsElemSymlink),
Attr(KernfsElemAttr),
}
AttributeGroup
AttributeGroup: A collection of multiple Attribute files, allowing the kernel to define and manage a group of attributes in a structured way. Using AttributeGroups facilitates the grouping and access of attributes.
struct AttributeGroup {
/// Group name
name: String,
/// Collection of attributes
attrs: Vec<Arc<Attribute>>,
}
These structures jointly maintain the interaction and hierarchy between kernel objects and user space in the sysfs file system.
Detailed Implementation
Building on the design, here is a detailed implementation of the SysFs
file system :
Initializing SysFS
struct SysFS {
sb: RwLock<SuperBlock>,
root: RwLock<Option<Arc<dyn Inode>>>,
inode_allocator: AtomicUsize,
}
impl SysFS {
pub fn new() -> Arc<Self> {
let procfs = {
let sb = SuperBlock::new(SYSFS_MAGIC, BLOCK_SIZE, NAME_MAX);
Arc::new(Self {
sb: RwLock::new(sb),
root: RwLock::new(None),
inode_allocator: AtomicUsize::new(SYSFS_ROOT_INO),
})
};
init_sysfs_root().new_root()
}
}
impl Filesystem for SysFS {
fn sync(&self) -> Result<()> {
Ok(())
}
fn root_inode(&self) -> Arc<dyn Inode> {
self.root.read().as_ref().unwrap().clone()
}
fn sb(&self) -> SuperBlock {
self.sb.read().clone()
}
fn flags(&self) -> FsFlags {
FsFlags::empty()
}
}
impl KernfsNode {
fn new_root() -> Arc<dyn Inode> {
unimplemented!()
}
}
impl Inode for KernfsNode {
fn lookup(&self, name: &str) -> Result<Arc<dyn Inode>> {
// Lookup the corresponding KernfsNode by FsPath and return a Dentry
unimplemented!()
}
// Other necessary methods such as read, write, etc., need to be implemented...
}
Mounting SysFS
// Assume `fs` is the handle for your filesystem mount point
let sysfs_dentry = fs.lookup(&FsPath::try_from("/sys")?)?; // Lookup /sys mount point
sysfs_dentry.mount(SysFS::new())?; // Create and mount sysfs using SysFS::new()
Initializing SysFS Root Directory
fn init_sysfs_root() -> Arc<Mutex<KObject>> {
let root_kobj = Arc::new(Mutex::new(KObject {
name: "/".to_string(),
parent: None,
children: HashMap::new(),
kset: None,
ktype: Some(Arc::new(KObjectType::default())),
sd: None,
}));
let kernfs_root = Arc::new(Mutex::new(KernfsNode {
parent: None,
name: "/".to_string(),
children: HashMap::new(),
}));
{
let mut root_kobj_lock = root_kobj.lock().unwrap();
root_kobj_lock.sd = Some(kernfs_root.clone());
}
root_kobj
}
Registering KObjects to KSets
fn register_kobject_to_kset(kset: &Arc<Mutex<KSet>>, kobj: Arc<Mutex<KObject>>) {
{
let mut kset_lock = kset.lock().unwrap();
// Insert the KObject into the KSet's kobjects map
kset_lock.kobjects.insert(kobj.lock().unwrap().name.clone(), kobj.clone());
}
// Create a KernfsNode for the KObject
let kobj_name = kobj.lock().unwrap().name.clone();
let kernfs_node = Arc::new(Mutex::new(KernfsNode {
parent: Some(Arc::downgrade(&kset.lock().unwrap().kobj)),
name: kobj_name.clone(),
children: HashMap::new(),
}));
{
// Link the KObject with its corresponding KernfsNode
let mut kobj_lock = kobj.lock().unwrap();
kobj_lock.sd = Some(kernfs_node.clone());
}
// Add the KernfsNode to its parent's children if applicable
if let Some(parent_sd) = &kobj.lock().unwrap().sd {
if let Some(parent_sd_upgraded) = parent_sd.upgrade() {
let mut parent_sd_lock = parent_sd_upgraded.lock().unwrap();
parent_sd_lock.children.insert(kobj_name, kernfs_node);
}
}
}
Adding Attribute Groups to SysFS Representation
impl KObjectType {
fn create_sysfs_representation_for_group(&self, kobj: &Arc<Mutex<KObject>>) {
for group in &self.default_groups {
let attr_dir_node = Arc::new(Mutex::new(KernfsNode {
parent: Some(Arc::downgrade(&kobj.lock().unwrap().sd.as_ref().unwrap())),
name: group.name.clone(),
children: HashMap::new(),
}));
for attr in &group.attrs {
let attr_node = Arc::new(Mutex::new(KernfsNode {
parent: Some(Arc::downgrade(&attr_dir_node)),
name: attr.name.clone(),
children: HashMap::new(),
}));
// Implement read/write logic for Attribute through KernfsElemAttr or other means
// ...
attr_dir_node.lock().unwrap().children.insert(attr.name.clone(), attr_node);
}
kobj.lock().unwrap().sd.as_ref().unwrap().lock().unwrap().children.insert(group.name.clone(), attr_dir_node);
}
}
}
Implementing Attribute File Operations
Each Attribute needs to have specific behavior for read and write operations in KernfsNode
. This involves callback functions for file system operations, such as open, read, write, and close. In kernel development, this might involve lower-level Virtual File System interfaces or custom Kernel File System implementation details.