mirror of
git://projects.qi-hardware.com/iris.git
synced 2024-11-17 01:24:39 +02:00
219 lines
11 KiB
TeX
219 lines
11 KiB
TeX
\documentclass{shevek}
|
|
\begin{document}
|
|
\title{Overview of my kernel}
|
|
\author{Bas Wijnen}
|
|
\date{\today}
|
|
\maketitle
|
|
\begin{abstract}
|
|
This document briefly describes the inner workings of my kernel, including the
|
|
reasons for the choices that were made. It is meant to be understandable (with
|
|
effort) for people who know nothing of operating systems. On the other hand,
|
|
it should also be readable for people who know about computer architecture, but
|
|
want to know about this kernel.
|
|
\end{abstract}
|
|
|
|
\tableofcontents
|
|
|
|
\section{Operating systems}
|
|
This section describes what the purpose of an operating system is, and defines
|
|
what I call an ``operating system''\footnote{Different people use very
|
|
different definitions, so this is not as trivial as it sounds.}. It also goes
|
|
into some detail about microkernels and capabilities. If you already know, you
|
|
can safely skip this section. It contains no information about my kernel.
|
|
|
|
\subsection{The goal of an operating system}
|
|
In the 1980s, a computer could only run one program at a time. When the
|
|
program had finished, the next one could be started. This follows the
|
|
processor itself: it runs a program, from the beginning until the end, and
|
|
can't run more than one program simultaneously\footnote{Multi-core processors
|
|
technically can run multiple programs simultaneously, but I'm not talking about
|
|
those here.}. In those days, an \textit{operating system} was the program that
|
|
allowed other programs to be started. The best known operating systems were
|
|
called \textit{Disk operating system}, or \textit{DOS} (of which there were
|
|
several).
|
|
|
|
At some point, there was a need for programs that would ``help'' other programs
|
|
in some way. For example, they could provide a calculator which would pop up
|
|
when the user pressed a certain key combination. Such programs were called
|
|
\textit{terminate and stay resident} programs, or TSRs. This name came from
|
|
the fact that they terminated, in the sense that they would allow the next
|
|
program to be run, but they would stay resident and do their job in the
|
|
background.
|
|
|
|
At some point, people wanted to de \textit{multitasking}. That is, multiple
|
|
``real'' programs should run concurrently, not just some helpers. The easiest
|
|
way to implement this is with \textit{cooperative multitasking}. Every program
|
|
returns control to the system every now and then. The system switches between
|
|
all the running programs. The result is that every program runs for a short
|
|
time, several times per second. For the user, this looks like the programs are
|
|
all running simultaneously, while in reality it is similar to a chess master
|
|
playing simultaneously on many boards: he really plays on one board at a time,
|
|
but switches a lot. On such a system, the \textit{kernel} is the program that
|
|
chooses which program to run next. The \textit{operating system} is the kernel
|
|
plus some support programs which allow the user to control the system.
|
|
|
|
On a system where multiple programs all think they ``own'' the computer, there
|
|
is another problem: if more than one program tries to access the same device,
|
|
it is very likely that at least one of them, and probably both, will fail. For
|
|
this reason, \textit{device drivers} on a multitasking system must not only
|
|
allow the device to be controlled, but they must also make sure that concurrent
|
|
access doesn't fail. The simplest way to achieve this is simply to disallow
|
|
it (let all operations fail that don't come from the first program using the
|
|
driver). A better way, if the device can handle it, is to somehow make sure
|
|
that both work.
|
|
|
|
There is one problem with cooperative multitasking: when one program crashes,
|
|
or for some other reason doesn't return control to the system, the other
|
|
programs stop running as well. The solution to this is \textit{preemptive
|
|
multitasking}. This means that every program is interrupted every now and
|
|
then, without asking for it, and the system switches to a different program.
|
|
This makes the kernel slightly more complex, because it must take care to store
|
|
every aspect of the running programs. After all, the program doesn't expect to
|
|
be interrupted, so it can't expect its state to change either. This shouldn't
|
|
be a problem though. It's just something to remember when writing the kernel.
|
|
|
|
Concluding, every modern desktop kernel uses preemptive multitasking. This
|
|
requires a timer interrupt. The operating system consists of this kernel, plus
|
|
the support programs that allow the user to control the system.
|
|
|
|
\subsection{Microkernel}
|
|
Most modern kernels are so-called \textit{monolithic} kernels: they include
|
|
most of the operating system. In particular, they include the device drivers.
|
|
This is useful, because the device drivers need special attention anyway, and
|
|
they are very kernel-specific. Modern processors allow the kernel to protect
|
|
access to the hardware, so that programs can't interfere with each other. A
|
|
device driver which doesn't properly ask the kernel will simply not be allowed
|
|
to control the device.
|
|
|
|
However, adding device drivers and everything that comes with them
|
|
(filesystems, for example) to the kernel makes it a very large program.
|
|
Furthermore, it makes it an ever-changing program: as new devices are built,
|
|
new drivers must be added. Such a program can never become stable and
|
|
bug-free.
|
|
|
|
Conceptually much nicer is the microkernel. It includes the minimum that is
|
|
needed for a kernel, and nothing more. It does include task switching and some
|
|
mehtod for tasks to communicate with each other. It also ``handles'' hardware
|
|
interrupts, but all it really does is passing them to the device driver, which
|
|
is mostly a normal program. Some microkernels don't do memory manangement
|
|
(deciding which programs get how much and which memory), while others do.
|
|
|
|
The drawback of a microkernel is that it requires much more communication
|
|
between tasks. Where a monolithic kernel can serve a driver request from a
|
|
task directly, a microkernel must pass it to a device driver. Usually there
|
|
will be an answer, which must be passed back to the task. This means more task
|
|
switches. This doesn't need to be a big problem, if task switching is
|
|
optimized: because of the simpler structure of the microkernel, it can be much
|
|
faster at this than a monolithic kernel. And even if the end result is
|
|
slightly slower, in my opinion the stability is still enough reason to prefer a
|
|
microkernel over a monolitic one.
|
|
|
|
Summarizing, a microkernel needs to do task switching and inter-process
|
|
communication. Because mapping memory into an address space is closely related
|
|
to task switching, it is possible to include memory management as well. The
|
|
kernel must accept hardware interrupts, but doesn't handle them (except the
|
|
timer interrupt).
|
|
|
|
\subsection{Capabilities}
|
|
Above I explained that the kernel must allow processes to communicate. Many
|
|
systems allow communication through the filesystem: one process writes to a
|
|
file, and an other process reads from it. This implies that any process can
|
|
communicate with any other process, if they only have a place to write in the
|
|
filesystem, where the other can read.
|
|
|
|
This is a problem because of security. If a process cannot communicate with
|
|
any part of the system, except the parts that it really needs to perform its
|
|
operation, it cannot leak or damage the other parts of the system either. The
|
|
reason that this is relevant is not that users will run programs that try to
|
|
ruin their system (although this may happen as well), but that programs may
|
|
break and damage random parts of the system, or be taken over by crackers. If
|
|
the broken or malicious process has fewer rights, it will also do less damage
|
|
to the system.
|
|
|
|
This leads to the goal of giving each process as little rights as possible.
|
|
For this, it is best to have rights in a very fine-grained way. Every
|
|
operation of a driver (be it a hardware device driver, or just a shared program
|
|
such as a file system) should have its own key, which can be given out without
|
|
giving keys to the entire driver (or even multiple drivers). Such a key is
|
|
called a capability.
|
|
|
|
Some operations are performed directly on the kernel itself. For those, the
|
|
kernel can provide its own capabilities. Processes can create their own
|
|
objects which can receive capability calls, and capabilities for those can be
|
|
generated by them. Processes can copy capabilities to other processes, if they
|
|
have a channel to send them (using an existing capability). This way, any
|
|
operation of the process with the external world goes through a capability, and
|
|
only one system call is needed, namely \textit{invoke}.
|
|
|
|
This has a very nice side-effect, namely that it becomes very easy to tap
|
|
communication of a task you control. This means that a user can redirect
|
|
certain requests from programs which don't do exactly what is desired to do
|
|
nicer things. For example, a program can be prevented from opening pop-up
|
|
windows. In other words, it puts control of the computer from the programmer
|
|
into the hands of the user (as far as allowed by the system administrator).
|
|
This is a very good thing.
|
|
|
|
\section{Kernel objects}
|
|
This section describes all the kernel objects, and the operations that can be
|
|
performed on them.
|
|
|
|
\subsection{Memory}
|
|
A memory object is a container for storing things. All objects live inside a
|
|
memory object. A memory object can contain other memory objects, capabilities,
|
|
receivers, threads and pages.
|
|
|
|
A memory object is also an address space. Pages can be mapped (and unmapped).
|
|
Any Thread in a memory object uses this address space while it is running.
|
|
|
|
Every memory object has a limit. When this limit is reached, no more pages can
|
|
be allocated for it (including pages which it uses to store other objects).
|
|
Using a new page in a memory object implies using it in all ancestor memory
|
|
objects. This means that setting a limit which is higher than the parent's
|
|
limit means that the parent's limit applies anyway.
|
|
|
|
Operations on memory objects:
|
|
\begin{itemize}
|
|
\item
|
|
\end{itemize}
|
|
|
|
\subsection{Page}
|
|
A page can be used to store user data. It can be mapped into an address space (a memory object). Threads can then use the data directly.
|
|
|
|
A page has no operations of itself; mapping a page is achieved using an
|
|
operation on a memory object.
|
|
|
|
\subsection{Receiver}
|
|
A receiver object is used for inter-process communication. Capabilities can be
|
|
created from it. When those are invoked, the receiver can be used to retrieve
|
|
the message.
|
|
|
|
Operations on receiver objects:
|
|
\begin{itemize}
|
|
\item
|
|
\end{itemize}
|
|
|
|
\subsection{Capability}
|
|
A capability object can be invoked to send a message to a receiver or the
|
|
kernel. The owner cannot see from the capability where it points. This is
|
|
important, because the user must be able to substitute the capability for a
|
|
different one, without the program noticing.
|
|
|
|
Operations or capability objects:
|
|
\begin{itemize}
|
|
\item
|
|
\end{itemize}
|
|
|
|
\subsection{Thread}
|
|
Thread objects hold the information about the current state of a thread. This
|
|
state is used to continue running the thread. The address space is used to map
|
|
the memory for the thread. Different threads in the same address space have
|
|
the same memory mapping. All threads in one address space (often just one)
|
|
together are called a process.
|
|
|
|
Operations on thread objects:
|
|
\begin{itemize}
|
|
\item
|
|
\end{itemize}
|
|
|
|
\end{document}
|