DOMAINS - multiple domains in NSC.

This explain file is aimed at helping you to understand the process of creating service domains using NS mode C. In order to do this, we first have to explain how normal C programs work.

Review of Normal Program Execution:

A normal NS C program is organized as a single domain in a run-unit. The run-unit is loaded into memory (instantiated) by the run program system service (RUN_PGM), which starts the program executing with an inward climb to the domain entry point.

The domain entry transfers control to the set-up routines of the run-time library. These routines initialize the program environment, and then make a regular subroutine call to the user's main-line function (usually "main()"). Library set-up includes things like initializing the memory allocation tables, allocating and initializing the primary co-routine (which includes the primary stack), opening standard I/O units, setting up fault handling and the default signal handlers, and finding and parsing the command line.

Once "main" has been called, execution continues in the user's code until "exit()" is called explicitly or implicitly. Implicit calls to "exit()" occur when "main()" returns, or when aborts or other fatal errors occurs.

Once "exit()" is called, the library proceeds to wrap up the program. This includes calling any wrap-up functions registered with "atexit()", closing any open files, and unwinding the stack. The process of unwinding the stack springs any function level wrap-up handlers (return traps). The fault and signal handling mechanism works with these return traps, so signal handling is disabled by this action. When all wrap-up actions have been completed, the library does an outward climb back to RUN_PGM which then deallocates the memory used by the program. This includes the memory initially allocated by RUN_PGM to get the program started, as well as any other memory allocated by the program itself during its execution.

When wrap-up is complete, the memory of the domain is in an unusable state -- it simply can't be restarted again. One reason is that the wrap-up process eliminates a number of the data structures required for the program to run. Such data structures can't even be reconstructed when the program starts executing again, because they depend on data objects which were initialized at compile-time and which no longer have their proper initial values. This is a result of various requirements of the ANSI standard for C. For example, a data object declared with

extern int i = 12;

is set up at compile-time and is given the value 12 as part of the instantiation process. Once the program actually starts execution, this initial value may be lost and there is no way to restore it if you want to start the program running again.

Compile-time initialized values occur in library code as well as user code. Thus a C program simply can't be restarted after it has finished wrap-up; the library isn't properly initialized and its data structures can't be reconstructed.

The fact that the domain memory is unusable after wrap-up is NOT a problem because the memory is immediately released by RUN_PGM. The program is intended to be called once, then discarded.

Multiple Domains in a Program:

Many programs programs only deal with three domains:

The program itself
RUN_PGM
The GCOS8 domain (supplying system services)

Interaction with RUN_PGM is just the initial inward climb to start program execution, and the final outward climb to terminate it. The program may also make inward climbs to the GCOS8 domain, mostly through PMME type calls but also because of faults, interrupts and dispatch timer runouts. For the most part, the RUN_PGM and GCOS8 domains are invisible to the program.

It is possible for a program to have other domains in addition to the one initially called by RUN_PGM. Occasionally these domains may have been built by LKED as part of the same run-unit and loaded into memory by RUN_PGM, but more commonly they will be attached as the result of dynamic linking. The memory for these secondary domains will be also be deallocated by RUN_PGM when the primary domain does its final outward climb.

For a program like a compiler, it might be appropriate to package a single pass (phase) as a secondary domain. A domain has a separate name space for external symbols, so this approach can be used to resolve external name conflicts. The technique will also help the hardware catch programming errors since the parts of the program are restricted to the smaller separate address spaces of their own domains.

Using separate address spaces means that the program will waste real and virtual memory because it won't be able to share common subroutine code, or a common heap for memory allocation. The programmer must evaluate the trade-offs between ease of maintenance, security, and efficiency. Packaging a program into separate single-use secondary domains like this is similar to breaking up a job into separate programs that are called with DRL CALLSS in TSS. Indeed, as far as the library and the programmer are concerned, these single use domains are treated like separate programs.

Service Domains:

Besides these single use secondary domains, there are times when a program will have secondary domains that it wishes to climb to repeatedly as "service" subroutines. This is analogous to the way the program uses PMMEs to climb into the GCOS8 domain to get services.

Usually, service domains are created to handle privileged operations. You can create a privileged service domain which unprivileged domains then invoke in order to perform privileged operations. Often the privileges have to do with accessing data in a work area that is shared between multiple processes.

