Kernel Specification
Last update: 2000-12-20
Copyright © 2000 Luca Abeni
Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.2 or any later version published by the Free Software Foundation; with no Invariant Sections, with no Front-Cover Texts, and with no Back-Cover Texts. A copy of the license is included by reference.
The FreeDOS-32 Kernel Specification describes the core of FreeDOS-32, the FD32 kernel. The goal of the FD32 kernel is to provide a very simple and basic execution environment to application programs and drivers. Moreover, it is responsible for the booting process, and it provides a set of basic functionality. This document describes the kernel execution environment, the boot process, the functionality of the kernel, and the basic interface used to export them. Everything else (the system structure, the drivers interface, and so on) is described into separate documents: see Document Organization in the System Specification.
Kernel Environment
As previously stated, the FD32 kernel is the core system component that sets up a basic execution environment, called the Kernel Environment, in which applications can run. Due to the "non-kernel nature" of FreeDOS-32 (see System Specification) and in order to enable a modular structure, the Kernel Environment can be modified, enhanced or even destroyed by drivers, wrappers and application programs.
In the following of this section, the Kernel Environment will be described, in terms of CPU settings and memory model:
- 32 bit Protect Mode (of course...)
- Flat Kernel Address Space
CPU Settings
The Kernel Environment is based on the simplest possible CPU settings that permit to execute in Protect Mode (PM):
- Paging disabled
- No multitasking
- Applications can run at ring 0, 1, 2 or 3: no protection
Again, it is worth to note that an application or a driver can change these settings, enabling for example multitasking or memory protection.
Memory Model
As previously stated, the Kernel Environment is based on a Flat Address Space; in order to specify the meaning of this term, we need some definitions.
- Physical Address: a 32 bit number, used by the processor chip to access physical memory. I think I am quite correct saying that this is the address that is sent on the CPU bus.
- Logical Address: a 16:32 bit number (but we can consider only the 32 bit part) used in programs to refer a memory location. It is composed by a 16 bit selector and a 32 bit offset. When we talk about 32 bit logical addresses, we are not considering the selector. Programs executes using logical addresses.
- Address Space: a mapping between logical addresses and physical addresses.
- Flat Address Space: a 1:1 mapping between logical addresses and physical addresses. In practice, physical address = logical address. To be more formal, we can say that a Flat Address Space is realized using two segments (a data and a code segment) S1 and S2, so that the logical addresses S1:x and S2:x are remapped in the physical address x.
- Linear Address Space: similar to the Flat Address space, but introduces an offset B!=0 (also called base). In practice, logical address x is mapped in physical address B+x. A Flat Address space is a Linear Address space with B=0.
Being the Kernel Environment based on a Flat Address space, the kernel can access memory using physical addresses (since physical address = logical address).
Since we are in a DOS system (see System Specification), each application is free to do whatever it wants with GDT & c, modifying it to create new segments (with new base values) and hence creating new mappings. In this way, a FD32 application can create a Linear Address space or a multi-segment address space.
The fact that the Kernel Environment is based on a Flat Address Space, does not mean that FreeDOS 32 does not support a segmented memory model: segmentation is simply moved from the kernel to the application domain. If an application needs more segments, it can create and use them as it prefers. Some DPMI calls could also be used to do this, but an application can also directly modify the GDT (it is deprecated, but possible... Of course, this application will not run at ring 1/2/3, and will not be supported by a multithreading/multitasking/virtual memory driver).
Loaders/Wrappers
FreeDOS-32 native applications can run in the kernel address space, in order to use the 1:1 mapping. In order to do this, an executable has to be relocated on loading, or has to be linked at a free physical address. If an application is linked at a non free physical address (for example, DJGPP links applications at 0x1000 by default) and cannot be relocated (because the executable does not contain relocation information), it cannot run in the Kernel Environment, and a loader is needed to run it. This is the idea:
- Problem
An application A is linked, for example, at logical address 0x1000, hence it has to be loaded at that logical address. This can be a problem in the Kernel Environment, since it is based on a Flat Address Space, and logical address = physical address, but physical memory at 0x1000 is not free. - Solution
A loader L is a FD32 native application that starts running in the Kernel Environment (Flat Address Space). It creates a new Linear Address Space with base B, where B is a free physical address (or Flat Address Space address, it is the same). To be precise, the memory region from B to B+Y (where Y is the amount of memory needed by A) must be free. After creating the Linear Address Space, the loader copies A into it and passes the control to A.
Note that the loader/wrapper approach is more general, and can be used to load'n'run RM applications and (more important) stubs in VM86 mode.
Kernel Characteristics
In addition to what already said, the FD32 kernel:
- Shall be fully reentrant. This mean that system calls can safely "intersect" other system calls. In other words, any system call can be called at any time, no matter if another system call is currently executing. In a monoprogrammed system, this allows the kernel to call itself (a syscall calling another syscall?), but also allows TSR or drivers or interrupt handlers to call the kernel through a regular system call. In a multiprocess or multiprocessor system, kernel reentrancy opens new perspectives... (like a fully preemptive kernel model, a multithreaded kernel model and so on).
- Shall be SMP safe. This basically means that atomic sections must be explicitly enforced using a spinlock mechanism.
- Shall provide non-blocking calls (for asynchronous I/O).
Boot Process
The FD32 kernel will be MultiBoot compliant, allowing to be loaded and booted by a MultiBoot compliant bootloader, such as GNU GRUB. For a complete description of the MultiBoot standard, see the GRUB documentation. The only important thing to be noted here is that the execution environment used by a MultiBoot compliant loader to start the kernel coincides with the FD32 Kernel Environment.
At boot time, the boot loader loads the kernel, sets up the Kernel Environment,
and pass the control to the FD32 kernel. Moreover, the boot loader can also load
some modules: this feature can be used for loading some drivers at boot time,
for loading the first FD32 application (likely the shell), and for passing some
text configuration file to the kernel (config.sys?) at boot time. Note that the
IDE or floppy driver MUST be loaded at boot time, otherwise the kernel will not
be able to load anything.
In addition, the FD32 kernel can also be booted from 16 bit DOS, using a custom eXtender. FreeDOS-32 will leave all the 16 bit DOS memory untouched and will be able to return to 16 bit DOS at exit.
Functionality
As said, the FD32 kernel will provide only a minimum set of basic functionality. That is, the functionality needed to load and execute drivers; for this reason, some functionality can be added/modified while defining the Driver Specification.
The most important functionalities implemented by the FD32 kernel are:
- Memory management
- Executable files parsing (at least one executable format)
- IDT/GDT/hw structures handling
Memory Management
The FD32 kernel must manage the physical memory, allowing applications to allocate and release chunks of physical memory directly (some way to allocate memory starting at a specified physical address is needed). Physical memory management (PMM) can be implemented using a list based manager, and initializing the list of free memory areas at boot time according to the information passed by the boot loader. Some way to manage virtual memory could be also needed.
Executable Formats
The FD32 kernel must provide a parser for at least one executable format (otherwise, it is impossible to run programs or load drivers/wrappers/loaders). Other parsers/loaders can be implemented as FD32 native programs.
Since every application starts executing in the Kernel Environment, the kernel must be able to place it at a free physical address (see 1.3 Loaders/Wrappers). For this reason the executable format should provide all the needed relocation information. COFF does not seem to provide relocation information in the executables, ELF seems to provide them. Hence, an ELF parser in the kernel seems to be the right choice.
This does not mean that DJGPP applications are not supported by FD32. Of course, they are (as all the DPMI compliant applications). They will be loaded in a Linear Address Space by an appropriate loader. Note that the loader can be embedded in the shell application.
Interface
This section describes the interface used by FD32 for exporting the functionalities described in the previous section.
DPMI Interface
The reference interface to be supported by the FD32 kernel is DPMI. DPMI is the DOS Protected Mode Interface. We refer to it as a programming interface, not as a server, nor as a bunch of code, nor as an implementation constraint, nor as a system layer nor as anything else. For a complete description of the DPMI interface, see the DPMI specifications.
Since the DPMI standard states that all the unhandled interrupts shall be reflected to Real Mode, supporting it also implies to support the old DOS/BIOS INT interface. Providing DOS/BIOS INTs does not necessarily mean to implement these services as DOS does, or to reflect BIOS calls to the real BIOS in RM. FD32 only needs to provide handlers for the DOS/BIOS interrupts (example: int 0x21, int 0x10, int 0x13, ...) that behave like the DOS/BIOS handlers. The services exported by these interrupt handlers can be implemented in some different ways, and the modular drivers mechanism provides the freedom needed for doing this.
The general idea is to implement the BIOS calls with loadable drivers. In this way the system development can be sped up: in a first stage, some drivers that simply reflect the call to the real BIOS will be provided. When the kernel will be up and working (using these "traditional" drivers), fast superoptimized 32 bit drivers will be developed to replace the "BIOS-based" ones.
FD32 Native Interface
In addition to the DPMI/DOS/BIOS interface (that is the first goal), the FD32 kernel will provide some additional system calls (FD32 Extended Interface) to export characteristics and functionalities not provided by the classical DPMI interface.
Moreover, a FD32 native interface, the Flat Interface from now, will be provided, in order to implement more efficient system calls for embedded developers and similar. The Flat Interface still has to be defined, but the calling mechanism has already be decided: it will be based on calls and not on interrupts. At load time, a native application using the Flat Interface will be dynamically linked with the kernel (using the exported/imported symbols provided by the ELF or PE executable format), hence invoking a system call will result in a near call. In order to use the flat interface, an application must run in the Kernel Environment (that is to say, the Flat Interface can be used only by native applications).
It is very likely that for the drivers the internal interface will be the same as the exported Flat Interface.