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

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

UCL
59 UCL Basics
60 Symbol Substitution
61 UCL Command execution
62 UCL Command execution, part 2
63 UCL Command Abbreviation
64 ASTs
65 UCL Expressions, Part 1
66 UCL Expressions, Part 2: Support code
67 UCL Expressions, part 3: Parsing
68 SYS_GETJPIW and SYS_TRNLNM
69 UCL Expressions, part 4: Evaluation
70 PROCESS_SCAN

Glossary/Index


Download sources
Download binaries

UOS API, The Executive side

In the previous article, we looked at the application side of using the UOS API. Now let's look at the Executive's side. First, we must connect the Kernel to the ring calls. In the Kernel startup, we will tell the HAL to connect all four rings to the Kernel instance. As discussed in the previous articles, ring 0 is always reserved for the executive. The other rings are (or might) be used for other purposes, but the default handler is the Executive. So we will assign all the rings to the Kernel and then if something else is later to be linked to the other rings, those rings will be assigned at that time. Here's the addition to the Kernel Startup routine:

// Hook the ring callbacks...
__HAL.Hook_Ring0( integer( @CB_Ring0 ) ) ;
__HAL.Hook_Ring1( integer( @CB_Ring1 ) ) ;
__HAL.Hook_Ring2( integer( @CB_Ring2 ) ) ;
__HAL.Hook_Ring3( integer( @CB_Ring3 ) ) ;

And here are the ring handler methods:

procedure CB_Ring0( Value : int64 ) ;

begin
    Kernel.CB_Ring( 0, Value ) ;
end ;


procedure CB_Ring1( Value : int64 ) ;

begin
    Kernel.CB_Ring( 1, Value ) ;
end ;


procedure CB_Ring2( Value : int64 ) ;

begin
    Kernel.CB_Ring( 2, Value ) ;
end ;


procedure CB_Ring3( Value : int64 ) ;

begin
    Kernel.CB_Ring( 3, Value ) ;
end ;

These functions simply call the Kernel CB_Ring method, passing the value of the ring that was called.

procedure TKernel.CB_Ring( Ring : integer ; Value : int64 ) ;

begin
    if( Ring = 0 ) then // Only respond to ring 0 callbacks
    begin
        API( Value ) ;
    end ;
end ;

The Kernel ignores any ring calls other than 0. For ring 0, the Kernel's API method is called.

The TKernel.API method simply redirects the ring 0 calls to the appropriate subsystem after some minimal validation.

procedure TKernel.API( Value : int64 ) ;

var Request : PSystem_Request ;
    Base : int64 ;
    Offset : cardinal ; // offset of Value in mapped user page

begin
    // Lock and map the user page(s) with the system request structure...
    Offset := MMC.Lock_Pages( PID, Value, sizeof( TSystem_Request ) ) ;
    if( MMC.Last_Error <> nil ) then // Failed to lock the pages
    begin
        Last_Error := MMC.Last_Error ;
        exit ;
    end ;
    try
        Base := MMC.Map_Pages( PID, 0, Value, sizeof( TSystem_Request ), 0 ) ;
        if( Base = 0 ) then
        begin
            Generate_Exception( UOSErr_Invalid_System_Request ) ;
            exit ;
        end ;

