04-对part2-building的搬运和翻译
原文放在文章末尾
非黑色字体均为我自己添加
创建makefile
我现在可以一条接一条的告诉你编译这个非常简单的内核的命令,但是让我们做一点不会过时的东西.我预计我们的内核将要变得非常复杂,有许多的C文件需要编译.因此,编写一个makefile是有意义的.makefile是用另一种为我们自动化编译过程的语言写的.(不懂makefile可以去百度 "跟我一起写makefile" 来入门makefile的编写)
如果你在linux上使用arm gcc ,把下面代码段保存为Makefile(在仓库里是Makefile.gcc)
CFILES = $(wildcard *.c)
OFILES = $(CFILES:.c=.o)
GCCFLAGS = -Wall -O2 -ffreestanding -nostdinc -nostdlib -nostartfiles
GCCPATH = ../../gcc-arm-10.3-2021.07-x86_64-aarch64-none-elf/bin
all: clean kernel8.img
boot.o: boot.S
$(GCCPATH)/aarch64-none-elf-gcc $(GCCFLAGS) -c boot.S -o boot.o
%.o: %.c
$(GCCPATH)/aarch64-none-elf-gcc $(GCCFLAGS) -c $< -o $@
kernel8.img: boot.o $(OFILES)
$(GCCPATH)/aarch64-none-elf-ld -nostdlib boot.o $(OFILES) -T link.ld -o kernel8.elf
$(GCCPATH)/aarch64-none-elf-objcopy -O binary kernel8.elf kernel8.img
clean:
/bin/rm kernel8.elf *.o *.img > /dev/null 2> /dev/null || true
CFILES 是一张目录里面已经存在的 .c文件的列表(我们的输入)
OFILES 也是一样,但是用 .o 代替了每个文件名中的 .c -- 这将成为包含二进制代码的对象文件(object files) ,它们将通过编译器生成(我们的输出)
GCCFLAGS 是一系列参数,它们告诉编译器我们正在裸机上编译,所以我们不能依赖任何可能用来实现简单的函数的标准库 -- 裸机上没有什么是免费的.
GCCPATH 是我们编译器的路径(你之前下载和解压ARM工具的位置)
紧接着是一列目标(target),在它们的冒号后面跟着一列它们的依赖(dependencies),每个目标下方缩进的命令会被执行来构建(build)这个目标.很容易看到为了编译 boot.o 我们依赖现有的源代码文件 boot.S ,然后我们用正确的标志(flag)运行编译器,将 boot.S 作为输入,生成 boot.o
%是makefile里面进行匹配的通配符.所以,当我读到下一个目标时,我看见为了编译任何其他以 .o 结尾的文件,我们需要与其命名相似的 .c 文件.下方列出的命令执行时 $< 被 .c 文件替换, $@ 被 .o 文件替换.
在makefile中,%会匹配名字对应的两个文件,$<和$@是特殊变量,前一个表示第一个依赖,后一个表示目标,一般在编译时都是先把源文件编译为名称对应的 .o 文件,然后再链接起来
继续,为了编译 kernel8.img 我们必须首先编译 boot.o 以及其他在OFILE列表里面的 .o 文件.如果我们有,我们就用链接脚本, link.ld 把它们链接起来,来定义我们创造的 kernel8.elf 镜像的布局.遗憾的是,ELF格式被设计为在另一个操作系统上运行,所以对于裸机,我们使用 objcopy 把elf文件的正确部分提取到 kernel8,img中.这是我们最终将从其中启动RPi4的内核映像.
我希望 "clean" 和 "all"目标是不言自明的.
其他平台上的Makefile
如果你是在Mac OS X 上使用 clang,你需要的是在仓库中已经被命名为Makefile的那一个文件.确保LLVMPATH被正确设置.当然,它和arm gcc的那个看上去并没有什么不同,所以上面的解释大部分都适用.
类似的,如果你在Windows上使用的arm gcc ,part8-breakout-ble 有一个 Makefile.gcc.windows 可以作为一个例子.
编译
现在我们有Makefile了,我们只用敲下make 来构建我们的kernnel镜像.因为目标 "all" 是我们Makefile的第一个目标,make将会编译这个目标除非你告诉他其他的(make命令总是默认构建makefile的第一个目标,而make clean可以构建clean目标,不过在这个文件里他会删掉你所构建好的一切)当构建"all" 目标时,他会清楚之前所构建的东西,然后重新构建 kernel8.img
把我们的内核文件复制到SD卡上
希望您已经有一个上面有可以工作的操作系统的micro-SD卡.为了启动我们的内核而不是树莓派操作系统,我们需要用我们自己的取代任何已有的内核.同时注意保持目录其余部分完整.
在我们的开发设备上,挂载SD卡然后删除任何以kernel开头的文件.更谨慎的方法可能是将这些文件从SD卡移到本地硬盘上的备份文件夹中。如果需要,你可以很容易的地恢复这些.
现在我们把kernel8,img 拷贝到SD卡上.这个名字是有含义的,它向树莓派发出信号,表示我们希望以64位模式启动.我们也可以通过在 config.txt 里设置arm_64bit位非零值强制实现它.以64位模式启动我们的树莓派意味着我们可以利用树莓派中空闲的更大的内存容量.
启动
从开发设备上安全的卸下SD卡,插回你的树莓派然后启动它.
你刚才启动了你自己的操作系统.
虽然这听起来很令人兴奋,但在RPi4自己的“彩虹启动屏”之后,你可能看到的只是一个空白的黑屏。然而,我们不应该如此惊讶:除了在无限循环中旋转之外,我们还没有要求它做任何事情。
不过基础已经奠定,我们现在可以开始做一些令人兴奋的事情了。恭喜你走了这么远!

