RTEMS File System
The RTEMS File System or RFS provides a fully featured file system that is fast and compact. The RFS interfaces to the Block Devices API and therefore supports all the block devices RTEMS has. It also interfaces to the RTEMS libc file system support layer. This means the POSIX interface to files in RTEMS is supported by the RFS file system.
The RFS file system consists of superblock at the start followed by a series of groups. A group is a group of blocks and exists to keep the bitmaps in a group to a file block size plus to also help localise blocks and inodes.
Superblock Group Group
A group is broken up into the block allocation bitmap the inode allocation bitmap, the inodes, and the data blocks.
Block Bitmap Inode Bitmap Inodes Data Blocks
The block bitmap contains a single bit for every block in a group. The default format is to allocate the size of a group based on the number of bits that fit into a single block. For example a block of 1024 bytes has 8,192 bits therefore a group can have 8192 blocks including the block bitmap block. The inode bitmap has a bit for ever inode in the group. An inode is the information about a node on the disk. A node is the data that links the elements of the disk together to create directories, files, and nodes as well as hold times and flags. The number of inodes in a group is a format configuration parameter. The default is use 1% of the disk's blocks for inode data. Again with a 1024 byte block size there are 18 inodes per block and 1448 inodes per group. Inodes do not span block boundaries.
An inode can be a device node (block or character), directory, FIFO, regular file, link, or socket. Current RTEMS does not support FIFO or socket nodes in its file systems so they are ignored. Inodes are numbered from 1 so there is no inode number 0. The root inode is always 1 and therefore the first inode of the first group and its mode is directory. This directory can contain any number of inodes including directories. Files can be link using hard or symbolic links. Directories cannot be linked therefore avoiding the creation of a directed cyclic graph or DCG. An inode can have a map of blocks associated to it. The use of the data depends on the mode of the inode. If the inode is a directory the data in the block map is the contents of the directory. If a regular file the block map contains the file's data and if a symbolic link the data is the link path. The block map has three levels. The first is a series of block numbers held in the inode. The current value is 5 blocks. When the block map is less than or equal to 5 blocks the mapping is called direct as the block numbers map directly to data blocks. If there are more than 5 blocks the 5 inode slots point to blocks that hold the data blocks. This called singly indirect. The number of blocks that can be addressed with a single indirect map depends on the block size. For a block size of 1024 bytes and a 32bit block number a block holds 256 blocks, and with 5 indirect blockes addressable from the inode this is a total of 1280 blocks or 1.3M bytes. If the map grows to have more blocks the map changes to a doubly indirect map. Here the block addressed from the inode holds block numbers to singly indirect blocks. With our example block size of 1024 bytes this takes the maximum file size up to 327,680 blocks or 335M bytes. A block size of 4096 allows a single file to be 21.4G bytes.
Inodes hold the number of links to itself. If 0 the inode is not linked and therefore should be marked as free in the group's inode allocator bitmap. An inode can have a number of links or hard links it is not of type directory. Inodes also hold the owner details and the permission bits. These control access to the inode via the operating calls. Finally inodes hold the access, modified and changed times. The access time if the last time the file was read, the modified time the last time file was written to and the changed time the last time the inode was written to.
A block or character device node is an inode that holds the major and minor device number. These numbers are held in the block map's slots. A directory entry links to the inode therefore giving it a name in the file system.
A directory is like a regular file how-ever its format is internal to the file system. The RFS directory is a block map of variable length directory entries. A directory has the following format:
Inode Number (INO) Hash Length Data
The Inode number of INO is the 32bit inode number this entry references, the hash is a 32bit hash of the data, and length is the number of bytes in the directory entry. This means the minimum size for a directory entry is 12 bytes. The hash provided a fast way to check if a directory entry matches.
Directories are maintained unsorted, sparse and not indexed. If directory performance is an issue it can be addressed in future versions. Blocks of directory entried are maintained compacted. As entries are removed the entried in the block are compacted, how-ever intermediate blocks in a block map are not removed and freed when they become empty while trailing empty blocks are.
Regular files are an inode with a block map. Internally the RFS code maintains the inode data in memory while a file is open. If the file is opened a second time the inode data is shared between the file handles. This allows the POSIX required time fields to be maintained with minimal overhead.
RFS Shell Configuration
The easiest way to use the RFS and to play with it is to enable the shell in your application. To do this add to the file that handles your condefs.h defines:
#define CONFIGURE_SHELL_COMMANDS_INIT #define CONFIGURE_SHELL_COMMANDS_ALL #define CONFIGURE_SHELL_MOUNT_RFS #define CONFIGURE_SHELL_DEBUGRFS #include <rtems/shellconfig.h>
In the main of your application start the shell then at the prompt list the devices you have. The RFS test application running on a MCF5235 lists 3 block type devices:
RTEMS SHELL (Ver.1.0-FRC):/dev/console. Feb 20 2010. 'help' to list commands. [/] # ls -las dev total 0 0 crwxr-xr-x 1 root root 0, 0 Jan 1 00:00 console 0 brwxr-xr-x 1 root root 3, 0 Jan 1 00:00 fdda 0 brwxr-xr-x 1 root root 4, 0 Jan 1 00:00 nvda 0 brwxr-xr-x 1 root root 2, 0 Jan 1 00:00 rda 0 crwxr-xr-x 1 root root 0, 1 Jan 1 00:00 tty01
For this example we will use the flash disk. The flash disk on the MCF5235 lives on the single flash device on the board which is shared with the boot monitor that comes with the board. We assume this is a new board so erase the flash:
[/] # fderase /dev/fdda erase flash disk: /dev/fdda flash disk erased successful
The fderase command can be found in the RFS test application. Next we format the disk with the RFS format command. This command is:
mkrfs [-v] [-s blksz] [-b grpblk] [-i grpinode] [-I] [-o %inode] dev
- -s blksize
- The file system block size.
- -b grpblk
- Number of blocks in a group.
- -i grpinode
- Number of inodes in a group.
- Initialise the inodes.
- -o %inode
- The percentage of blocks allocated to inodes in a group.
- The device to format.
Next we format the flash disk with the default configuration parameters then mounting it:
[/] # mkrfs /dev/fdda [/] # mkdir f [/] # mount -t rfs /dev/fdda /f mounted /dev/fdda -> /f [/] # ls f [/]
To see the format of the disk run the
debugrfs command with the
[/] # debugrfs /f data RFS Filesystem Data flags: 00000000 blocks: 1510 block size: 1024 size: 1546240 media block size: 512 media size: 1546240 inodes: 1458 bad blocks: 0 max. name length: 512 groups: 1 group blocks: 8192 group inodes: 1458 inodes per block: 18 blocks per block: 256 singly blocks: 1280 doublly blocks: 327680 max. held buffers: 5 blocks used: 84 (5.5%) inodes used: 1 (0.0%)
RFS Programmable Configuration
The RFS can be configured and manage directly from your software. To format a disk include rtems-rfs-format.h in your application code then create a variable of type rtems_rfs_format_config and initialise it to 0:
#include <rtems-rfs-format.h> rtems_rfs_format_config config; memset (&config, 0, sizeof (rtems_rfs_format_config));
The rtems_rfs_format_config structure has the following fields:
- The size of a block. If 0 the formatter will determine an optimal block by looking at the size of the disk.
- The number of blocks in a group. If 0 the formatter will attempt to make a group have as many blocks as bit fit in a block. This minimises the number of groups.
- Number of inodes in a group. If 0 the formatter will use the percentage configuration field to determine the number of inodes.
- The percentage overhead inodes consume. The percentage value, eg 1 for 1% means 1% of the total blocks will be used by inodes. If 0 the formatter will assume a value of 1%. If you know you will have only a few files you can limit the amount by using the group_inodes field.
- Initialise the inodes. By default the formatter will only initialise the superblock and the group allocator block and inode bit maps. An unallocated block or inode will not be referenced by the file system therefore its contents are not important. If you set this field to true the formatter will initialise all inodes to a know state.
- If set to true the formatter will output various configuration settings as well as the format progress.
When the configuration has been set call the format routine. In this example the RAM disk is being formatted:
const char* driver = "/dev/rda"; if (rtems_rfs_format (driver, &config) < 0) printf ("error: format of %s failed: %s\n", driver, strerror (errno));
With a formatted disk you can mount it using the standard mount call:
#include <rtems-rfs.h> rtems_filesystem_mount_table_entry_t* mt_entry; char* driver = "/dev/rda"; char* path = "/rd"; if (mount (&mt_entry, &rtems_rfs_ops, 0, driver, path)) printf ("error: mount of %s to %s failed: %s\n, driver, path, strerror (errno));