Meaty Skeleton

来自osdev
跳到导航 跳到搜索

本页面或章节指其读者或编者使用人称 , 我的, 我们 或者 我们的。 用encyclopedic百科全书式的语言风格进行编辑

等等!你是否已阅读过 起步指南, 起步错误, 以及一些相关的 操作系统理论

难度等级
Difficulty 1.png
初学者
内核设计
模型
其它概念

在本教程中,我们继续从 裸露的骨头 创建一个最小模板 适合进一步修改的操作系统或作为您的灵感 初始操作系统版本。 Bare Bones 教程只给你 绝对最少的代码来演示如何正确交叉编译内核, 但是,这不适合作为示例操作系统。 此外,这 教程实现了满足ABI和ABI所需的必要ABI功能 编译器合同以防止可能的神秘错误。

本教程也是关于如何 创建自己的libc (标准C库)。 GCC 文档明确指出,libgcc需要独立的 环境,以提供 memcmpmemcpymemmove, 和 memset 函数,以及某些平台上的 中止 。 我们 将通过创建一个特殊的内核C库 (libk) 来满足这一要求 包含用户空间libc中 “独立” 的部分 (不 需要任何内核功能),而不是需要 做系统调用。

前言

本教程是一个示例,介绍如何在 在可预见的将来,这种方式将继续为您提供良好的服务。 这个 既是灵感,也是那些希望有所作为的人的榜样 不同,而作为其余部分的基础。 本教程确实嵌入了一些重要的内容 操作系统中的概念,如libc的存在,以及 间接的其他次要Unix和ABI语义。 适应你希望从中得到的东西 教程。 请注意,中构建的shell脚本和基于制作的构建系统 本教程适用于Unix系统。 那里 没有迫切的需要使这种便携式的所有操作系统,因为 只是一个例子。

我们将把这个新的示例操作系统命名为 myos。 这只是一个 占位符,您应该将所有出现的 myos 替换为您 决定调用您的操作系统。

Bare Bones

正文: Bare Bones

在继续之前,你应该已经完成了 Bare Bones 教程 到本教程。 完成 Bare Bones 并不是严格必要的, 但是这样做可以确认您的开发环境以及 解释一些核心问题。

你可能应该丢弃你玩弄的代码 Bare Bones 并以本教程为基础重新开始。

构建交叉编译器

Main article: GCC Cross-Compiler, Why do I need a Cross Compiler?

在本教程中,您 “必须” 使用 GCC交叉编译器,如 裸露的骨头 教程。 您应该在您的i686-elf中使用 “” 目标 交叉编译器,尽管任何 ix86-elf 目标 (但不少于i386) 都将 在这里为我们的目的做好事。

您 “必须” 使用 -- 带有-sysroot 选项配置交叉binutils, 否则,链接将神秘地失败,并显示 “此链接器未配置为使用sysroots” 错误消息。 如果您忘记使用该选项配置交叉binutils,则必须重建它,但是可以保留交叉gcc。

依赖关系

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.

本教程需要一个GNU/Linux系统,或者一个足够相似的系统。 BSD系统几乎可以工作。 OS X is not supported but can possibly be made to work with some changes. 不支持Windows,但是像Cygwin和Windows子系统For Linux (WSL) 这样的Windows环境可能会工作。

Debian-家庭用户

如上所述安装i686-elf工具链,然后安装软件包 xorriso grub-pc-bin

系统根

通常,当您为本地操作系统编译程序时,编译器会在系统目录中定位开发文件,例如标头和库,例如:

/usr/include
/usr/lib

这些文件当然不能用于您的操作系统。 相反,您希望拥有这些目录的自己版本,其中包含操作系统的文件:

/home/bwayne/myos/sysroot/usr/include
/home/bwayne/myos/sysroot/usr/lib

The /home/bwayne/myos/sysroot directory acts as a fake root directory for your operating system. This is called a system root, or sysroot.

