Wednesday, August 29, 2007

Assembly Benchmarks for OOC and C++

I checked some benchmarks for inheritance, here are the two files:
 /* begin test1.c */

#include < stdlib.h >
#include < stdio.h >

typedef struct {
 void (*method)();
} abstract_class;

void base_method()
{
 printf("In base::func()\n");
}

void d1_method()
{
 printf("In d1::func()\n");
}

void d2_method()
{
 printf("In d2::func()\n");
}

int main(void)
{
 abstract_class a, b, c;
 a.method = base_method;
 b.method = d1_method;
 c.method = d2_method;
 
 a.method();
 b.method();
 c.method();
 
 return 0;
}
and the C++ program:
 /* test.cpp program */

#include < iostream >
#include < stdlib.h >
#include < stdio.h >
using namespace std;

class base                          //Base Class
{
 public:
   virtual void func()
   {
     printf("In base::func()\n");
   }
};

class d1:public base                       // Derived Class 1
{
 public:
   void func()
   {
    printf("In d1::func()\n");
   }
};

class d2:public d1     // Derived Class 2
{
 public:
   void func()
   {
    printf("In d2::func()\n");
   }
};

int main()
{
  base b;
  d1 d;
  d2 e;

  b.func();
  d.func();
  e.func();

  return 0;
}
The resulting assembly files (from typing $ g++ -S test.cpp and $ gcc -S test1.c). The C++ program generated 533 lines of assembly, whereas the C program generated 74 lines of assembly...the C program was 720.27027027% more efficient. The vtable for C++ makes it inefficient...though you wouldn't notice the how high powered hardware of today. Note I used the GNU Compiler Collection:
 $ gcc --version
gcc (GCC) 4.1.2 (Ubuntu 4.1.2-0ubuntu4)
Copyright (C) 2006 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

Appendix

(Revised appendix made on 19 September 2007) I recently reconsidered this as sort of unfair since I was using two different front ends for the GCC. I suppose this shows that the C front end is far far more efficient than the C++ one, which is ultimately all I proven. But upon reconsideration, I tried using the G++ compiler to compile the C program, and I found out that by doing this the number of lines of code in the resulting assembly is 216 lines of code. This C implementation is still 246.759259259% more efficient, but it was misleading how more efficient the C program was. I'm torn still as to which statistic to use, because if one were compiling a C program one would use the C Compiler...or at least I would. The moral of the story, I guess, is that object oriented C is 2.5 to 7.2 times more efficient than C++...depending on how you compile it. If you want to be efficient, use the C Compiler.

Friday, August 24, 2007

The Solaris/BSD Virtual File System Paradigm

A brief prefatory note: I thought I would have covered the xv6 file system prior to jumping to the virtual file system, but I got bored and decided to shake things up. Next file system we shall study it will be the xv6 file system...I hope.

Introduction

All right, after having studied some file systems, we move on to the next big advancement in the theory of file systems: the virtual file system.

The virtual file system was made in late '85, early '86, in order to let a single mechanism access various disks that are formatted with different file systems. Sun was the one who made it for the SunOS system, which was little more than a glorified, proprietary BSD system...so it wasn't too hard to get it working for BSD. For this reason, I shall call this first approach the Solaris/BSD paradigm.

The idea was to generalize the UNIX file system of inodes to have virtual inodes, or "vnodes". The virtual file system had a linked list of mounted file systems (represented by the struct vfs data structure). The first struct vfs in the linked list was always the native file system.

Despite being written in C, the Solaris/BSD paradigm used an object oriented approach. Roughly put, the classes were structs and the methods were function pointers.

The approach that we will take to investigate the Solaris/BSD paradigm will be loosely based on the influential technical paper explaining it [1]. We'll explore the data structures and their respective "member functions". The data structures that we shall examine will be given to us verbatim from the paper. There are more modern additions to the virtual file system in Solaris (e.g. the introduction of virtual events as a vnode type) and in BSD. We will not discuss it here.

The VFS Data Structure

The vfs data structure can be thought of as a sort of virtual file system equivalent to the mount table entry. They are represented by directories on the root file system. So if we were running a native ext3 file system (it would be the first in the linked list of struct vfs mind you), and we mounted say a UFS partition we would have a directory dedicated to it (e.g. /ufs_partition/ or something).

The VFS struct is rather simple to look at:
struct vfs {
        struct vfs    *vfs_next;
        struct vfsops *vfs_op;
        struct vnode  *vfs_vnodecovered;
        int           vfs_flag;
        int           vfs_bsize;
        caddr_t       vfs_data;
};
The first field, struct vfs *vfs_next is a pointer to the next mounted file system. The struct vfs is a linked list after all.

The next field is a pointer to the operations of the struct vfs. This is the object orientedness of the virtual file system implementation.

The struct vnode *vfs_vnodecovered is the mount point for the file system. It is worthy to note that this is NULL for the root file system (the head of the linked list).

The next several fields are rather...straight forward (read: dull). The int vfs_flag is any flag for the struct vfs, the int vfs_bsize is the block size for the native file system.

The caddr_t vfs_data is somewhat interesting...only because I had no clue what the hell a caddr_t type was! It turns out it is a character pointer. This field is supposed to be for private, file system dependent data. The example given in the text was - for the 4.2 BSD file system - vfs_data points to a mount table entry.

It should be noted that - in general - the struct vfs should be thought of as a sort of virtual file system mount table entry.

There are some fundamental data structures which should be inspected prior to investigating struct vfs_ops: the struct statfs data structure (which holds the results for a vfs_statfs() operation) and the struct fid data structure (which is a file identifier).

The struct statfs is the first thing we shall examine:
struct statfs {
        long f_type;
        long f_bsize;
        long f_blocks;
        long f_bfree;
        long f_bavail;
        long f_files;
        long f_ffree;
        fsid_t f_fsid;
        long f_spare[7];
};
This is basically the result of the statfs() operation. The entries are self-explanatory: the file type (ordinary file, directory, character or block device, socket, etc.) represented by long f_type; the native block size of the file system is represented by the long f_bsize; the long f_bfree is the number of free blocks; the long f_bavail is the "non-su blocks"; the total number of files is then represented by long f_files; the long f_ffree is the free nodes in the file system; the fsid_t f_sid is the file system id; and the last field long f_spare[7] is spare space used for later.

The file identifier is the next and last data structure we need to investigate prior to going on to the struct vfs_ops:
struct fid {
        u_short fid_len;
        char fid_data[1];
};
The unsigned short fid_len is the length of the data, and char fid_data is the actual data encapsulated in the file identifier.

The operations for the struct vfs is:
struct vfsops {
        int     (*vfs_mount)(struct vfs* vfs_ptr, char *path, char *data);
        int     (*vfs_unmount)(struct vfs* vfs_ptr, struct vnode* stuffResultsHere);
        int     (*vfs_root)(struct vfs* vfs_ptr, struct vnode* stuffResultsHere);
        int     (*vfs_statfs)(struct vfs* vfs_ptr, struct statfs* putResultsHere);
        int     (*vfs_sync)(struct vfs* vfs_ptr);
        int     (*vfs_fid)(struct vfs* vfs_ptr, struct vnode *file, struct fid* fid_ptr);
        int     (*vfs_vget)(struct vfs* vfs_ptr, struct vnode** vpp, struct fid* file);
};
This is a struct full of function pointers! Let us investigate each one in turn.

The vfs_mount() function mounts the vfs pointer (that is to say, it reads the superblock, etc.). The char *path points to the path name to be mounted for the sake of recording purposes. The char *data points to file system dependent data.

The vfs_unmount() function simply unmounts the vfs (syncs the superblock, etc.).

Our next function/method/whatever is vfs_root() which returns the root vnode for the file system represented by struct vfs* vfs_ptr. The struct vnode* stuffResultsHere vnode is a pointer to a vnode for the results.

Now we have int vfs_statfs() which returns the file system information. The struct statfs* putResultsHere argument is a pointer to a statfs structure for the results.

Then int vfs_sync() writes out all cached information for the struct vfs* vfs_ptr. This is not necessarily done synchronously. When the operation returns, all data has not been necessarily been written out...but it has been scheduled.

Next the int vfs_fid() gets a unique file identifier for the struct vnode* file which represents a file in this file system. The results are put in a struct fid and then struct fid* fid_ptr - the argument in the vfs_fid() function - points to the resulting struct fid.

Last but not least we have int vfs_vget() which turns a unique file identifier struct fid* file into a vnode representing the file which the file identifier identifies. The struct vnode** vpp points to a pointer to a vnode for the result.

The VNODE Data Structure

The vnode data structure is given to us, from the aforementioned paper, as:
enum vtype      { VNON, VREG, VDIR, VBLK, VCHR, VLINK, VSOCK, VBAD };
struct vnode {
        u_short         v_flag;
        u_short         v_count;
        u_short         v_shlockc;
        u_short         v_exlockc;
        struct vfs      *v_vfsmountedhere;
        struct vnodeops *v_op;
        union {
                struct socket   *v_Socket;
                struct stdata   *v_Stream;
        };
        struct vfs      *v_vfsp;
        enum vtype      v_type;
        caddr_t         v_data;
};
The various vnode types are given to us by an enumeration of all the various types.

The u_short v_flag points to the standard flags. The u_short v_count is the reference count for the vnode. It is maintained by generic vnode macros VN_HOLD and VN_RELE.

The next two fields deal with the number of shared locks and exclusive locks used by the vnode.

The struct vfs *v_vfsmountedhere points to a vfs if and only if the vnode is a mount point for the vfs. Otherwise, it is null and struct vfs* v_vfsp points to the vfs which the vnode is in.

The private data pointer (caddr_t v_data) which holds file dependent data. E.g. for the 4.2 BSD system, v_data points to an in memory inode data table.

The vnode has an interprocess communication apparatus...that's the anonymous union of the socket and the data stream.

Before continuing on to discuss the vnode_ops structure, we need to investigate a few structures. First the struct vattr data structure:
struct vattr {
        enum vtype     va_type;      /* vnode type */
        u_short        va_mode;      /* acc mode */
        short          va_uid;       /* owner uid */
        short          va_gid;       /* owner gid */
        long           va_fsid;      /* fs id */
        long           va_nodeid;    /* node # */
        short          va_nlink;     /* # links */
        u_long         va_size;      /* file size */
        long           va_blocksize; /* block size */
        struct timeval va_atime;     /* last acc */
        struct timeval va_mtime;     /* last mod */
        struct timeval va_ctime;     /* last chg */
        dev_t          va_rdev;      /* dev */
        long           va_blocks;    /* space used */
};
The various fields are self explanatory, especially since the comments explain all the fields! The only ones worthy of note would be the file system identifier long va_fsid, and the device the vnode's on dev_t va_rdev.

From the Modern openSolaris OS, we find the uio_t type's definition:
1217 typedef struct uio {
1218         struct iovec    *uio_iov;
1219         void    *uio_file;
1220         char    *uio_buf;
1221         int     uio_iovcnt;
1222         int     uio_offset;
1223         size_t  uio_resid;
1224         int     uio_rw;
1225 } uio_t;
I honestly do not understand this, and I suspect that this is far more complicated than it was when the original virtual file system was implemented.

And now a nightmarishly long structure of the operations on the vnode: the vnode_ops! Note that struct ucred cred is the credentials of the user, it is used to check for permissions while performing these operations.
struct vnodeops {
        int (*vn_open)(struct vnode* vn_ptr, unsigned short flags, struct ucred cred);
        int (*vn_close)(struct vnode* vn_ptr, unsigned short flags, struct ucred cred);
        int (*vn_rdwr)(struct vnode* vn_ptr, struct uio* args, bool read, unsigned short flags, struct ucred cred);
        int (*vn_ioctl)(struct vnode* vn_ptr, char* command,void* data, unsigned short flags, struct ucred cred);
        int (*vn_select)(struct vnode* vn_ptr, unsigned short ioDirection, struct ucred cred);
        int (*vn_getattr)(struct vnode* vn_ptr, struct vattr* va, struct ucred cred);
        int (*vn_setattr)(struct vnode* vn_ptr, struct vattr* va, struct ucred cred);
        int (*vn_access)(struct vnode* vn_ptr, unsigned short access_mode, struct ucred cred);
        int (*vn_lookup)(struct vnode* vn_ptr, char* name, struct vnode** vpp, struct ucred cred);
        int (*vn_create)(struct vnode* vn_ptr, char* name, struct vattr* va, bool exclusive, unsigned short open, struct vnode** vpp, struct ucred cred);
        int (*vn_remove)(struct vnode* vn_ptr, char* name, struct ucred cred);
        int (*vn_link)(struct vnode* vn_ptr, struct vnode* targetDir, char* targetName, struct ucred cred);
        int (*vn_rename)(struct vnode* vn_ptr, char* name, struct vnode* target_dir, char* target_name struct ucred cred)
        int (*vn_mkdir)(struct vnode* vn_ptr, char* name, struct vattr* va, struct vnode** vpp, struct ucred cred);
        int (*vn_rmdir)(struct vnode* vn_ptr, char* nm, struct ucred cred);
        int (*vn_readdir)(struct vnode* vn_ptr, struct uio* uiop, struct ucred cred);
        int (*vn_symlink)(struct vnode* vn_ptr, char *linkName, struct vattr* va, char* path, struct ucred cred);
        int (*vn_readlink)(struct vnode* vn_ptr, struct uio* uiop, struct ucred cred);
        int (*vn_fsync)(struct vnode* vn_ptr, struct ucred cred);
        int (*vn_inactive)(struct vnode* vn_ptr, struct ucred cred);
        int (*vn_bmap)(struct vnode* vn_ptr, unsigned int logicalBlockNumber, struct vnode** vpp, unsigned int* block_nmbr);
        int (*vn_strategy)(struct buf* buf_ptr);
        int (*vn_bread)(struct vnode* vn_ptr, unsigned int block_no, struct buf** bpp);
        int (*vn_brelse)(struct vnode* vn_ptr, struct buf* buf_ptr);
};
The int (*vn_open)(struct vnode* vn_ptr, unsigned short flags, struct ucred cred) function performs any open protocol on a vnode pointed to by struct vnode* vn_ptr (for example, devices). If the open is a clone open the operation may return a new vnode. The various open flags is given by unsigned short flags.

