Dorper Website of Paul Carver Harrison

LExec: Lightweight Executive

LExec is a 64-bit microkernel executive based off the design of the Amiga Exec. Currently it only supports the RV64GC architecture. Specifically, it supports the T-HEAD XuanTie C906 on the QEMU virt platform Eventually, I hope to support more architectures, especially 32-bit RISC-V as well as real hardware, for example, the CV1800B.

LExec is a microkernel. This means that it does not concern itself with disks, filesystems, graphics, sound, etc. Instead, it's primary job is to handle message passing. As with the Amiga Exec, message passing is handled though message passing where messages are pointers. Message queues, as well as most list-type data structures, are implemented using doubly linked lists. Threads are notified of incoming messages through the signaling mechanism. Each task can have up to 64 signals on 64-bit architectures. Signals work the same way as they work in Amiga Exec. LExec does have advantages over the Amiga Exec. A major benefit is proper mutex implmentation. In Amiga Exec, Permit/Forbid and Enable/Disable are used to do a lot of synchronization. Instead, LExec tries to ensure thread safety as much as possible without using these functions using its own implmentation of synchronization primitives like mutexs and sempaphores.

Because LExec is a clone of Amiga Exec, it runs entirely in machine (M) mode and does not have virtual memory or paging. Every task on the system has access to the memory of every other task on the system. This is necessary for the message passing system to work.

LExec uses the ELF64 executable format to load executable/library images into memory. LExec supports a subset of ELF64 relocations. The location of libraries is arbitrary and handled by "librarians." Librarians fetch the raw data that is fed into the ELF64 loader. This data could be held in ROM or on disk. This is a difference from the Amiga Exec to make LExec more portable.

Device I/O is the largest feature in the Amiga Exec not implemented in LExec. As it is largely an abstraction around message ports, those can be used until device I/O is implemented fully. Task based interrupts are also not fully implemented.

L/DOS

L/DOS will be the disk operating system for LExec. It will manage files, filesystems, processes, disks, etc. It will abstract disks using the SCSI interface, with non-SCSI drives having drivers that emulate the SCSI protocol.

Documentation

No written documentation is available for LExec specifically. But because the API is mostly compatible with Amiga® Exec, Amiga ROM Kernal Reference Manual: Libraries' 3rd edition, ISBN 0-201-56774-1 or the online version, along with reading LExec's code will suffice for now.

Preventing Memory Leaks

Currently, any memory allocated and most objects created by a program are not freed by the operating system upon program termination. LExec does not keep track of what process owns what resource. Programs interface with the same Exec library functions as the kernel does. This is a consequence of the Amiga Exec design and may be mitigated in the future with resource tracking functions (e.g. program says that it wants a list allocated for its own use and that list will not be shared between tasks). However, due to the design of the kernel, memory leakage will always still be possible for programs to commit. It is important that all programs free their resources (free memory, destroy objects, close files, etc.) when terminating, even under abnormal termination circumstances (e.g. crash).

Example Library

This library prints out a test message when it is loaded (lmain = library main, similar to DllMain on Windows). It exports two functions in its API: PrintHello and Square.

#include "../exec/exec.h"

struct ExecInterface *IExec;

void PrintHello() {
  IExec->Printf("Hello, World!\n");
}

int Square(int x) {
  return x*x;
}

void lmain(struct ExecInterface *_IExec) {
  IExec = _IExec;
  IExec->Printf("Test Library. &IExec=%p\n", &IExec);
}

/** API Table **/
struct ITest {
  struct Library library;
  void (*PrintHello)(void);
  int (*Square)(int x);
};

__attribute__((used, section(".api")))
static volatile struct ITest test_api = {
  .PrintHello = PrintHello,
  .Square = Square
};