• 文章
  • 如何使用 C++ 開發作業系統
釋出
2013 年 9 月 20 日(最後更新:2013 年 9 月 22 日)

如何使用 C++ 開發作業系統?

評分:3.8/5 (2038 票)
*****

作業系統是如何開發的?

你可以使用 C(或者實際上是)C++、組合語言以及 Ada、Fortran、Pascal 等任何程式語言來開發自己的作業系統。
但你必須在某些地方使用匯編語言。

組合語言簡介

組合語言是一種低階程式語言,你可以用它來控制一切,例如向 CPU 暫存器新增內容、控制記憶體等等。

我如何開始開發作業系統?

首先,你必須瞭解你所用程式語言的所有知識,例如指標、函式(在這裡我想使用 C++)。
其次,你必須對組合語言有一些瞭解。

開發作業系統需要哪些工具?

開發作業系統必須具備以下條件:
1. 彙編器
彙編器會處理你的彙編程式碼,並生成低階輸出,例如包含你對 CPU 暫存器控制的物件。
這裡我想使用的彙編器是 nasm(netwide assembler)。
你可以從 http://nasm.us 下載。
2. 交叉編譯器
為了開發作業系統,你必須有一個交叉編譯器,因為你必須為作業系統的可執行格式來編譯你的核心。
這裡我使用的是 gcc(gnu compiler collection)。
你可以從 http://gcc.gnu.org/ 下載。
3. 連結器
連結器會將你的物件檔案連結在一起。
這裡我使用 gnu binutils。
你可以從 http://gnu.org/software/binutils 下載。
4. 虛擬機器
為了測試你的作業系統,你必須有一個虛擬機器。
但這不是必需的。
這裡我使用 virtualbox。
你可以從 http://virtualbox.org/ 下載。

開始前的注意事項

1. 在開發作業系統時,你不能、不能、絕對不能使用 <iostream>、<fstream>、<memory>、<cstdio>、<cstdlib>、<windows.h>、<unistd.h> 以及所有平臺 API。
你必須自己建立所有這些。
2. 你必須非常非常小心。
在開發過程中,你控制著一切。
因此,你可能會損壞你的一個、幾個或全部硬體。
在這種情況下,我建議使用虛擬機器來測試你的作業系統,而不是一遍又一遍地重啟。

引導載入程式

引導載入程式是一段用匯編語言編寫的程式碼,必須是 512 位元組(1 個扇區)。
它會載入你的作業系統的核心。
我們跳過這一部分,使用 grub 作為我們的引導載入程式。
你可以從 http://gnu.org/software/grub 下載 grub 原始碼。
或者,你可能想要一個為軟盤編譯的版本:在 Google 上搜索會有幫助。

一個簡單的核心

我們要開發一個作業系統。
所以,我們必須自己建立函式。
首先,我們建立一個名為 boot.asm 的檔案,內容如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
;boot.asm:the bootloader to boot are operating system with grub
[bits 32] ;we are in 32 bit
global start ;start's the operating system:we call it in the linker script
extern _kernel_main ;this is in are .cpp file and it is the main function of are kernel
;do not modify these lines(these are needed by grub)!
section .mbHeader

align 0x4
 
; setting up the Multiboot header - see GRUB docs for details
MODULEALIGN equ  1<<0                   ; align loaded modules on page boundaries
MEMINFO     equ  1<<1                   ; provide memory map
FLAGS       equ  MODULEALIGN | MEMINFO  ; this is the Multiboot 'flag' field
MAGIC       equ    0x1BADB002           ; 'magic number' lets bootloader find the header
CHECKSUM    equ -(MAGIC + FLAGS)        ; checksum required
 
MultiBootHeader:
   dd MAGIC
   dd FLAGS
   dd CHECKSUM
 
;you can modify these
start:
push ebx ;this is optional and load's the grub structure
call _kernel_main

現在建立一個名為 kernel.cpp 的檔案,內容如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
#include <stddef.h> //we can use it: it doesnt use any platform-related api functions
#include <stdint.h> //include it to get int16_t and some integer types

