Meaty Skeleton: Difference between revisions

[unchecked revision][unchecked revision]
Content deleted Content added
Initial proof-of-concept draft
 
No edit summary
 
(88 intermediate revisions by 21 users not shown)
Line 1:
{{FirstPerson}}
{{Rating|1}}{{Template:Kernel designs}}
{{You}}
{{BeginnersWarning}}
{{Rating|1}}
{{Kernel designs}}
 
This tutorial continues from [[Bare Bones]] and creates a minimal template
'''Note: This is a first initial draft meant to simply be an example of the kind of tutorial I have in mind. Be sure to leave me feedback if you have any opinion on this matter. The tutorial has not been properly spell-checked and such yet.'''
operating system in the [[Stan Dard]] style suitable for further modification or
 
as inspiration for your initial operating system version. The [[Bare Bones]]
In this tutorial we continue from [[Bare Bones]] and create a minimal template
tutorial only gives you the absolutely minimal code to demonstrate how to
operating system suitable for further modification or as inspiration for your
correctly cross-compile a kernel, however this is unsuitable as an example
initial operating system version. The [[Bare Bones]] tutorial only gives you the
operating system. Additionally, this tutorial implements neccesary ABI features
absolutely minimal code to demonstrate how to correctly cross-compile a kernel,
needed to satisfy the ABI and compiler contracts to prevent possible mysterious
however this is unsuitable as an example operating system. Additionally, this
errors.
tutorial implements neccesary ABI features needed to satisfy the ABI and
compiler contracts to prevent possible mysterious errors.
 
