5 minute read

The Semester Project-VI: File System on Block Device

This article, which is part of the series on Linux device drivers, enhances the previously written bare-bones file system module, to connect with a real hardware partition.

After writing the bare-bones file system, the first thing Pugs figured out was how to read from the underlying block device. The following is a typical way of doing it:

Advertisement

struct buffer_head *bh;

bh = sb_bread(sb, block); /* sb is the struct super_block pointer */ // bh->b_data contains the data // Once done, bh should be released using: brelse(bh);

To do the above, and various other real SFS (Simula File System) operations, Pugs felt he needed to have his own handle to be a key parameter, which he added as follows (in the previous real_sfs_ds.h):

typedef struct sfs_info { struct super_block *vfs_sb; /* Super block structure from VFS for this fs */ sfs_super_block_t sb; /* Our fs super block */ byte1_t *used_blocks; /* Used blocks tracker */ byte1_t block[SIMULA_FS_BLOCK_SIZE]; /* A block-size scratch space */ } sfs_info_t;

The main idea behind this was to put all required static global variables in a single structure, and point to that by the private data pointer of the file system, which is the s_fs_info pointer in the struct super_block structure. With that, the key changes in the fill_super_block (in the previous real_sfs_ bb.c file) become: ƒ Allocate the structure for the handle, using kzalloc() ƒ Initialise the structure for the handle (through init_ browsing()) ƒ Read the physical super block, verify and translate the information from it into the VFS super block (through init_browsing()) ƒ Point to it by s_fs_info (through init_browsing())

ƒ Update the VFS super_block based on these changes

Accordingly, the error-handling code would need to do the shut_browsing(info) and kfree(info). And that would additionally need to go along with the function corresponding to the kill_sb function pointer, defined in the previous real_ sfs_bb.c by kill_block_super, called during umount. Here are the various code pieces:

