OpenBLT: An Overview by Brian J. Swetland OpenBLT is a microkernel operating system for PC's based on the Intel 80386 or later CPUs. Some aspects of OpenBLT were inspired by Andy Valencia's VSTa OS, Mach, assorted UNIX variants, and the author's deranged imagination. The acronym BLT expands to "Brian's Lightweight Tasker" or "Bacon, Lettuce, and Threads". The word Open was tacked on for no particular reason.
The OpenBLT kernel is responsible for managing system memory, providing IPC facilities, and creation, destruction, and scheduling threads of control.
Below, we'll examine Tasks, Aspaces, and Ports (the basic objects managed by the OpenBLT kernel) in terms of their use in and appearance to userland programs.
Tasks
Tasks in OpenBLT are single threads of control. Each task is associated with an address space, within which it runs. Tasks may terminate themselves using the
void os_terminate(int status); system call. A task may start another task (sharing the same address space) using the int os_thread(void *addr); system call. The new task begins execution at the specified address and a 4K stack is created for it. [ os_thread() will probably take a stack pointer address in the next revision ] Address Spaces
Address spaces are currently not readily manipulated from userland. In the near future, shared memory system calls should allow the creation of new address spaces and sharing memory between address spaces. A thread may request that its address space be expanded using the os_brk() system call:
int os_brk(int addr); Ports
IPC (interprocess communication) in OpenBLT occurs when one task sends a message to another task via a port. Ports are 'owned' by one task (the task that created them). Messages are send from one port to another port (thus a port must exist for the sending task to send from). A port may be restricted so that it can only receive messages from one specified other port [ future versions will likely allow a list of allowed senders ].
All messages sent or received via an OpenBLT port are described with a message header. The header indicates the source port, destination port, size of message, pointer to message data (or receive data buffer), and flags (to allow for future expansion -- non-blocking IO, scatter/gather IO, etc)
typedef struct {
int flags;
int src;
int dst;
int size;
void *data;
} msg_hdr_t;
int port_create(uint32 restrict);
Create a new port owned by the running thread. Only allow messages from port 'restrict' to be received. If restrict is 0, messages from any source will be received. Will return ERR_MEMORY if the system lacks the resources to create the port, otherwise the port number will be returned.
int port_destroy(uint32 port); Destroy an existing port. Returns ERR_RESOURCE if port is not a valid port or if there are other ports slaved to this port. Returns ERR_PERMISSION if the running thread is not the owner of this port. Returns ERR_NONE upon success/
int port_send(msg_hdr_t *mh); Send the message in mh->data, of length mh->size from port mh->src to port mh->dst. Returns mh->size upon success, ERR_MEMORY if there is a bounds error, ERR_RESOURCE if either the source or destination ports do not exist, ERR_PERMISSION if the source is not owned by the running thread or the destination is not sendable from the source port.
int port_recv(msg_hdr_t *mh); Receive a message (in buffer mh->data, max length mh->size) from port mh->dst. Upon success, mh->src will be set to the sender, mh->dst will be set to the destination if it was a slaved port, and the number of received bytes will be returned. If the data or header are out of bounds, ERR_MEMORY is returned. If the destination port does not exist ERR_RESOURCE is returned. If the running thread does not own the destination port, ERR_PERMISSION is returned. This call will block if no messages are available, unless the port is set to NOWAIT, in which case ERR_WOULDBLOCK is returned.
int port_option(uint32 port, int opt, int arg); Modify port options. The two functions below are wrappers around port_option()
int port_slave(uint32 master, uint32 slave); Cause the master port to receive all messages sent to the slave port. If master is 0, the slave is released from bondage. Returns ERR_NONE upon success, ERR_PERMISSION if the master and slave are not both owned by the running thread, or ERR_RESOURCE if the master or slave are not valid ports.
int port_set_restrict(uint32 port, uint32 restrict); Change the restriction on a port. Returns ERR_NONE on success, ERR_RESOURCE if the port does not exits, or ERR_PERMISSION if the running thread does not own the port. A restriction of 0 allows any port to send to this port.
Finding other tasks to talk to
Since port numbers are generated dynamically by the kernel and tend to be different depending on the order tasks are started, etc, there needs to be a mechanism for locating device drivers or just other tasks to talk to. The kernel automatically creates port 1 (the uberport) and gives ownership of this port to the first task created. This task is the namer, a service that allows drivers to register ports under specific names and allows tasks that wish to find these drivers to look up the port that is associated with a specific name.
The utility functions listed below are part of the OpenBLT libc and provide convenient ways to access the namer.
int namer_newhandle(void); int namer_delhandle(int nh); int namer_register(int nh, int port, char *name); int namer_find(int nh, char *name); Connections between Connectionless ports
Ports in OpenBLT are connectionless. Each port_send() specifies a source and destination port. Tasks may send from any port they own to any port they are allowed to write to. Ports may be writable to be any other port or restricted to just one other port. [ multiple port restrict lists will be in a later version ]
The general procedure for providing a service in OpenBLT is
Create an unrestricted port Register this port with the namer under a descriptive name (eg ne2000, console, etc) When one task (a client) wishes to connect to a service (the server), the following occurs: The client looks up the servers unrestricted port using the namer The client creates a port that is restricted to the server's unrestricted port The client sends a message to the servers unrestricted port requesting a connection If the server accepts the connection, it will send a response back to the client's port notifying it that the connection is accepted and possibly providing a different port than the unrestricted port that the client should send future messages too. [ a toolkit in libc will streamline this process ] Handling Devices
The security model in OpenBLT is still being designed. Most likely there will be a task that is a "security manager" which will be able to give permissions (for accession IO devices, etc) to tasks that need them. The two syscalls described below are sufficient for simple device drivers in the initial (overly trusting) version of OpenBLT:
void os_handle_irq(int irq); Ask the kernel to make this task eligible to process the specified IRQ. The kernel will also allow the task IO access.
void os_sleep_irq(void); Suspend this task until the IRQ it is registered to handle is triggered. When that IRQ is triggered, the task will be scheduled (preempting the currently running task). The IRQ will be ignored by the kernel when the task is no sleeping in os_sleep_irq().
Startup On startup, OpenBLT creates initializes its internal memory and resource management, sets up kernel space at the 2gb line (0x8000000), creates an address space and task for each userland program that it can find. An idle task is created (to give the scheduler something to schedule, should all other tasks be sleeping on something). The kernel schedules the first task in the run queue to execute and the fun starts.