4 Implementation

4.1 Libmach Emulation

The Libmach emulation has been written in C. Because of the object-oriented design of the port data management layer, we also considered using C++ for some of the internal parts, but we hesitated because we didn't have any experience with C++ cross compilers. It is well possible to write efficient object-oriented programs using C: The X11 toolkit library is a convincing example.

4.1.1 Ports

We've implemented a subset of Libmach's mach_port_* interface. The emulation routines manage a task's local port name space using the port data management layer. It currently is impossible to manipulate a remote task's port name space.

The routines implemented are:

mach_port_allocate and mach_port_allocate_name,
routines to create a new port.

mach_port_destroy,
a routine to destroy a port object and make its port name available for immediate reuse.

mach_port_deallocate,
a routine to deallocate send rights associated with a port.

mach_port_mod_refs,
a routine to modify the reference counts of rights associated with a port. [9]

Other routines of the Libmach interface will be implemented when necessary; their implementation is easy and straightforward.

The port data management layer consists of implementations of the classes shows in figure [here]. All classes implement constructor and destructor routines (if the class is not an abstract class), init and done methods, routines to change their port name, and a ,,friend''(1) routine which looks them up (i.e. returns a pointer to them) using a given port name. The higher level classes also contain routines to modify their list of rights, and to look them up using their system-wide name.

The object-oriented flavour of the port data management library is achieved in the same way as in the X11 toolkit library. [16]

The three-phase-commit protocol described in section [here] hasn't been implemented, yet: Only the special case of transferring send rights of ports the sending tasks holds the receive right for works. Also, port reference counting hasn't been implemented, yet: So far, receive right holders don't get notified when a tasks manipulates reference counts for their send rights. Consequently, dead name notifications and port-deleted notifications also don't work.

4.1.2 IPC

The mach_msg emulation provides the original mach_msg interface. The following routines are involved in the emulation and encapsulation:

mach_msg,
the original Mach wrapper around mach_msg_trap. It tries to restart aborted transmissions.

mach_msg_trap,
calls mach_msg_trap_send and mach_msg_trap_rcv according to the header options.

mach_msg_trap_send,
handles the whole conversion and encapsulation for the send case.

mach_msg_trap_rcv,
handles the whole conversion and decapsulation for the receive case.

The layout of the encapsulated message follows the example given in figure [here].

struct msg_encapsulated { 
  MessageDopeT message_size;
  MessageDopeT message;
  unsigned long un_name;
  int  msgnum;
  StrDopeT machmsg;
  StrDopeT machaux;
};

The original message buffer is transmitted in the machmsg field. So the message parsing in mach_msg_trap_rcv parses the same structure and is identical to the parsing in mach_msg_trap_send.

We only implemented the following port right transfer types: MACH_MSG_TYPE_MAKE_SEND and MACH_MSG_TYPE_MAKE_SEND_ONCE. The long form of message components (msgt_longform) is not supported. The msgt_deallocate flag is not supported, it is already depreciated in Mach [8]

Condition variables are not available yet, so busy-waiting is used for synchronization. In order to avoid the performance impact of busy-waiting the task yields its processor time (in L3: ipc_switch). The receive operation does not manage timouts, hence it always waits until a message has been received.

The port service thread receives messages. It has to provide preallocated buffers before it knows the required size of the buffers, so these buffers have a fixed size too. After the message has been received the buffers could be truncated, but the current memory management doesn't support this. When the receive buffer is too small the receive operation for this message fails.

The data type mqueue_t is intended to store received messages in a FIFO.

These routines manipulate message queues:

mqueue_new,
a routine to create a new message queue.

mqueue_destroy,
a routine to destroy a message queue (and free the allocated memory)

mqueue_queue,
a routine to append a message to the message queue.

mqueue_dequeue,
a routine to dequeue the first message from the message queue and return this element.

The MIG-generated stubs require some support routines that are part of Libmach. These routines provide allocation/deallocation of memory (mig_allocate, mig_deallocate), initialization (mig_init), port-specific functions and an improved strncopy.

These MIG-specific routines were copied from Mach's Libmach and adapted to the L3-specific functions. The memory management routines use _libmach_malloc and _libmach_free which allocate storage from the library's heap (see below).

4.1.3 Virtual Memory Interface

As explained in section [here], we've implemented only a small part of Mach's memory management interface:

vm_allocate,
a routine to allocate virtual memory for a task.

vm_deallocate,
a routine to release a memory region of a task's address space. [9]

Both routines have several restrictions compared to Mach's implementation, as explained in section [here]; basically, they only provide the functionality of the traditional Unix malloc/free interface.

Internally, the library knows about two classes of memory objects:

heap_space,
an object built upon an L3 data space, provides a routine to allocate memory blocks in an Unix-sbrk-like fashion. The implementation is currently limited to 8 heap_spaces (a compile-time constant).

heap,
built upon a heap_space, provides the usual malloc and free interface. These routines have been ported from FreeBSD.

At startup, the library initialization code allocates two data spaces and creates two heaps upon them: One is used to resolve vm_allocate requests, and the other one for library-internal memory management: allocation of port data structures, rights, message buffers, and so on. The size and location within the task's address space of these two heaps are compile-time constants.

4.1.4 Utilities

The Libmach emulation library contains several general utilities and helper routines.

Locking routines
To synchronize accesses to common data structures between the application and the port service thread, a simple spin lock implementation has been incorporated. Spin locks are utilized in two applications:

Simple byte string manipulation
To ease programming, the library contains implementations of the ANSI C memset, memcmp, memcpy and strlen functions.

Debugging aids
Debugging on L3 is very difficult because a decent debugger hasn't yet been ported, so we were mainly using just the ,,printf method'' and L3's kernel debugger.

To support printing messages on an L3 terminal using L3's native printf implementation, our library contains a wrapper which allows linking cross-compiled programs against L3's standard C libraries.

Other debugging aids include macros which allow priority-based tracing of events (using printf), a verbose exception handler (able to tell which exception killed a thread), a routine which can invoke the L3 kernel debugger, and assertion/panic routines which can trigger a core dump (which can later be analyzed in the cross development environment).


Footnotes:
  1. ,,friend'' in the sense of C++'s friend keyword

4.2 C Threads Library

The C Threads Library emulation discussed in section [here] hasn't been implemented, yet.


Michael Hohmuth, Sven Rudolph
April 9, 1996