您可以将sysroot视为操作系统的根目录。 您的构建过程将构建您的操作系统的每个组件 (内核、标准库、程序),并逐步将它们安装到系统根目录中。 最终,系统根将是您的操作系统的功能齐全的根文件系统,您格式化一个分区并在那里复制文件,添加适当的配置文件,配置一个引导加载程序从那里加载内核,并使用您的硬盘驱动程序和文件系统驱动程序从那里读取文件。 因此,系统根目录是一个临时目录,最终将成为您操作系统的实际根目录。

In this example the cross system root is located as sysroot/, which is a directory created by the build scripts and populated by the make install targets. The makefiles will install the system headers into the sysroot/usr/include directory, the system libraries into the sysroot/usr/lib directory and the kernel itself into the sysroot/boot directory.

我们已经使用了系统根,因为当你到达那一步时,它将使添加用户空间变得更平滑。 当您以后 端口第三方软件 时,该方案非常方便。

-elf 目标没有用户空间,也没有用户空间。 我们配置了具有系统根支持的编译器,因此它将按预期在 ${ SYSROOT}/usr/lib 中查找。 在构建 i686-elf-gcc 时,我们阻止编译器使用-无标题选项搜索标准库,因此它不会在 ${SYSROOT}/usr/include 中 “查看”。 (一旦添加了用户空间和libc,您将使用 -- 与-sysroot 配置自定义跨gcc,它将在 ${ SYSROOT}/usr/include 中查看。 作为一种临时解决方法,直到您走得那么远,我们通过传递 -isystem =/usr/include 来修复它。

如果你愿意,你可以改变系统根目录布局,但是你必须修改一些Binutils和GCC源代码,并告诉他们 你的操作系统是什么。 这是先进的,在添加适当的用户空间之前不值得这样做。 请注意,默认情况下,交叉链接器当前在 /lib/usr/lib/usr/local/lib 中查找,因此您可以在不更改Binutils的情况下将文件移动到那里。 另请注意,我们对GCC使用 -isystem 选项 (因为它是在没有系统包含目录的情况下配置的),因此您可以自由地移动它。

系统标题

The ./headers.sh script simply installs the headers for your libc and kernel (system headers) into sysroot/usr/include, but doesn't actually cross-compile your operating system. This is useful as it allows you to provide the compiler a copy of your headers before you actually compile your system. You will need to provide the standard library headers when you build a Hosted GCC Cross-Compiler in the future that is capable of an user-space.

Note how your cross-compiler comes with a number of fully freestanding headers such as stddef.h and stdint.h. These headers simply declare types and macros that are useful. Your kernel standard library will supply a number of useful functions (such as strlen) that doesn't require system calls and are freestanding except they need an implementation somewhere.

Makefile设计

The makefiles in this example respect the environment variables (such as CFLAGS that tell what default compile options are used to compile C programs). This lets the user control stuff such as which optimization levels 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 sets a default value and one that adds mandatory options the project makefile requires:

# Default CFLAGS:
CFLAGS?=-O2 -g

# Add mandatory options to CFLAGS:
CFLAGS:=$(CFLAGS) -Wall -Wextra

架构目录

The projects in this example (libc and kernel) store all the architecture dependent source files inside an arch/ directory with their own sub-makefile that has special configuration. This cleanly separates the systems you support and will make it easier to port to other systems in the future.

内核设计

我们已经将内核移动到它自己的名为 kernel/ 的目录中。 它会 如果您的内核有其他名称,也许最好将其称为其他名称 您的完整操作系统发行版,尽管将其称为 内核/ 使其他爱好者开发人员更容易找到新的核心部分 操作系统。

The kernel installs its public kernel headers into sysroot/usr/include/kernel. This is useful if you decide to create a kernel with modules, where modules can then simply include the public headers from the main kernel.

GNU GRUB is used as the bootloader and the kernel uses Multiboot as in the Bare Bones tutorial.

The kernel implements the correct way of invoking global constructors (useful for C++ code and C code using __attribute__((constructor)). The bootstrap assembly calls _init which invokes all the global constructors. These are invoked very early in the boot without any specific ordering. You should only use them to initialize global variables that could not be initialized at runtime.

The special __is_kernel macro lets the source code detect whether it is part of the kernel.

libc 和 libk 设计

The libc and libk are actually two versions of the same library, which is stored in the directory libc/. The standard library is split into two versions: freestanding and hosted. The difference is that the freestanding library (libk) doesn't contain any of the code that only works in user-space, such as system calls. The libk is also built with different compiler options, just like the kernel isn't built like normal user-space code.

You are not required to have a libk. You could just as easily have a regular libc and a fully seperate minimal project inside the kernel directory. The libk scheme avoids code duplication, so you don't have to maintain multiple versions of strlen and such.

此示例不附带可用的libc。 它编译了一个libc.a即 完全没用,除了是我们添加用户空间时可以建立的骨架 在以后的教程中。

每个标准函数都放在与该函数同名的文件中 在带有标题名称的目录内。 For instance, strlen from string.h is in libc/string/strlen.c and stat from sys/stat.h would be in libc/sys/stat/stat.c.

The standard headers use a BSD-like scheme where sys/cdefs.h declares a bunch of useful preprocessor macros meant for internal use by the standard library. All the function prototypes are wrapped in extern "C" { and } 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 __is_libc macro lets the source code detect whether it is part of the libc and __is_libk lets the source code detect whether it's part of the libk binary.

此示例附带少量标准函数,这些函数用作 示例并满足ABI要求。 Note that the printf function included is very minimal and intentionally doesn't handle most common features.

Source Code

You can easily download the source code using Git from the Meaty Skeleton Git repository. 这比手动复制容易出错的副本更可取,因为您可能会出错,或者由于我们语法突出显示中的错误,空格可能会出现乱码。 要克隆git存储库,请执行以下操作:

git clone https://gitlab.com/sortie/meaty-skeleton.git

Check for differences between the git revision used in this article and what you cloned (empty output means there is no difference):

git diff 084d1624bedaa9f9e395f055c6bd99299bd97f58..master

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.

kernel

kernel/include/kernel/tty.h

#ifndef _KERNEL_TTY_H
#define _KERNEL_TTY_H

#include <stddef.h>

void terminal_initialize(void);
void terminal_putchar(char c);
void terminal_write(const char* data, size_t size);
void terminal_writestring(const char* data);

#endif

kernel/Makefile

DEFAULT_HOST!=../default-host.sh
HOST?=DEFAULT_HOST
HOSTARCH!=../target-triplet-to-arch.sh $(HOST)

CFLAGS?=-O2 -g
CPPFLAGS?=
LDFLAGS?=
LIBS?=

DESTDIR?=
PREFIX?=/usr/local
EXEC_PREFIX?=$(PREFIX)
BOOTDIR?=$(EXEC_PREFIX)/boot
INCLUDEDIR?=$(PREFIX)/include

CFLAGS:=$(CFLAGS) -ffreestanding -Wall -Wextra
CPPFLAGS:=$(CPPFLAGS) -D__is_kernel -Iinclude
LDFLAGS:=$(LDFLAGS)
LIBS:=$(LIBS) -nostdlib -lk -lgcc

ARCHDIR=arch/$(HOSTARCH)

include $(ARCHDIR)/make.config

CFLAGS:=$(CFLAGS) $(KERNEL_ARCH_CFLAGS)
CPPFLAGS:=$(CPPFLAGS) $(KERNEL_ARCH_CPPFLAGS)
LDFLAGS:=$(LDFLAGS) $(KERNEL_ARCH_LDFLAGS)
LIBS:=$(LIBS) $(KERNEL_ARCH_LIBS)

KERNEL_OBJS=\
$(KERNEL_ARCH_OBJS) \
kernel/kernel.o \

OBJS=\
$(ARCHDIR)/crti.o \
$(ARCHDIR)/crtbegin.o \
$(KERNEL_OBJS) \
$(ARCHDIR)/crtend.o \
$(ARCHDIR)/crtn.o \

LINK_LIST=\
$(LDFLAGS) \
$(ARCHDIR)/crti.o \
$(ARCHDIR)/crtbegin.o \
$(KERNEL_OBJS) \
$(LIBS) \
$(ARCHDIR)/crtend.o \
$(ARCHDIR)/crtn.o \

.PHONY: all clean install install-headers install-kernel
.SUFFIXES: .o .c .S

all: myos.kernel

myos.kernel: $(OBJS) $(ARCHDIR)/linker.ld
$(CC) -T $(ARCHDIR)/linker.ld -o $@ $(CFLAGS) $(LINK_LIST)
grub-file --is-x86-multiboot myos.kernel

$(ARCHDIR)/crtbegin.o $(ARCHDIR)/crtend.o:
OBJ=`$(CC) $(CFLAGS) $(LDFLAGS) -print-file-name=$(@F)` && cp "$$OBJ" $@

.c.o:
$(CC) -MD -c $< -o $@ -std=gnu11 $(CFLAGS) $(CPPFLAGS)

.S.o:
$(CC) -MD -c $< -o $@ $(CFLAGS) $(CPPFLAGS)

clean:
rm -f myos.kernel
rm -f $(OBJS) *.o */*.o */*/*.o
rm -f $(OBJS:.o=.d) *.d */*.d */*/*.d

install: install-headers install-kernel

install-headers:
mkdir -p $(DESTDIR)$(INCLUDEDIR)
cp -R --preserve=timestamps include/. $(DESTDIR)$(INCLUDEDIR)/.

install-kernel: myos.kernel
mkdir -p $(DESTDIR)$(BOOTDIR)
cp myos.kernel $(DESTDIR)$(BOOTDIR)

-include $(OBJS:.o=.d)

kernel/kernel/kernel.c

#include <stdio.h>

#include <kernel/tty.h>

void kernel_main(void) {
terminal_initialize();
printf("Hello, kernel World!\n");
}

kernel/arch/i386/tty.c

#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>

#include <kernel/tty.h>

#include "vga.h"

static const size_t VGA_WIDTH = 80;
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 = vga_entry_color(VGA_COLOR_LIGHT_GREY, VGA_COLOR_BLACK);
terminal_buffer = VGA_MEMORY;
for (size_t y = 0; y < VGA_HEIGHT; y++) {
for (size_t x = 0; x < VGA_WIDTH; x++) {
const size_t index = y * VGA_WIDTH + x;
terminal_buffer[index] = vga_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] = 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)
		{
			for(line = 1; line <= VGA_HEIGHT - 1; line++)
			{
				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++)
terminal_putchar(data[i]);
}

void terminal_writestring(const char* data) {
terminal_write(data, strlen(data));
}

kernel/arch/i386/crtn.S

.section .init
	/* gcc will nicely put the contents of crtend.o's .init section here. */
	popl %ebp
	ret

.section .fini
	/* gcc will nicely put the contents of crtend.o's .fini section here. */
	popl %ebp
	ret

kernel/arch/i386/vga.h

#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

kernel/arch/i386/make.config

KERNEL_ARCH_CFLAGS=
KERNEL_ARCH_CPPFLAGS=
KERNEL_ARCH_LDFLAGS=
KERNEL_ARCH_LIBS=

KERNEL_ARCH_OBJS=\
$(ARCHDIR)/boot.o \
$(ARCHDIR)/tty.o \

kernel/arch/i386/crti.S

.section .init
.global _init
.type _init, @function
_init:
	push %ebp
	movl %esp, %ebp
	/* gcc will nicely put the contents of crtbegin.o's .init section here. */

.section .fini
.global _fini
.type _fini, @function
_fini:
	push %ebp
	movl %esp, %ebp
	/* gcc will nicely put the contents of crtbegin.o's .fini section here. */

kernel/arch/i386/linker.ld

/* The bootloader will look at this image and start execution at the symbol
   designated at the entry point. */
ENTRY(_start)

/* Tell where the various sections of the object files will be put in the final
   kernel image. */
SECTIONS
{
	/* Begin putting sections at 1 MiB, a conventional place for kernels to be
	   loaded at by the bootloader. */
	. = 1M;

	/* First put the multiboot header, as it is required to be put very early
	   early in the image or the bootloader won't recognize the file format.
	   Next we'll put the .text section. */
	.text BLOCK(4K) : ALIGN(4K)
	{
		*(.multiboot)
		*(.text)
	}

	/* Read-only data. */
	.rodata BLOCK(4K) : ALIGN(4K)
	{
		*(.rodata)
	}

	/* Read-write data (initialized) */
	.data BLOCK(4K) : ALIGN(4K)
	{
		*(.data)
	}

	/* Read-write data (uninitialized) and stack */
	.bss BLOCK(4K) : ALIGN(4K)
	{
		*(COMMON)
		*(.bss)
	}

	/* The compiler may produce other sections, put them in the proper place in
	   in this file, if you'd like to include them in the final kernel. */
}

kernel/arch/i386/boot.S

# Declare constants for the multiboot header.
.set ALIGN,    1<<0             # align loaded modules on page boundaries
.set MEMINFO,  1<<1             # provide memory map
.set FLAGS,    ALIGN | MEMINFO  # this is the Multiboot 'flag' field
.set MAGIC,    0x1BADB002       # 'magic number' lets bootloader find the header
.set CHECKSUM, -(MAGIC + FLAGS) # checksum of above, to prove we are multiboot

# Declare a header as in the Multiboot Standard.
.section .multiboot
.align 4
.long MAGIC
.long FLAGS
.long CHECKSUM

# Reserve a stack for the initial thread.
.section .bss
.align 16
stack_bottom:
.skip 16384 # 16 KiB
stack_top:

# The kernel entry point.
.section .text
.global _start
.type _start, @function
_start:
	movl $stack_top, %esp

	# Call the global constructors.
	call _init

	# Transfer control to the main kernel.
	call kernel_main

	# Hang if kernel_main unexpectedly returns.
	cli
1:	hlt
	jmp 1b
.size _start, . - _start

kernel/.gitignore

*.d
*.kernel
*.o

libc and libk

libc/include/string.h

#ifndef _STRING_H
#define _STRING_H 1

#include <sys/cdefs.h>

#include <stddef.h>

#ifdef __cplusplus
extern "C" {
#endif

int memcmp(const void*, const void*, size_t);
void* memcpy(void* __restrict, const void* __restrict, size_t);
void* memmove(void*, const void*, size_t);
void* memset(void*, int, size_t);
size_t strlen(const char*);

#ifdef __cplusplus
}
#endif

#endif

libc/include/stdio.h

#ifndef _STDIO_H
#define _STDIO_H 1

#include <sys/cdefs.h>

#define EOF (-1)

#ifdef __cplusplus
extern "C" {
#endif

int printf(const char* __restrict, ...);
int putchar(int);
int puts(const char*);

#ifdef __cplusplus
}
#endif

#endif

libc/include/sys/cdefs.h

#ifndef _SYS_CDEFS_H
#define _SYS_CDEFS_H 1

#define __myos_libc 1

#endif

libc/include/stdlib.h

#ifndef _STDLIB_H
#define _STDLIB_H 1

#include <sys/cdefs.h>

#ifdef __cplusplus
extern "C" {
#endif

__attribute__((__noreturn__))
void abort(void);

#ifdef __cplusplus
}
#endif

#endif

libc/Makefile

DEFAULT_HOST!=../default-host.sh
HOST?=DEFAULT_HOST
HOSTARCH!=../target-triplet-to-arch.sh $(HOST)

CFLAGS?=-O2 -g
CPPFLAGS?=
LDFLAGS?=
LIBS?=

DESTDIR?=
PREFIX?=/usr/local
EXEC_PREFIX?=$(PREFIX)
INCLUDEDIR?=$(PREFIX)/include
LIBDIR?=$(EXEC_PREFIX)/lib

CFLAGS:=$(CFLAGS) -ffreestanding -Wall -Wextra
CPPFLAGS:=$(CPPFLAGS) -D__is_libc -Iinclude
LIBK_CFLAGS:=$(CFLAGS)
LIBK_CPPFLAGS:=$(CPPFLAGS) -D__is_libk

ARCHDIR=arch/$(HOSTARCH)

include $(ARCHDIR)/make.config

CFLAGS:=$(CFLAGS) $(ARCH_CFLAGS)
CPPFLAGS:=$(CPPFLAGS) $(ARCH_CPPFLAGS)
LIBK_CFLAGS:=$(LIBK_CFLAGS) $(KERNEL_ARCH_CFLAGS)
LIBK_CPPFLAGS:=$(LIBK_CPPFLAGS) $(KERNEL_ARCH_CPPFLAGS)

FREEOBJS=\
$(ARCH_FREEOBJS) \
stdio/printf.o \
stdio/putchar.o \
stdio/puts.o \
stdlib/abort.o \
string/memcmp.o \
string/memcpy.o \
string/memmove.o \
string/memset.o \
string/strlen.o \

HOSTEDOBJS=\
$(ARCH_HOSTEDOBJS) \

OBJS=\
$(FREEOBJS) \
$(HOSTEDOBJS) \

LIBK_OBJS=$(FREEOBJS:.o=.libk.o)

#BINARIES=libc.a libk.a # Not ready for libc yet.
BINARIES=libk.a

.PHONY: all clean install install-headers install-libs
.SUFFIXES: .o .libk.o .c .S

all: $(BINARIES)

libc.a: $(OBJS)
$(AR) rcs $@ $(OBJS)

libk.a: $(LIBK_OBJS)
$(AR) rcs $@ $(LIBK_OBJS)

.c.o:
$(CC) -MD -c $< -o $@ -std=gnu11 $(CFLAGS) $(CPPFLAGS)

.S.o:
$(CC) -MD -c $< -o $@ $(CFLAGS) $(CPPFLAGS)

.c.libk.o:
$(CC) -MD -c $< -o $@ -std=gnu11 $(LIBK_CFLAGS) $(LIBK_CPPFLAGS)

.S.libk.o:
$(CC) -MD -c $< -o $@ $(LIBK_CFLAGS) $(LIBK_CPPFLAGS)

clean:
rm -f $(BINARIES) *.a
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

install-headers:
mkdir -p $(DESTDIR)$(INCLUDEDIR)
cp -R --preserve=timestamps include/. $(DESTDIR)$(INCLUDEDIR)/.

install-libs: $(BINARIES)
mkdir -p $(DESTDIR)$(LIBDIR)
cp $(BINARIES) $(DESTDIR)$(LIBDIR)

-include $(OBJS:.o=.d)
-include $(LIBK_OBJS:.o=.d)

libc/stdlib/abort.c

#include <stdio.h>
#include <stdlib.h>

__attribute__((__noreturn__))
void abort(void) {
#if defined(__is_libk)
	// TODO: Add proper kernel panic.
	printf("kernel: panic: abort()\n");
#else
	// TODO: Abnormally terminate the process as if by SIGABRT.
	printf("abort()\n");
#endif
	while (1) { }
	__builtin_unreachable();
}

libc/string/memmove.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;
}

libc/string/strlen.c

#include <string.h>

size_t strlen(const char* str) {
size_t len = 0;
while (str[len])
len++;
return len;
}

libc/string/memcmp.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;
}