Secondary "service" domains may be entered many times in the course of single run of a program, and they need to preserve state information between these calls. As a result, service domains can't be constructed like normal C programs which can only be called once. If you want to write a service domain in C, you must avoid the wrap-up process that destroys library data structures. You must also make sure that the domain's memory is left in a state that lets you re-enter the domain successfully on a later climb.

To ensure successful re-entry into a service domain, the program in the domain cannot be allowed to enter the unusuable state that results from wrap-up. This means you need some way for the program to OCLIMB (return) from the domain without going through the library wrap-up that would be done by a call to "exit()". Also, you must set up the library on the first inward climb to the domain, but subsequent climbs to the domain must bypass most of this set-up. Instead, each subsequent entry must restore the state of the domain at the time of the last outward climb.

The C library lets you create a resuable service domain like this by using a special module in the domain set-up sequence. In a normal program there is a module that does the following:

In a service domain, this is replaced by a module that loops calling the user's primary routine. When the user's primary routine returns, it arranges to save the state of the stack and the argument segment, and then arranges an outward climb that takes the status returned by the user's primary routine and returns that status to the domain caller. The next time execution enters the domain, the module restores the argument and stack state, and calls the user's primary routine again. The user code should not call "exit" unless it is completely finished and will not be invoked again; therefore it should close files explicitly when the files are no longer needed.

To link a service domain, you need to create a symref either to the name C$VSERVER or to C$SERVER. The user mainline will be called with the name C$SRV_ENTRY, and as always the domain entry point is called C$ENTRY. The C$SRV_ENTRY name may be defined using C's #equate directive. The C$ENTRY name will normally be the object of LKED's "ReName" and "Visible" directives.

If you create a symref to C$VSERVER, the arguments to the user's main function are passed in an "argc, argv" format. The "argc" argument is an integer giving the number of arguments being passed, and the "argv" value is a pointer to a list of "void *" pointers to each of the arguments. For example, you might have

#equate myserver C$SRV_ENTRY
int myserver(int argc, void **argv) {
      ...
    /* do the service actions */ 
    return(status);
}

The LKED directives might be

Generate_Run_Unit -Name SERV_RU
Create_Reference -SYMREF C$VSERVER C$PROGRAM
Library -Language C
ReName -From C$ENTRY -To MY_SERVICE
Visible -Entdef MY_SERVICE

If the main function was written in some language other than C, the LKED ReName directive could be used give the same effect as the #equate directive in the C source. If you create a symref to C$SERVER, the situation is similar except that the arguments to the main function are pointers to the parameters passed on the inward climb to the domain. For example, you might have

#equate myserver C$SRV_ENTRY
int myserver(void *p1, void *p2 /* and so on */) {

In general, we recommend the "argc, argv" format, since it is closer to the standard way of writing C programs. It also lets you specify a variable number of CLIMB parameters.

Advice For Writing Service Domains:

It is usually difficult to write a service domain that can share files with another domain. For example, suppose that a domain and a service domain intend to write to the same file. Since the two domains have separate copies of the library, they will use separate buffers. Output from the two domains will not be interleaved in the order that the output is actually written but will be interleaved in the order that the domains flush the buffers.

Also remember that the service domain doesn't automatically close files when it passes control back to the caller, since it doesn't go through normal program wrap-up. This means that service domains should explicitly close files when they're finished with the files.

Data objects declared static or external retain their values from one invocation to another. Thus, each invocation of the service domain picks up from where the last invocation left off.

In most cases, it is possible to invoke a service domain in a way that tells the service domain this is the last time it will be invoked. This gives the service domain a chance to clean up after itself, typically by calling "exit".

When linking your service domain with LKED, do NOT specify the

-use_Data_Stack

option. A service domain needs to preserve the software stack between successive entries to the domain, and acquiring the software stack from the data stack means that this cannot be done. If the "-use_Data_Stack" is specified for service domain, the program will abort in a strange manner when the domain is entered the second time. There is documentation for some other software packages which specifies that the "-use_Data_Stack" must be used. These instructions should be ignored. The C library setup will look after obtaining the effect that those packages were attempting to achieve.

Copyright © 1996, Thinkage Ltd.