[Brandon University crest][The famous "Halfway Tree" of the Prairies, between Winnipeg and Brandon]


KModel 
 


Site Map

  Kmodel Home  
  - Download
  - Installation
  - Javadoc

  Hardware Model
  - Serial  
  - Timer  
  - Disk  

  Kernel Design  
  - Modules  
  - User Process  
  - Kernel Entry  
  - Kernel Call  
  - Handles  
  - Signals

  Components  
  - Processes  
  - Console  
  - Timer  
  - Disk  

  File System  
  - Disk Layout
  - Buffer Cache
  - Inode Cache
  - Standard Files
  - Vnodes

  Study Questions  

 

 

 

Vnodes

A vnode is a kernel object that provides a common interface to any kind of open file: a regular or directory file, a block or character device file, a pipe, etc. The common interface includes methods to read and write data to the file and to truncate. A vnode maintains a position within the file that indicates where the next read or write operation will occur. The position is affected by the seek method.

Vnodes can be shared, which means that both the data in the file and the offset are shared. A Vnode contains a reference count that tracks the number of file handles that in which it appears. (File handles are not shareable, but the resources they encapsulate are.) Sharing occurs when a process changes a standard file handle and when a process creates another process. The new process is populated with duplicates of the vnodes associated with standard file handles in the parent process.

[View diagram full size]

A vnode contains a reference to an in-memory copy of the inode that describes the file. Inodes can be shared among vnodes, so they have their own reference count. The file system in which the file resides is referenced indirectly through the inode.

Opening a File

When a file is opened, its name is first converted to an absolute path and a search is started at the root directory of the root file system. The search is handled by an instance of FilePath. When the search crosses mount points, as determined by information in an instance of Mount, the file system is changed.

Elements of the path are traversed. A vnode is created and discarded as each element is processed. Vnodes for internal path elements must refer to directory files. 

An instance of FileSystem is used to create a vnode from an inode index. FileSystem uses information in its SuperBlock and the facilities of its KernelDeviceBlock as well as the InodeAllocator to prodeuce an in-memory version of the inode. Depending on the inode type -- file, directory, block, char, pipe, symbolic link -- an appropriate subclass of vnode is created by VnodeFactory.

The sys-open method in ModuleFileSystem starts this process and ends by wrapping the vnode in a handle.

    public void sys_open(String name) {
        Filepath fp = new Filepath(kernel.vf, rootfs, kernel.active.cwd, name);
        Vnode vnode = fp.open();
        if (vnode == null) {
            kernel.setResult(-1, fp.error);
        } else {
            HandleFile h = new HandleFile(vnode);
            kernel.active.addHandle(h);
            kernel.setResult(0, h);
        }
    }

The open process continues in the open() method of Filepath. Filepath is initialized with the absolute pathname in its name field and an empty parent. As each path element is processed, it is removed from name and appended to parent. At any point, the expression parent + "/" + name matches the original pathname. When the name has no more "/" separators, the parent has been found and only the final element remains to be processed. This first step is handled by openParent().

Filepath contains state information that reflects the position within the path. As each element is processed, the field inodeIndex is updated to contain the inode index of the last component of the growing parent field. The field inodeOffset contains the seek position of the directory entry for that element. The field fs contains the filesystem -- and hence the block device -- of the last element in parent

One element at a time is processed by step(). Initially, inodeIndex is 0, fs is the root file system, parent is empty and name is the absolute pathname with no leading "/". 

    protected void step() {
        if (name.length() == 0)
            return;
        int pos = name.indexOf("/");
        if (pos != -1) {
            Vnode vnode = vf.vnodeOpen(fs, inodeIndex);
            if (!vnode.inode.isDir()) {
                error = "invalid path";
                vf.release(vnode);
                return;
            }
            String element = name.substring(0, pos);
            int index = dirSearch(vnode, parent, element);
            if (index == -1) {
                error = "invalid path";
                vf.release(vnode);
                return;
            }
            inodeIndex = index;
            parent = parent + "/" + element;
            name = name.substring(pos+1);
            vf.release(vnode);
        }
    }