In virtual memory systems, a given virtual address in the application is not the same physical address as the same virtual address in the executive. The application passes an address within its own virtual address space, so the Kernel needs to somehow gain access to the memory of the application. This is done by "mapping" some of the application's virtual memory into the kernel's virtual address space. Because the page size and page boundaries are the same for both virtual address spaces, the offset from the start of the page is the same in both cases. What differs is the base address of the page where the application memory has been mapped into the kernel's address space. A further complication is that in some cases some, or all, of the application's memory contents may have been moved out to disk and is no longer physically resident in memory. Therefore, we need to make sure that the application's data is in memory (and remains there until we are doine with it). This is done via the Lock_Pages method of the MMC. This is responsible for making sure the pages are resident (in memory) and locked in place so they are not removed. The PID of the process whose memory is to be read is passed, along with the address of the system request structure in that process' virtual address space, and the number of bytes from that address to lock. The function returns the offset of the address in the page. We check to make sure there wasn't an error. If there was, we set the last error and exit.
Next we map the process page(s) that we locked into our address space with Map_Pages. We pass the process PID as the first parameter. The second parameter is 0 to indicate mapping the page(s) into the Kernel address space. The next parameters are the base virtual address and the size. The final parameter are flags. We pass 0 to indicate no flags. The result is the base address of the page that was mapped into the kernel address space. If multiple pages need to be mapped, the function will do so. However, if the operation cannot finish, the function returns 0. We know this is an error because nothing will ever be mapped to the first page of our address space. In this case, we set an error and exit.

        try
            Base := Base + Offset ;
            Request := PSystem_Request( Base ) ;

            if( Request.Subsystem <> UOS_Subsystem_Kernel ) then
            begin
                Generate_Exception( 0 ) ; // Clear exception
            end ;

            // Pass request to the proper subsystem...
            case Request.Subsystem of
                UOS_Subsystem_MMC : MMC.API( Base, Request^ ) ;
                UOS_Subsystem_FIP : FIP.API( Base, Request^ ) ;
                UOS_Subsystem_USC : USC.API( Base, Request^ ) ;
                UOS_Subsystem_SSC : SSC.API( Base, Request^ ) ;
                UOS_Subsystem_Kernel : _API( Base, Request^ ) ;
                else
                    begin
                        Request.Status := Error_Fatal ;
                        Generate_Exception( UOSErr_Invalid_Subsystem ) ;
                    end ;
            end ;
        finally
            // Unlock and unmap the user pages
            MMC.UnMap_Pages( 0, Base, sizeof( TSystem_Request ) ) ;
        end ;
    finally
        MMC.Unlock_Pages( PID, Value, sizeof( TSystem_Request ) ) ;
    end ;
end ; // TKernel.API

Next we create a pointer to the request structure. If the subsystem is not the Kernel itself, we clear any pending exceptions. The reason we don't clear them in the case of the Kernel is something we will discuss in the future. Next we dispatch the request to the indicated subsystem, generating an exception if the subsystem is not valid (and setting the status code). Finally, we unmap the pages from the Kernel address space and then unlock the pages.

Since the call is to the SSC component, we will now look at the SSC.API function.

procedure TUOS_SSC.API( Request : int64 ; SReq : TSystem_Request ) ;

var Base, Offset : int64 ;
    PID : TPID ;
    PS2I1 : PString2I1_Request ;
    S : TUOS_String ;
    Status : integer ;
    Work : string ;

begin
    PID := Kernel.PID ;
    case SReq.Request of
        UOS_SSC_Get_Symbol:
            begin
            .
            .
            .
            end ;
        else Generate_Exception( UOSErr_Invalid_System_Request ) ; // Error
    end ;
end ; // TUOS_SSC.API

The address of the calling user's request structure is passed along with a copy of the fixed portion of the request that was obtained by the kernel. First we get the current process ID. Then we base our operation on the request value (in this case, it is UOS_SSC_Get_Symbol). If it is not a recognized request, we generate an exception. We will discuss the code that handles the Get_Symbol request in a moment. But first, we'll look at the Generate_Exception method.

procedure TUOS_SSC.Generate_Exception( X : integer ) ;

begin
    if( X = 0 ) then
    begin
        USC.Set_Process_Exception( Kernel.PID, nil ) ;
        exit ;
    end ;
    USC.Set_Process_Exception( Kernel.PID, Create_Simple_UE( UOS_Facility_ID, 0, X, Error_Fatal, '', '' ) ) ;
end ;

This method sets the exception for the current process. If the passed value is 0, we clear the exception. Otherwise, we generate an exception object and set the process exception via USC.Set_Process_Exception.

Here is the code for USC.Set_Process_Exception:

procedure TUSC.Set_Process_Exception( PID : TPID ; E : TUnified_Exception ) ;

var P : TProcess ;

begin
    P := Get_Process( PID ) ;
    P.Last_Error := E ;
end ;

This method gets the TProcess object for the passed PID, and sets the Last_Error to the passed exception.

The TProcess class is used to keep track of information that is associated with a process. Here is the class definition:

type TProcess = class
                    public // Instance data...
                        _Last_Error : TUnified_Exception ;

                    protected // Property handlers...
                        function Get_Last_Error : TUnified_Exception ;
                        procedure Set_Last_Error( Value : TUnified_Exception ) ;

                    public // Properties...
                        property Last_Error : TUnified_Exception
                            read Get_Last_Error
                            write Set_Last_Error ;
                end ; // TProcess

