Previous Index Next

Implementation Details

This chapter describes the details of implementation of the general Grâl architecture on computers with "traditional" architectures, including single-processor sequential machines with RAM or ROM-stored programs, and loosely or tightly coupled multiprocessors.

On those machines Grâl processes look very much like the processes in traditional operating systems; i.e. every process has memory segment(s) with executable code and data, stack(s) and thread(s) of execution, as well as the set of "resources" (i.e. the context).

Since memory segments are not included into Grâl notion of process, they are assumed to belong to memory manager, which services the requests of the memory protocol for the memory segments residing in virtual memory.

Although individual memory stores and fetches are not visible to the system's kernel, attaching the segments of virtual memory to the processes conforms to the generic Grâl framework, i.e. the accesses to memory objects may be manipulated in exactly the same way as accesses to any user-defined objects.

The Grâl framework allows for easy and efficient simulation of the traditional message passing and Unix-style pipes, by passing accesses to memory objects from one process to another. Note that unlike the common message-passing microkernels Grâl kernel copies message data only once, and if the "source" of data was a device capable of DMA into the provided memory object, CPU will not do any copying.

The memory manager can provide both "empty" memory segments (initially filled by zeroes), or caches for other objects implementing the memory protocol (for example, disk files).

User programs perform operations on objects using system calls described in the subsequent chapters of this document. For efficiency, system calls can be chained into small programs, and the privileged mode kernel code is invoked to execute the "programs" using small per-thread stacks of accesses. Such chains of system calls are called vectors.

Vectors of system calls are executed sequentially, until the end, or a system call returning an error. On error condition the rest of the vector is skipped, and all accesses placed on stack by the previous system calls on the vector are destroyed. The special system call, ERR can be used to partition the vectors, to the effect that execution of the next sub-vector will be allowed when the current one failed.

Execution of a vector of system calls may be illustrated by the following pseudo-code:

procedure sys(vector)
begin
	result = 0
loop:
	get next element of the vector
	if element == END then
		return result
	fi
	if element == ERR then
		store result in a user variable
		result = 0
		goto loop
	fi
	execute system call (element)
	if error condition then
		result = error code
		skip until END or ERR
		unwind stack
		goto loop
	fi
	if system call changes result then
		result = result code
	fi
	goto loop
end

All events in Grâl processes are purely synchronous (i.e. user thread must explicitly wait for events); there is no equivalent of Unix signals. Execution errors, such as illegal operation codes or protection faults cause unconditional termination of the process. Software signals can be simulated by having an additional higher-priority thread to wait on signal-like events and execute appropriate signal handlers.

In most cases a thread will wait on a single event by using an access associated with the event and a special waiting system call. All other system calls requiring presence of an event as a precondition for an operation will fail immediately if the event didn't yet occur.

To handle cases when single thread needs to select one event from a set two extensions to the basic scheme are provided: the multiple-event wait, providing a simple way to prioritize events; and groups.

Groups are sets of accesses stored in context (local addresses on per-thread stack are not be members of groups, even if assigned to a group). Every access can belong to only one group. When access to a group is used in waiting system calls, those system calls will block until an event associated to any access in the group. Events in a group are not prioritized, simple FIFO discipline is used to determine which event would be processed first.

Although groups look like objects (i.e. there are accesses to groups), they are not objects; accesses to groups cannot be used as arguments of transactions.

To simplify location of user data structures associated with objects the kernel keeps an arbitrary user pointer with every access in context and per-thread stacks, so every time an access is returned as a result of waiting for multiple or group events the corresponding pointer can be quickly retrieved.


Previous Index Next