1 基本流程
代码来自:https://github.com/bztsrc/raspi3-tutorial/tree/master/02_multicorec

Makefile的流程可以看到,和上一篇改动也不是很大。修改的内容有三点,首先还是之前的那两点:
1 编译器改成aarch64-linux-gnu-gcc,里面有-ffreestanding -nostdinc倒是不用再加参数。
2 -Wl,–hash-style=sysv,增加这个参数
还有一个就是这次增加了.c文件。
值得注意的就是增加了
SRCS = $(wildcard *.c)
OBJS = $(SRCS:.c=.o)
kernel8.img: start.o $(OBJS)
这里表示把所有的.c编译并连接到最终的镜像,这里的第三行是指明了依赖,只要有改动.c就会重新生成img。我以前写makefile好像就没加这个,导致都要手动操作。今天又学到了。
改完如下:
SRCS = $(wildcard *.c)
OBJS = $(SRCS:.c=.o)
CFLAGS = -Wall -O2 -ffreestanding -nostdinc -nostdlib -nostartfiles
all: clean kernel8.img
start.o: start.S
aarch64-linux-gnu-gcc $(CFLAGS) -c start.S -o start.o
%.o: %.c
aarch64-linux-gnu-gcc $(CFLAGS) -c $< -o $@
kernel8.img: start.o $(OBJS)
aarch64-linux-gnu-gcc -nostdlib -nostartfiles -Wl,–hash-style=sysv start.o $(OBJS) -T link.ld -o kernel8.elf
aarch64-linux-gnu-objcopy -O binary kernel8.elf kernel8.img
clean:
rm kernel8.elf *.o >/dev/null 2>/dev/null || true
run:
qemu-system-aarch64 -M raspi3b -kernel kernel8.img -d in_asm
之后就是很正常的编译就过了。有两个warning,但是不影响。

