03-对part1-bootstrapping的搬运和翻译
原文放在文章末尾
非黑色字体,以及非黑体字的图片说明/图片标题为我自己添加
我们应该如何编写代码?
我们通过写代码来告诉树莓派做什么.你可能知道代码最终会变成一串0和1(二进制).然而你知道我们不用像这样编写代码后应该会很高兴,不然我们很容易忘记正在发生什么.实际上,将人类可读的语言转化为0和1正是编译器的工作之一.
想要开始,我们就得了解两种语言,汇编和C.C语言被大多数现代软件开发者所熟知的同时,汇编语言却只有少数人"说".这是一种低级语言,它与CPU的"思维"方式非常相似,因此它给我们了许多控制权,而C语言则把我们带到了更高层次的,可以被人类阅读的世界.虽然我们对编译器失去了一点掌控,但我觉得出于我们的目的我们可以相信它.
我们需要由汇编语言开头,但是不用写多少我们就会开始使用C.
关于本教程的说明
本教程并非教你使用汇编或者C.在这方面有充足的资源,而且我不是这方面的专家/权威!因此我将在这一过程中假设你有一些知识.如果你想的话,请按照我介绍的主题进行阅读.
自举代码
树莓派最先运行的代码需要用汇编语言编写.它做一些检查和设置,然后启动我们的第一段C程序 - 内核.
ARM Cortex A72 有4个核.我们只想让我们的代码在主核上运行.所以我们检查处理器ID并且运行我们的代码(主)或挂起一个无线循环(从).
我们需要告诉我们的操作系统如何访问栈.我把栈看作是正在执行的代码所使用的临时存储空间,就像暂存板.我们需要为它留出内存,并存储一个指向它的指针。
我们还需要初始化一个BSS节,这是内存中用来存储未初始化的变量的区域.在这里初始化更有效率,而不是在内核镜像中显式的占用空间.
最终,我们可以跳转到C语言的main()例程.
阅读并理解下面的代码,然后把他保存为 boot.S.我建议用 Arm Programmer's Guide ( 在ARM 官网上搜索 arm cortex-a series programmer's guide for armv8-a 即可得到相关文档) 作为参考
.section ".text.boot" // Make sure the linker puts this at the start of the kernel image
.global _start // Execution starts here
_start:
// Check processor ID is zero (executing on main core), else hang
mrs x1, mpidr_el1
and x1, x1, #3
cbz x1, 2f
// We're not on the main core, so hang in an infinite wait loop
1: wfe
b 1b
2: // We're on the main core!
// Set stack to start below our code
ldr x1, =_start
mov sp, x1
// Clean the BSS section
ldr x1, =__bss_start // Start address
ldr w2, =__bss_size // Size of the section
3: cbz w2, 4f // Quit loop if zero
str xzr, [x1], #8
sub w2, w2, #1
cbnz w2, 3b // Loop if non-zero
// Jump to our main() routine in C (make sure it doesn't return)
4: bl main
// In case it does return, halt the master core too
b 1b
现在我们到C了
你应该会注意到main()是未定义的,我们可以用C写它(保存为 kernel.c),现在先让让它保持简洁.
void main(){
while (1);
}
这让我们陷入了一个无线循环.
把它们链接起来
我们的代码使用了两种不同的语言.我们得想办法把它们粘合在一起,确保所创造的镜像文件可以按照我们计划的方式执行.我们使用链接脚本来完成这件事.链接脚本会定义BSS相关的标签,(可能你已经在想它们在哪里被定义?),我建议你把它保存为link.ld
SECTIONS
{
. = 0x80000; /* Kernel load address for AArch64 */
.text : { KEEP(*(.text.boot)) *(.text .text.* .gnu.linkonce.t*) }
.rodata : { *(.rodata .rodata.* .gnu.linkonce.r*) }
PROVIDE(_data = .);
.data : { *(.data .data.* .gnu.linkonce.d*) }
.bss (NOLOAD) : {
. = ALIGN(16);
__bss_start = .;
*(.bss .bss.*)
*(COMMON)
__bss_end = .;
}
_end = .;
/DISCARD/ : { *(.comment) *(.gnu*) *(.note*) *(.eh_frame*) }
}
__bss_size = (__bss_end - __bss_start)>>3;
写链接脚本是值得研究的,但是为了我们的目的,你所需要知道的就是通过先引用 .text .boot 然后使用 KEEP() ,我们确保 .text 节从我们的汇编代码开始执行.这意味着我们的首条指令在 0x80000 开始执行,它是树莓派启动的时候会寻找到的地址.我们的代码会被执行.
现在你可以编译,然后启动你的操作系统了!

Writing a "bare metal" operating system for Raspberry Pi 4 (Part 1)
How do we code?
We tell the RPi4 what to do by writing code. You may know that code ultimately ends up as a series of 0's and 1's (binary). You'll be pleased to know, however, that we don't need to write it like this, otherwise we'd easily lose track of what was going on! In fact, it's one of the jobs of the compiler to convert human-readable language into those 0's and 1's.
To get going we need to understand two languages: **assembly language** and **C**. Whilst C will likely be recognisable to most modern software developers, assembly language is "spoken" by fewer folks. It's a lower-level language that most closely resembles how the CPU "thinks" and it therefore gives us a lot of control, whereas C brings us into a higher-level, human-readable world. We lose a little control to the compiler though, but for our purposes I think we can trust it!
We will need to start out in assembly language, but there isn't much to write before we can then pick up in C.
A note about this tutorial
This tutorial is not intended to teach you how to code in assembly language or C. There are plenty of good resources on these topics and I am not an expert/authority! I will therefore be assuming some knowledge along the way. Please do read around the topics that I introduce if you need/want to.
Bootstrapping
The first code that the RPi4 will run will need to be written in assembly language. It makes some checks, does some setup and launches us into our first C program - the **kernel**.
* The Arm Cortex-A72 has four cores. We only want our code to run on the master core, so we check the processor ID and either run our code (master) or hang in an infinite loop (slave).
* We need to tell our OS how to access the **stack**. I think of the stack as temporary storage space used by currently-executing code, like a scratchpad. We need to set memory aside for it and store a pointer to it.
* We also need to initialise the BSS section. This is the area in memory where uninitialised variables will be stored. It's more efficient to initialise everything to zero here, rather than take up space in our kernel image doing it explicitly.
* Finally, we can jump to our main() routine in C!
Read and understand the code below and save it as _boot.S_. I suggest using the [Arm Programmer's Guide](https://developer.arm.com/documentation/den0024/a/) as a reference.
.section ".text.boot" // Make sure the linker puts this at the start of the kernel image
.global _start // Execution starts here
_start:
// Check processor ID is zero (executing on main core), else hang
mrs x1, mpidr_el1
and x1, x1, #3
cbz x1, 2f
// We're not on the main core, so hang in an infinite wait loop
1: wfe
b 1b
2: // We're on the main core!
// Set stack to start below our code
ldr x1, =_start
mov sp, x1
// Clean the BSS section
ldr x1, =__bss_start // Start address
ldr w2, =__bss_size // Size of the section
3: cbz w2, 4f // Quit loop if zero
str xzr, [x1], #8
sub w2, w2, #1
cbnz w2, 3b // Loop if non-zero
// Jump to our main() routine in C (make sure it doesn't return)
4: bl main
// In case it does return, halt the master core too
b 1b
```
And now we're in C
You will likely note that the `main()` routine is as yet undefined. We can write this in C (save it as _kernel.c_), keeping it very simple for now:
```c
void main()
{
while (1);
}
```
This simply spins us in an infinite loop!
Linking it all together
We've written code in two different languages. Somehow we need to glue these together, ensuring that the created image will be executed in the way that we intend. We use a **linker script** for this. The linker script will also define our BSS-related labels (perhaps you were already wondering where they get defined?). I suggest you save the following as _link.ld_:
```c
SECTIONS
{
. = 0x80000; /* Kernel load address for AArch64 */
.text : { KEEP(*(.text.boot)) *(.text .text.* .gnu.linkonce.t*) }
.rodata : { *(.rodata .rodata.* .gnu.linkonce.r*) }
PROVIDE(_data = .);
.data : { *(.data .data.* .gnu.linkonce.d*) }
.bss (NOLOAD) : {
. = ALIGN(16);
__bss_start = .;
*(.bss .bss.*)
*(COMMON)
__bss_end = .;
}
_end = .;
/DISCARD/ : { *(.comment) *(.gnu*) *(.note*) *(.eh_frame*) }
}
__bss_size = (__bss_end - __bss_start)>>3;
```
Writing linker scripts is [worth investigating](http://ftp.gnu.org/old-gnu/Manuals/ld-2.9.1/html_mono/ld.html#SEC6) but, for our purposes, all you need to know is that by referencing `.text.boot` first and using the `KEEP()`, we ensure the `.text` section starts with our assembly code. That means our first instruction starts at 0x80000, which is exactly where the RPi4 will look for it when it boots. Our code will be run.
_Now you're ready to compile and then boot your OS!_