# 寒假OS学习第七天
学不动了,后面的有点复杂
总结一下,搭建一个基于GRUB引导程序的toy OS kernel需要哪些知识
-
Linux下的存储维护相关的命令
用于制作GRUB引导盘 -
汇编语言,NASM或者AT&T。在x86架构下进行汇编的能力
NASM和Intel汇编语法一样,更简单,AT&T也有学的必要 -
C语言的高级用法
需要看一遍《C语言专家编程》,掌握一些C语言的高级用法 -
GCC的使用
起码知道做一个内核要用GCC的哪些参数 -
链接器ld的使用
要做到会写ld脚本的水平 -
Makefile脚本
会写Makefile脚本才是一个合格的程序员 -
Bochs或者QEMU的使用
我选择使用Bochs,因为QEMU不会装
选择Bochs,需要学会Bochs和GDB的联合调试 -
内核调试工具的使用
-
multiboot规范
使用GRUB引导,必须熟知Multiboot规范,并且必须熟知使用GRUB引导后机器处于什么状态 -
熟知x86架构下的各种CPU细节、规范
可以下一份Intel的开源文档来看 -
熟知操作系统原理
看那本操作系统概念
计划
接下来的时间里,我的打算是:
- 开始看James的教程
- 每天学一点基础知识
JamesM’s kernel
这个系列的教程会教你写一个基于x86架构的简单的UNIX-clone的操作系统。
使用语言:C、NASM
需要:
- GCC
- ld
- NASM
- GUN make
环境搭建
我们的kernel不会提供bootloader的教程。我们使用GRUB进行引导
floopy我在hurlex的教程里面已经做完了一次
我们的程序会在裸机上进行运行,因此调试会变得艰难,所以务必做到一次就成功
使用Bochs提供虚拟环境
Bochs配置如下:
# BOCHS配置
# 能使用的内存大小,单位为mb
megs: 32
# ROM文件,这个文件在/usr/share/bochs里面能找到
romimage: file="$BXSHARE/BIOS-bochs-latest"
vgaromimage: file="$BXSHARE/VGABIOS-lgpl-latest"
# 软盘。因为我们的目的是生成系统镜像,然后使用bochs运行,这个系统镜像就相
当于软盘。有一个软盘就用floppya,两个就是 floppyb,以此类推
floppya: 1_44=floppy.img, status=inserted
# 设置启动设备
boot: floppy
# 日志输出
log: bochs_log.txt
# 鼠标是否启用
mouse: enabled=0
# 启用键盘映射
keyboard: keymap="$BXSHARE/keymaps/x11-pc-us.map"
# CPU配置
clock: sync=realtime
cpu: ips=5000000
接下来,我们将要写一些脚本
Makefile脚本:
# 编译使用的源代码
C_SOURCES = $(shell find . -name "*.c")
S_SOURCES = $(shell find . -name "*.s")
# 编译生成的目标文件
C_OBJECTS = $(patsubst %.c, %.o, $(C_SOURCES))
S_OBJECTS = $(patsubst %.s, %.o, $(S_SOURCES))
# 输出
OUTPUT = "ymwm_kernel.img"
# 编译器
CC = gcc
ASM = nasm
# 链接器
LD = ld
# 编译参数
C_FLAGS =
# 生成ELF格式文件,并添加调试信息(-g),
# 使得调试信息有效(-F),格式为stabs
ASM_FALGS = -f elf -g -F stabs
# 脚本位置:scripts/link.ld, 格式:elf_i386, 不使用标准库
LD_FLAGS = -T scripts/link.ld -m elf_i386 -nostdlib
all: $(S_OBJECTS) $(C_OBJECTS) link update_image
# 对所有的C文件执行编译操作
.c.o:
$(CC) $(C_FLAGS) $< -o $@
# 对所有的的汇编文件执行编译操作
.s.o:
$(ASM) $(ASM_FALGS) $< -o $@
# 链接操作
.PHONY: link
link:
$(LD) $(LD_FLAGS) $(S_OBJECTS) $(C_OBJECTS) -o $(OUTPUT)
.PHONY: clean
clean:
$(RM) $(S_OBJECTS) $(C_OBJECTS) $(OUTPUT)
.PHONY: update_image
update_image:
sudo mount floppy.img /mnt/kernel
sudo cp ymwm_kernel.img /mnt/kernel/ymwm_kernel.img
sleep 1
sudo umount /mnt/kernel
.PHONY: run
run:
bochs
.PHONY: remake
remake:
@make clean
@make all
已经很熟悉了
LD脚本
/* 程序入口 */
ENTRY(start)
/* 程序section */
SECTIONS
{
/* 程序的起始位置 */
. = 0x100000;
.text:
{
/* 将所有输入文件的.text section合并 */
*(.text)
/* 进行对齐 */
. = ALIGN(4096);
}
.data:
{
*(.data)
/* 为了方便起见,将只读段也加入.data */
*(.rodata)
. = ALIGN(4096);
}
.bss:
{
*(.bss)
. = ALIGN(4096);
}
}
ld脚本告诉链接器要怎么去链接。
首先告诉链接器内核的入口为start
,然后告诉链接器.text
段应当被放在最开始的地方,且内核的起始地址为0x100000
(1MB)。
开始
首先要写boot代码
由于我们使用了GRUB帮助引导,因此这一段的代码比较简单
# boot/boot.s
MBOOT_HEADER_MAGIC equ 0x1BADB002 ; 魔数
MBOOT_PAGE_ALIGN equ 1 << 0 ; 进行页对齐
MBOOT_MEM_INFO equ 1 << 1 ; 将内存信息放入结构体
MBOOT_HEADER_FLAGS equ MBOOT_MEM_INFO | MBOOT_PAGE_ALIGN
MBOOT_CHECKSUM equ -(MBOOT_HEADER_MAGIC + MBOOT_HEADER_FLAGS)
[BITS 32] ; 32位
section .text ; 代码段
dd MBOOT_HEADER_MAGIC
dd MBOOT_HEADER_FLAGS
dd MBOOT_CHECKSUM
[GLOBAL start] ; 程序入口
[GLOBAL glb_mboot] ; mboot struct
[EXTERN kernel_entry] ; 入口
start: ; 入口
cli ; 关闭中断。此时尚未建立IDT, 发生中断会导致启动失败-
mov [glb_mboot], ebx ; GRUB将mboot_t结构体指针放在ebx处
mov ebp, 0
mov esp, STACK_TOP ; 函数运行时栈
call kernel_entry ;
stop:
hlt
jmp stop
section .bss ; bss段
glb_mboot:
resb 4 ;分配大小为4的空间
STACK_TOP equ 0x8000
我们的内核运行时栈的大小就只有0x8000这么大
为什么要把大小设置成这么大?
因为1MB下有很多的其他外设的接口,例如显卡就在0xB8000
但是0~0x8000这个区域就绝对什么都没有,一干二净
至于函数入口函数,写一个hello world
/*
* @Author: yingmanwumen
* @Date: 2021-02-04 21:49:12
* @Last Modified by: yingmanwumen
* @Last Modified time: 2021-02-04 23:40:15
*/
int kernel_entry()
{
char *input = (char *)0xB8000;
char color = (0 << 4) | (15 & 0x0F);
*input ++ = 'H'; *input ++ = color;
*input ++ = 'e'; *input ++ = color;
*input ++ = 'l'; *input ++ = color;
*input ++ = 'l'; *input ++ = color;
*input ++ = 'o'; *input ++ = color;
*input ++ = ','; *input ++ = color;
*input ++ = ' '; *input ++ = color;
*input ++ = 'W'; *input ++ = color;
*input ++ = 'o'; *input ++ = color;
*input ++ = 'r'; *input ++ = color;
*input ++ = 'l'; *input ++ = color;
*input ++ = 'd'; *input ++ = color;
*input ++ = '!'; *input ++ = color;
return 0;
}
屏幕输出
我们通过GRUB将显卡初始化为文本模式
文本模式一般的规格是80*25
本教程不会教你要怎么做图像模式的
显卡的帧缓冲通过0xB8000
进行访问
帧缓冲是一个16位的数组,每一个字节的0-7个位是字符,8-11位是前景色,12-15位是背景色
首先定义一些以后会经常使用的类型与接口操作函数
#ifndef INCLUDE_COMMON_H_
#define INCLUDE_COMMON_H_
typedef unsigned int u32_t;
typedef int s32_t;
typedef unsigned short u16_t;
typedef short s16_t;
typedef unsigned char u8_t;
typedef char s8_t;
void outb(u16_t port, u8_t value);
u8_t inb(u16_t port);
u16_t inw(u16_t port);