1 Introduction
2 Ground Rules

Building a File System
3 File Systems
4 File Content Data Structure
5 Allocation Cluster Manager
6 Exceptions and Emancipation
7 Base Classes, Testing, and More
8 File Meta Data
9 Native File Class
10 Our File System
11 Allocation Table
12 File System Support Code
13 Initializing the File System
14 Contiguous Files
15 Rebuilding the File System
16 Native File System Support Methods
17 Lookups, Wildcards, and Unicode, Oh My
18 Finishing the File System Class

The Init Program
19 Hardware Abstraction and UOS Architecture
20 Init Command Mode
21 Using Our File System
22 Hardware and Device Lists
23 Fun with Stores: Partitions
24 Fun with Stores: RAID
25 Fun with Stores: RAM Disks
26 Init wrap-up

The Executive
27 Overview of The Executive
28 Starting the Kernel
29 The Kernel
30 Making a Store Bootable
31 The MMC
32 The HMC
33 Loading the components
34 Using the File Processor
35 Symbols and the SSC
36 The File Processor and Device Management
37 The File Processor and File System Management
38 Finishing Executive Startup

Users and Security
39 Introduction to Users and Security
40 More Fun With Stores: File Heaps
41 File Heaps, part 2
42 SysUAF
43 TUser

Terminal I/O
45 Shells and UCL
46 UOS API, the Application Side
47 UOS API, the Executive Side
48 I/O Devices
49 Streams
50 Terminal Output Filters
51 The TTerminal Class
52 Handles
53 Putting it All Together
54 Getting Terminal Input
55 QIO
56 Cooking Terminal Input
57 Putting it all together, part 2
58 Quotas and I/O

59 UCL Basics
60 Symbol Substitution
61 UCL Command execution
62 UCL Command execution, part 2
63 UCL Command Abbreviation


Download sources
Download binaries

Loading the components

Loading the components
We now return to the Kernel startup routine. When we last looked at it, we had just loaded the HMC. After that we set the memory manager so that all further heap activity for the Kernel will use the HMC:

    HMC.Kernel := self ;
    __HMC := HMC ;
    _MMC.Set_Aside( Buffer, Buffer_Size ) ; // Set aside the temp heap
    _MMC.Kernel := self ;
    HMC.End_Startup ;

    SetMemoryManager( HMC_MM ) ;
    // Set FS memory to HMC...
    FS.Heap := THeap64_Wrapper.Create( HMC ) ;

We call the Set_Aside method to reserve the temporary heap buffer. Since it may still contain allocated data, we want to make sure that it isn't reused for something else later. Note, the previous articles showed a local variable named Buffer that was used for File System operations. This made the temporary heap buffer out of scope, so I renamed the local variable to Buff. Then we set the MMC's kernel to ourself. Next, we can call the End_Startup method of the HMC so that it is no longer in startup mode. And finally we set the memory manager for the kernel so that the HMC is used. At this point, we can breathe easier because the most complicated part of the kernel startup is complete. From this point on, we need not concern ourselves with having no, or little, heap - which means we can create objects on the heap, use strings, etc. as we would in any normal programming environment. We already have a file system object, so we need to set its heap to the HMC so that it can use the heap as well. We've been careful not to use any file system methods that would need to allocate data on the heap thus far.

The next step is to determine the path to UOS. As we discussed in previous articles, there can be multiple UOS installations. The \uos\installed.sys file links to the UOS installation that Init started, so we have to pull the path from it.

    // Determine path to UOS...
    Rebuild := False ;
    E := FS.Mount( '', '', FiP_Force or FiP_Flags ) ;
    if( E <> nil ) then
        if( E.Get_Error = UOS_File_System_Is_Dirty ) then
            if( H.Console <> nil ) then
                H.Console.Output( 'Rebuilding disk...' + CRLF, -1 ) ;
            end ;
            Rebuild := True ;
            FS.Rebuild ;
            E := FS.Mount( '', '', FiP_Force or FiP_Flags ) ;
        end ;
        if( E <> nil ) then
            Show_Error( E ) ;
            H.Halt ;
        end ;
    end ; // if( E <> nil )
    Info := FS.Get_File_Info( '\uos\installed.sys', 0 ) ;
    if( FS.Last_Error <> nil ) then
        Output( 'No installed operating system' ) ;
        H.Halt ;
    end ;
    Installation := Follow_Link( '\uos\installed.sys' ) + '\' ;

We clear the Rebuild flag and then try to mount the file system. If an error is returned, we check to see if it is a "dirty" indicator. If so, we set the rebuild flag, call the Rebuild method of the file system and then try to mount it again. If that fails, or if the error wasn't about the store being dirty, we show the error and exit. This is a fatal condition since it is rather important for the boot device to be accessible...
Once the file system is mounted, we make sure that the installed.sys file exists. If not, that means there is no installed UOS. This is an error and we let the user know, dismount the file system and halt. Otherwise, we follow the link in the file - setting the Installation variable to the path.

