20180808失忆的操作系统内核实现(三) 计算机的启动过程&bootloader的编写

20180808失忆的操作系统内核实现(三) 计算机的启动过程&bootloader的编写

三、PC机的启动过程&bootloader的编写

①传统启动方式

传统计算机的启动主角是BIOS,BIOS(Basic Input/Output System 基本输入输出系统),是一个固话在主板上的软件程序,由主板厂商定义。

启动过程主要分为几个阶段,首先是执行BIOS程序,它会做一堆检查,包括检索可以启动的存储设备——它需要直到"下一阶段的启动程序"放在什么地方;然后它将这个"启动程序"放在内存的一个特定位置,剩下的事情就交给这个"启动程序"了。硬盘的启动程序可能在硬盘的开头MBR(Main Boot Record)里,然后这段程序将加载操作系统内核到内存里,下面的事情就由内核管理了。

(这个不重要,我的电脑既然支持UEFI,就不想编写汇编来启动了)

②目前流行的启动方式

传统的BIOS启动方式没有一个统一的标准,后来Intel和其他厂商牵头搞了一个统一的标准:UEFI(Unified Extensible Firmware Interface统一的可扩展固件接口)。对于我的操作系统内核来说,只需要了解它的API(Application Programming Interface 应用程序接口)怎么调用就行了。

UEFI参考网站:http://wiki.phoenix.com/wiki/index.php/UEFI

需要准备的参考/引用项目有:

①gnu-efi:一个用于编写UEFI app的编译库文件,比巨大的UDK小得多得多,项目地址:https://github.com/vathpela/gnu-efi

②OVMF:可以为虚拟机提供UEFI的虚拟环境,下载地址:https://sourceforge.net/projects/edk2/files/OVMF/

编写uefi app有许多项目,比如UDK2018,但是我嫌它的配置太麻烦,这个系统并不是围绕UEFI展开的。我们需要的是轻便的项目,一个bootloader(启动加载器),它负责:显示一个好看的界面,并寻找硬盘上的内核,加载到内存中来。这里推荐使用gnu-efi。

好了,下面建立一个项目文件夹,预示着项目的开始。文件夹结构如下:

AmnesiaOS                        #项目文件夹

├── bootloader

│   ├── gnu-efi                #gnu-efi项目,可从上述链接下载,也可用git clone命令下载

│   ├── main.c                #bootloader主程序

│   └── Makefile

├── OVMF-X64-r15214            #下载的OVMF,解压到这个位置

│   └── OVMF.fd                #使用OVMF必须的文件

├── Makefile                    #Makefile,(一种帮忙执行编译链接命令的程序,写好之后,只需要一个make即可自动执行)

├── ReadMe.txt                #说明文档,内容随便写

└── start.sh                    #启动仿真器的命令

现在,可以开始编写main.c中的内容了,下面是一个bootloader的helloworld,效果是在屏幕上输出"Hello World!",预示着项目的开始。

(/bootloader/main.c)

 1 #include <efi.h>
 2 #include <efilib.h>
 3 
 4 EFI_STATUS
 5 efi_main(EFI_HANDLE image_handle, EFI_SYSTEM_TABLE *systab)
 6 {
 7     SIMPLE_TEXT_OUTPUT_INTERFACE *conout;
 8     
 9     //gnu-efi的要求,加载系统表systable
10     InitializeLib(image_handle, systab);
11 
12     //调用API在屏幕上输出Hello World!
13     conout = systab->ConOut;
14     uefi_call_wrapper(conout->OutputString, 2, conout, (CHAR16 *)L"Hello World!\n\r");
15 
16     //按任意键后退出模拟器
17     WaitForSingleEvent(systab->ConIn->WaitForKey, 0);
18     gRT->ResetSystem(EfiResetShutdown, EFI_SUCCESS,
19                      0, NULL);
20 
21     return EFI_SUCCESS;
22 }

下面开始对整个bootloader进行编译,首先是gnu-efi的编译,在bootloader目录下执行以下命令:

git clone https://github.com/vathpela/gnu-efi.git #如果已经下载则不需要进行这一步
cd gnu-efi                                    #进入gnu-efi目录
make                                        #执行gnu-efi项目的makefile
#这个时候gnu-efi目录下将生成x86_64文件夹,所需的目标文件在这里面
cd ..                                            #回到bootloader文件夹
gcc -Ignu-efi/inc -Ignu-efi/inc/x86_64 -Ignu-efi/inc/protocol \
  -Wno-error=pragmas -Wall -Wextra -Werror \
  -mno-red-zone -mno-avx \
  -fpic -fshort-wchar -fno-strict-aliasing -ffreestanding -fno-stack-protector -fno-stack-check -fno-merge-all-constants \
  -DCONFIG_x86_64 -DGNU_EFI_USE_MS_ABI -maccumulate-outgoing-args --std=c11 -D__KERNEL__ \
  -g -O2 \
  -c main.c -o main.o
#这条命令表示调用gcc进行编译,-c表示对main.c进行编译但不链接,-I表示Include包含文件夹,-W表示Warning显示/禁用某些警告,-g生成调试信息,-O2优化程度,-f顾名思义……
ld -nostdlib --warn-common --no-undefined --fatal-warnings --build-id=sha1 -shared -Bsymbolic \
  -T gnu-efi/gnuefi/elf_x86_64_efi.lds \
  gnu-efi/x86_64/gnuefi/crt0-efi-x86_64.o main.o -o bootloader.so \
  -L gnu-efi/x86_64/lib -lefi -L gnu-efi/x86_64/gnuefi -lgnuefi
