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
44 SysUAF API

UCL
45 Shells and UCL

Glossary/Index


Download sources
Download binaries

Init wrap-up

In this article, we will wrap up the Init program. Sort of. There are still some things that it needs to be able to do, but we will address those in the future.

The clock
Most computers these days have a built-in hardware clock. However, some computers have only a software clock - that is, they update internal counters upon some signal. In older hardware, this signal was based on the cycles in the power supply, which are 60 Hertz in the USA (50 Hertz in some other countries). In such a scheme, the seconds counter is incremented on every 60 cycles. Obviously, such situations mean that the time is not kept between machine resets. In such cases, we need the user to initialize the clock. The same situation can occur on modern systems if the CMOS battery is replaced or dies. Not to mention that some embedded systems have no clock either.
We replace the line in the main code that outputs two bells with the following:

if( not HAL.Valid_Time ) then
begin
    Query_Time( True ) ;
end else
begin
    Console.Output( BEL+BEL ) ;
end ;
If the Valid_Time function returns false, it means that there is no date/time information (for either of the reasons mentioned above, or possibly others) and the Query_Time function is called to ask the user for the time. If there is no valid time, the user must specify one before Init or the system boot can start.

function Query_Time( Force : boolean ) : boolean ; // Query user for date and time...

var Def, S, S1 : string ;
    M, D, Y : integer ;
    Seconds, V : int64 ;

label Ask_Clock, Ask_Date, Ask_Time ;

begin
    Result := False ;