static int sfs_fill_super(struct super_block *sb, void *data, int silent) { sfs_info_t *info;

if (!(info = (sfs_info_t *)(kzalloc(sizeof(sfs_info_t), GFP_ KERNEL)))) return -ENOMEM; info->vfs_sb = sb; if (init_browsing(info) < 0) { kfree(info); return -EIO; } /* Updating the VFS super_block */ sb->s_magic = info->sb.type; sb->s_blocksize = info->sb.block_size; sb->s_blocksize_bits = get_bit_pos(info->sb.block_size);

static void sfs_kill_sb(struct super_block *sb) { sfs_info_t *info = (sfs_info_t *)(sb->s_fs_info); if (info) { shut_browsing(info); kfree(info); } kill_block_super(sb); }

Note that kzalloc(), in contrast to kmalloc(), also zeroes out the allocated location. The get_bit_pos() is Pugs’ simple way to compute logarithm base 2, as follows:

static int get_bit_pos(unsigned int val) { int i;

for (i = 0; val; i++) { val >>= 1; } return (i - 1); }

And init_browsing() and shut_browsing() are basically the transformations of the earlier user-space functions of browse_real_sfs.c into kernel-space code real_sfs_ops.c, prototyped in real_sfs_ops.h. This basically involves the following transformations: ƒ ‘int sfs_handle’ into ‘sfs_info_t *info’ ƒ lseek() and read() into the read from the block device using sb_bread ƒ calloc() into vmalloc() and then appropriate initialisation by zeros. ƒ free() into vfree()

Here’s the transformed init_browsing() and shut_ browsing() in real_sfs_ops.c:

#include <linux/fs.h> /* For struct super_block */ #include <linux/errno.h> /* For error codes */ #include <linux/vmalloc.h> /* For vmalloc, ... */

#include “real_sfs_ds.h” #include “real_sfs_ops.h”

int init_browsing(sfs_info_t *info) { byte1_t *used_blocks; int i, j; sfs_file_entry_t fe;

if (read_sb_from_real_sfs(info, &info->sb) < 0) { return -EIO; } if (info->sb.type != SIMULA_FS_TYPE) { printk(KERN_ERR “Invalid SFS detected. Giving up.\n”); return -EINVAL; }

/* Mark used blocks */ used_blocks = (byte1_t *)(vmalloc(info->sb.partition_size)); if (!used_blocks) { return -ENOMEM; } for (i = 0; i < info->sb.data_block_start; i++) { used_blocks[i] = 1; } for (; i < info->sb.partition_size; i++) { used_blocks[i] = 0; }

for (i = 0; i < info->sb.entry_count; i++) { if (read_from_real_sfs(info, info->sb.entry_table_block_ start, i * sizeof(sfs_file_entry_t), &fe, sizeof(sfs_file_entry_t)) < 0) { vfree(used_blocks); return -ENOMEM; } if (!fe.name[0]) continue; for (j = 0; j < SIMULA_FS_DATA_BLOCK_CNT; j++) { if (fe.blocks[j] == 0) break; used_blocks[fe.blocks[j]] = 1; }

Figure 1: Connecting the SFS module with the pen drive partition

info->used_blocks = used_blocks; info->vfs_sb->s_fs_info = info; return 0; } void shut_browsing(sfs_info_t *info) { if (info->used_blocks) vfree(info->used_blocks); }

Similarly, all other functions in browse_real_sfs.c would also have to be transformed, one by one. Also, note the read from the underlying block device is being captured by the two functions, namely read_sb_from_real_sfs() and read_from_ real_sfs(), which are coded as follows:

#include <linux/buffer_head.h> /* struct buffer_head, sb_bread, ... */ #include <linux/string.h> /* For memcpy */

#include “real_sfs_ds.h”

static int read_sb_from_real_sfs(sfs_info_t *info, sfs_super_ block_t *sb) { struct buffer_head *bh;

if (!(bh = sb_bread(info->vfs_sb, 0 /* Super block is the 0th block */))) { return -EIO; } memcpy(sb, bh->b_data, SIMULA_FS_BLOCK_SIZE); brelse(bh); return 0; } static int read_from_real_sfs(sfs_info_t *info, byte4_t block, byte4_t offset, void *buf, byte4_t len) { byte4_t block_size = info->sb.block_size; struct buffer_head *bh;

if (offset >= block_size) { block += (offset / block_size); offset %= block_size; } if (offset + len > block_size) // Should never happen { return -EINVAL; } if (!(bh = sb_bread(info->vfs_sb, block))) { return -EIO; } memcpy(buf, bh->b_data + offset, len); brelse(bh); return 0; }

All the above code pieces put in together as the real_sfs_ minimal.c (based on the file real_sfs_bb.c created earlier), real_sfs_ops.c, real_sfs_ds.h (based on the same file created earlier), real_sfs_ops.h, and a supporting Makefile, along with the previously created format_real_sfs.c application, are available from http://www.linuxforu.com/article source_code/ oct12/file system block drive.zip

Real SFS on block device

Once compiled using make, Pugs didn’t expect the real_sfs_first.ko driver to be way different from the previous real_sfs_bb.ko driver, but hoped it would now read and verify the underlying partition. For that, he first tried mounting the usual partition of a pen drive to get an ‘Invalid SFS detected’ message in dmesg; and then tried after formatting it. Note that the same error of ‘Not a directory’, etc, as in the previous article, still exists, as this driver is still very similar to the previous bare-bones driver. The core functionality’s yet to be implemented; it’s just that it is now on a real block device partition. Figure 1 shows the exact commands for all these steps.

Note that the ./format_real_sfs and mount commands may take a lot of time (maybe in minutes), depending on the partition size—so preferably use a partition that’s less than, say, 1 MB.

With this important step of getting the file system module interacting with the underlying block device, the last step for Pugs would be to do the other transformations from browse_real_sfs.c and, accordingly, use them in the SFS module.

By: Anil Kumar Pugalia

The author is a freelance trainer in Linux internals, Linux device drivers, embedded Linux and related topics. Prior to this, he was at Intel and Nvidia. He has been working with Linux since 1994. A gold medallist from the Indian Institute of Science, Linux and knowledge sharing are two of his many passions. Creating and playing with open source hardware is one of his hobbies. Learn more about him at http://profession.sarika-pugs.com/, including information about his open source hardware freak-outs, and his various Linux related training sessions and workshops. He can be reached at email@sarika-pugs.com.

This article is from: