Meaty Skeleton: Difference between revisions

no edit summary
[unchecked revision][unchecked revision]
(Add Ubuntu build dependencies)
No edit summary
 
(22 intermediate revisions by 15 users not shown)
Line 1:
{{FirstPerson}}
{{Rating|1}}{{Template:Kernel designs}}
{{You}}
{{BeginnersWarning}}
{{Rating|1}}
{{Kernel designs}}
 
In thisThis tutorial we continuecontinues from [[Bare Bones]] and createcreates a minimal template
operating system in the [[Stan Dard]] style suitable for further modification or as inspiration for your
as inspiration for your initial operating system version. The [[Bare Bones]] tutorial only gives you the
tutorial only gives you the absolutely minimal code to demonstrate how to correctly cross-compile a kernel,
correctly cross-compile a kernel, however this is unsuitable as an example operating system. Additionally, this
operating system. Additionally, this tutorial implements neccesary ABI features needed to satisfy the ABI and
needed to satisfy the ABI and compiler contracts to prevent possible mysterious errors.
errors.
 
This tutorial also serves as the initial template tutorial on how to
Line 28 ⟶ 33:
indirectly other minor Unix and ABI semantics. Adapt what you wish from this
tutorial. Note that the shell script and Make-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.
Line 48 ⟶ 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
Line 56 ⟶ 61:
 
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.
 
== Dependencies ==
 
You will need these dependencies in order to complete this tutorial:
 
* i686-elf toolchain, as discussed above.
* GRUB, for the grub-mkrescue command, along with the appropriate runtime files.
* Xorriso, the .iso creation engine used by grub-mkrescue.
* GNU make 4.0 or later.
* 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 ==
Line 71 ⟶ 92:
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 80 ⟶ 101:
<tt>sysroot/boot</tt> directory.
 
We already use system roots already now 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 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>).
 
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.
 
== 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 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: one that setsets a default value and one that adds
mandatory options the project makefile requires:
 
<sourcesyntaxhighlight lang="make">
# Default CFLAGS:
CFLAGS?=-O2 -g
Line 117 ⟶ 139:
# Add mandatory options to CFLAGS:
CFLAGS:=$(CFLAGS) -Wall -Wextra
</syntaxhighlight>
</source>
 
== Architecture Directories ==
Line 144 ⟶ 166:
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_kernel</tt> macro lets the source code detect whether
Line 178 ⟶ 199:
The standard headers use 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>extern "C" {</tt> and <tt>}</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_libc</tt> macro lets the source code detect whether
Line 191 ⟶ 212:
This example comes 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 ==
Line 202 ⟶ 223:
--->
 
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 getsmay get garbled due to bugs in our syntax highlighting. To clone the git repository, do:
 
<sourcesyntaxhighlight lang="bash">
git clone https://gitlab.com/sortie/meaty-skeleton.git
</syntaxhighlight>
</source>
 
Check for differences between the git revision used in this article and what you cloned (empty output means there is no difference):
<sourcesyntaxhighlight lang="bash">
git diff 084d1624bedaa9f9e395f055c6bd99299bd97f58..master
</syntaxhighlight>
</source>
 
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.
Line 219 ⟶ 240:
==== kernel/include/kernel/tty.h ====
 
<sourcesyntaxhighlight lang="c">
#ifndef _KERNEL_TTY_H
#define _KERNEL_TTY_H
Line 231 ⟶ 252:
 
#endif
</syntaxhighlight>
</source>
 
==== kernel/Makefile ====
 
<sourcesyntaxhighlight lang="make">
DEFAULT_HOST!=../default-host.sh
HOST?=DEFAULT_HOST
Line 319 ⟶ 340:
 
-include $(OBJS:.o=.d)
</syntaxhighlight>
</source>
 
==== kernel/kernel/kernel.c ====
 
<sourcesyntaxhighlight lang="c">
#include <stdio.h>
 
Line 332 ⟶ 353:
printf("Hello, kernel World!\n");
}
</syntaxhighlight>
</source>
 
==== kernel/arch/i386/tty.c ====
 
<sourcesyntaxhighlight lang="c">
#include <stdbool.h>
#include <stddef.h>
Line 375 ⟶ 396:
const size_t index = y * VGA_WIDTH + x;
terminal_buffer[index] = vga_entry(c, color);
}
 
void terminal_scroll(int line) {
int loop;
char c;
 
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)
{
terminal_row = 0;
for(line = 1; line <= VGA_HEIGHT - 1; line++)
{
terminal_scroll(line);
}
terminal_delete_last_line();
terminal_row = VGA_HEIGHT - 1;
}
}
}
Line 395 ⟶ 444:
terminal_write(data, strlen(data));
}
</syntaxhighlight>
</source>
 
==== kernel/arch/i386/crtn.S ====
Line 413 ⟶ 462:
==== kernel/arch/i386/vga.h ====
 
<sourcesyntaxhighlight lang="c">
#ifndef ARCH_I386_VGA_H
#define ARCH_I386_VGA_H
Line 447 ⟶ 496:
 
#endif
</syntaxhighlight>
</source>
 
==== kernel/arch/i386/make.config ====
 
<sourcesyntaxhighlight lang="make">
KERNEL_ARCH_CFLAGS=
KERNEL_ARCH_CPPFLAGS=
Line 460 ⟶ 509:
$(ARCHDIR)/boot.o \
$(ARCHDIR)/tty.o \
</syntaxhighlight>
</source>
 
==== kernel/arch/i386/crti.S ====
Line 586 ⟶ 635:
==== libc/include/string.h ====
 
