asterinas icon indicating copy to clipboard operation
asterinas copied to clipboard

[RFC] Add basic SysFS

Open StanPlatinum opened this issue 8 months ago • 2 comments

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

  1. 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.
  2. 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.
  3. 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.
  4. 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.

StanPlatinum avatar Jun 20 '24 13:06 StanPlatinum