In the future we will discuss other items stored in the TProcess class.

Here are the method implementations:

// Property handlers...

function TProcess.Get_Last_Error : TUnified_Exception ;

begin
    Result := _Last_Error ;
end ;


procedure TProcess.Set_Last_Error( Value : TUnified_Exception ) ;

begin
    if( Value <> nil ) then
    begin
        Value.Attach ;
    end ;
    if( _Last_Error <> nil ) then
    begin
        _Last_Error.Detach ;
    end ;
    _Last_Error := Value ;
end ;

These routines are typical for the Get_Last_Error and Set_Last_Error methods we have seen in other classes in the past.

Now back to the handler for the UOS_SSC_Get_Symbol.

    if( SReq.Length < sizeof( TString2I1_Request ) - sizeof( TSystem_Request ) ) then
    begin
        Generate_Exception( UOSErr_Invalid_System_Request ) ;
        exit ;
    end ;
    Offset := MMC.Lock_Pages( PID, Request, sizeof( TString_Request ) ) ;
    Base := MMC.Map_Pages( PID, 0, Request, sizeof( TString_Request ), MAM_Read or MAM_Lock ) ;
    if( Base = 0 ) then // Couldn't map the memory
    begin
        USC.Set_Process_Exception( PID, MMC.Last_Error ) ;
        if( MMC.Last_Error = nil ) then
        begin
            Generate_Exception( UOSErr_Memory_Address_Error ) ;
        end ;
        exit ;
    end ;

First we check to make sure that the passed length is at least as large as the TString2I1_Request structure, and generate an exception if not. Next we lock and map the page(s) for the request. You may be asking, "didn't we already lock the pages back in the Kernel.API method?" The answer is "maybe". In Kernel.API, we only locked and mapped enough memory for the fixed portion of a system request structure. If the fixed portion is entirely within one page, but the rest of the TString2I1_Request structure extends into the next page, then the part that is in the next page will not have been locked or mapped by the kernel. On the other hand, if the page(s) for the entire structure have already been locked and mapped by the kernel, locking and mapping them again here will not harm us. Each page lock increments the lock count for a page. When the page is unlocked, the lock count for that page is decremented. As long as the lock count is greater than zero, the page will stay locked. If the mapping operation failed, we set an exception and exit.

    try
            PS2I1 := PString2I1_Request( Base + Offset ) ;
            S := Get_User_String( PID, PS2I1.String1, Status ) ; // Get symbol name
            if( Status <> UE_Success ) then
            begin
                exit ;
            end ;
            if( ( S = nil ) or ( S.Size = 0 ) ) then
            begin
                Generate_Exception( UOSErr_Missing_Value ) ;
                exit ;
            end ;

We set PS2I1 to the virtual address of the mapped structure. Then we obtain the string value via a call to Get_User_String. If that value is null, or we can't obtain the value (S = nil), we return an error.

               Work := S.Contents ;
                S.Detach ;
                S := Get_Symbol( PS2I1.Integer1, PID, PAnsiChar( Work ) ) ;
                if( Last_Error <> nil ) then
                begin
                    exit ;
                end ;
                if( S = nil ) then // Undefined
                begin
                    Work := '' ;
                end else
                begin
                    Work := S.Contents ;
                end ;

Now we get the string value from the TUOS_String object, and detach from the obejct. Work now contains the name of the symbol. We use that to call Get_Symbol to get the symbol contents. We then extract the returned symbol contents from the TUOS_String, or set it to null if nil was returned. We reuse Work, so that now it contains the contents of the symbol (or null).

                if( PS2I1.String2.Length = 0 ) then // Return length
                begin
                    PS2I1.String2.Length := length( Work ) ;
                end else
                begin
                    Status := Set_User_String( PID, PS2I1.String2, Work ) ;
                end ;
            finally
                MMC.UnMap_Pages( 0, Request, sizeof( TString2I1_Request ) ) ;
                MMC.Unlock_Pages( PID, Request, sizeof( TString2I1_Request ) ) ;
            end ;