Making a makefile
I could now just tell you the commands required to build this very simple kernel one after the other, but let's try to future-proof a little. I anticipate that our kernel will become more complex, with multiple C files needing to be built. It therefore makes sense to craft a makefile. A makefile is written in (yet another) language that automates the build process for us.
If you're using Arm gcc on Linux, save the following as Makefile (in the repo as Makefile.gcc):
CFILES = $(wildcard *.c)
OFILES = $(CFILES:.c=.o)
GCCFLAGS = -Wall -O2 -ffreestanding -nostdinc -nostdlib -nostartfiles GCCPATH = ../../gcc-arm-10.3-2021.07-x86_64-aarch64-none-elf/bin
all: clean kernel8.img
boot.o: boot.S
$(GCCPATH)/aarch64-none-elf-gcc $(GCCFLAGS) -c boot.S -o boot.o
%.o: %.c
$(GCCPATH)/aarch64-none-elf-gcc $(GCCFLAGS) -c $< -o $@
kernel8.img: boot.o $(OFILES)
$(GCCPATH)/aarch64-none-elf-ld -nostdlib boot.o $(OFILES) -T link.ld -o kernel8.elf
$(GCCPATH)/aarch64-none-elf-objcopy -O binary kernel8.elf kernel8.img clean:
/bin/rm kernel8.elf *.o *.img > /dev/null 2> /dev/null || true
CFILES is a list of the .c files already existing in the current directory (our input)
OFILES is that same list but replacing .c with .o in each filename - these will be our object files containing the binary code, and they'll be generated by the compiler (our output)
GCCFLAGS is a list of parameters that tell the compiler we're building for bare metal and so it can't rely on any standard libraries that it might normally use to implement simple functions - nothing is for free on bare metal!
GCCPATH is the path to our compiler binaries (the location where you unpacked the Arm tools you downloaded previously)
There then follows a list of targets with their dependencies listed after the colon. The indented commands underneath each target will be executed to build that target. It's hopefully easy to see that to build boot.o, we depend on the existence of the source code file boot.S. We then run our compiler with the right flags, taking boot.S as our input and generating boot.o.
%
is a matching wildcard character within a makefile. So, when I read the next target, I see that to build any other file that ends in .o we require its similarly-named .c file. The command list underneath is then executed with$<
being replaced by the .c filename and$@
being replaced by the .o filename.Carrying on, to build kernel8.img we must first have built boot.o and also every other .o file in the OFILES list. If we have, we run the
ld
linker to join boot.o with the other object files using our linker script, link.ld, to define the layout of the kernel8.elf image we create. Sadly, the ELF format is designed to be run by another operating system so, for a bare metal target, we useobjcopy
to extract the right sections of the ELF file into kernel8.img. This is the kernel image that we'll eventually boot our RPi4 from.I would now hope that the "clean" and "all" targets are self-explanatory.
Makefiles on other platforms
If you're using clang on Mac OS X, the file already named Makefile in the repo will be the one you need. Ensure the LLVMPATH is correctly set, of course. It doesn't look much different to the Arm gcc one, so the above explanation mostly applies.
Similarly, if you're using Arm gcc natively on Windows, part8-breakout-ble has a Makefile.gcc.windows just as an example.
Building
Now that we have our Makefile in place, we simply type
make
to build our kernel image. Since "all" is the first target listed in our Makefile,make
will build this unless you tell it otherwise. When building "all", it will first clean up any old builds and then make us a fresh build of kernel8.img.Copying our kernel image to the SD card
Hopefully you already have a micro-SD card with the working Raspbian image on it. To boot our kernel instead of Raspbian we need to replace any existing kernel image(s) with our own, whilst taking care to keep the rest of directory structure intact.
On your dev machine, mount the SD card and delete any files on it that begin with the word kernel. A more cautious approach may be to simply move these off the SD card into a backup folder on your local hard drive. You can then restore these easily if needed.
We'll now copy our kernel8.img onto the SD card. This name is meaningful and it signals to the RPi4 that we want it to boot in 64-bit mode. We can also force this by setting
arm_64bit
to a non-zero value in config.txt. Booting our OS into 64-bit mode will mean that we can take advantage of the larger memory capacity available to the RPi4.Booting
Safely unmount the SD card from your dev machine, put it back into your RPi4 and power it up.
You've just booted your very own OS!
As exciting as that sounds, all you're likely to see after the RPi4's own "rainbow splash screen" is an empty, black screen. However, we shouldn't be so surprised: we haven't yet asked it to do anything other than spin in an infinite loop.
The foundations are laid though, and we can start to do exciting things now. Congratulations for getting this far!