04-對(duì)part2-building的搬運(yùn)和翻譯
原文放在文章末尾
非黑色字體均為我自己添加
創(chuàng)建makefile
????????我現(xiàn)在可以一條接一條的告訴你編譯這個(gè)非常簡(jiǎn)單的內(nèi)核的命令,但是讓我們做一點(diǎn)不會(huì)過(guò)時(shí)的東西.我預(yù)計(jì)我們的內(nèi)核將要變得非常復(fù)雜,有許多的C文件需要編譯.因此,編寫(xiě)一個(gè)makefile是有意義的.makefile是用另一種為我們自動(dòng)化編譯過(guò)程的語(yǔ)言寫(xiě)的.(不懂makefile可以去百度 "跟我一起寫(xiě)makefile" 來(lái)入門(mén)makefile的編寫(xiě))
????????如果你在linux上使用arm gcc ,把下面代碼段保存為Makefile(在倉(cāng)庫(kù)里是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 是一張目錄里面已經(jīng)存在的 .c文件的列表(我們的輸入)
????????OFILES 也是一樣,但是用 .o 代替了每個(gè)文件名中的 .c -- 這將成為包含二進(jìn)制代碼的對(duì)象文件(object files)?,它們將通過(guò)編譯器生成(我們的輸出)
????????GCCFLAGS 是一系列參數(shù),它們告訴編譯器我們正在裸機(jī)上編譯,所以我們不能依賴(lài)任何可能用來(lái)實(shí)現(xiàn)簡(jiǎn)單的函數(shù)的標(biāo)準(zhǔn)庫(kù) -- 裸機(jī)上沒(méi)有什么是免費(fèi)的.
????????GCCPATH 是我們編譯器的路徑(你之前下載和解壓ARM工具的位置)
????????緊接著是一列目標(biāo)(target),在它們的冒號(hào)后面跟著一列它們的依賴(lài)(dependencies),每個(gè)目標(biāo)下方縮進(jìn)的命令會(huì)被執(zhí)行來(lái)構(gòu)建(build)這個(gè)目標(biāo).很容易看到為了編譯 boot.o 我們依賴(lài)現(xiàn)有的源代碼文件 boot.S ,然后我們用正確的標(biāo)志(flag)運(yùn)行編譯器,將 boot.S 作為輸入,生成 boot.o
????????%是makefile里面進(jìn)行匹配的通配符.所以,當(dāng)我讀到下一個(gè)目標(biāo)時(shí),我看見(jiàn)為了編譯任何其他以 .o 結(jié)尾的文件,我們需要與其命名相似的 .c 文件.下方列出的命令執(zhí)行時(shí) $< 被 .c 文件替換, $@ 被 .o 文件替換.?
????? ? 在makefile中,%會(huì)匹配名字對(duì)應(yīng)的兩個(gè)文件,$<和$@是特殊變量,前一個(gè)表示第一個(gè)依賴(lài),后一個(gè)表示目標(biāo),一般在編譯時(shí)都是先把源文件編譯為名稱(chēng)對(duì)應(yīng)的 .o 文件,然后再鏈接起來(lái)
????????繼續(xù),為了編譯 kernel8.img?我們必須首先編譯 boot.o 以及其他在OFILE列表里面的?.o 文件.如果我們有,我們就用鏈接腳本, link.ld 把它們鏈接起來(lái),來(lái)定義我們創(chuàng)造的 kernel8.elf 鏡像的布局.遺憾的是,ELF格式被設(shè)計(jì)為在另一個(gè)操作系統(tǒng)上運(yùn)行,所以對(duì)于裸機(jī),我們使用 objcopy 把elf文件的正確部分提取到 kernel8,img中.這是我們最終將從其中啟動(dòng)RPi4的內(nèi)核映像.
????????我希望 "clean" 和 "all"目標(biāo)是不言自明的.
其他平臺(tái)上的Makefile
????????如果你是在Mac OS X 上使用 clang,你需要的是在倉(cāng)庫(kù)中已經(jīng)被命名為Makefile的那一個(gè)文件.確保LLVMPATH被正確設(shè)置.當(dāng)然,它和arm gcc的那個(gè)看上去并沒(méi)有什么不同,所以上面的解釋大部分都適用.
????????類(lèi)似的,如果你在Windows上使用的arm gcc ,part8-breakout-ble 有一個(gè) Makefile.gcc.windows 可以作為一個(gè)例子.
編譯
????????現(xiàn)在我們有Makefile了,我們只用敲下make 來(lái)構(gòu)建我們的kernnel鏡像.因?yàn)槟繕?biāo) "all" 是我們Makefile的第一個(gè)目標(biāo),make將會(huì)編譯這個(gè)目標(biāo)除非你告訴他其他的(make命令總是默認(rèn)構(gòu)建makefile的第一個(gè)目標(biāo),而make clean可以構(gòu)建clean目標(biāo),不過(guò)在這個(gè)文件里他會(huì)刪掉你所構(gòu)建好的一切)當(dāng)構(gòu)建"all" 目標(biāo)時(shí),他會(huì)清楚之前所構(gòu)建的東西,然后重新構(gòu)建 kernel8.img
把我們的內(nèi)核文件復(fù)制到SD卡上
????????希望您已經(jīng)有一個(gè)上面有可以工作的操作系統(tǒng)的micro-SD卡.為了啟動(dòng)我們的內(nèi)核而不是樹(shù)莓派操作系統(tǒng),我們需要用我們自己的取代任何已有的內(nèi)核.同時(shí)注意保持目錄其余部分完整.
????????在我們的開(kāi)發(fā)設(shè)備上,掛載SD卡然后刪除任何以kernel開(kāi)頭的文件.更謹(jǐn)慎的方法可能是將這些文件從SD卡移到本地硬盤(pán)上的備份文件夾中。如果需要,你可以很容易的地恢復(fù)這些.
????????現(xiàn)在我們把kernel8,img 拷貝到SD卡上.這個(gè)名字是有含義的,它向樹(shù)莓派發(fā)出信號(hào),表示我們希望以64位模式啟動(dòng).我們也可以通過(guò)在 config.txt 里設(shè)置arm_64bit位非零值強(qiáng)制實(shí)現(xiàn)它.以64位模式啟動(dòng)我們的樹(shù)莓派意味著我們可以利用樹(shù)莓派中空閑的更大的內(nèi)存容量.
啟動(dòng)
????????從開(kāi)發(fā)設(shè)備上安全的卸下SD卡,插回你的樹(shù)莓派然后啟動(dòng)它.
????????你剛才啟動(dòng)了你自己的操作系統(tǒng).
? ?????雖然這聽(tīng)起來(lái)很令人興奮,但在RPi4自己的“彩虹啟動(dòng)屏”之后,你可能看到的只是一個(gè)空白的黑屏。然而,我們不應(yīng)該如此驚訝:除了在無(wú)限循環(huán)中旋轉(zhuǎn)之外,我們還沒(méi)有要求它做任何事情。
????????不過(guò)基礎(chǔ)已經(jīng)奠定,我們現(xiàn)在可以開(kāi)始做一些令人興奮的事情了。恭喜你走了這么遠(yuǎn)!

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 use?objcopy
?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!
????????