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

UCL Lexical Functions
70 PROCESS_SCAN
71 PROCESS_SCAN, Part 2
72 TProcess updates
73 Unicode revisted
74 Lexical functions: F$CONTEXT
75 Lexical functions: F$PID
76 Lexical Functions: F$CUNITS
77 Lexical Functions: F$CVSI and F$CVUI
78 UOS Date and Time Formatting
79 Lexical Functions: F$CVTIME
80 LIB_CVTIME
81 Date/Time Contexts
82 SYS_GETTIM, LIB_Get_Timestamp, SYS_ASCTIM, and LIB_SYS_ASCTIM
83 Lexical Functions: F$DELTA_TIME
84 Lexical functions: F$DEVICE
85 SYS_DEVICE_SCAN
86 Lexical functions: F$DIRECTORY
87 Lexical functions: F$EDIT and F$ELEMENT
88 Lexical functions: F$ENVIRONMENT
89 SYS_GETUAI
90 Lexical functions: F$EXTRACT and F$IDENTIFIER
91 LIB_FAO and LIB_FAOL
92 LIB_FAO and LIB_FAOL, part 2
93 Lexical functions: F$FAO
94 File Processing Structures
95 Lexical functions: F$FILE_ATTRIBUTES
96 SYS_DISPLAY
97 UCL Lexical functions: F$GETDVI
98 Parse_GetDVI

Glossary/Index


Download sources
Download binaries

Addendum to previous article
I overlooked one aspect of the PROCESS_SCAN system call that we covered in the previous article. The call needs to handle a reset of a context by passing a null item list to it. So, we will modify it slightly, as follows.

    if( ContextAdr = 0 ) then // Invalid address
    begin
        Generate_Exception( UOSErr_Bad_Parameter ) ;
        exit ;
    end ;
The first check in the TUSC.Process_Scan_Context only checks the context address. The check for the item list address being 0 is moved to be after the call to delete the process context, as follows:
    if( ItemList = 0 ) then // Clearing the context
    begin
        exit ;
    end ;

TProcess updates
In this article, we will look at some support code, modifications to the TProcess class, and how the TContext class is used in the GETJPI system call.

Here are the new TProcess public methods and data.

                        Contexts_List : TList ; // Process contexts

                        function Child_Count : integer ;
                        function Job : TProcess ; virtual ;
                        function Parent : TProcess ; virtual ;
                        procedure Free_Remote ; virtual ;
                        function Hardware_Description : string ; virtual ;
                        function Hardware_Code : int64 ; virtual ;
                        function CSID : int64 ; virtual ;
                        function NodeName : string ; virtual ;
                        procedure Assign_Device( D : Devices.TDevice ) ; virtual ;
                        function Terminal_Name : string ; virtual ;
                        function KThread_Count : int64 ; virtual ;
                        function MaxK_Threads : int64 ; virtual ;
                        
                    public // Contexts...
                        function Get_Context( Adr : int64 ) : TContext ;
                        procedure Add_Context( Value : TContext ) ;
                        procedure Delete_Context( Adr : int64 ) ;

Here are the implementations of these methods:

function TProcess.Child_Count : integer ;

begin
    Result := 0 ;
    if( _Children = nil ) then
    begin
        exit ;
    end ;
    Result := _Children.Count ;
end ;


function TProcess.Job : TProcess ;

begin
    Result := self ;
    if( _Parent = 0 ) then
    begin
        exit ;
    end ;
    while( Result._Parent <> 0 ) do
    begin
        Result := Get_Process( _Parent ) ;
    end ;
end ;


function TProcess.Parent : TProcess ;

begin
    Result := nil ;
    if( _Parent <> 0 ) then
    begin
        Result := Get_Process( _Parent ) ;
    end ;
end ;


procedure TProcess.Free_Remote ;

begin
    // Do nothing for local processes
end ;


function TProcess.Hardware_Description : string ;

begin
    Result := _Kernel.HAL.Hardware_Description ;
end ;


function TProcess.Hardware_Code : int64 ;

begin
    Result := _Kernel.HAL.Hardware_Code
end ;


function TProcess.CSID : int64 ;

begin
    Result := _Kernel.Cluster_ID ;
end ;


function TProcess.NodeName : string ;

begin
    Result := _Kernel.Node_Name ;
end ;


function TProcess.Terminal_Name : string ;

begin
    Result := '' ;
    if( Attached_Device = nil ) then // Detached process
    begin
        exit ;
    end ;
    Result := Device_Name( Attached_Device.Info ) ;
end ;


function TProcess.KThread_Count : int64 ;

begin
    Result := 0 ; // Thread count
end ;


function TProcess.MaxK_Threads : int64 ;

begin
    Result := 0 ; // Max threads
end ;
Most of these methods simply get values from the kernel, which begs the question: why not just get the values directly from the kernel rather than through these TProcess methods? We will get into this in detail in a future article, but suffice to say that we will have a descendent class of TProcess which will override these methods and do some more complicated work to get the values for remote processes on a UOS cluster. This is why the new methods are virtual.