/* Hardware text mode color constants. */
enum vga_color
{
	COLOR_BLACK = 0,
	COLOR_BLUE = 1,
	COLOR_GREEN = 2,
	COLOR_CYAN = 3,
	COLOR_RED = 4,
	COLOR_MAGENTA = 5,
	COLOR_BROWN = 6,
	COLOR_LIGHT_GREY = 7,
	COLOR_DARK_GREY = 8,
	COLOR_LIGHT_BLUE = 9,
	COLOR_LIGHT_GREEN = 10,
	COLOR_LIGHT_CYAN = 11,
	COLOR_LIGHT_RED = 12,
	COLOR_LIGHT_MAGENTA = 13,
	COLOR_LIGHT_BROWN = 14,
	COLOR_WHITE = 15,
};
 
uint8_t make_color(enum vga_color fg, enum vga_color bg)
{
	return fg | bg << 4;
}
 
uint16_t make_vgaentry(char c, uint8_t color)
{
	uint16_t c16 = c;
	uint16_t color16 = color;
	return c16 | color16 << 8;
}
 
size_t strlen(const char* str)
{
	size_t ret = 0;
	while ( str[ret] != 0 )
		ret++;
	return ret;
}
 
static const size_t VGA_WIDTH = 80;
static const size_t VGA_HEIGHT = 24;
 
size_t terminal_row;
size_t terminal_column;
uint8_t terminal_color;
uint16_t* terminal_buffer;
 
void terminal_initialize()
{
	terminal_row = 0;
	terminal_column = 0;
	terminal_color = make_color(COLOR_LIGHT_GREY, COLOR_BLACK);
	terminal_buffer = (uint16_t*) 0xB8000;
	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] = make_vgaentry(' ', terminal_color);
		}
	}
}
 
void terminal_setcolor(uint8_t color)
{
	terminal_color = color;
}
 
void terminal_putentryat(char c, uint8_t color, size_t x, size_t y)
{
	const size_t index = y * VGA_WIDTH + x;
	terminal_buffer[index] = make_vgaentry(c, color);
}
 
void terminal_putchar(char c)
{
	terminal_putentryat(c, terminal_color, terminal_column, terminal_row);
	if ( ++terminal_column == VGA_WIDTH )
	{
		terminal_column = 0;
		if ( ++terminal_row == VGA_HEIGHT )
		{
			terminal_row = 0;
		}
	}
}
 
void terminal_writestring(const char* data)
{
	size_t datalen = strlen(data);
	for ( size_t i = 0; i < datalen; i++ )
		terminal_putchar(data[i]);
}
 
void kernel_main()
{
terminal_initialize();
terminal_writestring("wellcome to my first operating system!");
for(;;);
}

連結器指令碼

建立一個名為 linker.ld 的檔案,內容如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/* The bootloader will look at this image and start execution at the symbol
   designated as 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)
	{
		*(.mbHeader)
		*(.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, by default it will put them in
	   a segment with the same name. Simply add stuff here as needed. */
}

如何編譯它

進入 shell(Windows 上需要 cygwin)。
輸入以下命令:
1
2
3
nasm -f elf boot.asm -o boot.o
g++ -c kernel.cpp -o kernel.o -ffreestandinng -fno-exceptions -fno-rtti
gcc loader.o kernel.o -T linker.ld -o kern -nostdlib -nodefaultlibs -lgcc

恭喜!
你的第一個作業系統已成功編譯!
現在你可以使用 grub-mkrescue 建立一個映象。
建立一個目錄:iso
在該目錄中,建立另一個目錄:boot,然後在 boot 目錄中,建立一個目錄:grub,然後建立一個名為 grub.cfg 的檔案,內容如下(不要將大括號放在新行上),放在 grub 目錄中。
1
2
3
menuentry "myOS" {
multiboot /boot/kern
}

然後將你的核心(kern)複製到 iso/boot 目錄,然後再次執行你的 shell。
切換到你的核心的主目錄,然後輸入:
 
grub-mkrescue iso --output=kern.iso

現在你可以啟動並享受你的第一個作業系統了:這個簡單的核心沒有任何功能。