Roadmap 7: File System

In this roadmap we study the following issues associated with the file system:

  • Two types of storage unit: byte (memory, user); sector (disk);
  • Data structures of file header (i-node), directory;
  • Implementations of the operations on files;
  • Synchronization in file system;
  • Disk management;
  • Interface between system (software) and disk (hardware device).

  • As before, examine and understand the file system code first. Nachos file operations are similar (but not identical) to those in UNIX. It is a good idea to read the UNIX man pages for creat, open, close, read, write, lseek, and unlink (e.g., type "man creat").

    Command line switches support the following file system operations:

    	-cp copies a file from UNIX to nachos
    	-p prints a nachos file to stdout
    	-r removes a nachos file from the file system
    	-l lists the contents of the nachos directory
    	-D prints the contents of the entire file system
    	-t tests the performance of the nachos file system
    

    If you want to use the real Nachos file system (based on the simulated disk), rather than the stub, remove
    -DFILESYS_STUB from DEFINE in build.sun/Makefile (or build.linux/Makefile).

    Examples

    	./nachos -f -cp ../test/halt halt
    

    Format the disk and copy the program halt into the simulated disk.

    	./nachos -x halt
    

    Run the program halt from the simulated disk.

    Before tracing the program, we first give an overview of nachos file system. It has the following objects:
    FileSystem { OpenFile *freeMapFile; OpenFile *directoryFile; };
    Directory { int tableSize; DirectoryEntry *table; };
    DirectoryEntry { bool inUse; int sector; char name[FileNameMaxLen + 1]; };
    OpenFile { FileHeader *hdr; int seekPosition; };
    FileHeader { Int numBytes; int numSectors; int dataSectors[NumDirect]; };

            ---------- FileSystem ----------
            |                               |
        freeMapFile -------------------- directoryFile
            |               |                     |
          BitMap         OpenFile             Directory
                            |                     |
                        FileHeader          DirectoryEntry
    

    Question

    What file system objects exist in memory and/or on disk?
    How are these objects moved between memory and disk?

    Now, we examine nachos file system by tracing the program. Start with threads/kernel.cc/Initialize() called by threads/main.cc/main(int argc, char **argv).

    void
    Kernel::Initialize()
    {
           ...
        synchDisk = new SynchDisk();
    #ifdef FILESYS_STUB
        fileSystem = new FileSystem();
    #else
        fileSystem = new FileSystem(formatFlag);
    #endif // FILESYS_STUB
           ...
    }
    

    If FILESYS_STUB is not defined, not using UNIX file system stub, then a FileSystem is constructed. The parameter formatFlag is set when the command line switch -f is present. First, a new synchDisk is constructed before the fileSystem is created. The file system consists of a root directory and a bitmap, which we have studied in user program. Both the directory and bitmap are treated as files, objects of class OpenFile, which provides operations on files. Then, you can test this simple file system using command line switch.

     

     

    machine/disk.*, filesys/synchdisk.*

    class SynchDisk : public CallBackObj {
      public:
        SynchDisk();                        // Initialize a synchronous disk,
                                            // by initializing the raw Disk.
        ~SynchDisk();                       // De-allocate the synch disk data
    
        void ReadSector(int sectorNumber, char* data);
                                            // Read/write a disk sector, returning
                                            // only once the data is actually read
                                            // or written.  These call
                                            // Disk::ReadRequest/WriteRequest and
                                            // then wait until the request is done.
        void WriteSector(int sectorNumber, char* data);
    
        void CallBack();                    // Called by the disk device interrupt
                                            // handler, to signal that the
                                            // current disk operation is complete.
    
      private:
        Disk *disk;                         // Raw disk device
        Semaphore *semaphore;               // To synchronize requesting thread
                                            // with the interrupt handler
        Lock *lock;                         // Only one read/write request
                                            // can be sent to the disk at a time
    };
    
    
    SynchDisk::SynchDisk(char* name)
    {
        semaphore = new Semaphore("synch disk", 0);
        lock = new Lock("synch disk lock");
        disk = new Disk(this);
    }
    

    Questions:

  • When synchDisk is constructed, it creates a asynchronous disk.
    What argument is passed to the disk? What does it do? This argument provides the interface between the system (software) and the disk (hardware).
  • What are the arguments passed to Disk::ReadRequest() or Disk::WriteRequest? They are also part of the interface between the system (software) and the disk (hardware).
  • When a thread sends a disk I/O request (SynchDisk::ReadSector(), SynchDisk::WriteSector()), how is it blocked?
  •  

     

    machine/disk.*

    
    class Disk {
      public:
        Disk(CallBackObj *toCall);          // Create a simulated disk.
                                            // Invoke toCall->CallBack()
                                            // when each request completes.
        ~Disk();                            // Deallocate the disk.
    
        void ReadRequest(int sectorNumber, char* data);
                                            // Read/write an single disk sector.
                                            // These routines send a request to
                                            // the disk and return immediately.
                                            // Only one request allowed at a time!
        void WriteRequest(int sectorNumber, char* data);
    
        void CallBack();                    // Invoked when disk request
                                            // finishes. In turn calls, callWhenDone
    .
    
        int ComputeLatency(int newSector, bool writing);
                                            // Return how long a request to
                                            // newSector will take:
                                            // (seek + rotational delay + transfer)
    
      private:
        int fileno;                         // UNIX file number for simulated disk
        char diskname[32];                  // name of simulated disk's file
        CallBackObj *callWhenDone;          // Invoke when any disk request finishes
        bool active;                        // Is a disk operation in progress?
        int lastSector;                     // The previous disk request
        int bufferInit;                     // When the track buffer started
                                            // being loaded
    
        int TimeToSeek(int newSector, int *rotate);
                                            // time to get to the new track
        int ModuloDiff(int to, int from);   // # sectors between to and from
        void UpdateLast(int newSector);
    };
    

    Questions:

  • How is a disk interrupt scheduled?
  • When a disk I/O completes, how does the thread blocked on I/O wake up?
  •  

     

    filesys/filesys.*

    
    class FileSystem {
      public:
    	...
      private:
       OpenFile* freeMapFile;               // Bit map of free disk blocks,
                                            // represented as a file
       OpenFile* directoryFile;             // "Root" directory -- list of
                                            // file names, represented as a file
    };
    

    The file system consists of freeMapFile (OpenFile *), a bit map treated as a file, and directoryFile (OpenFile *), the root directory is also treated as a file.
    Question: Where are their file headers?
    The baseline file system has the following restrictions: only one (root) directory, entries in directory are of fixed size, maximum number of entries in the directory is 10.

     

     

    
    FileSystem::FileSystem(bool format)
    {
        DEBUG(dbgFile, "Initializing the file system.");
        if (format) {
            PersistentBitmap *freeMap = new PersistentBitmap(NumSectors);
            Directory *directory = new Directory(NumDirEntries);
            FileHeader *mapHdr = new FileHeader;
            FileHeader *dirHdr = new FileHeader;
    
            DEBUG(dbgFile, "Formatting the file system.");
    
            // First, allocate space for FileHeaders for the directory
            // and bitmap (make sure no one else grabs these!)
            freeMap->Mark(FreeMapSector);
            freeMap->Mark(DirectorySector);
    
            // Second, allocate space for the data blocks containing
            // the contents of the directory and bitmap files.  There
            // better be enough space!
    
            ASSERT(mapHdr->Allocate(freeMap, FreeMapFileSize));
            ASSERT(dirHdr->Allocate(freeMap, DirectoryFileSize));
    
            // Flush the bitmap and directory FileHeaders back to disk
            // We need to do this before we can "Open" the file, since
            // open reads the file header off of disk (and currently
            // the disk has garbage on it!).
    
            DEBUG(dbgFile, "Writing headers back to disk.");
            mapHdr->WriteBack(FreeMapSector);
            dirHdr->WriteBack(DirectorySector);
    
            // OK to open the bitmap and directory files now
            // The file system operations assume these two files
            // are left open while Nachos is running.
    
            freeMapFile = new OpenFile(FreeMapSector);
            directoryFile = new OpenFile(DirectorySector);
    
            // Once we have the files "open", we can write the initial
            // version of each file back to disk.  The directory at this
            // point is completely empty; but the bitmap has been changed
            // to reflect the fact that sectors on the disk have been
            // allocated for the file headers and to hold the file data
            // for the directory and bitmap.
    
            DEBUG(dbgFile, "Writing bitmap and directory back to disk.");
            freeMap->WriteBack(freeMapFile);         // flush changes to disk
            directory->WriteBack(directoryFile);
    
            if (debug->IsEnabled('f')) {
                freeMap->Print();
                directory->Print();
            }
            delete freeMap;
            delete directory;
            delete mapHdr;
            delete dirHdr;
        } else {
            // if we are not formatting the disk, just open the files
            // representing the bitmap and directory; these are left
            // open while Nachos is running
            freeMapFile = new OpenFile(FreeMapSector);
            directoryFile = new OpenFile(DirectorySector);
        }
    }
    
    

    When the file system is constructed, if the command line switch is "-f", the disk is formatted (wipe out all files on the disk). It first initializes a bitmap and the root directory in memory (headers and files); flush the headers back to disk; open bitmap and root so that their headers are kept in memory and we can operate on them; flush bitmap and root directory files back to the disk; clean up memory. If the disk has been formatted, just open bitmap and root directory (bring their file headers into memory).

    Question: How many disk reads/writes when the file system is contructed with format=TRUE?

     

     

    The file system provides the following operations. These operations are associated with the file system (directory, bitmap). The operations associated with individual files are in OpenFile object.

    
    class FileSystem {
      public:
    	...
        bool Create(char *name, int initialSize);
    
        OpenFile* Open(char *name);
    
        bool Remove(char *name);
    	...
    };
    

     

     

    
    bool
    FileSystem::Create(char *name, int initialSize)
    {
        Directory *directory;
        PersistentBitmap *freeMap;
        FileHeader *hdr;
        int sector;
        bool success;
    
        DEBUG(dbgFile, "Creating file " << name << " size " << initialSize);
    
        directory = new Directory(NumDirEntries);
        directory->FetchFrom(directoryFile);
    
        if (directory->Find(name) != -1)
          success = FALSE;                  // file is already in directory
        else {
            freeMap = new PersistentBitmap(freeMapFile,NumSectors);
            sector = freeMap->FindAndSet(); // find a sector to hold the file header
                if (sector == -1)
                success = FALSE;            // no free block for file header
            else if (!directory->Add(name, sector))
                success = FALSE;            // no space in directory
            else {
                    hdr = new FileHeader;
                if (!hdr->Allocate(freeMap, initialSize))
                    success = FALSE;        // no space on disk for data
                else {
                    success = TRUE;
                    // everthing worked, flush all changes back to disk
                    hdr->WriteBack(sector);
                    directory->WriteBack(directoryFile);
                    freeMap->WriteBack(freeMapFile);
                }
                delete hdr;
            }
            delete freeMap;
        }
        delete directory;
        return success;
    }
    

    This function creates a file with no data by adding an entry in the directory. Study carefully the data movement between memory and disk.

    The file header is flushed back to the disk before the directory. Because, if system crashes in between, the directory does not have the entry for the file although the file header is on the disk. The file system still functions. If, however, the order is reversed, the directory were flushed back before the file header. If the system crashes in between, we would have the entry in the directory referencing to a sector number (i-number) which is not the file header. The file system would be corrupted.

    Question: What if the system crashes after the directory is flushed back but before the free map is flushed back?

     

     

    
    OpenFile *
    FileSystem::Open(char *name)
    {
        Directory *directory = new Directory(NumDirEntries);
        OpenFile *openFile = NULL;
        int sector;
    
        DEBUG(dbgFile, "Opening file" << name);
        directory->FetchFrom(directoryFile);
        sector = directory->Find(name);
        if (sector >= 0)
            openFile = new OpenFile(sector);// name was found in directory
        delete directory;
        return openFile;                    // return NULL if not found
    }
    

    Steps of opening a file:

    1. fetch directory file from the disk (directory header is always in memory);
    2. find the file header sector number in the directory given name;
    3. construct an OpenFile object using the file header sector number;
    4. clean up the directory file in memory.
    
    bool
    FileSystem::Remove(char *name)
    {
        Directory *directory;
        PersistentBitmap *freeMap;
        FileHeader *fileHdr;
        int sector;
    
        directory = new Directory(NumDirEntries);
        directory->FetchFrom(directoryFile);
        sector = directory->Find(name);
        if (sector == -1) {
           delete directory;
           return FALSE;                    // file not found
        }
        fileHdr = new FileHeader;
        fileHdr->FetchFrom(sector);
    
        freeMap = new PersistentBitmap(freeMapFile,NumSectors);
    
        fileHdr->Deallocate(freeMap);       // remove data blocks
        freeMap->Clear(sector);             // remove header block
        directory->Remove(name);
    
        freeMap->WriteBack(freeMapFile);    // flush to disk
        directory->WriteBack(directoryFile);// flush to disk
        delete fileHdr;
        delete directory;
        delete freeMap;
        return TRUE;
    }
    

    This function removes a file from disk. In order to find the data sectors and free them, we must bring in the file header, which requires the directory file and the bitmap file.

    Question: What if the system crashes after the free map is flushed back but before the directory is flushed back?

     

     

    filesys/openfile.*

    
    class OpenFile {
      public:
    	...
      private:
        FileHeader *hdr;                    // Header for this file
        int seekPosition;                   // Current position within the file
    };
    

    Data structures: a pointer to FileHeader and a position.

    
    	------------------
    	|       hdr      |---> FileHeader
    	------------------
    	|  seekPosition  |
    	|----------------|
    
    
    OpenFile::OpenFile(int sector)
    {
        hdr = new FileHeader;
        hdr->FetchFrom(sector);
        seekPosition = 0;
    }
    
    int
    OpenFile::Read(char *into, int numBytes)
    {
       int result = ReadAt(into, numBytes, seekPosition);
       seekPosition += result;
       return result;
    }
    
    int
    OpenFile::Write(char *into, int numBytes)
    {
       int result = WriteAt(into, numBytes, seekPosition);
       seekPosition += result;
       return result;
    }
    
    
    int
    OpenFile::ReadAt(char *into, int numBytes, int position)
    {
        int fileLength = hdr->FileLength();
        int i, firstSector, lastSector, numSectors;
        char *buf;
    
        if ((numBytes <= 0) || (position >= fileLength))
                return 0;                                 // check request
        if ((position + numBytes) > fileLength)
            numBytes = fileLength - position;
        DEBUG(dbgFile, "Reading " << numBytes << " bytes at " << position << " from
    file of length " << fileLength);
    
        firstSector = divRoundDown(position, SectorSize);
        lastSector = divRoundDown(position + numBytes - 1, SectorSize);
        numSectors = 1 + lastSector - firstSector;
    
        // read in all the full and partial sectors that we need
        buf = new char[numSectors * SectorSize];
        for (i = firstSector; i <= lastSector; i++)
            kernel->synchDisk->ReadSector(hdr->ByteToSector(i * SectorSize),
                                            &buf[(i - firstSector) * SectorSize]);
    
        // copy the part we want
        bcopy(&buf[position - (firstSector * SectorSize)], into, numBytes);
        delete [] buf;
        return numBytes;
    }
    
    int
    OpenFile::WriteAt(char *from, int numBytes, int position)
    {
        int fileLength = hdr->FileLength();
        int i, firstSector, lastSector, numSectors;
        bool firstAligned, lastAligned;
        char *buf;
    
        if ((numBytes <= 0) || (position >= fileLength))
            return 0;                                // check request
        if ((position + numBytes) > fileLength)
            numBytes = fileLength - position;
        DEBUG(dbgFile, "Writing " << numBytes << " bytes at " << position << " from
    file of length " << fileLength);
    
        firstSector = divRoundDown(position, SectorSize);
        lastSector = divRoundDown(position + numBytes - 1, SectorSize);
        numSectors = 1 + lastSector - firstSector;
    
        buf = new char[numSectors * SectorSize];
    
        firstAligned = (position == (firstSector * SectorSize));
        lastAligned = ((position + numBytes) == ((lastSector + 1) * SectorSize));
    
        // read in first and last sector, if they are to be partially modified
        if (!firstAligned)
            ReadAt(buf, SectorSize, firstSector * SectorSize);
        if (!lastAligned && ((firstSector != lastSector) || firstAligned))
            ReadAt(&buf[(lastSector - firstSector) * SectorSize],
                                    SectorSize, lastSector * SectorSize);
    
        // copy in the bytes we want to change
        bcopy(from, &buf[position - (firstSector * SectorSize)], numBytes);
    
        // write modified sectors back
        for (i = firstSector; i <= lastSector; i++)
            kernel->synchDisk->WriteSector(hdr->ByteToSector(i * SectorSize),
                                            &buf[(i - firstSector) * SectorSize]);
        delete [] buf;
        return numBytes;
    }
    

    Question: What happens when a new object OpenFile is constructed?

    OpenFile provides operations, Read and Write, on files, which are implemented by ReadAt and WriteAt using the internal seekPosition. Note that we can only transfer one sector at a time from the disk, thus when read, we have to fetch all associated sectors from the disk into memory (kernel space) then copy what we want (read may not start from the beginning of the first sector and may not end at the end of the last sector). When write, we first fetch the first and the last sectors from the disk into the kernel space and then modify them and write all sectors back to the disk (the first and the last sectors may be partially modified).

     

     

    filesys/filehdr.*

    
    class FileHeader {
      public:
    	...
      private:
        int numBytes;                       // Number of bytes in the file
        int numSectors;                     // Number of data sectors in the file
        int dataSectors[NumDirect];         // Disk sector numbers for each data
                                            // block in the file
    };
    

    Data structures of FileHeader:

    
    	-------------------
    	|    numBytes     |
    	-------------------
    	|   numSectors    |
    	-------------------
    	|  dataSectors[0] |
    	-------------------
    	|  dataSectors[1] |
    	-------------------
    	|                 |
    	|       ...       |
    	|                 |
    	-------------------
    

    Question: What is the value of NumDirect?

    
    bool
    FileHeader::Allocate(PersistentBitmap *freeMap, int fileSize)
    {
        numBytes = fileSize;
        numSectors  = divRoundUp(fileSize, SectorSize);
        if (freeMap->NumClear() < numSectors)
            return FALSE;                   // not enough space
    
        for (int i = 0; i < numSectors; i++) {
            dataSectors[i] = freeMap->FindAndSet();
            // since we checked that there was enough free space,
            // we expect this to succeed
            ASSERT(dataSectors[i] >= 0);
        }
        return TRUE;
    }
    
    
    void
    FileHeader::Deallocate(PersistentBitmap *freeMap)
    {
        for (int i = 0; i < numSectors; i++) {
            ASSERT(freeMap->Test((int) dataSectors[i]));
                                            // ought to be marked!
            freeMap->Clear((int) dataSectors[i]);
        }
    }
    
    
    void
    FileHeader::FetchFrom(int sector)
    {
        kernel->synchDisk->ReadSector(sector, (char *)this);
    }
    

    There are two ways of initializing a file header:

  • Allocate: initialize a fresh file header. In the baseline nachos, the file size must be specified and all data sectors are allocated when the file header is initialized.
  • FetchFrom: read the file header from the disk into memory (kernel space).
  • Question: How is the consistency checked when a file header is deallocated?

    Compare the functions FetchFrom() and WriteBack() here with those in directory. The bitmap also has FetchFrom() and WriteBack() functions.

     

     

    filesys/directory.*

    
    class DirectoryEntry {
      public:
        bool inUse;                         // Is this directory entry in use?
        int sector;                         // Location on disk to find the
                                            //   FileHeader for this file
        char name[FileNameMaxLen + 1];      // Text name for file, with +1 for
                                            // the trailing '\0'
    };
    

    A directory is a table (array) of DirectoryEntry. The data structures of a DirectoryEntry:

    
    	------------
    	|  inUse   |
    	------------
    	|  sector  |   sector number of file header
    	------------
    	|          |
    	|   name   |
    	|          |
    	------------
    
    
    
    class Directory {
      public:
    	...
        void FetchFrom(OpenFile *file);
        void WriteBack(OpenFile *file);
    	...
    };
    

    Compare FetchFrom() and WriteBack() functions with those in FileHeader.

     

     

    Example

    Here is a sample run of the file system. Can you identify the disk reads/writes?

    
    birkhoff-build.sun>nachos -f
    Machine halting!
    
    Ticks: total 41560, idle 41500, system 60, user 0
    Disk I/O: reads 3, writes 19
    Console I/O: reads 0, writes 0
    Paging: faults 0
    Network I/O: packets received 0, sent 0
    
    
    birkhoff-build.sun>nachos -f -cp ../test/halt halt
    Machine halting!
    
    Ticks: total 395560, idle 392570, system 2990, user 0
    Disk I/O: reads 42, writes 46
    Console I/O: reads 0, writes 0
    Paging: faults 0
    Network I/O: packets received 0, sent 0
    
    
    birkhoff-build.sun>nachos -x halt
    Machine halting!
    
    Ticks: total 301069, idle 297600, system 3450, user 19
    Disk I/O: reads 59, writes 18
    Console I/O: reads 0, writes 0
    Paging: faults 3
    Network I/O: packets received 0, sent 0
    
    
    birkhoff-build.sun>nachos -cp ../filesys/filehdr.h filehdr.h
    Machine halting!
    
    Ticks: total 4013060, idle 3993790, system 19270, user 0
    Disk I/O: reads 204, writes 240
    Console I/O: reads 0, writes 0
    Paging: faults 0
    Network I/O: packets received 0, sent 0
    
    
    birkhoff-build.sun>nachos -l
    halt
    swap58545
    filehdr.h
    Machine halting!
    
    Ticks: total 10180, idle 9430, system 750, user 0
    Disk I/O: reads 18, writes 0
    Console I/O: reads 0, writes 0
    Paging: faults 0
    Network I/O: packets received 0, sent 0
    
    
    birkhoff-build.sun>nachos -f -cp ../test/halt.c halt.c
    Machine halting!
    
    Ticks: total 428560, idle 425190, system 3370, user 0
    Disk I/O: reads 44, writes 52
    Console I/O: reads 0, writes 0
    Paging: faults 0
    Network I/O: packets received 0, sent 0
    
    
    birkhoff-build.sun>nachos -l
    halt.c
    Machine halting!
    
    Ticks: total 10180, idle 9430, system 750, user 0
    Disk I/O: reads 18, writes 0
    Console I/O: reads 0, writes 0
    Paging: faults 0
    Network I/O: packets received 0, sent 0
    
    
    birkhoff-build.sun>nachos -D
    Bit map file header:
    FileHeader contents.  File size: 128.  File blocks:
    2 
    File contents:
    \1\ff\ff\ff\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
    0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
    0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
    0\0\0\0\0\0\0\0\0\0
    Directory file header:
    FileHeader contents.  File size: 2000.  File blocks:
    3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 
    File contents:
    \1\0\0\0\0\0\0\13halt.c\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
    0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
    0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
    0\0\0\0\0\0
    \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0
    \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0
    \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0
    \0\0\0\0\0\0\0\0
    \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0
    \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0
    \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0
    \0\0\0\0\0\0\0\0
    \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0
    \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0
    \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0
    \0\0\0\0\0\0\0\0
    \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0
    \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0
    \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0
    \0\0\0\0\0\0\0\0
    \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0
    \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0
    \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0
    \0\0\0\0\0\0\0\0
    \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0
    \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0
    \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0
    \0\0\0\0\0\0\0\0
    \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0
    \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0
    \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0
    \0\0\0\0\0\0\0\0
    \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0
    \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0
    \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0
    \0\0\0\0\0\0\0\0
    \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0
    \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0
    \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0
    \0\0\0\0\0\0\0\0
    \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0
    \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0
    \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0
    \0\0\0\0\0\0\0\0
    \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0
    \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0
    \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0
    \0\0\0\0\0\0\0\0
    \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0
    \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0
    \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0
    \0\0\0\0\0\0\0\0
    \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0
    \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0
    \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0
    \0\0\0\0\0\0\0\0
    \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0
    \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0
    \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0
    \0\0\0\0\0\0\0\0
    \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0
    \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0
    Bitmap set:
    0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22
    , 23, 24, 
    Directory contents:
    Name: halt.c, Sector: 19
    FileHeader contents.  File size: 554.  File blocks:
    20 21 22 23 24 
    File contents:
    /* halt.c\a *      Simple program to test whether running a user program works.\
    a *\9\a *      Just do a "syscall" that shuts down t
    he OS.\a *\a *      NOTE: for some reason, user programs with global data struct
    ures \a *      sometimes haven't worked in the Nach
    os environment.  So be careful\a *      out there!  One option is to allocate da
    ta structures as \a *      automatics within a pro
    cedure, but if you do this, you have to\a *      be careful to allocate a big en
    ough stack to hold the automatics!\a */\a\a#include 
    "syscall.h"\a\aint\amain()\a{\a    Halt();\a}\a\a\a
    
    Machine halting!
    
    Ticks: total 24460, idle 22500, system 1960, user 0
    Disk I/O: reads 45, writes 0
    Console I/O: reads 0, writes 0
    Paging: faults 0
    Network I/O: packets received 0, sent 0