Of special note here are Free_Remote which does nothing (it will be overridden in the descendent class as mentioned above). The Job method follows the chain of parent IDs to the topmost parent, which is the job ID. The Child_Count method simply returns the count from the _Children list. If the list hasn't been created yet (is nil), it returns 0.

// Contexts...

function TProcess.Get_Context( Adr : int64 ) : TContext ;

begin
    if( Contexts_List.Indexof( pointer( Adr ) ) = -1 ) then
    begin
        Result := nil ; // Not a valid contextx
    end else
    begin
        Result := TContext( pointer( Adr ) ) ;
    end ;
end ;


procedure TProcess.Add_Context( Value : TContext ) ;

begin
    Delete_Context( int64( Value ) ) ;
    Contexts_List.Add( Value ) ;
end ;


procedure TProcess.Delete_Context( Adr : int64 ) ;

var I : integer ;

begin
    I := Contexts_List.Indexof( pointer( Adr ) ) ;
    if( I <> -1 ) then
    begin
        Contexts_List.Delete( I ) ;
    end ;
end ;
These three methods are simply concernted with inserting a new context into the list, deleting a context (with a check for whether or not it exits), and returning a context object instance given a context (or nil if such context doesn't exist).

GETJPI
Now we'll look at the changes to the Get_Job_Process_Info method. We add this to the local variables:

    Process_Context : TContext ;

    if( pidadr = -1 ) then
    begin
        if( Process.Wildcard_Context = nil ) then
        begin
            Process.Wildcard_Context := TContext.Create ;
        end ;
        Process_Context := Process.Wildcard_Context ;
    end else
    if( pidadr <> 0 ) then
    begin
        if( ( pidadr and 1 ) <> 0 ) then // A context
        begin
            Process_Context := Process.Get_Context( pidadr and ( not 1 ) ) ;
        end else
        begin
            Target_PID := Get_User_Integer( Kernel, PID, pidadr, Status ) ;
            if( Status = UE_Error ) then
            begin
                IOSB.r_io_64.w_status := Status ;
                if( MMC.Last_Error = nil ) then
                begin
                    Generate_Exception( UOSErr_Memory_Address_Error ) ;
                end ;
                exit ;
            end ;
        end ;
    end else
    if( prcnam <> '' ) then
    begin
        prcnam := lowercase( prcnam ) ;
        for I := 0 to Processes.Count - 1 do
        begin
            Process := TProcess( Processes[ I ] ) ;
            if( Process <> nil ) then
            begin
                if( prcnam = lowercase( Process.Name ) ) then
                begin
                    Target_PID := Process._PID ;
                    break ;
                end ; // if( prcnam = lowercase( Process.Name ) )
            end ; // if( Process <> nil )
        end ; // for I := 0 to Processes.Count - 1
    end ;
We've updated the beginning of this routine significantly. the pidadr parameter is the address of the process ID. However, there are a couple special values. A value of -1 indicates that the method will use a default context that will iterate through all processes. This context is saved as Wildcard_Context in the TProcess instance. If that context hasn't already been created, we create it. Then we set Process_Context to that context object.

If pidadr is a context, we take the context object for that context. Process contexts are always odd. We'll discuss how the contexts get assigned in a later article. But, they are the address of the actual context with the low bit set. The reason we do this is because we have to distinguish between an address from the user and an address in the executive for the context. It is possible, because these are two separate virtual address domains, that there is no way to determine if an address is the address of a PID in the user address space or a context address in the executive space. We could have implemented some scheme where we have indirect context references (maybe something like the index into a list) that exist at the top or bottom of the address range. However, there are no assumptions we can make about the CPU architecture and whether a index in a list might collide with a user address. So, we will simply declare that, for the GETJPI system call, non-context values for pidadr must be even addresses. It should be easy for the calling code to make sure that constraint is met. The Get_Context method will return the process context instance (or nil if the context is not valid). Note that we mask off the low bit before we request the the context instance.
Note: VMS doesn't have this problem because it stores the context in the user's virtual address space. UOS keeps the context in the executive.

On the other hand, a value of 0 indicates that we should return data for the current process (unless prcnam is specified). In all other cases, we get the ID from the specified address.

    if( Process_Context <> nil ) then
    begin
        Target_PID := Process_Context.Next_PID ;
        if( Target_PID = 0 ) then
        begin
            if( Process_Context = Process.Wildcard_Context ) then
            begin
                Process.Wildcard_Context.Free ;
                Process.Wildcard_Context := nil ;
            end ;
            Generate_Exception( UOSErr_Nonexistent_Process ) ;
            exit ;
        end ;
    end ; // if( Process_Context <> nil )
If we have a context object, we ask it for the next ID. If the result indicates the end of the iteration, we set the exception and exit. In addition, if the process context is the wildcard context, we recreate the wildcard context so that it is ready for the next wildcard iteration.

In the next article, we will review unicode, including the Compare function we used in the code from the previous article.

 

Copyright © 2019 by Alan Conroy. This article may be copied in whole or in part as long as this copyright is included.