云计算百科
云计算领域专业知识百科平台

RASPI裸机3(多核)

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

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

赞(0)
未经允许不得转载:网硕互联帮助中心 » RASPI裸机3(多核)
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!