The next step is to load the remaining executive components.

    // Load File Processor, system services, and interrupt manager...
    _IMC := TUOS_Interrupt_Manager( Load_Component( FS, Installation + 'imc.sys', IMC_Index ) ) ;
    if( _IMC = nil ) then
        Output( 'Error loading the IMC' ) ;
        FS.Dismount ;
        H.Halt ;
    end ;
    _IMC.Kernel := self ;
    _FiP := TUOS_File_Processor( Load_Component( FS, Installation + 'fip.sys', FiP_Index ) ) ; // Get_FiP
    if( _FiP = nil ) then
        Output( 'Error loading the FiP' ) ;
        FS.Dismount ;
        H.Halt ;
    end ;
    _FiP.Kernel := self ;
    _SSC := TUOS_System_Services( Load_Component( FS, Installation + 'ssc.sys', SSC_Index ) ) ; // Get_SSC
    if( _SSC = nil ) then
        Output( 'Error loading the SSC' ) ;
        FS.Dismount ;
        H.Halt ;
    end ;
    _SSC.Kernel := self ;

We load the IMC, FIP, and SSC in the same way. We call Load_Component to load the component. If the result is nil, we report the load failure, dismount the file system, and halt. Otherwise, we set the component's kernel property.

Here is the local Follow_Link function:

    function Follow_Link( Path : string ) : string ;

    var Info : TUOS_File_Info ;
        UF : TUOS_File ;

        Info := FS.Get_File_Info( PChar( Path ), 0 ) ;
        UF := FS.Get_File( PChar( Path ) ) ;
            setlength( Result, Info.EOF ) ;
            UF.Read( 0, 0, Info.EOF, PChar( Result )[ 0 ] ) ;
            UF.Detach ;
        end ;
    end ;

This function simply opens the passed filename, reads the path from the file, closes the file, and returns the path.

Here is the code for the Load_Component function:

type TFGet_Component = function() : pointer ;

function TKernel.Load_Component( FS : TUOS_File_System ; Filename : string ;
    Index : char ) : pointer ;

var F : TUOS_File ;
    FGet_Component : TFGet_Component ;
    Module : TBTMemoryModule ;
    Info : TUOS_File_Info ;
    Res : int64 ;

    Result := nil ; // Assume failure
    Info := FS.Get_File_Info( PChar( Filename ), 0 ) ;
    if( FS.Last_Error <> nil ) then
        Show_Error( FS.Last_Error ) ;
        exit ;
    end ;
    F := FS.Get_File( PChar( Filename ) ) ;
    if( F = nil ) then
        Show_Error( FS.Last_Error ) ;
        exit ;
    end ;

The first thing the function does is get information on the file, and then open the file. If there are any problems, we show the error and then exit with a return value of nil.

        // Allocate memory for image...
        Result := pointer( MMC.Allocate( 0, Info.EOF, 'A', 0, 0 ) ) ;
        if( Result = nil ) then
            exit ;
        end ;

        F.Read( 0, 0, Info.EOF, Result^ ) ; // Read file into memory
        if( FS.Last_Error <> nil ) then
            Show_Error( FS.Last_Error ) ;
            exit ;
        end ;
        F.Detach ;
    end ;

Next we allocate memory for the file image. Since this is executable code, we don't allocate the memory on the heap. Instead, we allocate memory via the MMC. If that fails, we exit with nil. Otherwise, we read the file contents into the allocated memory. If that fails, we show the error and exit with nil. Now we're done with the file and so we close it.

    // Get function...
    Module := BTMemoryLoadLibary( MMC, Result, Info.EOF ) ;
    if( Module = nil ) then
        Result := nil ;
        exit ;
    end ;
    Result := BTMemoryGetProcAddress( Module, 'Get_Component' ) ;
    if( Result = nil ) then
        exit ;
    end ;
    Res := HAL.Execute( int64( Result ), byte( Index ) ) ;
    if( Res = 0 ) then // HAL didn't return a value
        FGet_Component := TFGet_Component( Result ) ;
        Result := FGet_Component() ;
    end else
        Result := pointer( Res ) ;
    end ;
end ; // TKernel.Load_Component

There is more to loading a component than simply copying an image into memory. Our components are executable files of the PE format. We won't discuss the format in these articles, but here is a link for those who are interested: Wikipedia. This format is probably the most commonly used executable file format in the world at present. The process of loading the component involves several steps, including relocating address references. We use the open-source BTMemoryModule component to load the PE file. The version used in the kernel is a modified and simplified version of the component, which we aren't going to cover in these articles. The File Processor uses a more complete version of the component and we'll cover it in a later article.
If BTMemoryLoadLibrary fails to load the component, it will return nil. In that case we immediately exit with a nil. Otherwise, we call BTMemoryGetProcAddress to get the address of the component's Get_Component method. If not found, we exit with a nil. Finally we make the call to the address to get an instance of the component. This is done by calling the HAL's Execute method. This is done indirectly so that the HAL can substitute its own version of the component in cases where UOS is running virtualized. If the HAL returns nil, it means that something went wrong, so we try making the call directly to our loaded component. If the HAL does virtualize the component, it means that we will have a copy of the loaded component sitting in memory but not used. This isn't as bad as it may seem, however, since the same amount of memory is used as on a non-virtualized system. If UOS is being virtualized, there is probably enough system memory that the small increase in memory footprint shouldn't matter.

In the next article, we will discuss the use of the File Processor component.