This tutorial also serves as the initial template tutorial on how to
Line 15 ⟶ 18:
documentation explicitly states that libgcc requires the freestanding
environment to supply the <tt>memcmp</tt>, <tt>memcpy</tt>, <tt>memmove</tt>,
and <tt>memset</tt> functions, as well as <tt>abort</tt> on selectsome platforms. We
will satisfy this requirement by creating a special kernel C library (libk) that
contains the parts of the user-space libc that are ''freestanding'' (doesn't
Line 26 ⟶ 29:
a manner that will continue to serve you well for the foreseeable future. This
serves as both inspiration and as an example for those that wish to something
different, while as a base for the rest. The tutorial does embed a few important
concepts into your operating system such as the existence of a libc, as well as
indirectly other minor Unix and ABI semantics. Adapt what you wish from this
tutorial. Note that the shell script and MadeMake-based build system constructed in
this tutorial is meant for Unix systems, though it should work in Cygwin. There
is no pressing need to make this portable across all operating systems as this
is just an example.
 
We will name this new example operating system <tt>myos</tt>. This is just a
placeholder and you should replace all occurencesoccurrences of <tt>myos</tt> with what you
decide to call your operating system.
 
Line 50 ⟶ 53:
 
== Building a Cross-Compiler ==
:''{{Main article: [[|GCC Cross-Compiler]], [[|Why do I need a Cross Compiler?]]}}
 
You ''must'' use a [[GCC Cross-Compiler]] in this tutorial as in the
[[Bare Bones]] tutorial. You should use the '''i586i686-elf''' target in your
cross-compiler, though any <tt>ix86-elf</tt> target (but no less than i386) will
do fine for our purposes here.
 
You ''must'' configure your cross-binutils with the <tt>--with-sysroot</tt> option, otherwise linking will mysteriously fail with the ''this linker was not configured to use sysroots'' error message. If you forgot to configure your cross-binutils with that option, you'll have to rebuild it, but you can keep your cross-gcc.
== System Roots ==
 
== Dependencies ==
The system root (''sysroot'') for your operating system is simply a directory
 
into which your newly-built operating system will be installed. For instance,
You will need these dependencies in order to complete this tutorial:
the standard library and the kernel will install their headers into the sysroot,
 
but all the user-space programs for your OS will also be installed there (when
* i686-elf toolchain, as discussed above.
you get that far). This directory is essentially the root filesystem for your
* GRUB, for the grub-mkrescue command, along with the appropriate runtime files.
operating system, at least for the purpose of cross-development. When you
* Xorriso, the .iso creation engine used by grub-mkrescue.
include headers and link against libraries, your cross-compiler will look inside
* GNU make 4.0 or later.
the system root for those files.
* Qemu, optionally for testing the operating system.
 
This tutorial requires a GNU/Linux system, or a similar enough system. The BSD systems may almost work. OS X is not supported but can possibly be made to work with some changes. Windows is not supported, but Windows environments like Cygwin and Windows Subsystem For Linux (WSL) might work.
 
=== Debian-family Users ===
 
Install the i686-elf toolchain as described above and then install the packages <tt>xorriso grub-pc-bin</tt>.
 
== System Root ==
 
Normally when you compile programs for your local operating system, the compiler locates development files such as headers and libraries in system directories such as:
 
:<tt>/usr/include</tt>
:<tt>/usr/lib</tt>
 
These files are of course not usable for your operating system. Instead you want to have your own version of these directories that contains files for your operating system:
 
:<tt>/home/bwayne/myos/sysroot/usr/include</tt>
:<tt>/home/bwayne/myos/sysroot/usr/lib</tt>
 
The <tt>/home/bwayne/myos/sysroot</tt> directory acts as a fake root directory for your operating system. This is called a system root, or ''sysroot''.
 
You can think of the sysroot as the root directory for your operating system. Your build process will build each component of your operating system (kernel, standard library, programs) and gradually install them into the system root. Ultimately the system root will be a fully functional root filesystem for your operating system, you format a partition and copy the files there, add the appropriate configuration files, configure a bootloader to load the kernel from there, and use your harddisk driver and filesystem driver to read the files from there. The system root is thus a temporary directory that will ultimately become the actual root directory of your operating system.
 
In this example the cross system root is located as <tt>sysroot/</tt>, which is
Line 75 ⟶ 101:
<tt>sysroot/boot</tt> directory.
 
We already use system roots because it will make it smoother to add a user-space when you get that far. This scheme is very convenient when you later [[Cross-Porting_Software|Port Third-Party Software by Cross-Compiling It]].
The <tt>-elf</tt> targets doesn't properly support system roots as these targets
 
have no actual user-space or a directory structure. This means that your cross
The <tt>-elf</tt> targets have no user-space and are incapable of having one. We configured the compiler with system root support, so it will look in <tt>${SYSROOT}/usr/lib</tt> as expected. We prevented the compiler from searching for a standard library using the --without-headers option when building <tt>i686-elf-gcc</tt>, so it will ''not'' look in <tt>${SYSROOT}/usr/include</tt>. (Once you add a user-space and a libc, you will configure your custom cross-gcc with <tt>--with-sysroot</tt> and it will look in <tt>${SYSROOT}/usr/include</tt>. As a temporary work-around until you get that far, we fix it by passing <tt>-isystem=/usr/include</tt>).
compiler doesn't look at our <tt>sysroot/usr/include</tt> and
 
<tt>sysroot/usr/lib</tt> directories. We override in <tt>config.sh</tt> by
You can change the system root directory layout if you wish, but you will have to modify some Binutils and GCC source code and tell them [[OS Specific Toolchain|what your operating system is]]. This is advanced and not worth doing until you add a proper user-space. Note that the cross-linker currently looks in <tt>/lib</tt>, <tt>/usr/lib</tt> and <tt>/usr/local/lib</tt> by default, so you can move files there without changing Binutils. Also note that we use the <tt>-isystem</tt> option for GCC (as it was configured without a system include directory), so you can move that around freely.
passing additional compiler options that effectively does the same thing. We
will use the builtin toolchain support for system roots (the
<tt>--with-sysroot</tt> configure option when building binutils and gcc) in a
later tutorial when you add a proper user-space. We elect to simulate a system
root already now as it will make it smoother to add a user-space when you get so
far.
 
== System Headers ==
Line 92 ⟶ 113:
cross-compile your operating system. This is <tt>useful</tt> as it allows you to
provide the compiler a copy of your headers before you actually compile your
system. You will [[Hosted_GCC_Cross-Compiler#Sysroot_Headers|need to provide the
system. You will need this when we add proper system root support once it is
standard library headers]] when you build a [[Hosted GCC Cross-Compiler]] in the
time to add a proper user space.
future that is capable of an user-space.
 
Note how your cross-compiler comes with a number of fully freestanding headers
Line 99 ⟶ 121:
types and macros that are useful. Your kernel standard library will supply a
number of useful functions (such as <tt>strlen</tt>) that doesn't require system
calls and isare freestanding except they need an implementation somewhere.
 
== Makefile Design ==
Line 108 ⟶ 130:
are used, while a default is used if the user has no opinion. The makefiles also
make sure that particular options are always in CFLAGS. This is done by having
two phases in the makefiles: Oneone that setsets a default value and one that adds
mandatory options the project makefile requires:
 
<syntaxhighlight lang="make">
<pre>
# Default CFLAGS:
CFLAGS?=-O2 -g
Line 117 ⟶ 139:
# Add mandatory options to CFLAGS:
CFLAGS:=$(CFLAGS) -Wall -Wextra
</syntaxhighlight>
</pre>
 
== Architecture Directories ==
Line 139 ⟶ 161:
from the main kernel.
 
GNU GRUB is used as the bootloader and the kernel uses multibootMultiboot as in the
[[Bare Bones]] tutorial.
 
The kernel implements the correct way of [[Calling_Global_Constructors|invoking
global constructors]] (useful for C++ code and C code using
<tt>__attribute__((constructor))</tt>. The kernelbootstrap initializationassembly iscalls done in two<tt>_init</tt>
which invokes all the global constructors. These are invoked very early in the
steps: First the <tt>kernel_early()</tt> function is called which sets up the
boot without any specific ordering. You should only use them to initialize
essential kernel features (such as the kernel log). The bootstrap assembly then
global variables that could not be initialized at runtime.
proceeds to call <tt>_init</tt> (which invokes all the global constructors) and
finally invoke <tt>kernel_main()</tt>.
 
The special <tt>__is_myos_kernel</tt> macro lets the source code detect whether
it is part if the kernel.
 
The kernel is built withspecial <tt>-fbuiltin__is_kernel</tt>, whichmacro promiseslets thatthe thesource code Cdetect standardwhether
it is part of the kernel.
functions doesn't do anything unusual. This lets the compiler optimize certain
compile-time constant expressions such as <tt>strlen("foo")</tt> to <tt>3</tt>.
However, this can have unexpected consequences as a function call can be changed
into a call to another function. For instance, GCC likes to rewrite
<tt>printf("Hello, World!\n")</tt> into <tt>puts("Hello, World!")</tt>. This is
not a terrible problem if you add the needed functions to libk as you need them,
or you can simply remove <tt>-fbuiltin</tt> as <tt>-ffreestanding</tt> turns it
off by default.
 
== libc and libk Design ==
 
The libc and libk isare actually two versions of the same library, which is stored
in the directory <tt>libc/</tt>. The standard library is split into two
versions: freestanding and hosted. The difference is that the freestanding
Line 177 ⟶ 188:
of <tt>strlen</tt> and such.
 
This example doesn't come with ana usable libc. It compiles a libc.a that is
entirely useless, except being a skeleton we can build on when we add user-space
in a later tutorial.
Line 186 ⟶ 197:
from <tt>sys/stat.h</tt> would be in <tt>libc/sys/stat/stat.c</tt>.
 
The standard headers usesuse a BSD-like scheme where <tt>sys/cdefs.h</tt> declares
a bunch of useful preprocessor macros meant for internal use by the standard
library. For instance, allAll the function prototypes are wrapped in
<tt>__BEGIN_DECLSextern "C" {</tt> and <tt>__END_DECLS}</tt> such that C++ code can correctly
link against libc (as libc doesn't use C++ linkage). Note also how the compiler
provides the internal keyword __restrict unconditionally (even in C89) mode,
which is useful for adding the restrict keyword to function prototypes even when
compiling code in pre-C99 or C++ mode.
 
The special <tt>__is_myos_libc__is_libc</tt> macro lets the source code detect whether
it is part ifof the libc. and <tt>__is_libk</tt> lets the source code detect
whether it's part of the libk binary.
 
This example codescomes with a small number of standard functions that serve as
examples and serve to satisfy ABI requirements. Note that the <tt>printf</tt>
function included is very minimal and intentionally doesn't handle most common features, this
features.
is intentional.
 
== Source Code ==
 
<!---
=== kernel ===
 
Greetings, editors! It's not desirable to have parts of this wiki not actually be on this wiki, e.g. a code dump hosted on another service. To mitigate this, the source code is hosted here primarily. It's inconvenient and error-prone to manually copy it, though, so I've set up a git repository that people can easily clone. If you make any changes to the article, ping me, I'll update the git repository ASAP and update the git revision hash below. If you wish to pre-empt control (perhaps if I become unavailable) you can simply fork the git repository and update the links accordingly. --~~~~
==== kernel/include/kernel/vga.h ====
 
--->
<source lang="c">
#ifndef _KERNEL_VGA_H
#define _KERNEL_VGA_H
 
You can easily download the source code using [[Git]] from the [https://gitlab.com/sortie/meaty-skeleton Meaty Skeleton Git repository]. This is preferable to doing a manual error-prone copy, as you may make a mistake or whitespace may get garbled due to bugs in our syntax highlighting. To clone the git repository, do:
#include <stdint.h>
 
<syntaxhighlight lang="bash">
static const uint8_t COLOR_BLACK = 0;
git clone https://gitlab.com/sortie/meaty-skeleton.git
static const uint8_t COLOR_BLUE = 1;
</syntaxhighlight>
static const uint8_t COLOR_GREEN = 2;
static const uint8_t COLOR_CYAN = 3;
static const uint8_t COLOR_RED = 4;
static const uint8_t COLOR_MAGENTA = 5;
static const uint8_t COLOR_BROWN = 6;
static const uint8_t COLOR_LIGHT_GREY = 7;
static const uint8_t COLOR_DARK_GREY = 8;
static const uint8_t COLOR_LIGHT_BLUE = 9;
static const uint8_t COLOR_LIGHT_GREEN = 10;
static const uint8_t COLOR_LIGHT_CYAN = 11;
static const uint8_t COLOR_LIGHT_RED = 12;
static const uint8_t COLOR_LIGHT_MAGENTA = 13;
static const uint8_t COLOR_LIGHT_BROWN = 14;
static const uint8_t COLOR_WHITE = 15;
 
Check for differences between the git revision used in this article and what you cloned (empty output means there is no difference):
inline uint8_t make_color(uint8_t fg, uint8_t bg)
<syntaxhighlight lang="bash">
{
git diff 084d1624bedaa9f9e395f055c6bd99299bd97f58..master
return fg | bg << 4;
</syntaxhighlight>
}
 
Operating systems development is about being an expert. Take the time to read the code carefully through and understand it. Please seek further information and help if you don't understand aspects of it. This code is minimal and almost everything is done deliberately, often to pre-emptively solve future problems.
inline uint16_t make_vgaentry(char c, uint8_t color)
{
uint16_t c16 = c;
uint16_t color16 = color;
return c16 | color16 << 8;
}
 
=== kernel ===
static const size_t VGA_WIDTH = 80;
static const size_t VGA_HEIGHT = 24;
 
static uint16_t* const VGA_MEMORY = (uint16_t*) 0xB8000;
 
#endif
</source>
 
==== kernel/include/kernel/tty.h ====
 
<sourcesyntaxhighlight lang="c">
#ifndef _KERNEL_TTY_H
#define _KERNEL_TTY_H
Line 266 ⟶ 252:
 
#endif
</syntaxhighlight>
</source>
 
==== kernel/Makefile ====
 
<syntaxhighlight lang="make">
<pre>
HOST?DEFAULT_HOST!=$(shell ../default-host.sh)
HOST?=DEFAULT_HOST
HOSTARCH:=$(shell ../target-triplet-to-arch.sh $(HOST))
HOSTARCH!=../target-triplet-to-arch.sh $(HOST)
 
CFLAGS?=-O2 -g
Line 285 ⟶ 272:
INCLUDEDIR?=$(PREFIX)/include
 
CFLAGS:=$(CFLAGS) -ffreestanding -fbuiltin -Wall -Wextra
CPPFLAGS:=$(CPPFLAGS) -D__is_myos_kernelD__is_kernel -Iinclude
LDFLAGS:=$(LDFLAGS)
LIBS:=$(LIBS) -nostdlib -lk -lgcc
 
ARCHDIR:=arch/$(HOSTARCH)
 
include $(ARCHDIR)/make.config
 
CFLAGS:=$(CFLAGS) $(KERNEL_ARCH_CFLAGS)
CPFLAGSCPPFLAGS:=$(CPPFLAGS) $(KERNEL_ARCH_CPPFLAGS)
LDFLAGS:=$(LDFLAGS) $(KERNEL_ARCH_LDFLAGS)
LIBS:=$(LIBS) $(KERNEL_ARCH_LIBS)
 
KERNEL_OBJS=\
OBJS:=\
$(KERNEL_ARCH_OBJS) \
kernel/kernel.o \
 
OBJS=\
CRTI_OBJ:=$(ARCHDIR)/crti.o
$(ARCHDIR)/crti.o \
CRTBEGIN_OBJ:=$(shell $(CC) $(CFLAGS) $(LDFLAGS) -print-file-name=crtbegin.o)
$(ARCHDIR)/crtbegin.o \
CRTEND_OBJ:=$(shell $(CC) $(CFLAGS) $(LDFLAGS) -print-file-name=crtend.o)
$(KERNEL_OBJS) \
CRTN_OBJ:=$(ARCHDIR)/crtn.o
$(ARCHDIR)/crtend.o \
$(ARCHDIR)/crtn.o \
 
LINK_LIST=\
ALL_OUR_OBJS:=\
$(CRTI_OBJLDFLAGS) \
$(OBJSARCHDIR)/crti.o \
$(CRTN_OBJARCHDIR)/crtbegin.o \
$(KERNEL_OBJS) \
$(LIBS) \
$(ARCHDIR)/crtend.o \
$(ARCHDIR)/crtn.o \
 
.PHONY: all clean install install-headers install-kernel
OBJ_LINK_LIST:=\
.SUFFIXES: .o .c .S
$(CRTI_OBJ) \
$(CRTBEGIN_OBJ) \
$(OBJS) \
$(CRTEND_OBJ) \
$(CRTN_OBJ) \
 
all: myos.kernel
 
myos.kernel: $(OBJS) $(ARCHDIR)/linker.ld
.PHONY: all clean install install-headers install-kernel
$(CC) -T $(ARCHDIR)/linker.ld -o $@ $(CFLAGS) $(LINK_LIST)
grub-file --is-x86-multiboot myos.kernel
 
myos.kernel: $(OBJ_LINK_LISTARCHDIR)/crtbegin.o $(ARCHDIR)/linkercrtend.ldo:
OBJ=`$(CC) $(CFLAGS) $(LDFLAGS) -print-file-name=$(@F)` && cp "$$OBJ" $@
$(CC) -T $(ARCHDIR)/linker.ld -o $@ $(CFLAGS) $(OBJ_LINK_LIST) $(LDFLAGS) $(LIBS)
 
%.c.o: %.c
$(CC) -MD -c $< -o $@ -std=gnu11 $(CFLAGS) $(CPPFLAGS)
 
%.S.o: %.S
$(CC) -MD -c $< -o $@ $(CFLAGS) $(CPPFLAGS)
 
clean:
rm -f myos.kernel $(OBJS) $(ALL_OUR_OBJS) *.o */*.o */*/*.o
rm -f $(OBJS) *.o */*.o */*/*.o
rm -f $(OBJS:.o=.d) *.d */*.d */*/*.d
 
install: install-headers install-kernel
Line 340 ⟶ 333:
install-headers:
mkdir -p $(DESTDIR)$(INCLUDEDIR)
cp -RTvR --preserve=timestamps include/. $(DESTDIR)$(INCLUDEDIR)/.
 
install-kernel: myos.kernel
mkdir -p $(DESTDIR)$(BOOTDIR)
cp myos.kernel $(DESTDIR)$(BOOTDIR)
 
</pre>
-include $(OBJS:.o=.d)
</syntaxhighlight>
 
==== kernel/kernel/kernel.c ====
 
<sourcesyntaxhighlight lang="c">
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include <stdio.h>
 
#include <kernel/tty.h>
 
void kernel_earlykernel_main(void) {
{
terminal_initialize();
}
 
void kernel_main()
{
printf("Hello, kernel World!\n");
}
</syntaxhighlight>
</source>
 
==== kernel/arch/i386/tty.c ====
 
<sourcesyntaxhighlight lang="c">
#include <stdbool.h>
#include <stddef.h>
Line 376 ⟶ 363:
#include <string.h>
 
#include <kernel/vgatty.h>
 
#include "vga.h"
size_t terminal_row;
size_t terminal_column;
uint8_t terminal_color;
uint16_t* terminal_buffer;
 
static const size_t VGA_WIDTH = 80;
void terminal_initialize()
static const size_t VGA_HEIGHT = 25;
{
static uint16_t* const VGA_MEMORY = (uint16_t*) 0xB8000;
 
static size_t terminal_row;
static size_t terminal_column;
static uint8_t terminal_color;
static uint16_t* terminal_buffer;
 
void terminal_initialize(void) {
terminal_row = 0;
terminal_column = 0;
terminal_color = make_colorvga_entry_color(COLOR_LIGHT_GREYVGA_COLOR_LIGHT_GREY, COLOR_BLACKVGA_COLOR_BLACK);
terminal_buffer = VGA_MEMORY;
for ( size_t y = 0; y < VGA_HEIGHT; y++ ) {
for (size_t x = 0; x < VGA_WIDTH; x++) {
{
for ( size_t x = 0; x < VGA_WIDTH; x++ )
{
const size_t index = y * VGA_WIDTH + x;
terminal_buffer[index] = make_vgaentryvga_entry(' ', terminal_color);
}
}
}
 
void terminal_setcolor(uint8_t color) {
{
terminal_color = color;
}
 
void terminal_putentryat(unsigned char c, uint8_t color, size_t x, size_t y) {
{
const size_t index = y * VGA_WIDTH + x;
terminal_buffer[index] = make_vgaentryvga_entry(c, color);
}
 
void terminal_putcharterminal_scroll(charint cline) {
int loop;
{
char c;
terminal_putentryat(c, terminal_color, terminal_column, terminal_row);
 
if ( ++terminal_column == VGA_WIDTH )
for(loop = line * (VGA_WIDTH * 2) + 0xB8000; loop < VGA_WIDTH * 2; loop++) {
{
c = *loop;
*(loop - (VGA_WIDTH * 2)) = c;
}
}
 
void terminal_delete_last_line() {
int x, *ptr;
 
for(x = 0; x < VGA_WIDTH * 2; x++) {
ptr = 0xB8000 + (VGA_WIDTH * 2) * (VGA_HEIGHT - 1) + x;
*ptr = 0;
}
}
 
void terminal_putchar(char c) {
int line;
unsigned char uc = c;
 
terminal_putentryat(uc, terminal_color, terminal_column, terminal_row);
if (++terminal_column == VGA_WIDTH) {
terminal_column = 0;
if ( ++terminal_row == VGA_HEIGHT )
{
for(line = 1; line <= VGA_HEIGHT - 1; line++)
terminal_row = 0;
{
terminal_scroll(line);
}
terminal_delete_last_line();
terminal_row = VGA_HEIGHT - 1;
}
}
}
 
void terminal_write(const char* data, size_t size) {
for (size_t i = 0; i < size; i++)
{
for ( size_t i = 0; i < size; i++ )
terminal_putchar(data[i]);
}
 
void terminal_writestring(const char* data) {
{
terminal_write(data, strlen(data));
}
</syntaxhighlight>
</source>
 
==== kernel/arch/i386/crtn.S ====
Line 448 ⟶ 459:
ret
</pre>
 
==== kernel/arch/i386/vga.h ====
 
<syntaxhighlight lang="c">
#ifndef ARCH_I386_VGA_H
#define ARCH_I386_VGA_H
 
#include <stdint.h>
 
enum vga_color {
VGA_COLOR_BLACK = 0,
VGA_COLOR_BLUE = 1,
VGA_COLOR_GREEN = 2,
VGA_COLOR_CYAN = 3,
VGA_COLOR_RED = 4,
VGA_COLOR_MAGENTA = 5,
VGA_COLOR_BROWN = 6,
VGA_COLOR_LIGHT_GREY = 7,
VGA_COLOR_DARK_GREY = 8,
VGA_COLOR_LIGHT_BLUE = 9,
VGA_COLOR_LIGHT_GREEN = 10,
VGA_COLOR_LIGHT_CYAN = 11,
VGA_COLOR_LIGHT_RED = 12,
VGA_COLOR_LIGHT_MAGENTA = 13,
VGA_COLOR_LIGHT_BROWN = 14,
VGA_COLOR_WHITE = 15,
};
 
static inline uint8_t vga_entry_color(enum vga_color fg, enum vga_color bg) {
return fg | bg << 4;
}
 
static inline uint16_t vga_entry(unsigned char uc, uint8_t color) {
return (uint16_t) uc | (uint16_t) color << 8;
}
 
#endif
</syntaxhighlight>
 
==== kernel/arch/i386/make.config ====
 
<syntaxhighlight lang="make">
<pre>
KERNEL_ARCH_CFLAGS:=
KERNEL_ARCH_CPPFLAGS:=
KERNEL_ARCH_LDFLAGS:=
KERNEL_ARCH_LIBS:=
 
KERNEL_ARCH_OBJS:=\
$(ARCHDIR)/boot.o \
$(ARCHDIR)/tty.o \
</syntaxhighlight>
</pre>
 
==== kernel/arch/i386/crti.S ====
Line 523 ⟶ 572:
*(COMMON)
*(.bss)
*(.bootstrap_stack)
}
 
Line 534 ⟶ 582:
 
<pre>
# Declare constants used for creating athe multiboot header.
.set ALIGN, 1<<0 # align loaded modules on page boundaries
.set MEMINFO, 1<<1 # provide memory map
Line 549 ⟶ 597:
 
# Reserve a stack for the initial thread.
.section .bootstrap_stackbss
.align 16
stack_bottom:
.skip 16384 # 16 KiB
Line 560 ⟶ 609:
_start:
movl $stack_top, %esp
 
# Initialize the core kernel before running the global constructors.
call kernel_early
 
# Call the global constructors.
Line 572 ⟶ 618:
# Hang if kernel_main unexpectedly returns.
cli
1: hlt
jmp 1b
.Lhang:
jmp .Lhang
.size _start, . - _start
</pre>
Line 581 ⟶ 626:
 
<pre>
*.od
*.kernel
*.o
</pre>
 
Line 589 ⟶ 635:
==== libc/include/string.h ====
 
<sourcesyntaxhighlight lang="c">
#ifndef _STRING_STRING_H
#define _STRING_STRING_H 1
 
#include <sys/cdefs.h>
Line 597 ⟶ 643:
#include <stddef.h>
 
#ifdef __cplusplus
__BEGIN_DECLS
extern "C" {
#endif
 
int memcmp(const void*, const void*, size_t);
Line 605 ⟶ 653:
size_t strlen(const char*);
 
#ifdef __cplusplus
__END_DECLS
}
#endif
 
#endif
</syntaxhighlight>
</source>
 
==== libc/include/stdio.h ====
 
<sourcesyntaxhighlight lang="c">
#ifndef _STDIO_H
#define _STDIO_H 1
Line 618 ⟶ 668:
#include <sys/cdefs.h>
 
#define EOF (-1)
__BEGIN_DECLS
 
#ifdef __cplusplus
extern "C" {
#endif
 
int printf(const char* __restrict, ...);
Line 624 ⟶ 678:
int puts(const char*);
 
#ifdef __cplusplus
__END_DECLS
}
#endif
 
#endif
</syntaxhighlight>
</source>
 
==== libc/include/sys/cdefs.h ====
 
<sourcesyntaxhighlight lang="c">
#ifndef _SYS_CDEFS_SYS_CDEFS_H
#define _SYS_CDEFS_SYS_CDEFS_H 1
 
#define __myos_libc 1
 
#if defined(__cplusplus)
#define __BEGIN_DECLS extern "C" {
#define __END_DECLS }
#else
#define __BEGIN_DECLS
#define __END_DECLS
#endif
 
#endif
</syntaxhighlight>
</source>
 
==== libc/include/stdlib.h ====
 
<sourcesyntaxhighlight lang="c">
#ifndef _STDLIB_H
#define _STDLIB_H 1
Line 656 ⟶ 704:
#include <sys/cdefs.h>
 
#ifdef __cplusplus
__BEGIN_DECLS
extern "C" {
#endif
 
__attribute__((__noreturn__))
void abort(void);
 
#ifdef __cplusplus
__END_DECLS
}
#endif
 
#endif
</syntaxhighlight>
</source>
 
==== libc/Makefile ====
 
<syntaxhighlight lang="make">
<pre>
HOST?DEFAULT_HOST!=$(shell ../default-host.sh)
HOST?=DEFAULT_HOST
HOSTARCH:=$(shell ../target-triplet-to-arch.sh $(HOST))
HOSTARCH!=../target-triplet-to-arch.sh $(HOST)
 
CFLAGS?=-O2 -g
Line 683 ⟶ 736:
LIBDIR?=$(EXEC_PREFIX)/lib
 
CFLAGS:=$(CFLAGS) -ffreestanding -Wall -Wextra
CPPFLAGS:=$(CPPFLAGS) -D__is_myos_libcD__is_libc -Iinclude
LIBK_CFLAGS:=$(CFLAGS) -ffreestanding -fbuiltin
LIBK_CPPFLAGS:=$(CPPFLAGS) -D__is_myos_kernelD__is_libk
 
ARCHDIR:=arch/$(HOSTARCH)
 
include $(ARCHDIR)/make.config
 
CFLAGS:=$(CFLAGS) $(ARCH_CFLAGS)
CPFLAGSCPPFLAGS:=$(CPPFLAGS) $(ARCH_CPPFLAGS)
LIBK_CFLAGS:=$(LIBK_CFLAGS) $(KERNEL_ARCH_CFLAGS)
LIBK_CPFLAGSLIBK_CPPFLAGS:=$(LIBK_CPPFLAGS) $(KERNEL_ARCH_CPPFLAGS)
 
FREEOBJS:=\
$(ARCH_FREEOBJS) \
stdio/printf.o \
Line 709 ⟶ 762:
string/strlen.o \
 
HOSTEDOBJS:=\
$(ARCH_HOSTEDOBJS) \
 
OBJS:=\
$(FREEOBJS) \
$(HOSTEDOBJS) \
 
LIBK_OBJS:=$(FREEOBJS:.o=.libk.o)
 
#BINARIES=libc.a libglibk.a libk# Not ready for libc yet.a
BINARIES=libk.a
 
.PHONY: all clean install install-headers install-libs
.SUFFIXES: .o .libk.o .c .S
 
all: $(BINARIES)
 
.PHONY: all clean install install-headers install-libs
 
libc.a: $(OBJS)
$(AR) rcs $@ $(OBJS)
 
libg.a:
$(AR) rcs $@
 
libk.a: $(LIBK_OBJS)
$(AR) rcs $@ $(LIBK_OBJS)
 
%.c.o: %.c
$(CC) -MD -c $< -o $@ -std=gnu11 $(CFLAGS) $(CPPFLAGS)
 
%.S.o: %.S
$(CC) -MD -c $< -o $@ $(CFLAGS) $(CPPFLAGS)
 
%.c.libk.o: %.c
$(CC) -MD -c $< -o $@ -std=gnu11 $(LIBK_CFLAGS) $(LIBK_CPPFLAGS)
 
%.S.libk.o: %.S
$(CC) -MD -c $< -o $@ $(LIBK_CFLAGS) $(LIBK_CPPFLAGS)
 
clean:
rm -f $(BINARIES) $(OBJS) $(LIBK_OBJS) *.o */*.o */*/*.oa
rm -f $(OBJS) $(LIBK_OBJS) *.o */*.o */*/*.o
rm -f $(OBJS:.o=.d) $(LIBK_OBJS:.o=.d) *.d */*.d */*/*.d
 
install: install-headers install-libs
Line 752 ⟶ 806:
install-headers:
mkdir -p $(DESTDIR)$(INCLUDEDIR)
cp -RTvR --preserve=timestamps include/. $(DESTDIR)$(INCLUDEDIR)/.
 
install-libs: $(BINARIES)
mkdir -p $(DESTDIR)$(LIBDIR)
cp $(BINARIES) $(DESTDIR)$(LIBDIR)
 
</pre>
-include $(OBJS:.o=.d)
-include $(LIBK_OBJS:.o=.d)
</syntaxhighlight>
 
==== libc/stdlib/abort.c ====
 
<sourcesyntaxhighlight lang="c">
#include <stdlib.h>
#include <stdio.h>
#include <stdlib.h>
 
__attribute__((__noreturn__))
void abort(void) {
#if defined(__is_libk)
{
// TODO: Add proper kernel panic.
printf("Kernelkernel: Panicpanic: abort()\n");
asm volatile("hlt");
while ( 1 ) { }
#else
// TODO: Abnormally terminate the process as if by SIGABRT.
printf("abort()\n");
#endif
while (1) { }
__builtin_unreachable();
}
</syntaxhighlight>
</source>
 
==== libc/string/memmove.c ====
 
<sourcesyntaxhighlight lang="c">
#include <string.h>
 
void* memmove(void* dstptr, const void* srcptr, size_t size) {
{
unsigned char* dst = (unsigned char*) dstptr;
const unsigned char* src = (const unsigned char*) srcptr;
if ( dst < src) ){
for ( size_t i = 0; i < size; i++ )
dst[i] = src[i];
} else {
for ( size_t i = size; i != 0; i-- )
dst[i-1] = src[i-1];
}
return dstptr;
}
</syntaxhighlight>
</source>
 
==== libc/string/strlen.c ====
 
<sourcesyntaxhighlight lang="c">
#include <string.h>
 
size_t strlen(const char* stringstr) {
size_t len = 0;
{
while (str[len])
size_t result = 0;
len++;
while ( string[result] )
return len;
result++;
return result;
}
</syntaxhighlight>
</source>
 
==== libc/string/memcmp.c ====
 
<sourcesyntaxhighlight lang="c">
#include <string.h>
 
int memcmp(const void* aptr, const void* bptr, size_t size) {
{
const unsigned char* a = (const unsigned char*) aptr;
const unsigned char* b = (const unsigned char*) bptr;
for ( size_t i = 0; i < size; i++ ) {
if ( a[i] < b[i] )
return -1;
else if ( b[i] < a[i] )
return 1;
}
return 0;
}
</syntaxhighlight>
</source>
 
==== libc/string/memset.c ====
 
<sourcesyntaxhighlight lang="c">
#include <string.h>
 
void* memset(void* bufptr, int value, size_t size) {
{
unsigned char* buf = (unsigned char*) bufptr;
for ( size_t i = 0; i < size; i++ )
buf[i] = (unsigned char) value;
return bufptr;
}
</syntaxhighlight>
</source>
 
==== libc/string/memcpy.c ====
 
<sourcesyntaxhighlight lang="c">
#include <string.h>
 
void* memcpy(void* restrict dstptr, const void* restrict srcptr, size_t size) {
{
unsigned char* dst = (unsigned char*) dstptr;
const unsigned char* src = (const unsigned char*) srcptr;
for ( size_t i = 0; i < size; i++ )
dst[i] = src[i];
return dstptr;
}
</syntaxhighlight>
</source>
 
==== libc/stdio/puts.c ====
 
<sourcesyntaxhighlight lang="c">
#include <stdio.h>
 
int puts(const char* string) {
{
return printf("%s\n", string);
}
</syntaxhighlight>
</source>
 
==== libc/stdio/putchar.c ====
 
<sourcesyntaxhighlight lang="c">
#include <stdio.h>
 
#if defined(__is_myos_kernel__is_libk)
#include <kernel/tty.h>
#endif
 
int putchar(int ic) {
#if defined(__is_libk)
{
#if defined(__is_myos_kernel)
char c = (char) ic;
terminal_write(&c, sizeof(c));
#else
// TODO: YouImplement needstdio toand implement athe write system call.
#endif
return ic;
}
</syntaxhighlight>
</source>
 
==== libc/stdio/printf.c ====
 
<sourcesyntaxhighlight lang="c">
#include <limits.h>
#include <stdbool.h>
#include <stdarg.h>
Line 896 ⟶ 953:
#include <string.h>
 
static bool print(const char* data, size_t length) {
#if defined(__is_myos_kernel)
const unsigned char* bytes = (const unsigned char*) data;
#include <kernel/tty.h>
for (size_t i = 0; i < length; i++)
#endif
if (putchar(bytes[i]) == EOF)
 
return false;
static void print(const char* data, size_t data_length)
return true;
{
for ( size_t i = 0; i < data_length; i++ )
putchar((int) ((const unsigned char*) data)[i]);
}
 
int printf(const char* restrict format, ...) {
{
va_list parameters;
va_start(parameters, format);
 
int written = 0;
size_t amount;
bool rejected_bad_specifier = false;
 
while ( *format != '\0') ){
size_t maxrem = INT_MAX - written;
{
 
if ( *format != '%' )
if (format[0] != '%' || format[1] == '%') {
{
if (format[0] == '%')
print_c:
amount = 1 format++;
whilesize_t ( format[amount] && format[amount] != '%' )1;
while (format[amount] && format[amount] != '%')
amount++;
printif (format,maxrem < amount); {
// TODO: Set errno to EOVERFLOW.
return -1;
}
if (!print(format, amount))
return -1;
format += amount;
written += amount;
Line 929 ⟶ 987:
}
 
const char* format_begun_at = format++;
 
if ( *(++format) == '%c') ){
goto print_c;
 
if ( rejected_bad_specifier )
{
incomprehensible_conversion:
rejected_bad_specifier = true;
format = format_begun_at;
goto print_c;
}
 
if ( *format == 'c' )
{
format++;
char c = (char) va_arg(parameters, int /* char promotes to int */);
if (!maxrem) {
print(&c, sizeof(c));
// TODO: Set errno to EOVERFLOW.
}
return -1;
else if ( *format == 's' )
{ }
if (!print(&c, sizeof(c)))
return -1;
written++;
} else if (*format == 's') {
format++;
const char* sstr = va_arg(parameters, const char*);
print(s,size_t len = strlen(s)str);
if (maxrem < len) {
}
// TODO: Set errno to EOVERFLOW.
else
return -1;
{
}
goto incomprehensible_conversion;
if (!print(str, len))
return -1;
written += len;
} else {
format = format_begun_at;
size_t len = strlen(format);
if (maxrem < len) {
// TODO: Set errno to EOVERFLOW.
return -1;
}
if (!print(format, len))
return -1;
written += len;
format += len;
}
}
 
va_end(parameters);
 
return written;
}
</syntaxhighlight>
</source>
 
