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


Download sources
Download binaries

UOS API, the Application Side

In the previous article we described the ring architecture that UOS uses for security purposes. The UOS Executive runs in ring 0. This article, and the next, describe how we communicate between ring 3 (the application ring) and ring 0 (the executive ring), as we continue our discussion of UCL.

Since UCL is a textual user interface, it will need to let the user know when it is ready to accept input. This is called "prompting" the user. In an example of nouning verbs, the textual indicator used to prompt the user is called "a prompt". UCL allows the user to customize his own prompt. This is done by defining the symbol "$ucl_prompt". Whatever value that symbol has is used as the prompt. If the symbol isn't defined (which it isn't by default), or it is empty, UCL will display it's default prompt - a dollar sign ($) and space. Thus, the first order of business when displaying a prompt will be to obtain the contents of $ucl_prompt.

procedure Show_Prompt ;

var S : Ansistring ;

    S := LIB_Get_Symbol( '$ucl_prompt' ) ;
    if( S = '' ) then
        S := '$ ' ;
    end ;
    LIB_Put_Output( S ) ;
end ;

LIB_Get_Symbol and LIB_Put_Output are functions in the Starlet library that was first mentioned in the previous article. We make the call to get the value of $ucl_prompt. If it returns an empty string then the symbol is empty or non-existant, in which case we default to '$ '.

Let's look at the LIB_Get_Symbol call now. Because of the ring security model used by UOS, transferring data between the executive and application programs is not as straight-forward as passing parameters to normal functions. Exactly how much data can be passed to an inner ring is dependent upon the CPU architecture. In the case of MSDOS, for instance, the parameters were stored in registers. To provide compatibility with the widest range of potential hardware platforms, In UOS, the parameters are stored in a structure in memory, and a pointer to this structure is what is passed to the executive. The result of the call is usually stored back into that structure as well. Starlet contains the code to call into the executive which is suitable for low-level code, such as assembly language. It also includes a set of wrapper functions that translate normal functions into API calls, and return data in a way that is "natural" to most high-level programming languages.

The structure, whose address is passed to the executive, has a standard layout section followed by a section that is dependent upon the specific call. The standard layout is as follows:

type TSystem_Request = packed record
                           Subsystem : longint ; // Executive component
                           Request : longint ; // Request
                           Length : word ; // Length of call-specific following data, in bytes
                           Status : int64 ; // Ignored on call, result status on return
                           Flags : int64 ; // Flags (depends upon call)
                           // Call-specific details follow
                       end ;
     PSystem_Request = ^TSystem_Request ;

The Subsystem indicates which component the request is intended for. Request uniquely identifies the call. Length contains the length of the data, in bytes, following the standard portion of the structure. Flags indicate options which are specific to a given request. Flags should be set to 0 by default to provide for later backwards-compatibility. Status is ignored when passed, but contains the status of the operation on return. Following are the valid subsystems:

const UOS_Subsystem_MMC = 1 ;
const UOS_Subsystem_FIP = 2 ;
const UOS_Subsystem_USC = 3 ;
const UOS_Subsystem_SSC = 4 ;
const UOS_Subsystem_Kernel = 5 ;

Although the TSystem_Request structure is sufficient for passing fixed-size data, such as integers, it is less useful when passing arbitrary-length textual (string) data. Thus, the call-specific code for LIB_Get_Symbol includes additional structures which are used to pass string data to the Kernel (and back). The TSRB structure is used to transfer string data.

type TSRB = packed record
                Buffer : int64 ; // Pointer to string Buffer
                Length : int64 ; // Length of string buffer
                Flags : longint ; // Reserved for future use
            end ;

TSRB doesn't contain the string data. Rather it has a pointer to the data, and an integer value indicating the length. This provides the maximum flexibility as it allows any size chunk of data to be transferred to or from the executive. Obviously, Buffer must point to memory that is allocated to the application, and the length must indicate the actual length of the data. If either is incorrect, the result is likely that the application terminates with an error. This defines how we pass data to the executive, but passing data back is different. Inner rings can access the data of more outer rings so they can read the buffer pointed to by the TSRB structure. However, outer rings (such as where the applications run) cannot access the memory of more inner rings. Thus, there is no way for the application to directly retrieve data from the executive. Instead, we must follow a three-step process. First, we must request a size for the data that the executive will return to us. Second, we can allocate a buffer to hold that data. Third, we obtain the data by asking the executive to fill our buffer. Of course, we could allocate a really big buffer and hope that it was large enough for the data to fit, but there are two problems with this approach: 1) the data might exceed the size of the buffer resulting in truncated data, and 2) we may waste a large amount of memory for data which is usually smaller in size. Thus, we will always use the three-step method in Starlet.

