1. 注意,注意,以下代码或有两个版本,版本一有错误,版本二为测试过的版本,目前只支持树莓派4B
.
2. 原版出自 Github:
链接: link.
3. 以下操作大体与原文一样,但是添加了实际操作成功后的代码(版本二),以及增添了部分操作步骤,注意区分 !!
4. 制作完成后,将 SD卡 插入树莓派(4B),将观察到:
- 电源灯(红灯): 亮
- 状态灯(黄绿色灯):闪烁(启动中),不闪烁(启动成功)
- 屏幕:刚启动时显示树莓派彩虹界面.之后为黑色(无显示).
5 . 树莓派启动状况
-
3次闪烁:找不到start.elf
-
4次闪烁:start.elf无法启动,因此它可能已损坏。或者,未正确插入卡,或卡插槽不工作。
-
闪烁7次:kernel.img未找到
-
闪烁8次:未识别SDRAM。在这种情况下,您的SDRAM可能已损坏,或者无法读取bootcode.bin或start.elf。
6.想点亮 LED ?如下
https://blog.csdn.net/qq_37459242/article/details/119223215
一.制作裸机操作系统
F1. 基于树莓派 4B
F2. 注意,注意,kernel.img 针对于 树莓派 1B/B+ 等较低版本,kerne7.img 针对于 树莓派 ??? ,kerne8.img 针对于 树莓派 4(或可向下兼容).
F3. 我们现在将 kernel7.img 复制到 SD 卡上。这个名称是有意义的,它向 RPi4发出信号,表示我们希望它以64位模式引导。也可以通过在 config.txt 中将 arm _ 64bit 设置为一个非零值来强制执行此操作。将操作系统引导到64位模式意味着我们可以利用 RPi4更大的可用内存容量。在将 .img 文件放入 SD 卡后,可以修改其中 config.txt 文件:
arm_64bit = 1
- 启动引导
树莓派4B将要运行的第部分代码将需要用汇编语言编写。它进行一些检查与设置,然后将我们启动到我们的第一个C程序 – 操作系统内核。
-
ARM Cortex-A72具有四个核心。我们只希望代码在主核心上运行,因此我们检查处理器ID,然后让代码运行在主核心上,然后在小核上用死循环将其挂起。
-
我们需要告诉我们的操作系统如何访问堆栈。我认为堆栈是当前执行的代码(例如Scratchpad Memory)使用的临时存储空间。我们需要为其留出内存并存储指向它的指针。
-
我们还需要初始化BSS部分。这是内存中将存储未初始化变量的区域。在这里将所有内容初始化为零要比在我们的内核镜像中显式地初始化更有效。
-
最后,我们可以跳到C语言中的main()。阅读并理解下面的代码,并将其另存为boot.S。作为参考,你可以阅读ARM Programmer’s Guide。
-
版本一
section ".text.boot" // 用于确保链接器将代码段放置于内核镜像的开头
.global _start // 代码从这里开始执行
_start:
// 检查处理器ID,如果是0则为主核心,继续执行,否则挂起
mrs x1, mpidr_el1
and x1, x1, #3
cbz x1, 2f
// 代码没有运行在主核心上,所以用死循环挂起代码
1: wfe
b 1b
2: // 代码运行在主核心上,继续
// 设置堆栈区的起点
ldr x1, =_start
mov sp, x1
// 将BSS段清零
ldr x1, =__bss_start // 起始地址
ldr w2, =__bss_size // 段大小
3: cbz w2, 4f // 如果为0,退出循环
str xzr, [x1], #8
sub w2, w2, #1
cbnz w2, 3b // 如果不为0,继续循环
// 跳转到main(),请确保main()不会return
4: bl main
// 万一main函数return了,同样用死循环挂起主核心
b 1b
- 版本二
.equ Mode_USR, 0x10
.equ Mode_FIQ, 0x11
.equ Mode_IRQ, 0x12
.equ Mode_SVC, 0x13
.equ Mode_ABT, 0x17
.equ Mode_UND, 0x1B
.equ Mode_SYS, 0x1F
.section ".text.boot"
/* entry */
.globl _start
_start:
/* Check for HYP mode */
mrs r0, cpsr_all
and r0, r0, #0x1F
mov r8, #0x1A
cmp r0, r8
beq overHyped
b continue
overHyped: /* Get out of HYP mode */
adr r1, continue
msr ELR_hyp, r1
mrs r1, cpsr_all
and r1, r1, #0x1f ;@ CPSR_MODE_MASK
orr r1, r1, #0x13 ;@ CPSR_MODE_SUPERVISOR
msr SPSR_hyp, r1
eret
continue:
/* Suspend the other cpu cores */
mrc p15, 0, r0, c0, c0, 5
ands r0, #3
bne _halt
/* set the cpu to SVC32 mode and disable interrupt */
cps #Mode_SVC
/* disable the data alignment check */
mrc p15, 0, r1, c1, c0, 0
bic r1, #(1<<1)
mcr p15, 0, r1, c1, c0, 0
/* set stack before our code */
ldr sp, =_start
/* clear .bss */
mov r0,#0 /* get a zero */
ldr r1,=__bss_start /* bss start */
ldr r2,=__bss_end /* bss end */
bss_loop:
cmp r1,r2 /* check if data to clear */
strlo r0,[r1],#4 /* clear 4 bytes */
blo bss_loop /* loop until done */
/* jump to C code, should not return */
ldr pc, _main
b _halt
_main:
.word main
_halt:
wfe
b _halt
- 将以上代码链接到一起。我们使用了两种不同的语言编写了代码,我们需要以某种方式将它们组合在一起,以确保创建的镜像将按照我们期望的方式执行。我们为此使用链接脚本(linker script)。链接脚本还将定义与BSS相关的标签(也许你已经想知道它们在哪里定义了)。编写链接脚本是一件值得研究的事情。但是,对于我们的目标,你需要知道的只有将.text.boot放在第一行并使用KEEP(),我们能够保证text段(也就是代码段)出现在我们汇编代码的最开始。这也就意味着我们的第一条指令从0x80000开始,这正是树莓派4B在启动时将寻找内核的位置。我们的代码将从这里开始运行。现在你已经可以准备编译并启动你的OS。将以下内容另存为link.ld:
- 版本一
SECTIONS
{
. = 0x80000; /* AArch64架构下,CPU会从这个地址加载内核 */
.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;
- 版本二
SECTIONS {
/*
* First and formost we need the .init section, containing the code to
* be run first. We allow room for the ATAGs and stack and conform to
* the bootloader's expectation by putting this code at 0x8000.
*/
. = 0x8000;
.text : {
KEEP(*(.text.boot))
*(.text .text.* .gnu.linkonce.t*)
}
/*
* Next we put the data.
*/
.data : {
*(.data)
}
.bss : {
. = ALIGN(16);
__bss_start = .;
*(.bss*)
*(COMMON*)
__bss_end = .;
}
}
__bss_size = (__bss_end - __bss_start) >> 3;
- 编写 makefile。将下面的代码另存为Makefile 。
- 版本一
CFILES = $(wildcard *.c)
OFILES = $(CFILES:.c=.o)
GCCFLAGS = -Wall -O2 -ffreestanding -nostdinc -nostdlib -nostartfiles
GCCPATH = ../../gcc-arm-9.2-2019.12-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 -nostartfiles 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 :是相同的列表,但是用每个文件名中的 .c 被 .o 替换,这些是包含二进制代码的目标文件,它们将由编译器生成(我们的输出)。
-
gcCFLAGS :是一个参数列表,它告诉编译器我们正在为裸机构建程序,因此它不能依赖于我们常用的任何标准库。
-
GCCPATH :是我们的编译器的二进制文件的路径(你之前下载的ARM工具的解压缩的位置)
-
然后是一个目标列表,在冒号之后列出了它们的依赖项。将执行每个目标下的缩进命令以构建该目标。 要构建 boot.o,我们依赖于源代码文件 boot.S 的存在。 然后,使用正确的标志运行我们的编译器,将 boot.S 作为我们的输入并生成 boot.o。
-
% 是 makefile 中的匹配通配符。要构建以 .o 结尾的文件,需要其名称类似的 .c 文件。 然后执行下面的命令列表。
-
接下来,要构建 kernel8.img,我们首先必须构建 boot.o 以及OFILES 列表中的所有其他 .o 文件。如果有以上文件的话,我们使用命令与链接脚本 link.ld 将 boot.o 与其他目标文件连接起来,从而决定 kernel8.elf 镜像文件的布局。然而,ELF文件需要运行在一个操作系统下。因此,对于裸机环境,我们使用将ELF文件的必要部分提取到 kernel8.img 中。这是我们最终的树莓派4B的OS内核镜像。
-
对于编译目标"clean"和"all",我们不必做多余的解释。
-
版本二
CRO ?= arm-none-eabi-
#CRO ?= arm-linux-gnueabi-
TARGET ?= bsp
CC := $(CRO)gcc
LD := $(CRO)ld
OBJCOPY := $(CRO)objcopy
SRCS = $(wildcard *.c)
OBJS = $(SRCS:.c=.o)
CFLAGS = -march=armv8-a -mtune=cortex-a72 -Wall -O2 -ffreestanding -nostdinc -nostdlib -nostartfiles
all: clean kernel7.img
start.o: start.S
$(CC) $(CFLAGS) -c start.S -o start.o
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
kernel7.img: start.o $(OBJS)
$(LD) -nostdlib -nostartfiles start.o $(OBJS) -T link.ld -o kernel7.elf
$(OBJCOPY) -O binary kernel7.elf kernel7.img
clean:
rm kernel7.elf kernel7.img *.o >/dev/null 2>/dev/null || true
- 编译。现在我们已经有了我们的 Makefile,我们只需输入 make 来构建我们的内核映像。 由于“all”是我们的 Makefile 中列出的第一个目标,除非您另有说明,否则 make 将构建它。 在构建“all”时,它会首先清理所有旧构建,然后让我们重新构建 kernel7.img。
- 创建完成的文件夹
-rw-r--r-- 1 Lenovo None 815 Jul 27 21:29 Makefile
-rw-r--r-- 1 Lenovo None 610 Sep 16 2020 link.ld
-rw-r--r-- 1 Lenovo None 454 Jul 27 21:28 main.c
-rw-r--r-- 1 Lenovo None 1648 Sep 16 2020 start.S
- 将我们的内核映像复制到 SD 卡。
-
希望您已经拥有一张带有 Raspbian 映像的微型 SD 卡。 要引导我们的内核而不是 Raspbian,我们需要用我们自己的内核映像替换现有的内核映像,同时注意保持目录结构的其余部分完整无缺。
-
在开发机器上,挂载 SD 卡并删除其中以 kernel 开头的所有文件。 更谨慎的方法可能是简单地将这些从 SD 卡移到本地硬盘驱动器上的备份文件夹中。 如果需要,您可以轻松地恢复这些。
-
现在将我们的 kernel7.img 复制到 SD 卡上。 这个名字很有意义,它向 RPi4 发出信号,我们希望它以 64 位模式启动。 我们还可以通过在 config.txt 中将 arm_64bit 设置为非零值来强制执行此操作。 将我们的操作系统引导到 64 位模式意味着我们可以利用 RPi4 可用的更大内存容量。
- 制作完成的 SD卡 目录如下:
简略信息:
COPYING.linux bcm2710-rpi-3-b-plus.dtb fixup4.dat start.elf
LICENCE.broadcom bcm2710-rpi-3-b.dtb fixup4cd.dat start4.elf
bcm2708-rpi-b-plus.dtb bcm2710-rpi-cm3.dtb fixup4db.dat start4cd.elf
bcm2708-rpi-b-rev1.dtb bcm2711-rpi-4-b.dtb fixup4x.dat start4db.elf
bcm2708-rpi-b.dtb bcm2711-rpi-400.dtb fixup_cd.dat start4x.elf
bcm2708-rpi-cm.dtb bcm2711-rpi-cm4.dtb fixup_db.dat start_cd.elf
bcm2708-rpi-zero-w.dtb bootcode.bin fixup_x.dat start_db.elf
bcm2708-rpi-zero.dtb cmdline.txt issue.txt start_x.elf
bcm2709-rpi-2-b.dtb config.txt kernel7.img
bcm2710-rpi-2-b.dtb fixup.dat overlays
详细信息:
-rw-r--r-- 1 Lenovo None 18693 Jan 5 2021 COPYING.linux
-rw-r--r-- 1 Lenovo None 1594 Jan 5 2021 LICENCE.broadcom
-rw-r--r-- 1 Lenovo None 25870 Mar 3 13:40 bcm2708-rpi-b-plus.dtb
-rw-r--r-- 1 Lenovo None 25218 Mar 3 13:40 bcm2708-rpi-b-rev1.dtb
-rw-r--r-- 1 Lenovo None 25607 Mar 3 13:40 bcm2708-rpi-b.dtb
-rw-r--r-- 1 Lenovo None 25529 Mar 3 13:40 bcm2708-rpi-cm.dtb
-rw-r--r-- 1 Lenovo None 26545 Mar 3 13:40 bcm2708-rpi-zero-w.dtb
-rw-r--r-- 1 Lenovo None 25352 Mar 3 13:40 bcm2708-rpi-zero.dtb
-rw-r--r-- 1 Lenovo None 26745 Mar 3 13:40 bcm2709-rpi-2-b.dtb
-rw-r--r-- 1 Lenovo None 26894 Mar 3 13:40 bcm2710-rpi-2-b.dtb
-rw-r--r-- 1 Lenovo None 29011 Mar 3 13:40 bcm2710-rpi-3-b-plus.dtb
-rw-r--r-- 1 Lenovo None 28392 Mar 3 13:40 bcm2710-rpi-3-b.dtb
-rw-r--r-- 1 Lenovo None 26890 Mar 3 13:40 bcm2710-rpi-cm3.dtb
-rw-r--r-- 1 Lenovo None 49090 Mar 3 13:40 bcm2711-rpi-4-b.dtb
-rw-r--r-- 1 Lenovo None 48810 Apr 30 14:01 bcm2711-rpi-400.dtb
-rw-r--r-- 1 Lenovo None 49202 Mar 3 13:40 bcm2711-rpi-cm4.dtb
-rw-r--r-- 1 Lenovo None 52456 Jan 5 2021 bootcode.bin
-rw-r--r-- 1 Lenovo None 169 May 7 15:00 cmdline.txt
-rw-r--r-- 1 Lenovo None 1832 Jul 17 00:23 config.txt
-rw-r--r-- 1 Lenovo None 7314 Apr 30 14:01 fixup.dat
-rw-r--r-- 1 Lenovo None 5446 Apr 30 14:01 fixup4.dat
-rw-r--r-- 1 Lenovo None 3191 Apr 30 14:01 fixup4cd.dat
-rw-r--r-- 1 Lenovo None 8454 Apr 30 14:01 fixup4db.dat
-rw-r--r-- 1 Lenovo None 8452 Apr 30 14:01 fixup4x.dat
-rw-r--r-- 1 Lenovo None 3191 Apr 30 14:01 fixup_cd.dat
-rw-r--r-- 1 Lenovo None 10298 Apr 30 14:01 fixup_db.dat
-rw-r--r-- 1 Lenovo None 10298 Apr 30 14:01 fixup_x.dat
-rw-r--r-- 1 Lenovo None 145 May 7 15:00 issue.txt
-rw-r--r-- 1 Lenovo None 552 Jul 17 00:37 kernel7.img
drwxr-xr-x 1 Lenovo None 0 Jul 27 22:25 overlays
-rw-r--r-- 1 Lenovo None 2952928 Apr 30 14:01 start.elf
-rw-r--r-- 1 Lenovo None 2228768 Apr 30 14:01 start4.elf
-rw-r--r-- 1 Lenovo None 793084 Apr 30 14:01 start4cd.elf
-rw-r--r-- 1 Lenovo None 3722504 Apr 30 14:01 start4db.elf
-rw-r--r-- 1 Lenovo None 2981160 Apr 30 14:01 start4x.elf
-rw-r--r-- 1 Lenovo None 793084 Apr 30 14:01 start_cd.elf
-rw-r--r-- 1 Lenovo None 4794472 Apr 30 14:01 start_db.elf
-rw-r--r-- 1 Lenovo None 3704712 Apr 30 14:01 start_x.elf
-
从你的开发机器上安全地卸载 SD 卡,将它放回你的 RPi4 并启动它。
-
您刚刚启动了自己的操作系统!
-
尽管听起来令人兴奋,但在 RPi4 自己的“彩虹闪屏”之后,您可能看到的只是一个空白的黑色屏幕。 然而,我们不应该感到如此惊讶:我们还没有要求它做任何事情,除了在无限循环中旋转。
-
不过,基础已经奠定,我们现在可以开始做令人兴奋的事情了。 恭喜你走到这一步!