dirSearch() joins the parent with the first element of name to check for a mount point. If one exists, the file system in fs is changed and the inodeIndex is reset to 0. Otherwise, the vnode is treated as a directory and searched. The result of this is a valid inode index or -1. If the result is valid, inodeOffset is updated. 

    protected int dirSearch(Vnode vnode, String parent, String element) {
        FileSystem mountedFS = Kernel.kernel.mount.get(parent + "/" + element);
        if (mountedFS == null)
            return dirFileSearch(vnode, element);
        else {
            fs = mountedFS;
            return 0;
        }
    }

When openParent() succeeds, it returns a vnode for the parent directory.

    protected Vnode openParent() {
        stepPath();
        if (hasError())
            return null;
        if (name.length() == 0) {
            error = "invalid filename";
            return null;
        }
        Vnode parentVnode = vf.vnodeOpen(fs, inodeIndex);
        if (!parentVnode.inode.isDir()) {
            error = "invalid path";
            vf.release(parentVnode);
            return null;
        }
        return parentVnode;
    }    

open() wraps the ideas presented above. It calls openParent() to sequence through the path and produce the vnode of the parent. It searches the parent to find the file's inode and uses ModuleVnodeFactoryto create a vnode from the inode..

    public Vnode open() {
        Vnode parentVnode = openParent();
        if (hasError())
            return null;
        int index = dirSearch(parentVnode, parent, name);
        if (index == -1) {
            error = "invalid path";
            vf.release(parentVnode);
            return null;
        }
        inodeIndex = index;
        vf.release(parentVnode);
        return vf.vnodeOpen(fs, inodeIndex);
    }

The vnode is returned to sys_open which wraps it in a handle for use by the process.

Creating a file

Opening for write is similar to opening but has two special cases: one if the file already exists and one if the file must be created. If the file already exists, it is simply opened. If it must be created, the vnode of a new file on the current file system is acquired from the ModuleVnodeFactory. The inode of the vnode is added to the end of the current directory along with the name of the file. 

    protected Vnode create() {
        Vnode parentVnode = openParent();
        if (hasError())
            return null;
        int index = dirSearch(parentVnode, parent, name);
        if (index != -1) { // already exists
            Vnode vnode = vf.vnodeOpen(fs, index);
            parent = parent + "/" + name;
            vf.release(parentVnode);
            return vnode;
        } else {
            Vnode vnode = vf.vnodeCreateREG(fs, FSInode.S_IFREG);
            if (vnode == null)
                error = "file system full";
            else {
                vnode.inode.setLinkcount(1);
                parentVnode.seek(0, 2);
                parentVnode.putInt(vnode.inode.index);
                parentVnode.putString(name);
                parent = parent + "/" + name;
                inodeIndex = index;
            }
            vf.release(parentVnode);
            return vnode;
        }
    }    

If the file already exists, it is not automatically truncated. This is the responsibility of ModuleFileSystem, which must look at flags passed from the user process.

Creating special files

Creating a directory or a device file is a special case of creating a regular file. The distinction is not a simple matter of different bit settings in the mode field of the inode. When creating a directory (mkdir), the "." and ".." directory references must be written into the file and link counts must be updated. When creating a device file (mknod), the major and minor device numbers must be written into the inode. All these special cases are accommodated by different methods in ModuleVnodeFactory.

Removing a file

Removing a file occurs in two stages. In the first stage, a file is simply removed from a directory. This is a matter of closing up the space in directory where the inode and filename existed previously. Also, since the file no longer appears in the directory, the inode's link count is decremented. The original Unix command for removing a file was named unlink, since this is what happends.

The second stage occurs when the link count is decrement to zero and the last open occurrence of the file is closed. At this point, the file is truly orphaned. It's data blocks are returned to its containing file system and the inode is recycled.

A file is truly orphaned when a vnode is released and its reference count is about to be decremented to zero, and when the underlying inode's reference count is about to be decrement to zero and the inode's link count is already at zero. This all happens in the release() method of ModuleVnodeFactory.

    public void release(Vnode vnode) {
        if (vnode.inode.refcount == 1 && vnode.inode.getLinkcount() == 0) {
            if (vnode.inode.isDir() || vnode.inode.isReg()) {
                VnodeFile vnodeFile = (VnodeFile)vnode;
                vnodeFile.truncate();
                vnodeFile.getFS().free(vnode.inode);
            }
        }
        vnode.release();
        kernel.ia.release(vnode.inode);

 

     

Last update 01/28/05
Copyright © Gerald Dueck
[=]