Because LIB_Get_Symbol obtains a symbol's contents, which is of arbitrary length, the function needs to follow these three steps. The structure that we pass to UOS involves two strings: the name of the symbol, and where to return that symbol's contents. So, the structure we pass contains two TSRB substructures, plus an integer value (for the table), in addition to the fixed portion of TSystem_Request. We call this structure TString2I1_Request, since it is a system request that takes two strings and 1 integer.

type TString2I1_Request = packed record
                              Request : TSystem_Request ;
                              String1 : TSRB ; // Parameter
                              String2 : TSRB ; // Result
                              Integer1 : longint ;
                         end ;
     PString2I1_Request = ^TString2I1_Request ;

String1 contains the name of the symbol (or, rather, a pointer to the name), and String2 is used to indicate where to return the symbol contents. Our first call will pass String2 with a length of 0. This indicates to UOS that we are requesting the size of the data rather than the actual data.

One more complication exists that is a consequence of the UOS simulator itself. The simulator sets aside a large buffer of memory that acts as the system memory for UOS. By use of our memory management scheme, all memory allocations for strings and heap usage come from that memory. However, anything allocated in the stack or data segments might be allocated outside of this memory. If UOS tries to access this memory, it might see it as an invalid address. Perhaps in the future we can discuss the gory details of the simulator and the environment it creates for UOS to run within, but for now just take it for granted. Thus, we must make sure to allocate any data (including the request structure itself) on the heap. Normally, we wouldn't code anything specifically for a given platform - especially the simulator. However, this code will work on any/all platforms. Further, it is possible that using stack data on some platforms may require different access mechanisms.

function LIB_Get_Symbol( const Name : string ; Table : longint = 0 ) : string ;

var SysRequest : PString2I1_Request ;

    // Setup...
    Result := '' ;
    getmem( SysRequest, sizeof( SysRequest^ ) ) ;
    fillchar( SysRequest^, sizeof( SysRequest^ ), 0 ) ;
    SysRequest.Request.Subsystem := UOS_Subsystem_SSC ;
    SysRequest.Request.Request := UOS_SSC_Get_Symbol ;
    SysRequest.Request.Length := sizeof( SysRequest^ ) - sizeof( TSystem_Request ) ;

    Set_String( Name, SysRequest.String1 ) ;
    SysRequest.Integer1 := Table ;

LIB_Get_Symbol has one required parameter (the symbol's name) and one optional parameter (the symbol table to look in). By default, all tables are searched for the symbol. First we default the result to a null string. Next we allocate the system request structure and set up the static part - initializing it to 0. UOS_SSC_Get_Symbol is the system request we are making and it is handled by the SSC component. The length is set to the whole structure's size minus the size of the fixed part (sizeof TSystem Request). Next, we assign the Name parameter to the String1 substructure, and the table to Integer1. At this point, our structure is ready for the call to UOS. Here is the code for Set_String:

procedure Set_String( const Name : string ; var SRB : TSRB ) ;

    SRB.Buffer := int64( PChar( Name ) ) ; // Symbol name
    SRB.Length := length( Name ) ;
end ;

The function simply provides a shorthand means of setting up a TSRB structure.

Next we make the call to UOS:

    // Get size of symbol...
    Call_To_Ring0( integer( SysRequest ) ) ;
    if( Status.Status <> UE_Success ) then // Failed
        exit ;
    end ;

We call to UOS via the Call_To_Ring0 function, passing the address of the system request. If the status indicates an error, we exit. The length value of String2 will be set to the actual length of the symbol's contents.

    // Receive value...
    setlength( Result, SysRequest.String2.Length ) ;
    if( SysRequest.String2.Length > 0 ) then
        SysRequest.String2.Buffer := int64( PChar( Result ) ) ;
        Call_To_Ring0( integer( SysRequest ) ) ;
        if( Status.Status <> UE_Success ) then // Failed
            Result := '' ;
            exit ;
        end ;
    end ;
end ; // LIB_Get_Symbol

If the returned length is 0, we exit (with a null string). Otherwise, we set the length of Result to the returned length, then set String2 to point to Result. We leave String2.Length alone since it is the length of the symbol contents. Then we call UOS again. If there was an error, we set the result to null, otherwise, the symbol's contents are in Result and we exit.

The Call_to_Ring0 function is how Starlet jumps into the Executive, which runs in ring 0. The actual mechanism that is used for this is dependent upon the platform that UOS is running on and so we won't cover that here. We are guaranteed, however, that the kernel will return to this point when it is done, regardless of the architecture.

In the next article, we will look at how the Executive handles calls that are made via Call_to_Ring0.