libc/string/memset.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;
}

libc/string/memcpy.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;
}

libc/stdio/puts.c

#include <stdio.h>

int puts(const char* string) {
return printf("%s\n", string);
}

libc/stdio/putchar.c

#include <stdio.h>

#if defined(__is_libk)
#include <kernel/tty.h>
#endif

int putchar(int ic) {
#if defined(__is_libk)
	char c = (char) ic;
	terminal_write(&c, sizeof(c));
#else
	// TODO: Implement stdio and the write system call.
#endif
	return ic;
}

libc/stdio/printf.c

#include <limits.h>
#include <stdbool.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>

static bool print(const char* data, size_t length) {
const unsigned char* bytes = (const unsigned char*) data;
for (size_t i = 0; i < length; i++)
if (putchar(bytes[i]) == EOF)
return false;
return true;
}

int printf(const char* restrict format, ...) {
va_list parameters;
va_start(parameters, format);

	int written = 0;

	while (*format != '\0') {
		size_t maxrem = INT_MAX - written;

		if (format[0] != '%' || format[1] == '%') {
			if (format[0] == '%')
				format++;
			size_t amount = 1;
			while (format[amount] && format[amount] != '%')
				amount++;
			if (maxrem < amount) {
				// TODO: Set errno to EOVERFLOW.
				return -1;
			}
			if (!print(format, amount))
				return -1;
			format += amount;
			written += amount;
			continue;
		}

		const char* format_begun_at = format++;

		if (*format == 'c') {
			format++;
			char c = (char) va_arg(parameters, int /* char promotes to int */);
			if (!maxrem) {
				// TODO: Set errno to EOVERFLOW.
				return -1;
			}
			if (!print(&c, sizeof(c)))
				return -1;
			written++;
		} else if (*format == 's') {
			format++;
			const char* str = va_arg(parameters, const char*);
			size_t len = strlen(str);
			if (maxrem < len) {
				// TODO: Set errno to EOVERFLOW.
				return -1;
			}
			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;
}

libc/arch/i386/make.config

ARCH_CFLAGS=
ARCH_CPPFLAGS=
KERNEL_ARCH_CFLAGS=
KERNEL_ARCH_CPPFLAGS=

ARCH_FREEOBJS=\

ARCH_HOSTEDOBJS=\

libc/.gitignore

*.a
*.d
*.o

Miscellaneous

These files go into the root source directory.

build.sh

#!/bin/sh
set -e
. ./headers.sh

for PROJECT in $PROJECTS; do
(cd $PROJECT && DESTDIR="$SYSROOT" $MAKE install)
done

您应该通过运行以下操作使此可执行脚本可执行:

chmod +x build.sh

clean.sh

#!/bin/sh
set -e
. ./config.sh

for PROJECT in $PROJECTS; do
(cd $PROJECT && $MAKE clean)
done

rm -rf sysroot
rm -rf isodir
rm -rf myos.iso

您应该通过运行以下操作使此可执行脚本可执行:

chmod +x clean.sh

config.sh

SYSTEM_HEADER_PROJECTS="libc kernel"
PROJECTS="libc kernel"

export MAKE=${MAKE:-make}
export HOST=${HOST:-$(./default-host.sh)}

export AR=${HOST}-ar
export AS=${HOST}-as
export CC=${HOST}-gcc

export PREFIX=/usr
export EXEC_PREFIX=$PREFIX
export BOOTDIR=/boot
export LIBDIR=$EXEC_PREFIX/lib
export INCLUDEDIR=$PREFIX/include

export CFLAGS='-O2 -g'
export CPPFLAGS=''

# Configure the cross-compiler to use the desired system root.
export SYSROOT="$(pwd)/sysroot"
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 CC="$CC -isystem=$INCLUDEDIR"
fi

default-host.sh

#!/bin/sh
echo i686-elf

您应该通过运行以下操作使此可执行脚本可执行:

chmod +x default-host.sh

headers.sh

#!/bin/sh
set -e
. ./config.sh

mkdir -p "$SYSROOT"

for PROJECT in $SYSTEM_HEADER_PROJECTS; do
(cd $PROJECT && DESTDIR="$SYSROOT" $MAKE install-headers)
done

You should make this executable script executable by running:

chmod +x headers.sh

iso.sh

#!/bin/sh
set -e
. ./build.sh

mkdir -p isodir
mkdir -p isodir/boot
mkdir -p isodir/boot/grub

cp sysroot/boot/myos.kernel isodir/boot/myos.kernel
cat > isodir/boot/grub/grub.cfg << EOF
menuentry "myos" {
multiboot /boot/myos.kernel
}
EOF
grub-mkrescue -o myos.iso isodir

您应该通过运行以下操作使此可执行脚本可执行:

chmod +x iso.sh

qemu.sh

#!/bin/sh
set -e
. ./iso.sh

qemu-system-$(./target-triplet-to-arch.sh $HOST) -cdrom myos.iso

You should make this executable script executable by running:

chmod +x qemu.sh

target-triplet-to-arch.sh

#!/bin/sh
if echo "$1" | grep -Eq 'i[[:digit:]]86-'; then
  echo i386
else
  echo "$1" | grep -Eo '^[[:alnum:]_]*'
fi

您应该通过运行以下操作使此可执行脚本可执行:

chmod +x target-triplet-to-arch.sh

.gitignore

*.iso
isodir
sysroot

交叉编译操作系统

The system is cross-compiled in the same manner as Bare Bones, though with the complexity of having a system root with the final system and using a libk. In this example, we elected to use shell scripts to to the top-level build process, though you could possibly also use a makefile for that or a wholly different build system. Though, assuming this setup works for you, you can clean the source tree by invoking:

./clean.sh

您可以将所有系统头安装到系统根目录中,而无需依赖 编译器,稍后切换到 托管GCC交叉编译器,通过调用:

./headers.sh

您可以通过调用以下方式构建操作系统的可引导cdrom映像:

./iso.sh

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:

./qemu.sh

Troubleshooting

如果您在构建过程中收到奇怪的错误,您可能在手动复制过程中犯了一个错误,可能错过了一个文件,忘记制作一个文件可执行文件,或者我们使用的突出显示软件中的错误导致出现意外空白。 如上所述执行git存储库克隆,并使用该代码,或者将两个目录树与 diff(1) diff命令行实用程序进行比较。 如果您对代码进行了个人更改,则可能是错误的。

向前迈进

你应该根据你的需要调整这个模板。 你有很多事情 应该考虑现在做:

将MyOS重命名为YourOS

当然,您希望以您最喜欢的花朵命名操作系统, 家乡,布尔值,或者任何营销告诉你的。 进行搜索并替换替换 myos with whatever you wish to call it. 请记住, 出于技术原因,该名称在某些地方故意小写。

Improving the Build System

正文: Hard Build System

It is probably worth improving the build system. For instance, it could be useful if build.sh accepted command-line options, or perhaps if it used make's important -j option for concurrent builds.

值得考虑的是,贡献者将如何构建您的操作系统。 这是一个容易陷入的陷阱,认为你可以制作超级脚本,做所有的事情。 这将最终变得复杂和不够灵活; 或者它将变得灵活,甚至更加复杂。 最好记录用户准备交叉工具链应该做什么,以及要安装哪些先决条件程序。 本教程展示了一个仅构建操作系统的示例硬构建系统。 您可以通过记录如何构建交叉编译器以及如何使用它来完成它。

堆栈粉碎保护器

正文: 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

正文: 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.

用户空间

本系列的后续教程将使用适当的 用户空间和充分利用系统根的 OS特定工具链

论坛帖子