==== libc/arch/i386/make.config ====
 
<syntaxhighlight lang="make">
<pre>
ARCH_CFLAGS:=
ARCH_CPPFLAGS:=
KERNEL_ARCH_CFLAGS:=
KERNEL_ARCH_CPPFLAGS:=
 
ARCH_FREEOBJS:=\
 
ARCH_HOSTEDOBJS:=\
</syntaxhighlight>
</pre>
 
==== libc/.gitignore ====
 
<pre>
*.o
*.a
*.d
*.o
</pre>
 
=== Miscellaneous ===
 
These build scriptsfiles go into the root source directory.
 
==== build.sh ====
 
<sourcesyntaxhighlight lang="bash">
#!/bin/bashsh
set -e
. ./headers.sh
 
for PROJECT in $PROJECTS; do
(cd $PROJECT && DESTDIR="$PWD/sysrootSYSROOT" $MAKE -C $PROJECT install)
done
</syntaxhighlight>
</source>
 
You should make this executable script executable by running:
<sourcesyntaxhighlight lang="bash">
chmod +x build.sh
</syntaxhighlight>
</source>
 
==== clean.sh ====
 
<sourcesyntaxhighlight lang="bash">
#!/bin/bashsh
set -e
. ./config.sh
 
for PROJECT in $PROJECTS; do
(cd $MAKEPROJECT -C&& $PROJECTMAKE clean)
done
 