#这条命令表示调用ld进行链接,-T表示指定链接脚本(可以打开内容看看),-L和-l表示加载需要的库文件这里是libefi.a和libgnuefi.a,输出bootloader.so
objcopy -j .text -j .sdata -j .data -j .dynamic -j .dynsym -j .rel -j .rela -j .rel.* -j rela.* -j .rela* -j .reloc \
  --target efi-app-x86_64 \
  bootloader.so bootx64.efi
#这条命令表示调用objcopy将so文件转换为所需要的efi文件,-j命令后面接的是so文件中各个段的名称,这些段将按照命令的顺序生成efi文件。

到这一步为止,生成的bootx64.efi是一个合格的UEFI app,如果手头上有U盘,且U盘的文件系统格式为fat,那么在U盘根目录下建立文件夹boot/efi,并将这个文件拷贝到该文件夹里,重启电脑,通过UEFI的U盘启动,即可在屏幕上看到令人兴奋的输出"Hello World!"——

*这里我曾经想过,做一个UEFI app的小游戏,并替换掉当前电脑内的启动文件,当且仅当游戏通过的时候才调用真正的引导文件,才能进入系统,这个似乎比较好玩……

注:UEFI协议中规定,UEFI将从ESP分区(FAT文件系统格式)中的boot/efi或者boot/Microsoft目录中加载启动程序。

而,作为测试,我们需要在支持UEFI的虚拟机里启动这个文件。然而,截至目前为止,WSL中并未加入挂载文件到回环设备(mount as loop device)的功能,这里暂时找不到办法来挂载并修改仿真用的虚拟磁盘。

因此,这里采用折中的办法,进入UEFI Shell(命令行)中手动调用这个启动程序。输入下列命令启动qemu:

export DISPLAY=:0                            #确保X服务与Win10的Xming连接
qemu-system-x86_64 -bios ../OVMF-*/OVMF.fd \
  -drive file=fat:.,media=disk,format=raw

命令的大意是,指定仿真用的bios为OVMF,并将当前目录仿真一个fat格式的硬盘

此时将启动一个窗口,相当于一个仿真的显示器,等待一会进入UEFI Shell

输入:

fs0:

bootx64.efi

运行后界面截图如下:

上述的过程大致上就是C语言从源文件到可执行文件的编译过程,总结起来如下图所示。

gcc命令将c文件和h文件进行预处理、汇编、编译,生成目标文件o文件,o文件包含了可执行的机器码,也包含未链接的符号、函数名称等等链接所需要的信息。

ld命令将这些目标文件可其他库文件按照链接文件的格式、要求,生成动态链接库文件或者可执行文件,在Linux系统下,可执行文件是ELF格式的,具体格式需查阅相关信息。

而,我们这里需要的是efi格式的文件,它有它自己的格式。objcopy命令就是将上述生成的动态链接库转换成efi格式。

为了以后不需要输入那么多的命令,上述过程可以写成一个Makefile文件,该文件内容如下:

(/bootloader/Makefile)

#这些是变量
SOURCE = main.c
TARGET = bootx64.efi
EFILIB = gnu-efi/x86_64/gnuefi/crt0-efi-x86_64.o

#Makefile的第一项可以由make命令直接执行,该项目也可以由make all命令执行
all: $(TARGET)

#表示生成$(TARGET)目标,需要$(SOURCE)和$(EFILIB),下同
$(TARGET): $(SOURCE) $(EFILIB)
	gcc -Ignu-efi/inc -Ignu-efi/inc/x86_64 -Ignu-efi/inc/protocol \
		-Wno-error=pragmas -Wall -Wextra -Werror \
		-mno-red-zone -mno-avx  \
		-fpic -fshort-wchar -fno-strict-aliasing -ffreestanding -fno-stack-protector -fno-stack-check -fno-merge-all-constants \
		-DCONFIG_x86_64 -DGNU_EFI_USE_MS_ABI -maccumulate-outgoing-args --std=c11 -D__KERNEL__ \
		-g -O2 \
		-c main.c -o main.o
	ld -nostdlib --warn-common --no-undefined --fatal-warnings --build-id=sha1 -shared -Bsymbolic \
		-T gnu-efi/gnuefi/elf_x86_64_efi.lds \
		gnu-efi/x86_64/gnuefi/crt0-efi-x86_64.o main.o -o bootloader.so \
		-L gnu-efi/x86_64/lib -lefi -L gnu-efi/x86_64/gnuefi -lgnuefi
	objcopy -j .text -j .sdata -j .data -j .dynamic -j .dynsym -j .rel -j .rela -j .rel.* -j rela.* -j .rela* -j .reloc \
		--target efi-app-x86_64 \
		bootloader.so bootx64.efi

$(EFILIB):
	make -C gnu-efi

#通过git下载gnu-efi,该项目可以由make dl_gnuefi执行
dl_gnuefi:
	git clone https://github.com/vathpela/gnu-efi.git

#清楚目录下的生成的文件,由make clean执行
clean:
	rm *.o *.so *.efi

#打开qemu并测试的项目,make test
test:
	export DISPLAY=:0							#确保有X服务,Win10下的Xming需要处于运行状态
	qemu-system-x86_64 -bios ../OVMF-*/OVMF.fd \
		-drive file=fat:.,media=disk,format=raw

注:编译、链接所需的参数不是我想出来的,而是参考gnu-efi项目中的Makefile得到的

猜你喜欢

转载自www.cnblogs.com/AmnesiaBeing/p/9451801.html