Next int (*vn_close)(struct vnode* vn_ptr, unsigned short flags, struct ucred cred) corresponds to the previous operation. This performs any close protocol on a vnode pointed to us by struct vnode* vn_ptr. It is called on the closing of the last reference to the vnode from the file table if the vnode is a device. Otherwise this is called on the last user close of a file descriptor. The flags are the open flags.

THen int (*vn_rdwr)(struct vnode* vn_ptr, struct uio* args, unsigned short flags, bool read, struct ucred cred) reads or writes to the vnode pointed to us by struct vnode* vn_ptr. It reads or writes a number of bytes at a specified offset in the file. The input/output arguments are pointed to by the struct uio* args argument. The bool read argument tells us if the operation is read if true, write if false. The input/output flags is given to us by unsigned short flags which specifies if the input/output is done synchronously (doesn't return until all the volatile data is on disk) and/or in a unit (lock the file to write a large unit).

The infamous int (*vn_ioctl)(struct vnode* vn_ptr, char* command, void* data, unsigned short flags, struct ucred cred) functions performs an ioctl on a vnode point to us by struct vnode* vn_ptr. It performs (or more accurately invokes) the command char* command, with the data given by the argument of void* data. The unsigned short flags deal with the open flags.

Next int (*vn_select)(struct vnode* vn_ptr, unsigned short flags, struct ucred cred) performs a "select" operation on the vnode pointed to us by struct vnode* vn_ptr. The flags specify the input/output direction.

The int (*vn_getattr)(struct vnode* vn_ptr, struct vattr* va, struct ucred cred) operation gets the attributes for the struct vnode* vn_ptr vnode. It is written, I think, to the struct vattr that is given as an argument.

Our next operation int (*vn_setattr)(struct vnode* vn_ptr, struct vattr* va, struct ucred cred) sets the attributes for the struct vnode* vn_ptr. We set the vnode's attributes to be those pointed to by struct vattr* va. The catch is only: mode, uid, gid, file size, and times can be set. This necessarily maps UNIX file attributes to file system dependent attributes.

The int (*vn_access)(struct vnode* vn_ptr, unsigned short access_mode, struct ucred cred) operation checks access permissions for the struct vnode* vn_ptr vnode. If error is denied, an error is returned. The unsigned short access_mode is the mode to check for access (e.g. access, write, execute). It is necessary that this maps UNIX file protectection information to file system dependent protection information.

Next the int (*vn_lookup)(struct vnode* vn_ptr, char* name, struct vnode** vpp, struct ucred cred) operation, which looks up a component name char* name in the directory struct vnode* vn_ptr. The result is put in an vnode, and struct vnode** vpp points to a pointer which points to this resultant vnode.

Now the int (*vn_create)(struct vnode* vn_ptr, char* name, struct vattr* va, bool exclusive, unsigned short open, struct vnode** vpp, struct ucred cred) operation creates a new file char* name in a directory struct vnode* vn_ptr. The attributes of the new file is given by struct vattr* va. The bool exclusive is the exclusive/non-exclusive create flag, unsigned short open is the open mode. The struct vnode** vpp points to a pointer pointing to the resulting file.

The int (*vn_remove)(struct vnode* vn_ptr, char* name, struct ucred cred) operation is simple: it removes a file char* name in a directory struct vnode* vn_ptr.

To link, the int (*vn_link)(struct vnode* vn_ptr, struct vnode* targetDir, char* targetName, struct ucred cred) operation links the struct vnode* vn_ptr to the target name char* targetName in the directory struct vnode* targetDir.

Then the int (*vn_rename)(struct vnode* vn_ptr, char* name, struct vnode* target_dir, char* target_name struct ucred cred) function renames the file char* name in the directory struct vnode* vn_ptr to a new name char* target_name in the target directory struct vnode* target_dir. It is noted that even if the system crashes in the middle of this operation, the vnode's not lost.

Next the int (*vn_mkdir)(struct vnode* vn_ptr, char* name, struct vattr* va, struct vnode** vpp, struct ucred cred) method creates a directory char* name in the directory struct vnode* vn_ptr. The resulting directory's attributes are set to be struct vattr* va, and struct vnode** vpp points to a pointer which points to the resulting directory.

The int (*vn_rmdir)(struct vnode* vn_ptr, char* nm, struct ucred cred) method removes the char* nm directory from the struct vnode* vn_ptr directory.

Now, the int (*vn_readdir)(struct vnode* vn_ptr, struct uio* uiop, struct ucred cred) operation reads entries from the struct vnode* vn_ptr directory. The input/output arguments are given by struct uio* uiop pointer. The uio offset is notionally made to be a file system dependent number...it's supposed to represent the logical offset in the directory when the reading is done. Not only is this a good idea, but it's necessary because the number of bytes returned by vn_readdir is not necessarily the number of bytes in the equivalent part of the on disk directory.

Then int (*vn_symlink)(struct vnode* vn_ptr, char *linkName, struct vattr* va, char* path, struct ucred cred) symbolically links the path char* path to the name char* linkName in the struct vnode* vn_ptr directory.

The int (*vn_readlink)(struct vnode* vn_ptr, struct uio* uiop, struct ucred cred) operation reads the symbolic link struct vnode* vn_ptr with the input/output arguments supplied with the struct uio* uiop pointer.

Next the int (*vn_fsync)(struct vnode* vn_ptr, struct ucred cred) function writes out all cached information for the struct vnode* vn_ptr file...this is synchronous and does not return until the input/output is done.

Then the int (*vn_inactive)(struct vnode* vn_ptr, struct ucred cred) operation checks if the struct vnode* vn_ptr is still used by the vnode layer; if not, it may be deallocated.

The int (*vn_bmap)(struct vnode* vn_ptr, unsigned int logicalBlockNumber, struct vnode** vpp, unsigned int* block_nmbr) operation maps the logical block number unsigned int logicalBlockNumber in the struct vnode* vn_ptr file to a physical block number and a physical device. The unsigned int* block_nmbr points to a block number for the physical device and struct vnode** vpp is a pointer to a vnode pointer for the physical device. The returned vnode may or may not be a physical device.

And now the int (*vn_strategy)(struct buf* buf_ptr) function is a block oriented interface to read or write a logical block from a file into or out of a buffer. The struct buf* buf_ptr pointer is a pointer to a buffer header which contains a pointer to the vnode to be operated on. This does not copy through the buffer cache if the file system uses it. This function is used by the buffer cache routines and the paging system to read blocks into memory.

Next int (*vn_bread)(struct vnode* vn_ptr, unsigned int block_no, struct buf** bpp) reads a logical block unsigned int block_no from the struct vnode* vn_ptr file, returns a pointer to a buffer header in struct buf** bpp which contains a pointer to the data. This does not necessarily imply the use of the buffer cache; this function is useful in avoiding extra data copying on the server side of a remote file system.

Our last function int (*vn_brelse)(struct vnode* vn_ptr, struct buf* buf_ptr) basically releases the buffer returned by vn_bread().

So...What?

Well, this is nice for handling data on various partitions that is formatted in different file systems...but what if one is smart and formats all partitions to have the same file system? What's the advantage of the virtual file system?

One could argue that it's object oriented...that's always nice ;)

A serious advantage of the virtual file system is that it allows one to mount pseudo-file systems as struct vfs-es. It is pointed out in [1] (sections 4.7 and 4.8) that the /dev/ and /proc/ pseudo-file systems are implemented in this manner in SunOS back in the day.

This way, when one types into the command prompt:

$ sudo rm -rf /proc/63

One would kill the process with pid == 63. The usefulness of pseudo-file systems is more in keeping in line with the UNIX philosophy ("Everything is a file" -- something object oriented programmers would like, a sort of parallel to "Everything is an object"). So it only should appeal to zealots ;)

References

[1] Kleiman, S.R. Vnodes: An Architecture for Multiple File System Types in Sun UNIX (1986)

[2] Rosenthal, D. Evolving the Virtual File System (1992?)

[3] A 4.3 BSD vnode header, 4.3 BSD UFS_VNOPS.C

Revision History

Revision 0: 24 August 2007 - published.
Revision 1: 24 August 2007 - revised to fit code snippets on page correctly.

Object Oriented Skelix File System

Prefatory note: I wrote this all in about an hour, so the chances of there being typos is extremely high...so I will revise this as needed. Also the code is in the D programming language.

Introduction

We previously covered the Skelix File System. Today we will be programming an alternative to it in the D programming language. The D programming language was created as an alternative to C++. It has a cleaner syntax, and is a far superior language. That is why we shall use it. One short coming of the language is that it has a garbage collector, which will require us to override the new() and delete() operators. NOTE NOTE NOTE: that this code WILL NOT compile in the skelix operating system because there is no kmalloc() or kfree() functions. Otherwise this code would compile. If someone wants to make these functions, go right ahead :)

The BitMap Class

Let us begin by typing into the command prompt $ touch bitmap.d and then use your favorite editor to open it. We begin by writing:
module bitmap;

class bitmap
{
 /* To be implemented */
}
This is as far as we normally get with our first code. Now we need to add some methods and fields, not to mention constructors and destructors. Let us begin with the constructors and destructors. Recall that D has a garbage collector. We need to override the new() and delete() operators such that it will call the kmalloc() and kfree() functions respectively. That means we need to import the kmalloc() and kfree() functions into the code.
module bitmap;

extern (C) void *kmalloc(uint size);
extern (C) void kfree(void *ptr);

class bitmap
{
 /* fields to be implemented */
 /* constructors to be implemented */

 new(uint sz)
 {
  void *p;
  p = kmalloc(sz);
  return p;
 }
 delete(void *p)
 {
  kfree(p);
 }

 /* methods to be implemented */
}
Alternatively one could use the garbage collector to make sure that one does not run out of memory. If that were the case, the class would be:
module bitmap;

extern (C) void *kmalloc(uint size);
extern (C) void kfree(void *ptr);

class bitmap
{
 /* fields to be implemented */
 /* constructors to be implemented */

 new(uint sz)
 {
  void* p;
  p = kmalloc(sz);
  if (!p)
  {
   printf("ERROR! OUT OF MEMORY!\n");
   return;
  }
  gc.addRange(p, p + sz);
  return p;
 }

 delete(void *p)
 {
  if(p)
  {
   gc.removeRange(p);
   kfree(p);
  }
 }