rm -rfvrf sysroot
rm -rfvrf isodir
rm -rfvrf myos.iso
</syntaxhighlight>
</source>
 
You should make this executable script executable by running:
<sourcesyntaxhighlight lang="bash">
chmod +x clean.sh
</syntaxhighlight>
</source>
 
==== config.sh ====
 
<sourcesyntaxhighlight lang="bash">
SYSTEM_HEADER_PROJECTS="libc kernel"
PROJECTS="libc kernel"
Line 1,050 ⟶ 1,114:
export CPPFLAGS=''
 
# Work around thatConfigure the cross-elfcompiler targetsto doesn'tuse properlythe supportdesired system rootsroot. We
export SYSROOT="$(pwd)/sysroot"
# can emulate it by manually telling it where it finds headers and libraries.
export CC="$CC --sysroot=$SYSROOT"
 
# Work around that the -elf gcc targets doesn't have a system include directory
# because it was configured with --without-headers rather than --with-sysroot.
if echo "$HOST" | grep -Eq -- '-elf($|-)'; then
export LDFLAGSCC="$LDFLAGSCC -L$PWD/sysroot$LIBDIR -B$PWD/sysrootisystem=$LIBDIRINCLUDEDIR"
export CPPFLAGS="$CPPFLAGS -I$PWD/sysroot$INCLUDEDIR"
else
export CC="$CC --sysroot=$PWD/sysroot"
fi
</syntaxhighlight>
</source>
 
