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

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

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