<sourcesyntaxhighlight lang="c">
#ifndef _STRING_H
#define _STRING_H 1
Line 609 ⟶ 658:
 
#endif
</syntaxhighlight>
</source>
 
==== libc/include/stdio.h ====
 
<sourcesyntaxhighlight lang="c">
#ifndef _STDIO_H
#define _STDIO_H 1
Line 634 ⟶ 683:
 
#endif
</syntaxhighlight>
</source>
 
==== libc/include/sys/cdefs.h ====
 
<sourcesyntaxhighlight lang="c">
#ifndef _SYS_CDEFS_H
#define _SYS_CDEFS_H 1
Line 645 ⟶ 694:
 
#endif
</syntaxhighlight>
</source>
 
==== libc/include/stdlib.h ====
 
<sourcesyntaxhighlight lang="c">
#ifndef _STDLIB_H
#define _STDLIB_H 1
Line 667 ⟶ 716:
 
#endif
</syntaxhighlight>
</source>
 
==== libc/Makefile ====
 
<sourcesyntaxhighlight lang="make">
DEFAULT_HOST!=../default-host.sh
HOST?=DEFAULT_HOST
Line 739 ⟶ 788:
$(CC) -MD -c $< -o $@ -std=gnu11 $(CFLAGS) $(CPPFLAGS)
 
.c.S.o:
$(CC) -MD -c $< -o $@ $(CFLAGS) $(CPPFLAGS)
 
Line 765 ⟶ 814:
-include $(OBJS:.o=.d)
-include $(LIBK_OBJS:.o=.d)
</syntaxhighlight>
</source>
 
==== libc/stdlib/abort.c ====
 
<sourcesyntaxhighlight lang="c">
#include <stdio.h>
#include <stdlib.h>
Line 778 ⟶ 827:
// TODO: Add proper kernel panic.
printf("kernel: panic: abort()\n");
asm volatile("hlt");
#else
// TODO: Abnormally terminate the process as if by SIGABRT.
Line 785 ⟶ 835:
__builtin_unreachable();
}
</syntaxhighlight>
</source>
 
==== libc/string/memmove.c ====
 
<sourcesyntaxhighlight lang="c">
#include <string.h>
 
Line 804 ⟶ 854:
return dstptr;
}
</syntaxhighlight>
</source>
 
==== libc/string/strlen.c ====
 
<sourcesyntaxhighlight lang="c">
#include <string.h>
 
Line 817 ⟶ 867:
return len;
}
</syntaxhighlight>
</source>
 
==== libc/string/memcmp.c ====
 
<sourcesyntaxhighlight lang="c">
#include <string.h>
 
Line 835 ⟶ 885:
return 0;
}
</syntaxhighlight>
</source>
 
==== libc/string/memset.c ====
 
<sourcesyntaxhighlight lang="c">
#include <string.h>
 
Line 848 ⟶ 898:
return bufptr;
}
</syntaxhighlight>
</source>
 
==== libc/string/memcpy.c ====
 
<sourcesyntaxhighlight lang="c">
#include <string.h>
 
Line 862 ⟶ 912:
return dstptr;
}
</syntaxhighlight>
</source>
 
==== libc/stdio/puts.c ====
 
<sourcesyntaxhighlight lang="c">
#include <stdio.h>
 
Line 872 ⟶ 922:
return printf("%s\n", string);
}
</syntaxhighlight>
</source>
 
==== libc/stdio/putchar.c ====
 
<sourcesyntaxhighlight lang="c">
#include <stdio.h>
 
Line 892 ⟶ 942:
return ic;
}
</syntaxhighlight>
</source>
 
==== libc/stdio/printf.c ====
 
<sourcesyntaxhighlight lang="c">
#include <limits.h>
#include <stdbool.h>
Line 977 ⟶ 1,027:
return written;
}
</syntaxhighlight>
</source>
 
==== libc/arch/i386/make.config ====
 
<sourcesyntaxhighlight lang="make">
ARCH_CFLAGS=
ARCH_CPPFLAGS=
Line 990 ⟶ 1,040:
 
ARCH_HOSTEDOBJS=\
</syntaxhighlight>
</source>
 
==== libc/.gitignore ====
Line 1,006 ⟶ 1,056:
==== build.sh ====
 
<sourcesyntaxhighlight lang="bash">
#!/bin/sh
set -e
Line 1,014 ⟶ 1,064:
(cd $PROJECT && DESTDIR="$SYSROOT" $MAKE 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/sh
set -e
Line 1,035 ⟶ 1,085:
rm -rf isodir
rm -rf 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,073 ⟶ 1,123:
export CC="$CC -isystem=$INCLUDEDIR"
fi
</syntaxhighlight>
</source>
 
==== default-host.sh ====
 
<sourcesyntaxhighlight lang="bash">
#!/bin/sh
echo i686-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/sh
set -e
Line 1,099 ⟶ 1,149:
(cd $PROJECT && DESTDIR="$SYSROOT" $MAKE 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/sh
set -e
Line 1,124 ⟶ 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/sh
set -e
Line 1,139 ⟶ 1,189:
 
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,155 ⟶ 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 ====
Line 1,179 ⟶ 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
Line 1,187 ⟶ 1,237:
[[Hosted GCC Cross-Compiler]], by invoking:
 
<sourcesyntaxhighlight lang="bash">
./headers.sh
</syntaxhighlight>
</source>
 
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, forgottenforgot 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.
 
===Ubuntu Users===
In order to build the iso image one must first install the following packages:
 
- xorriso
 
- grub-pi-bin
 
'''$ sudo apt-get install xorriso grub-pi-bin'''
 
== Moving Forward ==
Line 1,252 ⟶ 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]]