==== default-host.sh ====
 
<sourcesyntaxhighlight lang="bash">
#!/bin/sh
echo i586i686-elf
</syntaxhighlight>
</source>
 
You should make this executable script executable by running:
<sourcesyntaxhighlight lang="bash">
chmod +x default-host.sh
</syntaxhighlight>
</source>
 
==== headers.sh ====
 
<sourcesyntaxhighlight lang="bash">
#!/bin/bashsh
set -e
. ./config.sh
 
mkdir -p sysroot"$SYSROOT"
 
for PROJECT in $SYSTEM_HEADER_PROJECTS; do
(cd $PROJECT && DESTDIR="$PWD/sysrootSYSROOT" $MAKE -C $PROJECT install-headers)
done
</syntaxhighlight>
</source>
 
You should make this executable script executable by running:
<sourcesyntaxhighlight lang="bash">
chmod +x headers.sh
</syntaxhighlight>
</source>
 
==== iso.sh ====
 
<sourcesyntaxhighlight lang="bash">
#!/bin/bashsh
set -e
. ./build.sh
Line 1,109 ⟶ 1,174:
EOF
grub-mkrescue -o myos.iso isodir
</syntaxhighlight>
</source>
 
You should make this executable script executable by running:
<sourcesyntaxhighlight lang="bash">
chmod +x iso.sh
</syntaxhighlight>
</source>
 