 /* methods to be implemented */
}
I will not use the garbage collector at all in my approach, so I won't use these operators. They can work, unless Walter Brighton changes things up, but I'm just too old school to use the damn garbage collector! Now, we need to add some fields to the bitmap. The bitmap will refer to a buffer which we usually represent as a void pointer in C. In D however we shall use a void array. It is important to note that in order to avoid calling the garbage collector, we need to avoid: dynamic arrays (arrays without a defined length), and associative arrays (which I still don't understand what the hell it is). So, we will take in as an argument to the constructor the number of blocks the bitmap takes up (represented as a ushort numBlocks) and have a void[] buffer as a field.
module bitmap;

extern (C) void *kmalloc(uint size);
extern (C) void kfree(void *ptr);

class bitmap
{
 /* fields to be implemented */
 ubyte[] buffer;

 /** Constructor of the bitmap
   *
   * The bitmap is supposed to take up some number
   * of blocks for the file system. There is a
   * inode bitmap and a block bitmap.
   *
   * @PARAM: ushort numBlocks is the number of blocks
   *     that the bitmap takes up.
   */
 public this(ushort numBlocks)
 {
  buffer = new ubyte[512*numBlocks];
  buffer[0]=0;
 }
 public ~this()
 {
  //may possibly be implemented?
 }

 new(uint sz)
 {
  void* p;
  p = kmalloc(sz);
  if (!p)
  {
   printf("ERROR! OUT OF MEMORY!\n");
   return;
  }
  gc.addRange(p, p + sz);
  return p;
 }

 delete(void *p)
 {
  if(p)
  {
   gc.removeRange(p);
   kfree(p);
  }
 }

 /* methods to be implemented */

}
Now we need to implement methods to manipulate the buffer. Recall in Skelix the operations to set a bit, clear a bit, and test a bit in a given buffer void *s is:
void
setb(void *s, unsigned int i) {
 unsigned char *v = s;
 v += i>>3;
 *v |= 1<<(7-(i%8));
}

void
clrb(void *s, unsigned int i) {
 unsigned char *v = s;
 v += i>>3;
 *v &= ~(1<<(7-(i%8)));
}

int
testb(void *s, unsigned int i) {
 unsigned char *v = s;
 v += i>>3;
 return (*v&(1<<(7-(i%8)))) !=0;
}
/* from Skelix revision 7 /fs/fs.c */
So we shall implement these methods in our class:
module bitmap;

extern (C) void *kmalloc(uint size);
extern (C) void kfree(void *ptr);

class bitmap
{
 /* fields to be implemented */
 ubyte[] buffer;

 /** Constructor of the bitmap
   *
   * The bitmap is supposed to take up some number
   * of blocks for the file system. There is a
   * inode bitmap and a block bitmap.
   *
   * @PARAM: ushort numBlocks is the number of blocks
   *     that the bitmap takes up.
   */
 public this(ushort numBlocks)
 {
  buffer = new ubyte[512*numBlocks];
  buffer[0]=0;
 }
 public ~this()
 {
  //may possibly be implemented?
 }

 new(uint sz)
 {
  void* p;
  p = kmalloc(sz);
  return p;
 }

 delete(void *p)
 {
  kfree(p);
 }

 /** The Set Bit method.
   * 
   * The set bit method sets a bit in the buffer
   * provided the bit is in the buffer. It sets
   * the bit to be 1 if it is 0, otherwise it 
   * does nothing.
   *
   * @PARAM: uint i is the bit number to set.
   *
   **/
 void setBit(uint i) {
  if(i < (buffer.length*8))
  {
   ubyte[] v = cast(ubyte)buffer;
   v += i>>3;
   *v |= 1<<(7-(i%8));
  }
 }

 /** The Clear Bit method
   *
   * The clear bit method sets the bit in the buffer
   * to be 0. It first checks to make sure that the 
   * bit is in the buffer.
   *
   * @PARAM: uint i bit number referring to
   *      the bit that we want to clear. 
   **/
 void clearBit(uint i) {
  if(i < (buffer.length*8))
  {
   unsigned char *v = cast(ubyte)buffer;
   v += i>>3;
   *v &= ~(1<<(7-(i%8)));
  }
 }

 /** The Test Bit method
   *
   * The test bit method checks if the bit in the
   * buffer is occupied or free. It returns 1 if
   * the bit is occupied, 0 if it is free, and -1
   * if it is out of bounds.
   *
   * @PARAM: uint i is the bit we wish to
   *     test to see if it is free or occupied.
   * @RETURNS: 1 if the bit is occupied.
   * @RETURNS: 0 if the bit is free.
   * @RETURNS: -1 if the bit is out of bounds.
   **/
 int testBit(uint i) {
  if(i < (buffer.length*8))
  {
   unsigned char *v = cast(ubyte)buffer;
   v += i>>3;
   return (*v&(1<<(7-(i%8)))) !=0;
  }
  return -1;
 }
}
One last thing that we would like to have in our bitmap class is a getBuffer() method which will be used by a member function of the struct SUPER_BLOCK:
module bitmap;

extern (C) void *kmalloc(uint size);
extern (C) void kfree(void *ptr);

class bitmap
{
 /**
   * The buffer which represents the blocks which
   * the bitmap takes up. Recall that file system
   * uses two bitmaps which takes up some number of
   * blocks. The first bitmap is the inode bitmap
   * the second is the data block bitmap. This is
   * the in-memory representation of a bitmap.
   */
 public ubyte[] buffer;

 /** Constructor of the bitmap
   *
   * The bitmap is supposed to take up some number
   * of blocks for the file system. There is a
   * inode bitmap and a block bitmap.
   *
   * @PARAM: ushort numBlocks is the number of blocks
   *     that the bitmap takes up.
   */
 public this(ushort numBlocks)
 {
  buffer = new ubyte[512*numBlocks];
  buffer[0]=0;
 }
 public ~this()
 {
  //may possibly be implemented?
 }

 new(uint sz)
 {
  void* p;
  p = kmalloc(sz);
  return p;
 }

 delete(void *p)
 {
  kfree(p);
 }


 /** The Set Bit method.
   * 
   * The set bit method sets a bit in the buffer
   * provided the bit is in the buffer. If the bit
   * is 1 (free), then it is set to be 0 (occupied).
   *
   * @PARAM: uint i is the bit number to set.
   *
   **/
 void setBit(uint i) {
  if(i < (buffer.length*8))
  {
   ubyte[] v = cast(ubyte)buffer;
   v += i>>3;
   *v |= 1<<(7-(i%8));
  }
 }

 /** The Clear Bit method
   *
   * The clear bit method sets the bit in the buffer
   * to be 1. It first checks to make sure that the 
   * bit is in the buffer.
   *
   * @PARAM: uint i bit number referring to
   *      the bit that we want to clear. 
   **/
 void clearBit(uint i) {
  if(i < (buffer.length*8))
  {
   unsigned char *v = cast(ubyte)buffer;
   v += i>>3;
   *v &= ~(1<<(7-(i%8)));
  }
 }

 /** The Test Bit method
   *
   * The test bit method checks if the bit in the
   * buffer is occupied or free. It returns 1 if
   * the bit is free, 0 if it is occupied, and -1
   * if it is out of bounds.
   *
   * @PARAM: uint i is the bit we wish to
   *     test to see if it is free or occupied.
   * @RETURNS: 1 if the bit is free.
   * @RETURNS: 0 if the bit is occupied.
   * @RETURNS: -1 if the bit is out of bounds.
   **/
 int testBit(uint i) {
  if(i < (buffer.length*8))
  {
   unsigned char *v = cast(ubyte)buffer;
   v += i>>3;
   return (*v&(1<<(7-(i%8)))) !=0;
  }
  return -1;
 }
}
The last thing that we will do, simply because it saves memory and so much more time later on, is we'll make the buffer a public field rather than a private one. This allows us to get rid of the getBuffer() and setBuffer() methods. There we have the bitmap class done as far as we are concerned. The next module we want to deal with is the inode module.

The Inode Module

The Inode is the atom of the file system. We begin as normal by typing into the command line $ touch inode.d and then open it with our favorite editor (emacs :P) and start out writing
module inode;

class inode
{
 /* to be implemented */
}
If we look into the revision 7 of the Skelix operating system we find the file /fs/inc/fs.h which we will quote selectively as we go along. First we shall note the struct INODE and various macros dealing with it:
#define FT_NML 1
#define FT_DIR 2

struct INODE {
 unsigned int i_mode;  /* file mode */
 unsigned int i_size;  /* size in bytes */
 unsigned int i_block[8];
};

extern struct INODE iroot;

/* from Skelix revision 7 /fs/inc/fs.h */
So we will take the struct fields and make them private fields in the inode class, make the macros const uints, and a global variable (the root inode). Let us put these things in our code:
module inode;

const uint FT_NML = 1;
const uint FT_NML = 2;

class inode
{
 private uint i_mode;
 private uint i_size;
 private uint[8] i_block;

 /* constructors to be implemented */
 /* new() and delete() to be implemented */
 /* methods to be implemented */
}
The reason why we keep the same name for these fields rather than make the common sense names is because we want to modify as little code as possible...yes, I am lazier than you. Now we want to also override the new() and delete() operators, which is simply the same thing that we've done before. We simply call extern (C) for the kmalloc() and kfree() methods:
module inode;

extern (C) void *kmalloc(uint size);
extern (C) void kfree(void *ptr);

const uint FT_NML = 1;
const uint FT_NML = 2;

class inode
{
 private uint i_mode;
 private uint i_size;
 private uint[8] i_block;

 /* constructors to be implemented */

 /* new() and delete() operators */
 new(uint sz)
 {
  void* p;
  p = kmalloc(sz);
  return p;
 }

 delete(void *p)
 {
  kfree(p);
 }

 /* methods to be implemented */
}
In the skelix /fs/inc/fs.h file we continue to analyze and find:
#define MAX_NAME_LEN 11

struct DIR_ENTRY {
 char de_name[MAX_NAME_LEN];
 int de_inode;
};
We follow our method of changing macros into functions or consts. However, we will keep the struct DIR_ENTRY as a struct in the D module because D structs are simple data types. So we add to our module:
module inode;

extern (C) void *kmalloc(uint size);
extern (C) void kfree(void *ptr);

const uint FT_NML = 1;
const uint FT_NML = 2;

class inode
{
 private uint i_mode;
 private uint i_size;
 private uint[8] i_block;

 /* constructors to be implemented */

 /* new() and delete() operators */
 new(uint sz)
 {
  void* p;
  p = kmalloc(sz);
  return p;
 }

 delete(void *p)
 {
  kfree(p);
 }

 /* methods to be implemented */
}

const uint MAX_NAME_LEN = 11

struct DIR_ENTRY {
 char de_name[MAX_NAME_LEN];
 int de_inode;
};
Now we look again at the /fs/inc/fs.h file again and we find:
struct SUPER_BLOCK {
 unsigned char sb_magic;
 /* DPT 0x08 */
 unsigned int sb_start;
 /* DPT 0x0c */
 unsigned int sb_blocks;

 unsigned int sb_dmap_blks;
 unsigned int sb_imap_blks;
 unsigned int sb_inode_blks;
};

#define FST_FS 0x2e   /* normal partition */
#define FST_SW 0x2f   /* swap partition */

/* skelix revision 7 /fs/inc/fs.h */
It is important that the super block remains a struct in D, so we shall continue our approach thus far of changing macros into consts and translate the struct:
module inode;

extern (C) void *kmalloc(uint size);
extern (C) void kfree(void *ptr);

const uint FT_NML = 1;
const uint FT_NML = 2;

class inode
{
 private uint i_mode;
 private uint i_size;
 private uint[8] i_block;

 /* constructors to be implemented */

 /* new() and delete() operators */
 new(uint sz)
 {
  void* p;
  p = kmalloc(sz);
  return p;
 }

 delete(void *p)
 {
  kfree(p);
 }

 /* methods to be implemented */
}

const uint MAX_NAME_LEN = 11

struct DIR_ENTRY {
 char de_name[MAX_NAME_LEN];
 int de_inode;
};

struct SUPER_BLOCK {
 ubyte sb_magic;
 /* DPT 0x08 */
 uint sb_start;
 /* DPT 0x0c */
 uint sb_blocks;

 uint sb_dmap_blks;
 uint sb_imap_blks;
 uint sb_inode_blks;
};

const uint FST_FS = 0x2e;   /* normal partition */
const uint FST_SW = 0x2f;   /* swap partition */
Now we are going to add the macros that we have stuffled so hard to avoid:
#define ABS_BOOT_BLK(sb)  ((sb).sb_start)
#define ABS_SUPER_BLK(sb)  ((ABS_BOOT_BLK(sb))+1)
#define ABS_DMAP_BLK(sb)  ((ABS_SUPER_BLK(sb))+1)
#define ABS_IMAP_BLK(sb)  ((ABS_DMAP_BLK(sb))+(sb).sb_dmap_blks)
#define ABS_INODE_BLK(sb)  ((ABS_IMAP_BLK(sb))+(sb).sb_imap_blks)
#define ABS_DATA_BLK(sb)  ((ABS_INODE_BLK(sb))+INODE_BLKS)

#define INODE_BIT_BLKS  1 /* 512*8 = 4096 inodes */
#define INODES_PER_BLK  (512/sizeof(struct INODE))
#define INODE_BLKS   \
 ((INODE_BIT_BLKS*512*8+INODES_PER_BLK-1)/(INODES_PER_BLK))

/* From skelix revision 7 /fs/inc/fs.h */
Well, we follow our same routine and change these into inline functions or consts:
module inode;

extern (C) void *kmalloc(uint size);
extern (C) void kfree(void *ptr);

const uint FT_NML = 1;
const uint FT_NML = 2;

class inode
{
 private uint i_mode;
 private uint i_size;
 private uint[8] i_block;

 /* constructors to be implemented */

 /* new() and delete() operators */
 new(uint sz)
 {
  void* p;
  p = kmalloc(sz);
  return p;
 }

 delete(void *p)
 {
  kfree(p);
 }

 /* methods to be implemented */
}

const uint MAX_NAME_LEN = 11

struct DIR_ENTRY {
 char de_name[MAX_NAME_LEN];
 int de_inode;
};

inline uint ABS_BOOT_BLK(SUPER_BLOCK sb) 
{ return ((sb).sb_start); }
inline uint ABS_SUPER_BLK(SUPER_BLOCK sb) 
{ return ((ABS_BOOT_BLK(sb))+1); }
inline uint ABS_DMAP_BLK(SUPER_BLOCK sb) 
{ return ((ABS_SUPER_BLK(sb))+1); }
inline uint ABS_IMAP_BLK(SUPER_BLOCK sb) 
{ return ((ABS_DMAP_BLK(sb))+(sb).sb_dmap_blks); }
inline uint ABS_INODE_BLK(SUPER_BLOCK sb) 
{ return ((ABS_IMAP_BLK(sb))+(sb).sb_imap_blks); }
inline uint ABS_DATA_BLK(SUPER_BLOCK sb) 
{ return ((ABS_INODE_BLK(sb))+INODE_BLKS); }

const uint INODE_BIT_BLKS = 1; /* 512*8 = 4096 inodes */
const uint INODES_PER_BLK = (512/sizeof(struct INODE));
const uint INODE_BLKS  = 
((INODE_BIT_BLKS*512*8+INODES_PER_BLK-1)/(INODES_PER_BLK));


struct SUPER_BLOCK {
 ubyte sb_magic;
 /* DPT 0x08 */
 uint sb_start;
 /* DPT 0x0c */
 uint sb_blocks;

 uint sb_dmap_blks;
 uint sb_imap_blks;
 uint sb_inode_blks;
};

const uint FST_FS = 0x2e;   /* normal partition */
const uint FST_SW = 0x2f;   /* swap partition */
So you think we're done with this module? Dream on, the revision 7 of the skelix operating system comes with three (for lack of a better word) "sub versions": dpt, fs, and root. We just finished dealing with fs, now we shall investigate the methods for root. Basically we need to add some member functions to the superblock...and the inode class. From the /root/fs.c file we have several new methods (prototyped in /root/inc/fs.h): void verify_fs(void); void verify_dir(void); unsigned int alloc_blk(struct SUPER_BLOCK *sb);void free_blk(struct SUPER_BLOCK *sb, unsigned int n); and void install_color(void). We will add most of these functions. The first two are going to be in our fs module. The next two are going to be member functions of our super block. Let us therefore investigate them here:
unsigned int
alloc_blk(struct SUPER_BLOCK *sb) {
 unsigned int i = 0, j = 0, n = 0, m = 0;
 unsigned char sect[512] = {0};

 n = ABS_DMAP_BLK(*sb);
 for (; isb_dmap_blks; ++i) {
  hd_rw(n, HD_READ, 1, sect);
  for (j=0; j<512*8; ++j) {
   if (testb(sect, j)) {
    ++m;
   } else {   /* gotcha */
    setb(sect, j);
    if (m >= sb->sb_blocks)
     return 0;
    else {
     hd_rw(n, HD_WRITE, 1, sect);
     return ABS_BOOT_BLK(*sb) + m;
    }
   }
  }
  ++n;
 }
 return 0;
}

void
free_blk(struct SUPER_BLOCK *sb, unsigned int n) {
 unsigned char sect[512] = {0};
 unsigned int t = (n-ABS_BOOT_BLK(*sb))/(512*8)+ABS_DMAP_BLK(*sb);
 hd_rw(t, HD_READ, 1, sect);
 clrb(sect, (n-ABS_BOOT_BLK(*sb))%(512*8));
 hd_rw(t, HD_WRITE, 1, sect);
}

/* From revision 7 of skelix /root/fs.c */
These methods are added to the SUPER_BLOCK struct:
module inode;

extern (C) void *kmalloc(uint size);
extern (C) void kfree(void *ptr);

const uint FT_NML = 1;
const uint FT_NML = 2;

class inode
{
 private uint i_mode;
 private uint i_size;
 private uint[8] i_block;

 /* constructors to be implemented */

 /* new() and delete() operators */
 new(uint sz)
 {
  void* p;
  p = kmalloc(sz);
  return p;
 }

 delete(void *p)
 {
  kfree(p);
 }

 /* methods to be implemented */
}

const uint MAX_NAME_LEN = 11

struct DIR_ENTRY {
 char de_name[MAX_NAME_LEN];
 int de_inode;
};

inline uint ABS_BOOT_BLK(SUPER_BLOCK sb) 
{ return ((sb).sb_start); }
inline uint ABS_SUPER_BLK(SUPER_BLOCK sb) 
{ return ((ABS_BOOT_BLK(sb))+1); }
inline uint ABS_DMAP_BLK(SUPER_BLOCK sb) 
{ return ((ABS_SUPER_BLK(sb))+1); }
inline uint ABS_IMAP_BLK(SUPER_BLOCK sb) 
{ return ((ABS_DMAP_BLK(sb))+(sb).sb_dmap_blks); }
inline uint ABS_INODE_BLK(SUPER_BLOCK sb) 
{ return ((ABS_IMAP_BLK(sb))+(sb).sb_imap_blks); }
inline uint ABS_DATA_BLK(SUPER_BLOCK sb) 
{ return ((ABS_INODE_BLK(sb))+INODE_BLKS); }

const uint INODE_BIT_BLKS = 1; /* 512*8 = 4096 inodes */
const uint INODES_PER_BLK = (512/sizeof(struct INODE));
const uint INODE_BLKS  = 
((INODE_BIT_BLKS*512*8+INODES_PER_BLK-1)/(INODES_PER_BLK));

/* hd_rw is prototyped as
void
hd_rw(unsigned int lba, unsigned int com,
   unsigned int sects_to_access, void *buf); 
also some useful definitions:
#define HD_READ  0x20
#define HD_WRITE 0x30 
*/

extern (C) hd_rw(uint lba, uint com, uint sects_to_access, void[] buf);
const uint HD_READ = 0x20;
const uint HD_WRITE = 0x30;


struct SUPER_BLOCK {
 ubyte sb_magic;
 /* DPT 0x08 */
 uint sb_start;
 /* DPT 0x0c */
 uint sb_blocks;

 uint sb_dmap_blks;
 uint sb_imap_blks;
 uint sb_inode_blks;

 uint alloc_blk() {
  uint i = 0, j = 0, n = 0, m = 0;
  bitmap sect = new bitmap(1);

  n = ABS_DMAP_BLK((*this));
  for (; i<(*this).sb_dmap_blks; ++i) {
   hd_rw(n, HD_READ, 1, sect.buffer);
   for (j=0; j<512*8; ++j) {
    if (sect.testBit(j)) {
     ++m;
    } else {   /* gotcha */
     sect.setBit(j);
     if (m >= (*this).sb_blocks)
     {
      delete sect;
      return 0;
     }
     else {
      hd_rw(n, HD_WRITE, 1, sect.buffer);
      delete sect;
      return ABS_BOOT_BLK((*this)) + m;
     }
    }
   }
   ++n;
  }
  delete sect;
  return 0;
 }

 void free_blk(uint n) {
  bitmap sect = new bitmap(1);
  uint t = (n-ABS_BOOT_BLK((*this)))/(512*8)+ABS_DMAP_BLK((*this));
  hd_rw(t, HD_READ, 1, sect.buffer);
  sect.clearBit(n-ABS_BOOT_BLK((*this)))%(512*8);
  hd_rw(t, HD_WRITE, 1, sect.buffer);
  delete sect;
 }
};

const uint FST_FS = 0x2e;   /* normal partition */
const uint FST_SW = 0x2f;   /* swap partition */
There are a few more operations that we would like to add to the superblock struct (in retrospect, perhaps it would have been better to have made it a class...less efficiency from a memory perspective). The operations from the Skelix revision 7 /root/fs.c are:
static int
alloc_inode(struct SUPER_BLOCK *sb) {
 unsigned char sect[512] = {0};
 int i = 0;
 hd_rw(ABS_IMAP_BLK(*sb), HD_READ, 1, sect);
 for (; i<512; ++i) {
  if (! testb(sect, i)) {
   setb(sect, i);
   hd_rw(ABS_IMAP_BLK(*sb), HD_WRITE, 1, sect);
   break;
  }
 }
 return (i==512)?-1:i;
}

static void
free_inode(struct SUPER_BLOCK *sb, int n) {
 unsigned char sect[512] = {0};
 hd_rw(ABS_IMAP_BLK(*sb), HD_READ, 1, sect);
 clrb(sect, n);
 hd_rw(ABS_IMAP_BLK(*sb), HD_WRITE, 1, sect);
}

/* Skelix revision 7 /root/fs.c */
Because I am getting lazy, I will simply copy/paste the relevant code from the inode module:
/* hd_rw is prototyped as
void
hd_rw(unsigned int lba, unsigned int com,
   unsigned int sects_to_access, void *buf); 
also some useful definitions:
#define HD_READ  0x20
#define HD_WRITE 0x30 
*/

extern (C) hd_rw(uint lba, uint com, uint sects_to_access, void[] buf);
const uint HD_READ = 0x20;
const uint HD_WRITE = 0x30;


struct SUPER_BLOCK {
 ubyte sb_magic;
 /* DPT 0x08 */
 uint sb_start;
 /* DPT 0x0c */
 uint sb_blocks;

 uint sb_dmap_blks;
 uint sb_imap_blks;
 uint sb_inode_blks;

 uint alloc_blk() {
  uint i = 0, j = 0, n = 0, m = 0;
  bitmap sect = new bitmap(1);

  n = ABS_DMAP_BLK((*this));
  for (; i<(*this).sb_dmap_blks; ++i) {
   hd_rw(n, HD_READ, 1, sect.buffer);
   for (j=0; j<512*8; ++j) {
    if (sect.testBit(j)) {
     ++m;
    } else {   /* gotcha */
     sect.setBit(j);
     if (m >= (*this).sb_blocks)
     {
      delete sect;
      return 0;
     }
     else {
      hd_rw(n, HD_WRITE, 1, sect.buffer);
      delete sect;
      return ABS_BOOT_BLK((*this)) + m;
     }
    }
   }
   ++n;
  }
  delete sect;
  return 0;
 }

 void free_blk(uint n) {
  bitmap sect = new bitmap(1);
  uint t = (n-ABS_BOOT_BLK((*this)))/(512*8)+ABS_DMAP_BLK((*this));
  hd_rw(t, HD_READ, 1, sect.buffer);
  sect.clearBit(n-ABS_BOOT_BLK((*this)))%(512*8);
  hd_rw(t, HD_WRITE, 1, sect.buffer);
  delete sect;
 }
 static int alloc_inode() {
  bitmap sect = new bitmap(1);
  int i = 0;
  hd_rw(ABS_IMAP_BLK(*this), HD_READ, 1, sect.buffer);
  for (; i<512; ++i) {
   if (! sect.testBit(i)) {
    sect.setBit(i);
    hd_rw(ABS_IMAP_BLK(*this), HD_WRITE, 1, sect.buffer);
    break;
   }
  }
  delete bitmap;
  return (i==512)?-1:i;
 }

 static void free_inode(int n) {
  bitmap sect = new bitmap(1);
  hd_rw(ABS_IMAP_BLK(*this), HD_READ, 1, sect.buffer);
  sect.clearBit(n);
  hd_rw(ABS_IMAP_BLK(*this), HD_WRITE, 1, sect.buffer);
  delete sect;
 }
};

const uint FST_FS = 0x2e;   /* normal partition */
const uint FST_SW = 0x2f;   /* swap partition */
Some more final SUPER_BLOCK operations to add would be:
static struct INODE *
iget(struct SUPER_BLOCK *sb, INODE *inode, int n) {
 unsigned char sect[512] = {0};
 int i = n/INODES_PER_BLK;
 int j = n%INODES_PER_BLK;

 hd_rw(ABS_INODE_BLK(*sb)+i, HD_READ, 1, sect);
 memcpy(inode, sect+j*sizeof(struct INODE), sizeof(struct INODE));
 return inode;
}

static void
iput(struct SUPER_BLOCK *sb, INODE *inode, int n) {
 unsigned char sect[512] = {0};
 int i = n/INODES_PER_BLK;
 int j = n%INODES_PER_BLK;
 hd_rw(ABS_INODE_BLK(*sb)+i, HD_READ, 1, sect);
 memcpy(sect+j*sizeof(struct INODE), inode, sizeof(struct INODE));
 hd_rw(ABS_INODE_BLK(*sb)+i, HD_WRITE, 1, sect);
}

static struct INODE iroot = {FT_DIR, 2*sizeof(struct DIR_ENTRY), {0,}};

/* Skelix revision 7 /root/fs.c */
We then put these to work in our SUPER_BLOCK structure:
/* hd_rw is prototyped as
void
hd_rw(unsigned int lba, unsigned int com,
   unsigned int sects_to_access, void *buf); 
also some useful definitions:
#define HD_READ  0x20
#define HD_WRITE 0x30 
*/

extern (C) hd_rw(uint lba, uint com, uint sects_to_access, void[] buf);
const uint HD_READ = 0x20;
const uint HD_WRITE = 0x30;


struct SUPER_BLOCK {
 ubyte sb_magic;
 /* DPT 0x08 */
 uint sb_start;
 /* DPT 0x0c */
 uint sb_blocks;

 uint sb_dmap_blks;
 uint sb_imap_blks;
 uint sb_inode_blks;

 uint alloc_blk() {
  uint i = 0, j = 0, n = 0, m = 0;
  bitmap sect = new bitmap(1);

  n = ABS_DMAP_BLK((*this));
  for (; i<(*this).sb_dmap_blks; ++i) {
   hd_rw(n, HD_READ, 1, sect.buffer);
   for (j=0; j<512*8; ++j) {
    if (sect.testBit(j)) {
     ++m;
    } else {   /* gotcha */
     sect.setBit(j);
     if (m >= (*this).sb_blocks)
     {
      delete sect;
      return 0;
     }
     else {
      hd_rw(n, HD_WRITE, 1, sect.buffer);
      delete sect;
      return ABS_BOOT_BLK((*this)) + m;
     }
    }
   }
   ++n;
  }
  delete sect;
  return 0;
 }

 void free_blk(uint n) {
  bitmap sect = new bitmap(1);
  uint t = (n-ABS_BOOT_BLK((*this)))/(512*8)+ABS_DMAP_BLK((*this));
  hd_rw(t, HD_READ, 1, sect.buffer);
  sect.clearBit(n-ABS_BOOT_BLK((*this)))%(512*8);
  hd_rw(t, HD_WRITE, 1, sect.buffer);
  delete sect;
 }
 int alloc_inode() {
  bitmap sect = new bitmap(1);
  int i = 0;
  hd_rw(ABS_IMAP_BLK(*this), HD_READ, 1, sect.buffer);
  for (; i<512; ++i) {
   if (! sect.testBit(i)) {
    sect.setBit(i);
    hd_rw(ABS_IMAP_BLK(*this), HD_WRITE, 1, sect.buffer);
    break;
   }
  }
  delete sect;
  return (i==512)?-1:i;
 }

 static void free_inode(int n) {
  bitmap sect = new bitmap(1);
  hd_rw(ABS_IMAP_BLK(*this), HD_READ, 1, sect.buffer);
  sect.clearBit(n);
  hd_rw(ABS_IMAP_BLK(*this), HD_WRITE, 1, sect.buffer);
  delete sect;
 }
 static INODE* iget(INODE *inode, int n) {
  bitmap sect = new bitmap(1);
  int i = n/INODES_PER_BLK;
  int j = n%INODES_PER_BLK;

  hd_rw(ABS_INODE_BLK(*this)+i, HD_READ, 1, sect.buffer);
  memcpy(inode, sect.buffer+j*(INODE.sizeof), INODE.sizeof);
  delete sect;
  return inode;
 }

 static void iput(INODE *inode, int n) {
  bitmap sect = new bitmap(1);
  int i = n/INODES_PER_BLK;
  int j = n%INODES_PER_BLK;
  hd_rw(ABS_INODE_BLK(*this)+i, HD_READ, 1, sect.buffer);
  memcpy(sect.buffer+j*(INODE.sizeof), inode, INODE.sizeof);
  hd_rw(ABS_INODE_BLK(*this)+i, HD_WRITE, 1, sect.buffer);
  delete sect;
 }
};

const uint FST_FS = 0x2e;   /* normal partition */
const uint FST_SW = 0x2f;   /* swap partition */

static inode iroot = new inode(FT_DIR);
iroot.setSize(2*(DIR_ENTRY.sizeof));
iroot.setBlock(0, 0);
Now we have condemned ourselves to commit several methods and a constructor type to the inode. This is not too bad, we could have screwed up worse. We shall then integrate these things into the inode class:
class INODE
{
 private uint i_mode;
 private uint i_size;
 private uint[8] i_block;

 /* constructors to be implemented */
 public this(uint mode)
 {
  i_mode = mode;
 }
 public ~this()
 {
  
 }

 /* new() and delete() operators */
 new(uint sz)
 {
  void* p;
  p = kmalloc(sz);
  return p;
 }

 delete(void *p)
 {
  kfree(p);
 }

 /* methods to be implemented */
 public void setSize(uint size)
 {
  i_size = size;
 }
 public bool setBlock(ushort arrayIndex, uint address)
 {
  if(arrayIndex < 8)
  {
   i_block[arrayIndex] = address;
   return true;
  }
  return false;
 }
}
We have only a few functions left in the skelix file system, let us examine one:
static void
stat(INODE *inode) {
 unsigned int i = 0;
 char sect[512] = {0};
 struct DIR_ENTRY *de;

 kprintf(KPL_DUMP, "======== stat / ========\n");
 switch (inode->i_mode) {
 case FT_NML:
  kprintf(KPL_DUMP, "File, ");
  break;
 case FT_DIR:
  kprintf(KPL_DUMP, "Dir,  ");
  break;
 default:
  kprintf(KPL_PANIC, "UNKNOWN FILE TYPE!!");
  halt();
 }
 kprintf(KPL_DUMP, "Size: %d, ", inode->i_size);
 kprintf(KPL_DUMP, "Blocks: ");
 for (; i<8; ++i)
  kprintf(KPL_DUMP, "%d, ", inode->i_block[i]);
 hd_rw(inode->i_block[0], HD_READ, 1, sect);
 switch (inode->i_mode) {
 case FT_DIR:
  kprintf(KPL_DUMP, "\nName\tINode\n");
  de = (struct DIR_ENTRY *)sect;
  for (i=0; ii_size/sizeof(struct DIR_ENTRY); ++i) {
   kprintf(KPL_DUMP, "%s\t%x\n", de[i].de_name, de[i].de_inode);
  }
  break;
 default:
  break;
 }
}

/* From Skelix Revision 7 /root/fs.c */
We simply "plug and chug" this into the inode class:
extern (C) enum KP_LEVEL {KPL_DUMP, KPL_PANIC};
extern (C) void kprintf(enum KP_LEVEL, const char []fmt, ...);
extern (C) void halt();

class inode
{
 private uint i_mode;
 private uint i_size;
 private uint[8] i_block;

 /* constructors to be implemented */
 public this(uint mode)
 {
  i_mode = mode;
 }
 public ~this()
 {
  
 }

 /* new() and delete() operators */
 new(uint sz)
 {
  void* p;
  p = kmalloc(sz);
  return p;
 }
 delete(void *p)
 {
  kfree(p);
 }

 /**
   * This sets the size of the inode to be a given size.
   *
   * @PARAM: uint size the size we are setting
   *     the inode to be.
   **/
 public void setSize(uint size)
 {
  i_size = size;
 }
 /**
   * This sets an address for the inode.
   *
   * @PARAM: ushort arrayIndex is the index of
   *     the i_block array that we are setting.
   * @PARAM: uint address is the address
   *     we are setting the thing to be.
   * @RETURNS: true if the arrayIndex is valid.
   * @RETURNS: false otherwise.
   **/
 public bool setBlock(ushort arrayIndex, uint address)
 {
  if(arrayIndex < 8)
  {
   i_block[arrayIndex] = address;
   return true;
  }
  return false;
 }
 /**
   * The stat method basically prints out to the
   * screen everything you need to know about the
   * inode. If it's a directory, it prints out the
   * contents of the directory. If it's a file, it
   * prints out nothing special. Regardless, it 
   * prints out the size of the inode, and the block
   * addresses.
   *
   * @PARAM: void
   * @RETURNS: void
   **/
 public void stat() {
  uint i = 0;
  bitmap sect = new bitmap(1);
  DIR_ENTRY[(i_size/sizeof(struct DIR_ENTRY))] *de;

  kprintf(KPL_DUMP, "======== stat / ========\n");
  switch (i_mode) {
  case FT_NML:
   kprintf(KPL_DUMP, "File, ");
   break;
  case FT_DIR:
   kprintf(KPL_DUMP, "Dir,  ");
   break;
  default:
   kprintf(KPL_PANIC, "UNKNOWN FILE TYPE!!");
   halt();
  }
  kprintf(KPL_DUMP, "Size: %d, ", i_size);
  kprintf(KPL_DUMP, "Blocks: ");
  for (; i<8; ++i)
   kprintf(KPL_DUMP, "%d, ", i_block[i]);
  hd_rw(i_block[0], HD_READ, 1, sect.buffer);
  switch (i_mode) {
  case FT_DIR: //I think this needs to be re-implemented...
   kprintf(KPL_DUMP, "\nName\tINode\n");
   de = cast(struct DIR_ENTRY *)sect.buffer;
   for (i=0; i < de.length; ++i) {
    kprintf(KPL_DUMP, "%s\t%x\n", de[i].de_name, de[i].de_inode);
   }
   break;
  default:
   break;
  }
 }
}
So to tie it all together, we end up with two modules (one for superblock and the other for the inode):
module inode;
import bitmap;

extern (C) enum KP_LEVEL {KPL_DUMP, KPL_PANIC};
extern (C) void kprintf(enum KP_LEVEL, const char []fmt, ...);
extern (C) void halt();

class inode
{
 private uint i_mode;
 private uint i_size;
 private uint[8] i_block;

 /* constructors to be implemented */
 public this(uint mode)
 {
  i_mode = mode;
 }
 public ~this()
 {
  
 }

 /* new() and delete() operators */
 new(uint sz)
 {
  void* p;
  p = kmalloc(sz);
  return p;
 }
 delete(void *p)
 {
  kfree(p);
 }

 /**
   * This sets the size of the inode to be a given size.
   *
   * @PARAM: uint size the size we are setting
   *     the inode to be.
   **/
 public void setSize(uint size)
 {
  i_size = size;
 }
 /**
   * This sets an address for the inode.
   *
   * @PARAM: ushort arrayIndex is the index of
   *     the i_block array that we are setting.
   * @PARAM: uint address is the address
   *     we are setting the thing to be.
   * @RETURNS: true if the arrayIndex is valid.
   * @RETURNS: false otherwise.
   **/
 public bool setBlock(ushort arrayIndex, uint address)
 {
  if(arrayIndex < 8)
  {
   i_block[arrayIndex] = address;
   return true;
  }
  return false;
 }
 /**
   * The stat method basically prints out to the
   * screen everything you need to know about the
   * inode. If it's a directory, it prints out the
   * contents of the directory. If it's a file, it
   * prints out nothing special. Regardless, it 
   * prints out the size of the inode, and the block
   * addresses.
   *
   * @PARAM: void
   * @RETURNS: void
   **/
 public void stat() {
  uint i = 0;
  bitmap sect = new bitmap(1);
  DIR_ENTRY[(i_size/sizeof(struct DIR_ENTRY))] *de;

  kprintf(KPL_DUMP, "======== stat / ========\n");
  switch (i_mode) {
  case FT_NML:
   kprintf(KPL_DUMP, "File, ");
   break;
  case FT_DIR:
   kprintf(KPL_DUMP, "Dir,  ");
   break;
  default:
   kprintf(KPL_PANIC, "UNKNOWN FILE TYPE!!");
   halt();
  }
  kprintf(KPL_DUMP, "Size: %d, ", i_size);
  kprintf(KPL_DUMP, "Blocks: ");
  for (; i<8; ++i)
   kprintf(KPL_DUMP, "%d, ", i_block[i]);
  hd_rw(i_block[0], HD_READ, 1, sect.buffer);
  switch (i_mode) {
  case FT_DIR: //I think this needs to be re-implemented...
   kprintf(KPL_DUMP, "\nName\tINode\n");
   de = cast(struct DIR_ENTRY *)sect.buffer;
   for (i=0; i < de.length; ++i) {
    kprintf(KPL_DUMP, "%s\t%x\n", de[i].de_name, de[i].de_inode);
   }
   break;
  default:
   break;
  }
 }
}


const uint MAX_NAME_LEN = 11

struct DIR_ENTRY {
 char de_name[MAX_NAME_LEN];
 int de_inode;
};

extern INODE iroot;
module superblock;
import bitmap;
import inode;

inline uint ABS_BOOT_BLK(SUPER_BLOCK sb) { return ((sb).sb_start); }
inline uint ABS_SUPER_BLK(SUPER_BLOCK sb) { return ((ABS_BOOT_BLK(sb))+1); }
inline uint ABS_DMAP_BLK(SUPER_BLOCK sb) { return ((ABS_SUPER_BLK(sb))+1); }
inline uint ABS_IMAP_BLK(SUPER_BLOCK sb) { return ((ABS_DMAP_BLK(sb))+(sb).sb_dmap_blks); }
inline uint ABS_INODE_BLK(SUPER_BLOCK sb) { return ((ABS_IMAP_BLK(sb))+(sb).sb_imap_blks); }
inline uint ABS_DATA_BLK(SUPER_BLOCK sb) { return ((ABS_INODE_BLK(sb))+INODE_BLKS); }

const uint INODE_BIT_BLKS = 1; /* 512*8 = 4096 inodes */
const uint INODES_PER_BLK = (512/(INODE.sizeof));
const uint INODE_BLKS  = ((INODE_BIT_BLKS*512*8+INODES_PER_BLK-1)/(INODES_PER_BLK));

extern (C) hd_rw(uint lba, uint com, uint sects_to_access, void[] buf);
const uint HD_READ = 0x20;
const uint HD_WRITE = 0x30;

struct SUPER_BLOCK {
 ubyte sb_magic;
 /* DPT 0x08 */
 uint sb_start;
 /* DPT 0x0c */
 uint sb_blocks;

 uint sb_dmap_blks;
 uint sb_imap_blks;
 uint sb_inode_blks;

 uint alloc_blk() {
  uint i = 0, j = 0, n = 0, m = 0;
  bitmap sect = new bitmap(1);

  n = ABS_DMAP_BLK((*this));
  for (; i<(*this).sb_dmap_blks; ++i) {
   hd_rw(n, HD_READ, 1, sect.buffer);
   for (j=0; j<512*8; ++j) {
    if (sect.testBit(j)) {
     ++m;
    } else {   /* gotcha */
     sect.setBit(j);
     if (m >= (*this).sb_blocks)
     {
      delete sect;
      return 0;
     }
     else {
      hd_rw(n, HD_WRITE, 1, sect.buffer);
      delete sect;
      return ABS_BOOT_BLK((*this)) + m;
     }
    }
   }
   ++n;
  }
  delete sect;
  return 0;
 }

 void free_blk(uint n) {
  bitmap sect = new bitmap(1);
  uint t = (n-ABS_BOOT_BLK((*this)))/(512*8)+ABS_DMAP_BLK((*this));
  hd_rw(t, HD_READ, 1, sect.buffer);
  sect.clearBit(n-ABS_BOOT_BLK((*this)))%(512*8);
  hd_rw(t, HD_WRITE, 1, sect.buffer);
  delete sect;
 }
 int alloc_inode() {
  bitmap sect = new bitmap(1);
  int i = 0;
  hd_rw(ABS_IMAP_BLK(*this), HD_READ, 1, sect.buffer);
  for (; i<512; ++i) {
   if (! sect.testBit(i)) {
    sect.setBit(i);
    hd_rw(ABS_IMAP_BLK(*this), HD_WRITE, 1, sect.buffer);
    break;
   }
  }
  delete sect;
  return (i==512)?-1:i;
 }

 static void free_inode(int n) {
  bitmap sect = new bitmap(1);
  hd_rw(ABS_IMAP_BLK(*this), HD_READ, 1, sect.buffer);
  sect.clearBit(n);
  hd_rw(ABS_IMAP_BLK(*this), HD_WRITE, 1, sect.buffer);
  delete sect;
 }
 static inode* iget(INODE *inode, int n) {
  bitmap sect = new bitmap(1);
  int i = n/INODES_PER_BLK;
  int j = n%INODES_PER_BLK;

  hd_rw(ABS_INODE_BLK(*this)+i, HD_READ, 1, sect.buffer);
  memcpy(inode, sect.buffer+j*(INODE.sizeof), INODE.sizeof);
  delete sect;
  return inode;
 }

 static void iput(INODE *inode, int n) {
  bitmap sect = new bitmap(1);
  int i = n/INODES_PER_BLK;
  int j = n%INODES_PER_BLK;
  hd_rw(ABS_INODE_BLK(*this)+i, HD_READ, 1, sect.buffer);
  memcpy(sect.buffer+j*(INODE.sizeof), inode, INODE.sizeof);
  hd_rw(ABS_INODE_BLK(*this)+i, HD_WRITE, 1, sect.buffer);
  delete sect;
 }
};

const uint FST_FS = 0x2e;   /* normal partition */
const uint FST_SW = 0x2f;   /* swap partition */

static inode iroot = new inode(FT_DIR);
iroot.setSize(2*(DIR_ENTRY.sizeof));
iroot.setBlock(0, 0);
There you have it, your superblock and inode modules. Now we need to turn our attention to the actual file system itself.

The FS Module

The fs module is the module that people would be using if they would like to use a system call like read(), open(), etc. The last two remaining functions in the skelix file system are in /root/fs.c:
static void
check_root(void) {
 struct SUPER_BLOCK sb;
 unsigned char sect[512] = {0};
 struct DIR_ENTRY *de = NULL;

 sb.sb_start = *(unsigned int *)(HD0_ADDR);
 hd_rw(ABS_SUPER_BLK(sb), HD_READ, 1, sect);
 memcpy(&sb, sect, sizeof(struct SUPER_BLOCK));
 hd_rw(ABS_IMAP_BLK(sb), HD_READ, 1, sect);
 if (! testb(sect, 0)) {
  kprintf(KPL_DUMP, "/ has not been created, creating....\t\t\t\t\t  ");
  if (alloc_inode(&sb) != 0) {
   kprintf(KPL_PANIC, "\n/ must be inode 0!!!\n");
   halt();
  }
  iroot.i_block[0] = alloc_blk(&sb);
  iput(&sb, &iroot, 0);
  
  de = (struct DIR_ENTRY *)sect;
  strcpy(de->de_name, ".");
  de->de_inode = 0;
  ++de;
  strcpy(de->de_name, "..");
  de->de_inode = -1;
  hd_rw(iroot.i_block[0], HD_WRITE, 1, sect);
  kprintf(KPL_DUMP, "[DONE]");
 }
 iget(&sb, &iroot, 0);
 hd_rw(iroot.i_block[0], HD_READ, 1, sect);
 de = (struct DIR_ENTRY *)sect;

 if ((strcmp(de[0].de_name, ".")) || (de[0].de_inode) ||
  (strcmp(de[1].de_name, "..")) || (de[1].de_inode) != -1) {
  kprintf(KPL_PANIC, "File system is corrupted!!!\n");
  halt();
 }
}

void
verify_dir(void) {
 unsigned char sect[512] = {0};
 unsigned int *q = (unsigned int *)(HD0_ADDR);
 struct INODE inode;
 struct SUPER_BLOCK sb;

 sb.sb_start = q[0];
 hd_rw(ABS_SUPER_BLK(sb), HD_READ, 1, sect);
 check_root();
 memcpy(&sb, sect, sizeof(struct SUPER_BLOCK));
 stat(iget(&sb, &inode, 0));
}

/* From the revision 7 of skelix /root/fs.c */
We now translate this into D as usual we start by writing ot the command line: $ touch fs.d and then open it with our favorite editor. We then start ripping away at it:
module fs;
import inode;
import bitmap;
import superblock;

extern (C) void halt();

/* begin macros from C */
const uint IDT_ADDR = 0x80000;
const uint IDT_SIZE = (256*8);
const uint GDT_ADDR = ((IDT_ADDR)+IDT_SIZE);
const uint GDT_ENTRIES = 5;
const uint GDT_SIZE = (8*GDT_ENTRIES);
const uint HD0_ADDR = (GDT_ADDR+GDT_SIZE);
const uint HD0_SIZE = (4*8);
/* end macros from C */


void check_root() {
 SUPER_BLOCK sb;
 bitmap sect = new bitmap(1);
 DIR_ENTRY[((i_size/(DIR_ENTRY.sizeof))] *de = NULL;

 sb.sb_start = *(unsigned int *)(HD0_ADDR);
 hd_rw(ABS_SUPER_BLK(sb), HD_READ, 1, sect.buffer);
 memcpy(&sb, sect.buffer, SUPER_BLOCK.sizeof);
 hd_rw(ABS_IMAP_BLK(sb), HD_READ, 1, sect.buffer);
 if (! sect.testBit(0)) {
  kprintf(KPL_DUMP, "/ has not been created, creating....\t\t\t\t\t  ");
  if ((&sb).alloc_inode() != 0) {
   kprintf(KPL_PANIC, "\n/ must be inode 0!!!\n");
   halt();
  }
  iroot.setBlock(0,(&sb).alloc_blk());
  (&sb).iput(&iroot, 0);
  
  de = cast(struct DIR_ENTRY *)(sect.buffer);
  strcpy(de.de_name, ".");
  de.de_inode = 0;
  ++de;
  strcpy(de.de_name, "..");
  de.de_inode = -1;
  hd_rw(iroot.getBlock(0), HD_WRITE, 1, sect.buffer);
  kprintf(KPL_DUMP, "[DONE]");
 }
 (&sb).iget(&iroot, 0);
 hd_rw(iroot.getBlock(0), HD_READ, 1, sect.buffer);
 de = cast(struct DIR_ENTRY *)(sect.buffer);

 if ((strcmp(de[0].de_name, ".")) || (de[0].de_inode) || (strcmp(de[1].de_name, "..")) || (de[1].de_inode) != -1) {
  kprintf(KPL_PANIC, "File system is corrupted!!!\n");
  halt();
 }
}

void verify_dir() {
 bitmap sect = new bitmap(1);
 unsigned int *q = cast(unsigned int *)(HD0_ADDR);
 INODE inode;
 SUPER_BLOCK sb;

 sb.sb_start = q[0];
 hd_rw(ABS_SUPER_BLK(sb), HD_READ, 1, sect.buffer);
 check_root();
 memcpy(&sb, sect.buffer, SUPER_BLOCK.sizeof);
 ((&sb).iget(&inode, 0)).stat();
}
This I think is all we have to do in order to change the skelix file system into using the D programming language. Unfortunately as I have stressed earlier in this piece, we cannot actually implement this since Skelix does not implement kfree() and kmalloc(). It should not be too hard to implement a virtual file system for Skelix now using the D programming language...creating an abstract vnode class which the inode class extends, etc.

The Bitmap Module Reconsidered

I have a problem with the implementation with the bitmap module...largely because it still uses the garbage collector due to its somewhat dynamic array. Well, what if we make our bitmap class a linked list of these classes? That way the array remains fixed, and the bitmap can be several blocks big...and no garbage collector is needed! The revised module is below:
module bitmap;

extern (C) void *kmalloc(uint size);
extern (C) void kfree(void *ptr);

class bitmap
{
 /**
   * The buffer which represents the blocks which
   * the bitmap takes up. Recall that file system
   * uses two bitmaps which takes up some number of
   * blocks. The first bitmap is the inode bitmap
   * the second is the data block bitmap. This is
   * the in-memory representation of a bitmap.
   */
  public ubyte[512] buffer;
  private bitmap* next;

 /** Constructor of the bitmap
   *
   * The bitmap is supposed to take up some number
   * of blocks for the file system. There is a
   * inode bitmap and a block bitmap.
   *
   * @PARAM: ushort numBlocks is the number of blocks
   *     that the bitmap takes up.
   */
 public this(ushort numBlocks)
 {
 buffer[0]=0;
 if(numBlocks > 1)
  next = &(new bitmap(numBlocks-1, false);

 }
 public this(ushort numBlocks, bool first)
 {
 if(numBlocks > 1)
  next = &(new bitmap(numBlocks-1, false);

 }
 public ~this()
 {
  //may possibly be implemented?
 }

 new(uint sz)
 {
  void* p;
  p = kmalloc(sz);
  return p;
 }

 delete(void *p)
 {
 delete next;
 kfree(p);
 }


 /** The Set Bit method.
   * 
   * The set bit method sets a bit in the buffer
   * provided the bit is in the buffer. If the bit
   * is 1 (free), then it is set to be 0 (occupied).
   *
   * @PARAM: uint i is the bit number to set.
   *
   **/
 void setBit(uint i) {
  if(i < (buffer.length*8))
  {
 if(uint > buffer.length*8)
  next.setBit(i-512);
 else { 
  ubyte[] v = cast(ubyte)buffer;
  v += i>>3;
  *v |= 1<<(7-(i%8));
 }
  }
 }

 /** The Clear Bit method
   *
   * The clear bit method sets the bit in the buffer
   * to be 1. It first checks to make sure that the 
   * bit is in the buffer.
   *
   * @PARAM: uint i bit number referring to
   *      the bit that we want to clear. 
   **/
 void clearBit(uint i) {
  if(i < (buffer.length*8))
  {
 if(uint > buffer.length*8)
  next.clearBit(i-512);
 else { 
    unsigned char *v = cast(ubyte)buffer;
    v += i>>3;
    *v &= ~(1<<(7-(i%8)));
 }
  }
 }

 /** The Test Bit method
   *
   * The test bit method checks if the bit in the
   * buffer is occupied or free. It returns 1 if
   * the bit is free, 0 if it is occupied, and -1
   * if it is out of bounds.
   *
   * @PARAM: uint i is the bit we wish to
   *     test to see if it is free or occupied.
   * @RETURNS: 1 if the bit is free.
   * @RETURNS: 0 if the bit is occupied.
   * @RETURNS: -1 if the bit is out of bounds.
   **/
 int testBit(uint i) {
  if(i < (buffer.length*8))
  {
 if(uint > buffer.length*8)
  return next.testBit(i-512);
 else { 
    unsigned char *v = cast(ubyte)buffer;
    v += i>>3;
    return (*v&(1<<(7-(i%8)))) !=0;
 }
  }
  return -1;
 }
}

Revision History

Revision 0: 24 August 2007 - published. Revision 1: 24 August 2007 - revised code snippets to fit on the page. Revision 2: 24 August 2007 - revised the bitmap class. Revision 3: 29 August 2007 - revised the inode class fields.

Saturday, August 11, 2007

C++ for Java Programmers

I just wrote this in half an hour, but it's been on my mind since a week ago or so! This is subject to corrections as I experiment around, get feed back, etc. Revision 0: Published. Revision 1: Modified the headers to be presented correctly. Added tags. Revision 2: (16 August 2007) Added information about polymorphism. Revision 3: (19 August 2007) Added a link for systems programming in C++. Revision 4: (19 September 2007) Added information about the virtual qualifier's use in Java.

Introduction

Back when I took computer programming in High School (way way back when :P), I learned C++ but only the basics (e.g. cout, cin, endl, etc.). I only learned about object oriented programming in "AP" (the so-called "advanced placement") programming course. As one may expect, object oriented C++ was something I had learned independent of the course work (although with the help of the teacher, Dr. Neat). Well, I learned Object Oriented programming through Java...so I expect that object orientedness should be reasonably similar to Java programming. Through some experimenting and fiddling around with C++, I managed to make C++ a wee bit more java-like.

"Classes in their own files"

In java, if one were to program a new class it is typically its own file. Consider the Bicycle class from the Java tutorial reproduced below:
class Bicycle {     int cadence = 0;     int speed = 0;     int gear = 1;     void changeCadence(int newValue) {         cadence = newValue;     }     void changeGear(int newValue) {         gear = newValue;     }     void speedUp(int increment) {         speed = speed + increment;     }     void applyBrakes(int decrement) {         speed = speed - decrement;     }     void printStates() {         System.out.println("cadence:"+cadence+" speed:"+speed+" gear:"+gear);     } }
This is in Bicycle.java. Consider this code in C++ which we put in Bicycle.h:
#include < iostream > #ifndef BICYCLE_H #define BICYCLE_H class Bicycle { private:     int cadence;     int speed;     int gear; /* Note how all the public methods are grouped together! */ public:     Bicycle() {     /* This is the constructor which initializes the variables */         cadence = 0;         speed = 0;         gear = 1;     }     void changeCadence(int newValue) {         cadence = newValue;     }     void changeGear(int newValue) {         gear = newValue;     }     void speedUp(int increment) {         speed = speed + increment;     }     void applyBrakes(int decrement) {         speed = speed - decrement;     }     void printStates() {         std::cout << "cadence:" << cadence << " speed:" << speed << " gear:" << gear << std::endl;     } }; #endif /* BICYCLE_H */
The member functions are effectively the same, but now we can deal with pointers! It has the cleanliness of Java with the low level-ness of C. NOTE NOTE NOTE that the class definition ends with a semicolon! This is really odd, strange, foreign, and outlandish to a Java programmer (it was for me anyways!). We create a main program main.cpp which is simply:
#include "Bicycle.h" int main() {     Bicycle bike;     bike.printStates();     return 0; }
Now, the command line when compiling this program, the command line reads: $ g++ --version g++ (GCC) 4.1.2 (Ubuntu 4.1.2-0ubuntu4) Copyright (C) 2006 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. $ g++ main.cpp $ ./a.out cadence:0 speed:0 gear:1 $ Which is exactly what we want, which is what we get if we program this in Java. Note if we were to make the constructor for the bicycle:
    Bicycle(int Cadence, int Speed, int Gear) {     /* This is the constructor which initializes the variables */         cadence = Cadence;         speed = Speed;         gear = Gear; }
We would have to modify the main.cpp code to be:
#include "Bicycle.h" int main() {     Bicycle bike(0,0,1); /* Note the difference from "= new Bicycle(0,0,1);" */     bike.printStates();     return 0; }
We end up having the same results.

Instantiating Objects

This next part was relatively odd for me. As a java/C#/D programmer, I am used to saying: Foo bar = new Foo(/* constructor arguments */);. In order to do this, we must create a pointer object: Foo *fooPointer = new Foo(/* arguments */); or else you simply construct a new object via lines like: Foo foo; without a constructor called at all. Perhaps I am approaching this the wrong way, and if I am any pointers (pun not intended, really) would be greatly appreciated!

On Inheritance

Consider the following foo.h header file that contains, logically as described by the approach above (of defining classes in independent header files), the foo class:
#ifndef FOO_H #define FOO_H #include < iostream > class foo { public:     virtual void sayHello()     {         std::cout << "foo says hello" << std::endl;     } };     #endif
The method must be virtual in order for us to take advantage of polymorphism. That is, if we have several lines of code like
foo *Bar = new bar(); Bar->sayHello();
What is printed out without the virtual quantifier is "foo says hello". With the virtual quantifier we invoke the bar class' sayHello() method. Now note the derived class bar in its respective bar.h header file:
#ifndef BAR_H #define BAR_H #include < iostream > #include "foo.h" //Note how this base header is included class bar : public foo /* This should be read as "public class bar extends foo" for you java programmers ;) */ { public:     void sayHello()     {         std::cout << "bar says hello" << std::endl;     } };         #endif
So we write the following main.cpp program that exploits our inheritance:
#include "foo.h" #include "bar.h" int main() {     foo var1;     bar var2;     var1.sayHello();     var2.sayHello();     return 0; }
Which prints out to the screen: $ g++ --version g++ (GCC) 4.1.2 (Ubuntu 4.1.2-0ubuntu4) Copyright (C) 2006 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. $ g++ main.cpp $ ./a.out foo says hello bar says hello $ There you have it!

Systems Programming

If you are like me, you are itching to start programming an operating system (even a toy one!) in C++ now that you know how to program C++ to be like Java. Well, there are some difficulties. These are outlined and overcome in a nice wiki page on Systems Programming at OSDev. Mainly one needs to over-ride the new() and delete() operators such that it calls kmalloc() and kfree():
//overload the operator "new"
void * operator new (uint_t size)
{
    return kmalloc(size);
}

//overload the operator "new[]"
void * operator new[] (uint_t size)
{
    return kmalloc(size);
}

//overload the operator "delete"
void operator delete (void * p)
{
    kfree(p);
}

//overload the operator "delete[]"
void operator delete[] (void * p)
{
    kfree(p);
}
Note this code is taken from the wiki.

Appendix

I decided to look up what some old fart C++ programmers had to say about Java in their comparison of the two languages. It was really interesting, perhaps even Enlightening, to say the least. One point they raised which I neglected was that every method has the virtual qualifier. On the one hand, this bloats the program; but on the other, it allows for every method to be over-ridden. Also, another issue that is raised is that in Java one can use the final qualifier in the following example:
final Rectangle r = new Rectangle(); r.side = 5; //Legal in Java
But in C++, one has to use const slightly differently:
const Rectangle r; r.side = 5; //ILLEGAL in C++
Just two different properties worthy of note. For a comparison of the try-catch-finally blocks, see this exerpt from the book Java in a Nutshell. For a comparison of the resource management, there was a technical paper which should be worthy of note.

Saturday, August 4, 2007

The Skelix File System

The Skelix File System

The Skelix Operating System was created by a hacker that wanted to hack FreeBSD but found its code too disorganized (a common flaw with old operating systems). He wrote everything from scratch. It is (sort of) a Unix-like operating system...it is perhaps more of a tutorial to introduce a youngling to the scary inner workings of operating systems.

The Skelix File System is loosely based off of the Unix file system. Thus it is the first along the line of file systems to be studied because of its simplicity. Note that the author's permission is granted to use the source code.

Data Structures

The investigation shall begin with the introduction of the inode. The inode is a file, basically. Tanenbaum and Woodhull [1] note that:
our last method for keeping track of which blocks belong to which file is to associate with each file a data structure called an i-node (index-node), which lists the attributes and disk addresses of the file's blocks. [...] Given the i-node, it is then possible to find all the blocks of the file. The big advantage of this scheme over linked files using in-memory table is that the i-node need only be in memory when the corresponding file is open. If each i-node occupies n bytes and a maximum of k files may be open at once, the total memory occupied by the array holding the i-nodes for the open files is kn bytes. Only this much space needs to be reserved in advance. (Page 502 --emphasis is Tanenbaum and Woodhull's)
The inode method is the technique which will be investigated thoroughly, as it is the technique that most (if not, all) Unix-like operating systems use. We use Skelix because, as previously noted, it is the simplest introduction to the underlying concepts and picture. It should be noted that there is an inode table (that is, in practice, little more than a dynamic array or linked list of the inodes that are loaded into the memory).
06. #define FT_NML           1
07. #define FT_DIR          2
08. 
09. struct INODE {
10.       unsigned int i_mode;                /* file mode */
11.           unsigned int i_size;                /* size in bytes */
12.           unsigned int i_block[8];
13. };Skelix Revision 7: /skelix/include/fs.h
Line 10 indicates what sort of file the inode is: a FT_NML = 1 (normal file) or a FT_DIR = 2 (directory). That is the entire raison d'etre for the inode mode: it tells us what sort of file the inode is. Luckily, the Skelix operating system is simple enough to support only two file types: the ordinary file, and the directory. So we aren't concerned here about pipes, sockets, block or character device files.

Line 11, unsigned int i_size, tells us the size of the file in units of bytes. It's a convenient field that simplifies things greatly later on, so we won't need a complicated function to figure out the size of the file.

A little discussion about the structure of the inode's last field (line 12). First it should be noted that disk geometry is organized into blocks which consist of 512 bytes. The i_blocks are addresses to blocks. The first 6 are direct addresses. The seventh is an address of a block which consists solely of addresses to other blocks. It is a singly indirect block. The last address is an address to a doubly indirect block. That is, it refers to a block full of addresses to singly indirect blocks.

For those like me who are lazy and want to figure out how much space this is total that is supported by the inode, allow a quick calculation. There are 6 direct blocks, that is:
1. (6 blocks) * (512 bytes per block) = 3072 bytes
Now time to calculate out the singly indirect block. There are:
2. (512 bytes per block) * (4 bytes per address) = 128 addresses
3. (128 addresses) * (1 block per address) * (512 bytes per block) = 65536 bytes
Lastly, the doubly indirect block. By our calculations in step 3, we have 128 addresses per block. Each address refers to another indirect block. So that is 128 addresses referring to 128 addresses each, for a grand total of:
4. (128 addresses) * (128 addresses) = 16384 addresses
5. (16384 addresses) * (1 block per address) * (512 bytes per address) = 8388608 bytes
So that means we are dealing with a total of: 8388608 + 65536 + 3072 = 8457216 bytes. Note that 1024 bytes are a kilobyte, 1024 kilobytes are a megabyte, and "thus" 1048576 bytes are a megabyte. We are then dealing with 8 megabytes, 67 kilobytes total from 1 inode. That means that the file supports at most 8 megabytes, 67 kilobytes. No more, but certainly it supports less. This concludes our investigation of the inode data structure.

We can now continue our investigation of the /skelix/include/fs.h header file for more data structures:
15. extern struct INODE iroot;
16. 
17. #define MAX_NAME_LEN 11
18. 
19. struct DIR_ENTRY {
20.        char de_name[MAX_NAME_LEN];
21.        int de_inode;
22. };
Skelix Revision 7: /skelix/include/fs.h
Line 15 refers to the global variable which refers to the inode of the root file system.

Line 17 defines the maximum name length for a file or directory. (Remember, a directory is-a file!) For Skelix, it is 11 characters. That is 11 bytes.

Line 19 introduces a new data structure: the dir_entry. It is the directory entry, which stores the data about the files within the directory. For skelix this is two fields. One is the name of the file (line 20), the other is the inode number that is used to refer to the inode table (line 21). The size of the struct dir_entry structure is:
(11 chars)*(1 byte per char) + (1 int) * (4 bytes per int) = 15 bytes
These directory entries are used to find other files. The way one searches through the inodes via directory entries is to first select an entry, then the search method looks up the inode number, it then looks it up in the inode table.

The "root" directory is the directory which contains a few key directories. The exact directories vary according to the operating system. Linux, e.g., has the root hierarchy of: bin, dev, initrd, lib, mnt, root, sys, usr, boot, etc, lost+found, opt, sbin, tmp, var, home, media, proc, srv, u. Note that in Skelix has reserved the zeroeth inode for the root directory.

Now, there is an important block that we should pay some attention to: the super block. It is the first block. It contains all the key parameters about the file system, and is read into memory when the computer is booted or when the file system is first touched. Often times the super block is a complicated beast, luckily for us Skelix provides a simpler version of it.
35. struct SUPER_BLOCK {
36.        unsigned char sb_magic;
37.        /* DPT 0x08 */
38.        unsigned int sb_start;
39.        /* DPT 0x0c */
40.        unsigned int sb_blocks;
41. 
42.        unsigned int sb_dmap_blks;
43.        unsigned int sb_imap_blks;
44.        unsigned int sb_inode_blks;
45. };
46. 
47. #define FST_FS       0x2e                     /* normal partition */
48. #define FST_SW       0x2f                     /* swap partition */
Skelix Revision 7: /skelix/include/fs.h
Line 36 tells us what type of partition we are dealing with. The two possible partitions are: swap and the regular file system. That is the role of the magic number. We can tell what identifier to use by looking at lines 47 and 48.

The next line of importance is line 39. It tells us the address to the start sector number. Line 40 tells us the capacity of the partition. The count is in units of 512 block sectors.

The next field, unsigned int sb_dmap_blks, tells us the number of 512 byte blocks we have to contain the bit map of blocks. This "bitmap" is a series of ones and zeroes that tell us if the block is in use (1) or if it is free (0).

Correspondingly, we have a bitmap for the inodes. It tells us if inodes are in use or not. The idea is the allocate a certain amount of space to the inodes before hand. The partition is of size x bytes, and we know that a skelix inode can deal with 8457216 bytes. So the number of inodes that are allocated are thus x/8457216 inodes, roughly.

The last field in the structure is the sb_inode_blks. It tells us how many block we use for the inode array.

Now there are some fundamental macros that are defined which should be noted.

24. #define ABS_BOOT_BLK(sb)              ((sb).sb_start)
25. #define ABS_SUPER_BLK(sb)             ((ABS_BOOT_BLK(sb))+1)
26. #define ABS_DMAP_BLK(sb)              ((ABS_SUPER_BLK(sb))+1)
27. #define ABS_IMAP_BLK(sb)              ((ABS_DMAP_BLK(sb))+(sb).sb_dmap_blks)
28. #define ABS_INODE_BLK(sb)             ((ABS_IMAP_BLK(sb))+(sb).sb_imap_blks)
29. #define ABS_DATA_BLK(sb)              ((ABS_INODE_BLK(sb))+INODE_BLKS)
30. 
31. #define INODE_BIT_BLKS              1       /* 512*8 = 4096 inodes */
32. #define INODES_PER_BLK              (512/sizeof(struct INODE))
33. #define INODE_BLKS                  ((INODE_BIT_BLKS*512*8+INODES_PER_BLK-1)/(INODES_PER_BLK))
Skelix Revision 7: /skelix/include/fs.h
The macros defined on lines 24-29 deal specifically with the superblock data structure. On the other hand, lines 31-33 deal specifically with the inode mechanics. So, logically, we shall first investigate the first group and then the second group of macros.

The first super block macro (ABS_BOOT_BLK) is rather simple. It returns the boot block. The second macro (ABS_SUPER_BLK) gives where the super block is. The third macro (ABS_DMAP_BLK) tells us where the block bitmap starts. Correspondingly the fourth macro (ABS_IMAP_BLK) tells us where the inode bitmap starts. The fifth macro (ABS_INODE_BLK) returns the beginning of the inode array (the inode table). The sixth and last super block macro (ABS_DATA_BLK) returns the block where the data really starts. These macros are merely glorified accessor methods of a given super block structure.

On the other hand, the next set of macros deals with the geometry of the inodes. For example, line 31 defines INODE_BIT_BLKS. This tells us the number of 512 byte blocks dedicated to the inode bitmap block.

Line 32 tells us the inner workings of the mysterious INODES_PER_BLK block. It is essentially: (the size of a block) / (the size of an inode). There is not too much in this.

The last macro (line 33) INODE_BLKS tells us roughly this: (Inodes supported by the inode bitmap + Inodes per block - 1)/(Inodes per block). Mathematically, this is formally equivalent to: (Inodes supported by the inode bitmap - 1)/(Inodes per block) + 1. In a hand-wavy argument, we can roughly argue that this is approximately:

(Inodes supported by the inode bitmap)/(Inodes per block).

Intuitively this rough approximation (I emphasize this is a ROUGH APPROXIMATION) gives us the

(number of inodes)/(x inodes/block) = block * (total inodes / x inodes).

This gives us the number of blocks of the inode bitmap.

File System Source Code Analysis

Now that we have examined the fundamental data structures of the Skelix file system, we shall turn our focus on the bread and butter functions. First we shall examine the atomic block operations:
10. void
11. setb(void *s, unsigned int i) {
12.        unsigned char *v = s;
13.        v += i>>3;
14.        *v |= 1<<(7-(i%8));
15. }
16. 
17. void
18. clrb(void *s, unsigned int i) {
19.        unsigned char *v = s;
20.        v += i>>3;
21.        *v &= ~(1<<(7-(i%8)));
22. }
23. 
24. int
25. testb(void *s, unsigned int i) {
26.        unsigned char *v = s;
27.        v += i>>3;
28.        return (*v&(1<<(7-(i%8)))) !=0;
29. }

Skelix Revision 7: /skelix/fs.c
The first function, setb(void *buffer, unsigned int bitNumberToSet), tells us: given a buffer void *buffer, we set bit bitNumberToSet (offset is always 0). The function uses the voodoo of bit operators, as do the other three.

The second method is used to set a bit in the block bitmap to 0.

The third method is actually a test. It is used to test if a bit is set or unset. It returns 1 if it is set, 0 if it is not set I think (I could very well be wrong!).
125. unsigned int
126. alloc_blk(struct SUPER_BLOCK *sb) {
127.        unsigned int i = 0, j = 0, n = 0, m = 0;
128.        unsigned char sect[512] = {0};
129. 
130.        n = ABS_DMAP_BLK(*sb);
131.        for (; isb_dmap_blks; ++i) {
132.               hd_rw(n, HD_READ, 1, sect);
133.               for (j=0; j<512*8; ++j) {
134.                      if (testb(sect, j)) {
135.                             ++m;
136.                      } else {                     /* gotcha */
137.                             setb(sect, j);
138.                             if (m >= sb->sb_blocks)
139.                                    return 0;
140.                             else {
141.                                    hd_rw(n, HD_WRITE, 1, sect);
142.                                    return ABS_BOOT_BLK(*sb) + m;
143.                             }
144.                      }
145.               }
146.               ++n;
147.        }
148.        return 0;
149. }

Skelix Revision 7: /skelix/fs.c
Now it is probably important to note that the purpose of this function is for locating the first free block on the disk, then returning the logical block address. It takes one argument, struct SUPER_BLOCK *sb, a pointer to a superblock. It returns 0 if the allocation didn't happen. Otherwise it returns return ABS_BOOT_BLK(*sb) + m (line 142)...which is the address in units of blocks offset from the boot block.

Line 127 initiates four dummy variables: i, j, m, and n. Line 128 initiates a dummy sector that is 512 (signed) bytes big. We set the variable n to be the start of the block bitmap (line 130). We let m be the offset from the beginning of the disk. The variable j is used as a counter in the for-loop started on line 133. Similarly, i is used as a counter in the for-loop on line 131.

There is an odd function that is called on line 132 and 141 that we haven't seen before: hd_rw()! So a brief "explanation" of what this function does will be given. Behold the stripped down code (yes, it is modified so it simplifies the explanation and changes nothing): void hd_rw(unsigned int logicalBlockAddress, unsigned int ReadOrWrite, unsigned int SectorsToAccess, void *buf);

There are 4 arguments the function takes in: the logical block address of the block we are going to read from or write to, the flag that tells us whether we are reading from or writing to the block, an unsigned int referring to the sectors to access, and a buffer that is either being written to (if we are reading from the hardware) or being read from (if we are writing it to the hardware).

So line 132, the logical block address is the beginning of the block bitmap. We are reading from the bitmap (HD_READ tells us this). We are accessing the first sector, and we are using a buffer sect that is the dummy variable for 512 bytes defined on line 128.

We cycle through the block bitmap (by using the for-loop on line 131), and we cycle through each bit of each block (by using the for-loop on line 133). On each bit we test to see if the block is being used. (line 134). If it is, we increment the m dummy variable (line 135). If it is free, we set the block (line 137). However, if we somehow run through more blocks than we have, than we have to return 0 (lines 138-139). If we have not run through more blocks than we have, then we write to the super block that we have no modified the blocks in use slightly. Then we return the address offset from the boot block (lines 141-142).

151. void
152. free_blk(struct SUPER_BLOCK *sb, unsigned int n) {
153.        unsigned char sect[512] = {0};
154.        unsigned int t = (n-ABS_BOOT_BLK(*sb))/(512*8)+ABS_DMAP_BLK(*sb);
155.        hd_rw(t, HD_READ, 1, sect);
156.        clrb(sect, (n-ABS_BOOT_BLK(*sb))%(512*8));
157.        hd_rw(t, HD_WRITE, 1, sect);
158. }

Skelix Revision 7: /skelix/fs.c
The purpose of free_blk() is clear: it works in the opposite manner of alloc_blk(). It takes in two arguments: struct SUPER_BLOCK *sb which is a super block pointer, and unsigned int n which is the address of the

We initialize a dummy variable on line 153. It is used as a buffer for the block bitmap for a 512 byte block. Line 154 is a little tricky. It defines an unsigned int t to be equal to n minus the boot block address, divided by the number of bits in a block (512 bytes per block * 8 bits per byte = 4096 bits per block), added to the start of the block bit map. So the numerator (number on top of the fraction) is the offset from the beginning of the partition, the denominator is the number of bits in a block; i.e. (offset to the block)/(bits per block) + (start of the block bitmap).

We then read in the block bitmap, and store it in the buffer sect which is 512 bytes. We clear the block, and then write these changes back to the hardware on the next line. That's all there is to this method.

160. static int
161. alloc_inode(struct SUPER_BLOCK *sb) {
162.        unsigned char sect[512] = {0};
163.        int i = 0;
164.        hd_rw(ABS_IMAP_BLK(*sb), HD_READ, 1, sect);
165.        for (; i<512; ++i) {
166.               if (! testb(sect, i)) {
167.                      setb(sect, i);
168.                      hd_rw(ABS_IMAP_BLK(*sb), HD_WRITE, 1, sect);
169.                      break;
170.               }
171.        }
172.        return (i==512)?-1:i;
173. }

Skelix Revision 7: /skelix/fs.c


This method allocates a new inode. It takes in one argument: struct SUPER_BLOCK *sb which is the pointer to a super block.

It first initializes a 512 byte buffer on line 162 sect. The next line initializes a dummy variable i. Then on line 164, the inode bitmap is read into the sect buffer on line 164.

Then we cycle through every byte of the sect buffer (using the for-loop on line 165). It tests to see if the block is free (line 166); if so, then it writes to the block (line 167). It then writes to the inode bitmap the changes (a new inode has been allocated!) on line 168, and exits the for-loop.

Line 172, we check to see if we cycled through the entire inode bitmap (i==512). If so, we return -1: there was an error! Largely that we ran out of space for the inode. Otherwise, if i<512 we return i.

175. static void
176. free_inode(struct SUPER_BLOCK *sb, int n) {
177.        unsigned char sect[512] = {0};
178.        hd_rw(ABS_IMAP_BLK(*sb), HD_READ, 1, sect);
179.        clrb(sect, n);
180.        hd_rw(ABS_IMAP_BLK(*sb), HD_WRITE, 1, sect);
181. }

Skelix Revision 7: /skelix/fs.c
The point of free_inode() is clear: it frees an inode. It takes in two arguments: struct SUPER_BLOCK *sb, a pointer to a super block; and int n which is the number of the inode in the inode table which we would like to have freed.

We have a dummy variable that represents a 512 byte block of data initialized on line 177. The next line, we read from the inode bitmap into the buffer sect. We then call the clear block method clrb() to clear the block (which modifies the buffer representing the inode bitmap block), and then write the modifications to the disk (the importance of line 180).

183. static struct INODE *
184. iget(struct SUPER_BLOCK *sb, struct INODE *inode, int n) {
185.        unsigned char sect[512] = {0};
186.        int i = n/INODES_PER_BLK;
187.        int j = n%INODES_PER_BLK;
188. 
189.        hd_rw(ABS_INODE_BLK(*sb)+i, HD_READ, 1, sect);
190.        memcpy(inode, sect+j*sizeof(struct INODE), sizeof(struct INODE));
191.        return inode;
192. } 

Skelix Revision 7: /skelix/fs.c


The point of this function is to get an inode. It takes in 3 arguments: struct SUPER_BLOCK *sb, a pointer to a super block; struct INODE *inode, a pointer to an inode; and int n, which I assume is the inode number in the inode table. The method: copies the inode into the argument struct INODE *inode and then returns it.

We allocate a 512 byte buffer sect (as usual!). We then have a variable int i and int j which gives us the block and offset therein respectively (by lines 186 and 187).

We then read into sect the block where the inode lives (ABS_INODE_BLK(*sb)+i) on line 189. We then copy this into the inode pointer, and return it on lines 190 and 191 respectively.

194. static void
195. iput(struct SUPER_BLOCK *sb, struct INODE *inode, int n) {
196.        unsigned char sect[512] = {0};
197.        int i = n/INODES_PER_BLK;
198.        int j = n%INODES_PER_BLK;
199.        hd_rw(ABS_INODE_BLK(*sb)+i, HD_READ, 1, sect);
200.        memcpy(sect+j*sizeof(struct INODE), inode, sizeof(struct INODE));
201.        hd_rw(ABS_INODE_BLK(*sb)+i, HD_WRITE, 1, sect);
202. }

Skelix Revision 7: /skelix/fs.c


This method essentially is the opposite of iget(). It takes in, also, three arguments: struct SUPER_BLOCK *sb, which is a pointer to a super block; struct INODE *inode, which is an inode pointer; and lastly int n which is the inode number. This method returns nothing (it is static void after all!).

We begin, as usual, by initializing a buffer for a 512 byte block and call it sect. We then figure out which block (offset from the beginning of the inode section) the inode lives on, and the offset within that block (lines 197 and 198 respectively).

We then read into sect the inode we wish to "put away" (line 199). We then copy the memory location of this inode, the inode argument where the changes were done, to the buffer (sect+j*sizeof(struct INODE) is where to be precise) (line 200) and then write to disk the change in the sect buffer (line 201).

That is the end of this method.

(NOTE: for those interested about the root directory's inode, it is dealt with on line 204 in /skelix/fs.c: static struct INODE iroot = {FT_DIR, 2*sizeof(struct DIR_ENTRY), {0,}}; Recall the inode Structure which had three fields: i_mode which told us whether we were dealing with a directory or an ordinary file; i_size which told us the size of the inode; and i_blocks which is the addresses. The initialization of iroot tells us several things: 1) the root directory is a FT_DIR (directory); 2) the size is 2*sizeof(struct DIR_ENTRY), 3) it's blocks include the zeroeth block among others.)

There are other methods which are not really going to be surveyed here (e.g. void verify_dir() or static void check_root() but these can be inspected on the reader's time, they are not difficult to understand and is fairly straightforward). The last method we shall discuss is:
206. static void
207. stat(struct INODE *inode) {
208.        unsigned int i = 0;
209.        char sect[512] = {0};
210.        struct DIR_ENTRY *de;
211. 
212.        kprintf(KPL_DUMP, "======== stat / ========\n");
213.        switch (inode->i_mode) {
214.        case FT_NML:
215.               kprintf(KPL_DUMP, "File, ");
216.               break;
217.        case FT_DIR:
218.               kprintf(KPL_DUMP, "Dir,  ");
219.               break;
220.        default:
221.               kprintf(KPL_PANIC, "UNKNOWN FILE TYPE!!");
222.               halt();
223.        }
224.        kprintf(KPL_DUMP, "Size: %d, ", inode->i_size);
225.        kprintf(KPL_DUMP, "Blocks: ");
226.        for (; i<8; ++i)
227.               kprintf(KPL_DUMP, "%d, ", inode->i_block[i]);
228.        hd_rw(inode->i_block[0], HD_READ, 1, sect);
229.        switch (inode->i_mode) {
230.        case FT_DIR:
231.               kprintf(KPL_DUMP, "\nName\tINode\n");
232.               de = (struct DIR_ENTRY *)sect;
233.               for (i=0; ii_size/sizeof(struct DIR_ENTRY); ++i) {
234.                      kprintf(KPL_DUMP, "%s\t%x\n", de[i].de_name, de[i].de_inode);
235.               }
236.               break;
237.        default:
238.               break;
239.        }
240. }

Skelix Revision 7: /skelix/fs.c


The static void stat() method is used to get the stats on a certain file. That is why the only argument it takes in is struct INODE *inode - an inode pointer to the inode we'd like to gauge.

We have a few dummy variables and buffers defined first on lines 208-210. An unsigned int i, a 512 byte block buffer char sect[512], and a struct DIR_ENTRY *de buffer.

We announce we are gauging a given inode on line 212 by announcing it to the world with kprintf(). Note that, if you didn't guess already, kprintf() is essentially printf() written from scratch with a few extra parameters.

Line 213 we are investigating what sort of file we are gauging. If it is a file or directory (or unknown) we announce it (lines 215, 218, and 221 respectively - only if the file type is unknown will we actually halt()).

We then announce how big the file is with line 224. There is also an announcement of the blocks that are being used by the inode (we cycle through the blocks on line 227).

Recall the parameters for the hd_rw() (I reiterate it here only because I myself forgot!): void hd_rw(unsigned int logicalBlockAddress, unsigned int ReadOrWrite, unsigned int SectorsToAccess, void *buf);

So looking to line 228 with knowledge of this method renewed, we see that we read from inode->i_block[0], and we stuff it into the block buffer char sect[512].

IF we are dealing with a directory, then we print out the contents of the directory (lines 230-236). Otherwise we simply end the method.

References

Tanenbaum, Andrew S. and Woodhull, Albert S. Operating Systems: Design and Implementation. Prentice Hall: Upper Saddle River, NJ. (2006)