Now that we have the symbol value (or a null string), we check the length of String2 (the return string). If the length is 0, then the user is simply asking for the length of the symbol contents, rather than the actual contents. In that case, we simply set the String2.Length value to the length of Work. Otherwise, we set the result string via Set_User_String, and set the status (Set_User_String returns a status). Finally, we unmap and unlock the application memory.

Here is the Set_User_String function.

function TUOS_SSC.Set_User_String( PID : TPID ; SRB : TSRB ;
    const S : string ) : integer ;

var Buffer : PAnsiChar ;
    Len, Offset : int64 ;

begin
    Result := UE_Success ;
    Len := SRB.Length ;
    if( Len > length( S ) ) then
    begin
        Len := length( S ) ;
    end ;
    Offset := MMC.Lock_Pages( PID, SRB.Buffer, Len ) ;
    try
        Buffer := PAnsiChar( MMC.Map_Pages( PID, 0, SRB.Buffer, Len, MAM_Read or MAM_Lock ) ) ;
        if( Buffer = nil ) then
        begin
            USC.Set_Process_Exception( PID, MMC.Last_Error ) ;
            if( MMC.Last_Error = nil ) then
            begin
                Generate_Exception( UOSErr_Memory_Address_Error ) ;
            end ;
            Result := UE_Error ;
        end else
        begin;
            move( PChar( S )[ 0 ], Buffer[ Offset ], Len ) ;
        end ;
        MMC.UnMap_Pages( 0, SRB.Buffer, Len ) ;
    finally
        MMC.Unlock_Pages( PID, SRB.Buffer, Len ) ;
    end ;
end ;

This function writes the value of a string into the application virtual address space. The parameters are the process ID, the TSRB describing the string, and the string value to write. The function returns UE_Success unless there is a problem mapping the user's address space. The length specified in the TSRB structure is compared with the length of the passed string - the smaller of the two values is used. The string contents address from the TSRB structure is used to lock and map the page(s) containing the target buffer. If there is an error mapping the page, we set the process exception. The exception is the one set by the MMC or, if the MMC has no errors set, we generate a Memory Address Error exception. Assuming the pages are sucessfully mapped, the string contents are copied to the application buffer. Either way we then unmap and unlock the pages.

Here is the Get_User_String method:

function TUOS_SSC.Get_User_String( PID : TPID ; SRB : TSRB ;
    var Status : integer ) : TUOS_String ;

var Buffer : PAnsiChar ;
    Len : int64 ;
    Offset : int64 ;
    S : string ;

begin
    Status := UE_Success ;
    Result := nil ;
    if( ( SRB.Length = 0 ) or ( SRB.Buffer = 0 ) ) then
    begin
        exit ;
    end ;
    Len := SRB.Length ;
    if( Len < 1 ) then
    begin
        exit ;
    end ;
    Offset := MMC.Lock_Pages( PID, SRB.Buffer, Len ) ;
    try
        Buffer := PAnsiChar( MMC.Map_Pages( PID, 0, SRB.Buffer, Len, MAM_Read or MAM_Lock ) ) ;
        if( Buffer = nil ) then
        begin
            Status := UE_Error ;
            exit ;
        end ;
        setlength( S, Len ) ;
        move( Buffer[ Offset ], PAnsiChar( S )[ 0 ], Len ) ;
        Result := Pascal_To_UOS_String( S ) ;
        MMC.UnMap_Pages( 0, SRB.Buffer, Len ) ;
    finally
        MMC.Unlock_Pages( PID, SRB.Buffer, Len ) ;
    end ;
end ;

This function is, essentially, the Set_User_String method in reverse. If the TSRB structure length is 0, a null string is returned. Otherwise, we map the pages and copy the data into the local string. We set the result to UE_Success unless a problem happens, in which case UE_Error is returned. The pages are then unmapped and unlocked.

In summary, the excecutive, in ring 0, can access the application memory, in ring 3. But not the other way around. The application passes its virtual address of the system request structure to the executive. This request indicates the application's virtual address for the name of the symbol. By use of the Lock_Pages and Map_Pages methods, part of the application's memory is mapped into the executive's memory space so that the executive can access the application's data. The application makes two calls: the first to get the length of the symbol contents, and the second to get the actual symbol contents. Now that we've worked out the details of how the executive and applications interact, we can look at future API calls at a higher level without all of this gory detail.

In the next articles, we will discuss outputting the prompt to the user's I/O device.