==== qemu.sh ====
 
<sourcesyntaxhighlight lang="bash">
#!/bin/bashsh
set -e
. ./iso.sh
 
qemu-system-$(./target-triplet-to-arch.sh $HOST) -cdrom myos.iso
</syntaxhighlight>
</source>
 
You should make this executable script executable by running:
<sourcesyntaxhighlight lang="bash">
chmod +x qemu.sh
</syntaxhighlight>
</source>
 
==== target-triplet-to-arch.sh ====
 
<sourcesyntaxhighlight lang="bash">
#!/bin/sh
if echo "$1" | grep -Eq 'i[[:digit:]]86-'; then
Line 1,140 ⟶ 1,205:
echo "$1" | grep -Eo '^[[:alnum:]_]*'
fi
</syntaxhighlight>
</source>
 
You should make this executable script executable by running:
<sourcesyntaxhighlight lang="bash">
chmod +x target-triplet-to-arch.sh
</syntaxhighlight>
</source>
 
==== .gitignore ====
 
<pre>
*.iso
isodir
sysroot
</pre>
 
== Cross-Compiling the Operating System ==
Line 1,156 ⟶ 1,229:
the source tree by invoking:
 
<sourcesyntaxhighlight lang="bash">
./clean.sh
</syntaxhighlight>
</source>
 
