Tyros is an operating system based on the Neutronix microkernel. What does term 'microkernel' mean? It means that only a small part of the system works in a supervisor mode (ring 0 on an x86). This part of the system is called a microkernel. All other services usually found in classic operating systems (such as filesystem, networking etc) are implemented as standalone user-space processes, and they are called servers. Thus, a microkernel-based operating system is a combination of the microkernel and various servers.
Why yet another operating system?
Classic operating system architectures (both monolithic and microkernel ones) are not flexible enough. It is almost impossible for a monolithic system designer to move some services from kernel to user mode. And generally it is not easy to implement some of the functionality of microkernel servers in the microkernel itself.
There is only one way to solve this problem: neutrality. The kernel of a flexible operating system should not be "too monolithic" or "too micro". It can have any architecture "de jure", but developer should not decide himself what to implement as a part of the kernel and what to load as a user process. This should be user's choice
Thus, our primary goal is to make the kernel architecture flexible enough to allow it to load the same service as a part of the kernel (and have all advantages of the monolithic design) or as a standalone user server (and have all advantages of the microkernel design)
In ideal, the same service should have three different ways of working:
And there should not be need of having three different variant of the service to accomplish this goal ;)
What has been already done?
Currently (Jun 27, 2003) we have the working microkernel with basic functionality implemented (multitasking, multithreading, virtual memory management, signals and messages - everything it needs except synchronization primitives) which is capable of running on IA-32 (x86) computers with 8+ Mb RAM.
Tyros is logically divided into three parts:
The first program that receives control after the bootloader had switched into protected mode is called Tyros Kernel Loader. Its task is to create kernel image in memory thus making it capable of executing (the kernel is in ELF format). After this the control is passed to HAL entry point (hal_entry()
func). Then hal_main()
is called which initializes internal kernel facilities:
Then core_entry()
is executed which starts up some critical servers (such as nameserver and debugging console)
The terms 'process' and 'task' are the same, but the first one is generally used when referring to a process in CORE and the second one is used when referring to a process in HAL
Though, each process is identified by its TID (task identifier - that's because this identifier is the same for the HAL and for the CORE)
Primary IPC primitives of the Neutronix are messages. Each process receives its mailbox at startup which can be used to receive messages. Messages can only be send from one process to another (no ports etc) and the receiver is identified by TID.
There are three system calls which are used for sending and receiving messages. They are:
send
- synchronous (blocking) message sendrecv
- asynchronous (non-blocking) message receivewait
- waiting for a messageThe combination of wait
and recv
is generally used to receive a message in synchronous mode
A message is a message_t
structure which consists of:
int header
- the header of the message. If the message is very short (<= 4 bytes) it can be entirely sent in headervoid *pdata
- pointer to the message data. When the message is sent between different address spaces, this pointer is automatically correctedint size
- size of the message data in bytesThe Neutronix microkernel gives no special meaning to the contents of the message - this meaning is only defined by the sender and the receiver
User-space process can install its own interrupt handler (given it has enough privelege). This is accomplished through attach
system call. The only type of attaching supported at present is AT_REALTIME
- that means that control will be immediately passed to attached handler AND interrupts will be disabled during its execution (I know this is a major stability problem, but...)
Uninstalling of interrupt handler is done using detach
system call
Each process is granted its own virtual address space. Its system area is first 2 gigabytes (0x00000000 - 0x7FFFFFFF) and its user area is high two gigabytes (0x80000000 - 0xFFFFFFFF). System areas of all processes are the same and usually user processes have no access to system area (it becomes possible on a ring3->ring0 switch, which occurs during system call)
High (user) memory areas are different between processes thus guaranting memory protection
The only way for a process to receive access to the system memory area is a system memory request. A process, given it has enough privelege, can request a part of system memory area to be mapped into his user virtual address space. This is accomplished through req_sys_mem
system call
The Neutronix microkernel supports a simple, but very efficient way of protection. Each process is given its privelege level which may be 0 (supervisor) or non-zero (user). Only supervisors are capable of installing theirs interrupt handlers or requesting system memory and only supervisors can spawn other supervisors. Process' privelege level may be equal or less than its parent's and each thread is given privelege level of its parent.
Such a simple security system allows to implement much more complex systems using only servers and not touching the microkernel itself
© 2003 Andrew 'Lonesome' Ptitsyn <lonesome AT users DOT sourceforge.net>