2 main.c
void main()
{
while(1);
}
基本上也没什么内容。但是这个main会最后链接到img中。
3 link.ld
SECTIONS
{
. = 0x80000;
.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;
link可以看到,这次增加了几个数据分区,rodata,text,data,bss。
BSS段的内容有(NOLOAD),告诉objcopy,这一段在img中不占空间。ALIGN(16)是内存对齐。*(COMMON)兼容老式C编译器。
__bss_size = (__bss_end – __bss_start)>>3;这里的>>3表示除以8,因为每次循环清空 8 个字节(一个 64 位寄存器)。
4 start.S
改动最大的是strat.s
.section ".text.boot"
.global _start
_start:
// read cpu id, stop slave cores
mrs x1, mpidr_el1
and x1, x1, #3
cbz x1, 2f
// cpu id > 0, stop
1: wfe
b 1b
2: // cpu id == 0
// set top of stack just before our code (stack grows to a lower address per AAPCS64)
ldr x1, =_start
mov sp, x1
// clear bss
ldr x1, =__bss_start
ldr w2, =__bss_size
3: cbz w2, 4f
str xzr, [x1], #8
sub w2, w2, #1
cbnz w2, 3b
// jump to C code, should not return
4: bl main
// for failsafe, halt this core too
b 1b
代码说明说下:
多核运行:
// read cpu id, stop slave cores
mrs x1, mpidr_el1 // 从寄存器mpidr_el1读取多核标识寄存器 and x1, x1, #3 // 掩码操作,只保留最后两位(核心编号 0, 1, 2, 3) cbz x1, 2f // 如果编号是 0,跳转到标签 2:(主核逻辑)
// cpu id > 0, stop 1: wfe // 如果编号 > 0,执行 WFE(等待事件,低功耗睡眠) b 1b // 睡眠醒来后继续跳回 1: 形成死循环
设置栈指针:
当发现CPU的id是0时到这里。
2: ldr x1, =_start // 获取 _start 标签的地址(即 0x80000) mov sp, x1 // 将这个地址赋给栈指针寄存器 SP
清空BSS
按照C语言规范,BSS的未初始化的全局变量必须清零,这部分实际上也是手动做的。
ldr x1, =__bss_start // 从链接脚本获取 BSS 段的起始地址 ldr w2, =__bss_size // 从链接脚本获取 BSS 段的大小 3: cbz w2, 4f // 如果大小为 0,跳过清零直接去 4: str xzr, [x1], #8 // 将 64 位零寄存器 (xzr) 写入 x1 指向的内存,并将 x1 后移 8 字节 sub w2, w2, #1 // 计数器减 1 cbnz w2, 3b // 如果计数器不为 0,继续循环
跳转到main
4: bl main // Branch with Link:跳转到 main 函数,并把返回地址存在 lr 寄存器 b 1b // 万一 main 函数返回了(通常不应该),让 Core 0 也去睡眠死循环
5 运行
对kernel8.elf进行反汇编分析结果如下:
aarch64-linux-gnu-objdump -d kernel8.elf
kernel8.elf: file format elf64-littleaarch64
Disassembly of section .text:
0000000000080000 <_start>: 80000: d53800a1 mrs x1, mpidr_el1 80004: 92400421 and x1, x1, #0x3 80008: b4000061 cbz x1, 80014 <_start+0x14> 8000c: d503205f wfe 80010: 17ffffff b 8000c <_start+0xc> 80014: 58000161 ldr x1, 80040 <_start+0x40> 80018: 9100003f mov sp, x1 8001c: 58000161 ldr x1, 80048 <_start+0x48> 80020: 180000e2 ldr w2, 8003c <_start+0x3c> 80024: 34000082 cbz w2, 80034 <_start+0x34> 80028: f800843f str xzr, [x1], #8 8002c: 51000442 sub w2, w2, #0x1 80030: 35ffffa2 cbnz w2, 80024 <_start+0x24> 80034: 94000007 bl 80050 <main> 80038: 17fffff5 b 8000c <_start+0xc> 8003c: 00000000 .word 0x00000000 80040: 00080000 .word 0x00080000 80044: 00000000 .word 0x00000000 80048: 00080250 .word 0x00080250 8004c: 00000000 .word 0x00000000
0000000000080050 <main>: 80050: 14000000 b 80050 <main>
运行结果如下:
tom@PC-20241221RKUQ:~/rp3/raspi3-tutorial/02_multicorec$ make run
qemu-system-aarch64 -M raspi3b -kernel kernel8.img -d in_asm
—————-
IN:
0x00000000:
OBJD-T: c0000058e1031faae2031faae3031faa8400005880001fd6
—————-
IN:
0x00080000:
OBJD-T: a10038d521044092610000b4
—————-
IN:
0x00080014:
OBJD-T: 610100583f00009161010058e200001882000034
—————-
IN:
0x00080034:
OBJD-T: 07000094
—————-
IN:
0x00080050:
OBJD-T: 00000014
—————-
IN:
0x00000300:
OBJD-T: 051b80d2a60038d5c60440925f2003d5a47866f8c4ffffb4
—————-
IN:
0x00000300:
OBJD-T: 051b80d2a60038d5c60440925f2003d5a47866f8c4ffffb4
—————-
IN:
0x0000030c:
OBJD-T: 5f2003d5a47866f8c4ffffb4
—————-
IN:
0x00000300:
OBJD-T: 051b80d2a60038d5c60440925f2003d5a47866f8c4ffffb4
—————-
IN:
0x0000030c:
OBJD-T: 5f2003d5a47866f8c4ffffb4
在0x80000地址时,机器码是a10038d521044092610000b4。
a1 00 38 d5对应读取 MPIDR_EL1 到 x1。 21 04 40 92:执行 and x1, x1, #3。 61 00 00 b4:执行 cbz x1, 2f。最后是运行到80008。
在0x80014,机器码是610100583f00009161010058e200001882000034。
61 01 00 58:这是 ldr x1, =_start。 3f 00 00 91:这是 mov sp, x1。C 语言需要的栈正式建立。 后面是加载 __bss_start 和 __bss_size 并开始循环清零。
在0x80034,机器码是07000094。
0x94000007就是对应的bl跳转指令,跳转到main。
后面的0x80050就是main执行后的返回,0x300是另外三个核心的异常。
此时查看多核的状态:

Core0运行在main。

其它核在wfe

最后,看了一下寄存器的状况。所有核心的寄存器类型是一样,但是数据不同,也就是说每个核的寄存器都是单独的。在这个例子中白色的部分就是变化的寄存器。

网硕互联帮助中心



评论前必须登录!
注册