You can install all the system headers into the system root without relying on
the compiler at all, which will be useful later on when switching to a
[[Hosted GCC Cross-Compiler]], by invoking:
 
<syntaxhighlight lang="bash">
./headers.sh
</syntaxhighlight>
 
You can build a bootable cdrom image of the operating system by invoking:
 
<sourcesyntaxhighlight lang="bash">
./iso.sh
</syntaxhighlight>
</source>
 
It's probably a good idea to create a quick ''build-and-then-launch'' short-cut
like used in this example to run the system in your favorite emulator quickly:
 
<sourcesyntaxhighlight lang="bash">
./qemu.sh
</syntaxhighlight>
</source>
 
== Troubleshooting ==
 
If you receive odd errors during the build, you may have made a mistake during manual copying, perhaps missed a file, forgot to make a file executable, or bugs in the highlighting software we use cause unintended whitespace to appear. Perform a git repository clone as described above, and use that code instead, or compare the two directory trees with the <tt>diff(1)</tt> diff command line utility. If you made personal changes to the code, those may be at fault.
 
== Moving Forward ==
Line 1,186 ⟶ 1,271:
 
=== Improving the Build System ===
{{Main|Hard Build System}}
 
It is probably worth improving the build system. For instance, it could be
useful if <tt>build.sh</tt> accepted command-line options, or perhaps if it used
<tt>make</tt>'s important <tt>-j</tt> option for concurrent builds.
 
It's worth considering how contributors will build your operating system. It's an easy trap to fall into thinking you can make super script that does everything. This will end up complex and insufficiently flexible; or it will be flexible and even more complex. It's better to document what the user should do to prepare a cross toolchain and what prerequisite programs to install. This tutorial shows an example hard build system that merely builds the operating system. You can complete it by documenting how to build a cross-compiler and how to use it.
 
=== Stack Smash Protector ===
{{Main|Stack Smashing Protector}}
 
Early is not too soon to think about security and robustness. You can take advantage of the optional stack smash protector offered by modern compilers that detect stack buffer overruns rather than behaving unexpectedly (or nothing happening, if unlucky).
 
=== Going Further ===
{{Main|Going Further on x86}}
This guide is meant as an overview of what to do, so you have a kernel ready for more features, without actually redesigning it radically when adding them.
 
=== User-Space ===
Line 1,196 ⟶ 1,293:
user-space and an [[OS Specific Toolchain]] that fully utilizes the system root.
 
== Forum Posts ==
[[Category:Bare bones tutorials]]
 
* [[topic:36584|A link error found & fixed]]
 
[[Category:Tutorials]]
[[Category:C]]