Ask_Date:
    Def := '' ;
    S1 := '' ;
    if( not Force ) then
    begin
        Def := Format_Sirius_Date( '', HAL.Timestamp ) ;
        S1 := ' <' + Def + '> ' ;
    end ;
    Output( 'Today''s date' + S1 + ': ' ) ;
    S := Input( Def ) ;
    if( ( S = ESC ) or ( S = #26 ) or ( S = #3 ) ) then
    begin
        if( Force ) then
        begin
            goto Ask_Date ;
        end else
        begin
            Result := ( S = #3 ) ;
            exit ;
        end ;
    end ;
    if( S = '' ) then
    begin
        goto Ask_Date ;
    end ;
    if( not Parse_Date( S, M, D, Y ) ) then
    begin
        Output_Line( 'Invalid date format' ) ;
        goto Ask_Date ;
    end ;

Ask_Time:
    Def := '' ;
    S1 := '' ;
    if( not Force ) then
    begin
        Def := Format_Sirius_Time( 'HH:MM', HAL.Timestamp ) ;
        S1 := ' <' + Def + '> ' ;
    end ;
    Output( 'Current time' + S1 + ': ' ) ;
    S := Input( Def ) ;
    if( ( S = ESC ) or ( S = #26 ) or ( S = #3 ) ) then
    begin
        if( Force ) then
        begin
            goto Ask_Date ;
        end else
        begin
            Result := ( S = #3 ) ;
            exit ;
        end ;
    end ;
    if( S = '' ) then
    begin
        goto Ask_Time ;
    end ;
    Seconds := Convert_Time_To_Seconds( S ) ;
    if( Seconds < 0 ) then
    begin
        Output_Line( 'Invalid time format' ) ;
        goto Ask_Time ;
    end ;

    HAL.Set_Timestamp( Encode_Sirius_Timestamp( Y, M, D, 0, Seconds div 60, Seconds - ( Seconds div 60 ) * 60, 0 ) ) ;

Ask_Clock:
    if( HAL.Use_Line_Clock ) then
    begin
        S := inttostr( HAL.Line_Clock ) ;
        Output( 'Line clock <' + S + '> : ' ) ;
        S := Input( S ) ;
        if( ( S = ESC ) or ( S = #26 ) or ( S = #3 ) ) then
        begin
            if( Force ) then
            begin
                goto Ask_Time ;
            end else
            begin
                Result := ( S = #3 ) ;
                exit ;
            end ;
        end ;
        if( S = '' ) then
        begin
            goto Ask_Clock ;
        end ;
        if( not trystrtoint64( S, V ) ) then
        begin
            Output_Line( 'Invalid number' ) ;
            goto Ask_Clock ;
        end ;
        HAL.Line_Clock := V ;
        if( HAL.Line_Clock <> V ) then
        begin
            Output_Line( 'Invalid value' ) ;
            goto Ask_Clock ;
        end ;
    end ; // if( HAL.Use_Line_Clock )
end ; // Query_Time
This function asks for the current date and time. If the time is set it will default the values from the HAL. If the Force parameter is true, the user is not allowed to exit the function until he has provided the required information. If the Line clock is supported, the user is also queried for that value as well. It is a numeric value indicating the Hertz (frequency per second). The value may not have to do specifically with the power line frequency - it might have to do with some other frequency source as dictated by the hardware and understood by the HAL. "HARDWARE DATE" (and "HARDWARE TIME") are the commands that will bring up this dialog. For instance:
if( Match( 'DATE', S, 2 ) ) then
begin
    if( Query_Time( False ) ) then
    begin
        exit ;
    end ;
end else

Disabling devices
Under some circumstances, it may be desirable to disable a device. This can be necessary if there is a hardware problem or a driver problem. In some cases, a malfunctioning device can result in a cascade of processor interrupts which result in degraded system performance. The ability to disable a device before starting UOS can, thus, be important. And, if we can disable devices, we need to have a way to enable them again. Init provides the ability to disable/enable a specific device or an entire controller. A device in this context is the device type and controller by themselves, or with a unit on the controller. Disabling a controller will disable all of the units it controls. Although, technically speaking, the individual units are still enabled, without an enabled controller there is no way to get to those units. We implement the disabled devices via a string list that contains the disabled controllers and/or units. This list is passed to UOS on startup.

Note that although some controller hardware may support a disabled state we cannot be sure that it will. However, Init will make no attempt to restrict access to a disabled device. If the controller truly supports a disabled state, then any attempt to access it while disabled will result in errors that Init will report to the user. Otherwise, the user can continue to access the device in Init. However, UOS will honor the disabled state even if such state is only logical. A device disabled in Init will not even be visible to UOS. Here is a support routine and the definition of the list:

function Parse_Spec( Spec : string ) : TDevice_Info ;

var U : integer ;

begin
    fillchar( Result, sizeof( Result ), 0 ) ;
    Spec := Edit( Spec, 8 or 32 or 128 ) ;
    if( copy( Spec, 1, 4 ) = 'TERM' ) then
    begin
        Result.Device_Type := DT_Serial ;
    end else
    if( copy( Spec, 1, 4 ) = 'DISK' ) then
    begin
        Result.Device_Type := DT_Store ;
    end ;
    if( length( Spec ) > 4 ) then
    begin
        Result.Controller := pos( Spec[ 5 ], 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' ) - 1 ;
        Spec := copy( Spec, 6, length( Spec ) ) ;
        if( Trystrtoint( Spec, U ) ) then
        begin
            Result.Device_Unit := U ;
        end else
        begin
            dec( Result.Device_Unit ) ;
        end ;
    end ;
end ;

var Disabled_Devices : TStringList = nil ;
The function simply parses a device specification into a TDevice_Info structure. If the device unit is $FFFF, then the unit wasn't provided or was invalid. Likewise, if the controller is $FFFF, then the controller wasn't specified (or was invalid).

Here is the code at the end of the Hardware_Device_List routine, which shows the disabled controllers:

if( Disabled_Devices <> nil ) then
begin
    First := True ;
    for Index := 0 to Disabled_Devices.Count - 1 do
    begin
        S := Disabled_Devices[ Index ] ;
        if( length( S ) = 5 ) then
        begin
            if( First ) then
            begin
                First := False ;
                Output_Line( '' ) ;
                Output_Line( 'Disabled controllers:' ) ;
            end ;
            Output_Line( S ) ;
        end ;
    end ; // for Index := 0 to Disabled_Devices.Count - 1
end ; // if( Disabled_Devices <> nil )

Here is the routine to disable a device or controller:

function Hardware_Disable( S : string ) : boolean ;

var Dummy : integer ;
    Info : TDevice_Info ;
    S1 : string ;
    Prompt : boolean ;

begin
    if( Disabled_Devices = nil ) then
    begin
        Disabled_Devices := TStringList.Create ;
    end ;
    Result := False ;
    Prompt := S = '' ;
    while( True ) do
    begin
        if( Prompt ) then
        begin
            Output( 'Hardware device(s) to disable: ' ) ;
            S := Input( '' ) ;
        end ;
        if( S = #3 ) then
        begin
            Result := True ;
            exit ;
        end ;
        if( ( S = ESC ) or ( S = #26 ) ) then
        begin
            exit ;
        end ;
        S := Edit( S, 4 or 8 or 32 or 128 ) ;
        if( S <> '' ) then
        begin
            if( not ( Valid_Device( S ) or Valid_Controller( S ) ) ) then
            begin
                Output_Line( 'Invalid device' ) ;
            end else
            if( length( S ) < 5 ) then
            begin
                Output_Line( 'Invalid device' ) ;
            end else
            if( Disabled_Devices.Indexof( S ) <> -1 ) then
            begin
                Output_Line( 'Device already disabled' ) ;
            end else
            begin
                Disabled_Devices.Add( S ) ;
                Info := Parse_Spec( S ) ;
                HAL.Disable( Info.Device_Type, Info.Controller, Info.Device_Unit ) ;
            end ;
        end ;
        if( not Prompt ) then
        begin
            exit ;
        end ;
    end ; // while( True )
end ; // Hardware_Disable
We call the HAL Disable routine so that the HAL can physically disable the device, if the hardware supports it. In any case, we maintain the list of disabled devices and controllers.

Here is the equivalent routine to enable a device or controller. It is almost exactly the same as the disable device routine.

function Hardware_Enable( S : string ) : boolean ;

var Dummy : integer ;
    Info : TDevice_Info ;
    S1 : string ;
    Prompt : boolean ;

begin
    if( Disabled_Devices = nil ) then
    begin
        Disabled_Devices := TStringList.Create ;
    end ;
    Result := False ;
    Prompt := S = '' ;
    while( True ) do
    begin
        if( Prompt ) then
        begin
            Output( 'Hardware device(s) to enable: ' ) ;
            S := Input( '' ) ;
        end ;
        if( S = #3 ) then
        begin
            Result := True ;
            exit ;
        end ;
        if( ( S = ESC ) or ( S = #26 ) ) then
        begin
            exit ;
        end ;
        S := Edit( S, 4 or 8 or 32 or 128 ) ;
        if( S <> '' ) then
        begin
            if( not ( Valid_Device( S ) or Valid_Controller( S ) ) ) then
            begin
                Output_Line( 'Invalid device' ) ;
            end else
            if( length( S ) < 5 ) then
            begin
                Output_Line( 'Invalid device' ) ;
            end else
            begin
                Dummy := Disabled_Devices.Indexof( S ) ;
                if( Dummy = -1 ) then
                begin
                    Output_Line( 'Device not disabled' ) ;
                end else
                begin
                    Disabled_Devices.Delete( Dummy ) ;
                    Info := Parse_Spec( S ) ;
                    HAL.Enable( Info.Device_Type, Info.Controller, Info.Device_Unit ) ;
                end ;
            end ;
        end ; // if( S <> '' )
        if( not Prompt ) then
        begin
            exit ;
        end ;
    end ; // while( True )
end ; // Hardware_Enable

Finally, we add the HARDWARE DEVICE RESET command to reset all system devices. All we do here is clear the disabled device list and tell the HAL to reset the devices.

procedure Hardware_Reset ;

begin
    Disabled_Devices.Clear ;
    HAL.Device_Reset ;
end ;

Version numbers
Now would be a good time to talk about version numbering in the context of UOS. These days, a full version number looks something like this:
Major.Minor Edit - Build
For instance, version "5.1B-470" means major version 5, minor version 1, edit B (2), and build 470. Although there is a general consensus as to the meaning of version numbers, I will be specific here as to what these mean for UOS.

Build indicates the number of times that the source code has been rebuilt in the current edit. This is typically used by developers to keep track of the number of times that the code has been built, which is useful to the technical types, but practically meaningless to "normal" people. This is especially true when the build numbers get very large. Although most people recognize that a larger number means a later version, it can be easy to mistake the numbers (for instance, "14787" and "14767" look the same at a quick glance). Our plan is not to release different builds of the same edit to the general public. Internally, we developers can use the build number between public releases. Note: this doesn't mean that there isn't a build number in a public release - it simply means that we don't display it to the user because it is redundant since they will never see the same major/minor/edit with different build numbers. Normally, all the user will see is something like "5.1B", which is easier for them to deal with.

Major version numbers indicate some major change to the software - usually something that is not foreward compatible with earlier versions in terms of algorithms, approaches, and/or data structures. It is one of our goals to never drop support for the features of older UOS versions, because that will break programs. Old programs can use the "old ways" even while UOS provides new ways (that is, UOS is always backwards compatible). In the future, we will discuss exactly how we will manage this without making the UOS code bloated and unmaintainable. The only other reason we would increase the Major version number is if we had a slew of minor version changes. It would be better to increase the major version number every so often rather than end up with a minor version in the hundreds or thousands. As a general rule, we will avoid exceeding 9 minor builds before incrementing the major number. Thus, version 2.0 would follow version 1.9 as a rule of thumb.

Minor version numbers (also called "point releases" because the number follows the decimal point) indicate minor enhancements over the previous version. For instance, it might indicate the addition of a new capability to an existing service (or several new capabilities of several existing services).

Edit numbers indicate bug-fix only changes between minor version releases. Edits are indicated with letters and the initial release has a null edit (ie simply "5.1"). This does limit us to 27 total edit releases between minor builds, and I suppose that if we hit edit Z, we'd need to update the minor version for the next release. However, in all of my years of programming, no project I've ever worked on has ever reached that many bug fixes before other changes mandated a major or minor version number change. I anticipate the same situation with UOS.

To keep things simple for the user, all components of a public release will have the same major and minor version numbers. There will be no mixing and matching of components from different versions. Even if there have been no changes, all components will be updated to the new version number to avoid possible confusion for the user. Edit numbers, however, can vary between components. Typically edit changes will be released as patches to UOS rather than full builds. Some components (such a system programs) may be updated while others are not, meaning that different programs may have different edit values in their version number.

We will also encode the major/minor version number in some data structures to indicate the format of the data structure. If we plan things well, these version numbers should not change much over time. When it becomes necessary to do so, however, the data structure version will be changed to match the first version of UOS that supports the new/extended structure. If a structure doesn't change, we will not bother to increment the version number simply because we release a new public build of UOS. We will encode the version as an integer that is the sum of the minor version and 10 x the major version. For instance, a value of 51 would indicate version 5.1. Obviously, this limits the minor versions to 0 through 9 in terms of data strutures. However, as mentioned, these data structures won't be changing often. And when they do, it will almost always be in the context of a major version number change for UOS. In other words, it shouldn't ever be an issue. But, then, if we've made 9 public releases of a given major number, we've probably reached the point when it is time for a major version number change anyway.

This initial version of Init, and UOS components, will be 0.1. A major version number of 0 is typically used to indicate something that is yet unfinished.

Read-only File Systems
We've talked about read-only stores, but we haven't included much code for them. Here are the areas we need to address: 1) being able to mark a store as read-only, 2) booting from read-only stores (such as ROM store images), which involves mounting and dismounting them, and 3) Mirror sets.

There are three forms of read-only states. The first is when the store is inherently read-only - for instance a ROM image. The second is when the store is physically read-only. This can be the case for a floppy disk with the write-lock tab or USB thumb drive with a write-lock switch. From the software's standpoint, there is no difference between inherently and physically read-only. UOS also allows a store to be set to a logical read-only state. In this case, the software prevents writing to the store, even it it isn't physically or inherently read-only. All of our store classes support setting the read-only flag, but we need to add an Init command that allows the user to set (and reset) that value. The commands for this are "DISK SET device READONLY" and "DISK SET device WRITEABLE".

When we start up UOS, the boot store is automatically mounted, since that is the source of all the loaded components, etc. But, if we can't write to the store, then we cannot set the dirty bit, which is part of the process of mounting the store. However, if we can't update the store, then there is no need to mark it as dirty. Therefore, in the case of a read-only store, we can safely mount it without marking it as dirty. Another consideration is that if the read-only store is already dirty, we can't rebuild it. Since it isn't writeable, there is no chance of corrupting data by writing to it based on an inconsistent allocation table. Therefore, it is always safe to mount a read-only file system. The only consideration is that we don't want to change a read-only store to writeable while it is mounted read-only. We will have to account for that when we get to the File Processor code in UOS. For now, we will simply make some minor alterations to the Mount and Dismount methods of our file system.

A mirror set can be read-only if all the members of the set are read-only. In this case, there is no performance drop on writes, since we cannot write to the set. But the read performance will still be improved. But, what if only some of the members are read-only? Obviously, once we write to the set, the read-only members are, by definition, no longer consistent with the rest of the set. The class already handles this situation by setting read-only stores to the dirty state. Since they cannot be reconciled, they remain dirty and the code skips them for both reads and writes. If the store becomes writable at some point, it will be reconciled automatically. However, if the reference store is read-only, then upon a write operation, we will need to make one of the writeable members the reference store. This is a simple swap operation. The following code replaces the previous write loop in Write_Data:

// Loop through the stores, writing the data...
Last_Writable := -1 ; // Assume no writeable members
for Loop := 0 to Stores.Count - 1 do
begin
    Store := TCOM_Store64( Stores[ Loop ] ) ;
    if( Store.Read_Only ) then
    begin
        Dirty := Dirty or XBit_Values[ Loop ] ;
    end else
    begin
        if( ( Dirty and XBit_Values[ Loop ] ) = 0 ) then
        begin
            Last_Writable := Loop ;
        end ;
        Store.Write_Data( Data, Address, _Size, E ) ;
        if( E <> nil ) then
        begin
            inc( _Error_Count ) ;
            UEC := E ;
        end ;
    end ;
end ; // for Loop := 0 to Stores.Count - 1
if( ( Last_Writable >= 0 ) and ( ( Dirty and 1 ) = 1 ) ) then
begin
    { Reference store is now dirty, so make first writeable store be the
      new reference store. }
    // Swap the stores...
    Store := TCOM_Store64( Stores[ 0 ] ) ; // Save old reference store
    Stores[ 0 ] := Stores[ Last_Writable ] ;
    Stores[ Last_Writable ] := Store ;

    // Swap the dirty flags
    Dirty := ( Dirty and ( not 1 ) ) or ( XBit_Values[ Last_Writable ] ) ;
end ;

UOS startup
There is a lot that goes into starting UOS from Init, and we will save that discussion for the future. However, one aspect of this that we ought to discuss now is how Init passes configuration information to UOS. In the process of loading UOS, we will lose our heap object (and, therefore, the heap itself), and all that implies. In fact, loading the Memory Management component may result in unpredictable disposition of all of memory. So how do we pass the information to UOS? One might be tempted to write it to the boot store where UOS could read it in. However, since we can boot from read-only stores, we cannot be guaranteed that this approach is even possible. If we can't rely on stores or memory, then where on earth do we save configuration information for UOS? The solution is platform dependent and so it is up to the HAL to concern itself with. Init will simply give HAL the information to pass to UOS, and UOS will ask the HAL for it. The HAL may store it in non-volatile RAM, or it may set a special portion of RAM apart for this use, or use some other mechanism. But from the perspective of Init and UOS, it matters not where or how the HAL stores the information.

One thing we do have to concern ourselves with is how to communication this configuration information in the smallest number of bytes as possible. It goes without saying that any given resources is always less than desired. Fortunately for us, we only need to indicate items that the HAL doesn't know about and states that are not the default. For instance, we don't need to communicate about enabled devices since the newly loaded HAL will figure those out for itself. Further, the startup will figure out RAID sets just as Init does. So we only need to indicate when an otherwise enabled device was disabled, and about dynamic (RAM) disks. We also need to indicate which device we are booting from.

We will pass the configuration information as a buffer that contains a series of records with configuration data. Each record will consist of a one byte record type, followed by one or more parameters appropriate to the type of record. The parameters will consist of fixed and variable-length values. The fixed values vary in size from 1 to 8 bytes (1, 2, 4, and 8 byte values). By using 1 byte values instead of 8 byte values, when we can, will significantly reduce the size of the buffer. To make things simple, we will encode the size in the record type.
Mask (value and 3)Value
01 byte
12 bytes
24 bytes
38 bytes

So, record types 0-3 are the same record type, but with different sizes for fixed-size parameters. Here are the record types we need to support:
Type(s)DescriptionParameters
0-3Disabled devicetype,controller,unit
4-7RAM diskaddress,size
8-11Boot devicetype,controller,unit

Note that any record types not shown are reserved for future use. For instance, there will be records that tell UOS where to load the HAL and file system components. We will discuss these items in the future. For now, we will pass records for disabled devices, RAM disks, and the boot device. Here is the code for the Start routine in Init:

const UOS_Config_Disabled_Device = 0 ;
const UOS_Config_RAM_Disk = 4 ;
const UOS_Config_Boot_Device = 8 ;

function Encode_Parameters( RecType : byte ; P1, P2, P3 : int64 ;
    Count : integer ) : string ;

var Size, Mask : integer ;

begin
    if( ( P1 > $FFFFFFFF ) or ( P2 > $FFFFFFFF ) or ( P3 > $FFFFFFFF ) ) then
    begin
        Size := 8 ;
        Mask := 3 ;
    end else
    if( ( P1 > $FFFF ) or ( P2 > $FFFF ) or ( P3 > $FFFF ) ) then
    begin
        Size := 4 ;
        Mask := 2 ;
    end else
    if( ( P1 > $FF ) or ( P2 > $FF ) or ( P3 > $FF ) ) then
    begin
        Size := 2 ;
        Mask := 1 ;
    end else
    begin
        Size := 1 ;
        Mask := 0 ;
    end ;
    Result := chr( RecType + Mask ) ;
    setlength( Result, Size * Count + 1 ) ;
    move( P1, PChar( Result )[ 1 ], Size ) ;
    move( P2, PChar( Result )[ Size + 1 ], Size ) ;
    if( Count > 2 ) then
    begin
        move( P3, PChar( Result )[ Size * 2 + 1 ], Size ) ;
    end ;
end ;


function Start : boolean ;

var Info : TDevice_Info ;
    Loop : integer ;
    S : string ;
    Store : TRAM_Disk64 ;

begin
    Result := False ;
    Info := HAL.Boot_Device ;
    S := Encode_Parameters( UOS_Config_Boot_Device, Info.Device_Type, Info.Controller, Info.Device_Unit, 3 ) ;
    if( Disabled_Devices <> nil ) then
    begin
        for Loop := 0 to Disabled_Devices.Count - 1 do
        begin
            Info := Parse_Spec( Disabled_Devices[ Loop ] ) ;
            S := S +
                Encode_Parameters( UOS_Config_Disabled_Device, Info.Device_Type, Info.Controller, Info.Device_Unit, 3 ) ;
        end ;
    end ;
    if( RAM_Disks <> nil ) then
    begin
        for Loop := 0 to RAM_Disks.Count - 1 do
        begin
            Store := TRAM_Disk64( RAM_Disks[ Loop ] ) ;
            S := S + Encode_Parameters( UOS_Config_RAM_Disk, integer( Store.Get_Buffer ), Store.Max_Storage, 0, 2 ) ;
        end ;
    end ;
    if( HAL.Set_Configuration( PChar( S ), length( S ) ) <> length( S ) ) then
    begin
        if( HAL.Console <> nil ) then
        begin
            Output( 'Failed to set configuration' ) ;
        end ;
        exit ;
    end ;
    //~~~Not yet finished
end ; // Start
Encode_Parameters builds a string that consists of the number of parameters (of appropriate fixed size based on their content), and the correct record type. We encode the boot device, any disabled devices, and any RAM disks. Then we send the configuration to the HAL. In the future, we will discuss how to change the boot device and the rest of the process of starting up UOS.

Source file layout
The source files folder is named Sources and contains the Delphi project group (named UOS). The sub-folders are:

  • Common - units used by sources in other sub-folders
  • Init - Sources for the Init program
  • lb - Trimmed-down copy of the subroutine library used by the rest of the sources
  • Sim - Sources for the Windows simulator
A sibling to the Sources folder is the WWW folder, which contains the image of the UOS site.
If you want to use the projects in Delphi, you may need to update the options based on where you place the sources. They default to "E:\Work\UOS\Sources".

The compiler conditional "UOS" is used for all projects other than the simulator. It excludes Windows-specific code from the subroutine library. The simulator uses Windows-specific code because it is a Windows program.
The following diagram illustrates the relationships between the various sets of code:

Init code size
The Init code is about 530 Kb in length. Add in about 32Kb for heap requirements and 8Kb for stack, and the total memory footprint is about 570Kb. That isn't large by the standards of most 32-bit and 64-bit systems, and even some 16-bit systems. But for an 8-bit microcontroller, this significantly exceeds the maximum address space (most of them are limited to 64Kb of RAM). To run Init on such a system would require some sort of change to the code to support smaller memory models. Of course, the compiler for an 8-bit target might generate more compact code, but even so, Init would be too large. Especially when we consider that it will be about twice its current size when we have finally completed it. We won't get bogged down in the details of how to aqueeze Init (or UOS) into small amounts of RAM for now, but for the sake of satisfying your curiousity for the interim, the solution lies in breaking the program into smaller chunks, where the only permanently resident portion of the code is the part that loads the necessary chunks over the no-longer-needed chunks as required.

Some people believe that a solution to this issue is to hand-craft code in assembly, as was done in the past. However, modern commercial compilers are very good at optimizing code. In fact, one can usually tell the compiler whether to optimize for speed or code size. Even a good programmer isn't able to write more optimized code than the common optimizing compilers, assuming that the high-level language code is well-written (anyone can write bad code in any language, including assembly). Some would say, "okay, but C code is more efficient than languages other than C++ or Pascal". That may have been true years ago. But with today's compilers, equivalent code written in any of those languages is going to be of equivalent efficiency. About the only thing that could said about C in this case is that it is more efficient in source code size, due to the terse syntax. But that brings us to another problem with writing in assembly or C. That problem is one of maintenance. All code needs changes over time, whether to fix it or to otherwise improve it. On an on-going basis, assembly code is going to take longer to alter and be more likely to suffer defects due to those alterations. Even if a high-level language was less efficient in terms of performance or size, ease of maintenance will almost always override those considerations in the real world. Those who believe that they can do better than an optimizing compiler suffer from one, or both, of the following:

  • Hubris leads them to believe they are capable of feats that they are not
  • They are ignorant of just how good modern compilers are at generating code
This doesn't mean that UOS will have no assembly code. But it will almost entirely be isolated to the HAL.

Downloads
The current set of sources and the binaries are now available online. They will updated every so often as we progress through the articles. The download links are on the left. The sources include all of the code that has been shown in the articles, including any changes made since the article was first posted, plus some of the support code that we didn't bother to cover (such as the subroutine library). The sources also include the code for the simulator, which we haven't bothered to cover. Finally, header files for C++ are also included.

Documentation
Most of the time that went into this article was actually spent working on end-user documentation. I decided to use HelpNDoc to generate the HTML. The product is annoying in several ways, but less annoying than any of the alternatives I looked at. In the end, the HTML is what I wanted. The online documentation includes HTML documentation for all UOS manuals, up to the current state of the project as of this article. As the articles progress, the documentation will be updated accordingly.

At present, the documentation includes a user manual for the simulator, a user manual for Init, and an internals manual. The internals manual is, in many respects, a redacted version of these articles - if you want a tutorial, read the articles; if you want a reference to the data structures and a short overview, then read the manual.

In the next article, we will begin looking at the UOS Executive.