操作系统内核Hack:(三)引导程序制作
关于本文涉及到的完整源码请参考MiniOS的v1_bootloader分支。
1.制作方法
现在我们已经了解了关于BootLoader的一切知识,让我们开始动手做一个BootLoader吧!但真正开始之前,我们还要做出一个选择,在之前的讨论中我们曾说过,有两种学习和制作引导程序和操作系统内核的路线:1)《Orange’s:一个操作系统的实现》书中的路线;2)Linux 0.11的路线。
1.1 两种实现思路
具体来说,第一种路线就是将BootLoader和Kernel都放到预先格式化成FAT的软盘上,BootLoader通过FAT和ELF的知识定位并加载Kernel,借助FreeDOS启动或调试我们的操作系统。并非对FAT和DOS心存偏见,毕竟现在的确很多汇编语言书籍仍然以DOS作为学习平台,但我个人更喜欢第二种路线。在引导过程,我们不依赖任何文件系统,在裸扇区中加载运行第二阶段BootLoader和Kernel。进入Kernel之后,我们再挂载一个根文件系统。为何要这样做而不跟着《Orange’s》作者的思路走呢?因为我们毕竟主要学习和模仿的是*nix族的操作系统,既然如此,为何不纯粹一些?直接循着当年Linus在Linux 0.11中的思路,这才算是一次酣畅淋漓的操作系统内核的Hack之旅!
经过了本系列前两回对实验环境和底层编程基础知识的准备,加上刚才对实现思路的分析,现在我们就参考《Linux内核完全注释》中Linux 0.11的思路,借鉴《Orange’s:一个操作系统的实现》中NASM的汇编代码,“双剑合璧,威力无穷”。让我们站在前人的肩膀上,一起动手来实现一个属于我们自己的操作系统BootLoader!
1.2 Linux 0.11参考
既然要借鉴思路,我们就先看一下Linux 0.11这个成品是什么样子。因为《Linux内核完全注释》中提供的源代码链接要做很多手动修改和准备工作才能使用,而网上有很多热心的网友分享了开箱即用的0.11源码安装包。
1.2.1 编译内核
cdai@cdai ~/Source $ git clone https://github.com/yuanxinyu/Linux-0.11
cdai@cdai ~/Source $ cd Linux-0.11
cdai@cdai-Vostro-1450 ~/Source/Linux-0.11 $ make help
<<<<This is the basic help info of linux-0.11>>>
Usage:
make --generate a kernel floppy Image with a fs on hda1
make start -- start the kernel in qemu
make debug -- debug the kernel in qemu & gdb at port 1234
make disk -- generate a kernel Image & copy it to floppy
make cscope -- genereate the cscope index databases
make tags -- generate the tag file
make cg -- generate callgraph of the system architecture
make clean -- clean the object files
make distclean -- only keep the source code files
Note!:
* You need to install the following basic tools:
ubuntu|debian, qemu|bochs, ctags, cscope, calltree, graphviz
vim-full, build-essential, hex, dd, gcc 4.3.2...
* Becarefull to change the compiling options, which will heavily
influence the compiling procedure and running result.
...
<<<Be Happy To Play With It :-)>>>
cdai@cdai ~/Source/Linux-0.11 $ sudo apt-get install -y ctags cscope graphviz qemu
cdai@cdai ~/Source/Linux-0.11 $ make
1.2.2 下载硬盘IMG
从OldLinux下载硬盘镜像hdc-0.11.img,大小为127MB。将其拷贝到Linux0.11目录下后,就可以执行make start
用qemu模拟Linux 0.11的运行环境了。
cdai@cdai ~/Source/Linux-0.11 $ tree
├── boot
├── fs
├── hdc-0.11.img
├── Image
├── include
├── init
├── kernel
├── lib
├── Makefile
├── Makefile.header
├── mm
├── README.md
├── readme.old
├── System.map
└── tools
8 directories, 10 files
cdai@cdai ~/Source/Linux-0.11 $ make start
1.2.3 安装运行
启动时,系统会从软盘引导,并挂载根文件系统:
SeaBIOS (version 1.7.4-20140219_122725-roseapple)
iPXE (http://ipxe.org) 00:0.3.0 C900 PCI2.10 PnP PMM+00FC1110+00F21110 C900
Booting from Floppy...
Loading system...
Partition table ok.
43012/62000 free blocks
19719/20666 free inodes
3446 buffers = 3528704 bytes buffer space
Free mem: 12574720 bytes
Ok.
[/usr/root]# ls
README hello mtools.howto shoelace.tar.Z
gcclib140 hello.c shoe
2.文件目录组织
文件目录组织是按照打包后的模块划分,一级目录分为boot1、boot2、system三个文件夹,tools中放的则是打包工具类。对应《Orange’s》代码,boot1/bootsect.s相当于boot.asm,boot2/setup.s相当于loader.asm。
cdai@cdai ~/Workspace/syspace/1-assembly $ tree -d minios/
minios/
├── boot1
│ └── bootsect.s
├── boot2
│ └── setup.s
├── Makefile
├── system
│ ├── fs
│ ├── include
│ ├── init
│ ├── kernel
│ ├── lib
│ └── mm
└── tools
└── build.c
10 directories, 4 files
3.实用工具
3.1 构建工具
3.1.1 Makefile
首先,Makefile份内的工作就是编译出bootsect、setup、system、build。之后,调用build处理前三者,最终产生拼接好的可引导Image镜像文件。在此过程中,Makefile还有一些小任务要完成。例如利用临时汇编文件tmp.s将system模块的大小拼接到bootsect.asm的头部,加载system的代码中会用到它。
#################
# Macro & Rule
#################
AS = nasm
ASFLAGS = -f elf
CC = gcc
CFLAGS = -Wall -O
LD = ld
# -Ttext org -e entry -s(omit all symbol info)
# -x(discard all local symbols) -M(print memory map)
LDFLAGS = -Ttext 0 -e main --oformat binary -s -x -M
%.o: %.c
$(CC) $(CFLAGS) -c -o $@ $<
#%.o: %.asm
# $(AS) $(ASFLAGS) -o $@ $<
#################
# Default
#################
all: Image
Image: boot1/bootsect boot2/setup system/system tools/build
tools/build boot1/bootsect boot2/setup system/system > a.bin
dd if=a.bin of=Image bs=8192 conv=notrunc
rm -f a.bin
tools/build: tools/build.c
$(CC) $(CFLAGS) -o $@ $<
# SYSSIZE= number of clicks (16 bytes) to be loaded
boot1/bootsect: boot1/bootsect.asm system/system
(echo -n "SYSSIZE equ (";ls -l system/system | grep system \
| cut -d " " -f 5 | tr '\012' ' '; echo "+ 15 ) / 16") > tmp.asm
cat $< >> tmp.asm
$(AS) -o $@ tmp.asm
rm -f tmp.asm
boot2/setup: boot2/setup.asm
$(AS) -o $@ $<
system/system: system/init/main.o system/init/myprint.o
$(LD) $(LDFLAGS) \
system/init/main.o \
system/init/myprint.o \
-o $@ > System.map
system/init/main.o: system/init/main.c
system/init/myprint.o: system/init/myprint.asm
$(AS) $(ASFLAGS) -o $@ $<
#################
# Create floppy
#################
disk:
bximage -q -fd -size=1.44 Image
.PHONY: disk
#################
# Start Vm
#################
start: Image
bochs -q -f bochsrc
qemu: Image
qemu-system-x86_64 -m 16M -boot a -fda Image
.PHONY: start
#################
# GitHub
#################
commit:
git add .
git commit -m "$(MSG)"
#################
# Clean
#################
clean:
rm -f boot1/bootsect boot2/setup system/system tools/build
rm -f system/**/*.o
rm -f a.bin tmp.asm System.map
.PHONY: clean
3.1.2 build.c
build.c主要完成一些不便于或无法在Makefile中实现的操作:
- 文件有效性的检查:例如bootsect末尾的0x55AA签名
- 文件填充:例如对不足四个扇区大小的setup进行填充
- 文件内容解析:例如解析出a.out或ELF格式的system,将其中二进制代码部分提取出来
- 拼接:将bootsect、setup、system三者拼接成Image可引导镜像
#include <stdio.h> /* fprintf */
#include <string.h>
#include <stdlib.h> /* exit */
#include <sys/types.h> /* unistd.h needs this */
#include <unistd.h> /* read/write */
#include <fcntl.h>
#define BUFFER_SIZE 1024
#define SETUP_SECTS 4 /* max number of sectors of setup */
#define STRINGIFY(x) #x /* cat string */
#define GCC_HEADER 1024 /* GCC header length */
#define SYS_SIZE 0x2000 /* max system length (SYS_SIZE*16=128KB) */
void die(const char *str)
{
fprintf(stderr, "%s\n", str);
exit(