前言

在看正点原子 i.MX6ULL 的裸机代码时,看到每个工程里面都有一个汇编代码文件 start.S,那么裸机程序为什么一定要以汇编代码作为开始呢?直接使用 C 语言不行吗?

汇编都干了啥

start.S

.global _start

_start:
    ldr sp, =0x80200000		/* 设置 SP 指针 */
    b main              	/* 跳转到 C 语言 main 函数 */

就干了两件事

  1. 设置堆栈指针
  2. 跳转到 C 语言 main() 函数

所以汇编起到的作用就是设置栈指针。

因为 C 语言执行的都是函数,只要执行函数就需要使用栈空间。如果没有设置栈指针,程序可能会因为栈溢出或其他未定义行为而崩溃或者产生错误。

测试一下

编个纯 C 测下

main.c

#include "main.h"

/* 使能外设时钟 */
void clk_enable(void)
{
	CCM_CCGR1 = 0xFFFFFFFF;
	CCM_CCGR2 = 0xFFFFFFFF;
	CCM_CCGR3 = 0xFFFFFFFF;
	CCM_CCGR4 = 0xFFFFFFFF;
	CCM_CCGR5 = 0xFFFFFFFF;
	CCM_CCGR6 = 0xFFFFFFFF;
}

/* 初始化 LED */
void led_init(void)
{
	SW_MUX_GPIO1_IO03 = 0x5;	/* 复用为 GPIO1-IO03 */
	SW_PAD_GPIO1_IO03 = 0x10B0; /* 设置 GPIO1-IO03 电气属性 */

	/* GPIO 初始化 */
	GPIO1_GDIR = 0x08; /* 设置为输出 */
	GPIO1_DR = 0x0;	   /* 打开 LED 灯 */
}

/* 短延时 */
void delay_short(volatile unsigned int n)
{
	while (n--) {
	}
}

/* 延时,大概 ms 级别,主频 396MHz */
void delay(volatile unsigned int n)
{
	while (n--) {
		delay_short(0x7ff);
	}
}

void led_on(void)
{
	GPIO1_DR &= ~(1 << 3); /* bit3 清零 */
}

void led_off(void)
{
	GPIO1_DR |= (1 << 3); /* bit3 置位 */
}

int main(void)
{
	clk_enable(); /* 使能外设时钟 */
	led_init();	  /* 初始化 LED */

	while (1) {
		led_on();
		delay(2000);
		led_off();
		delay(2000);
	}

	return 0;
}

Makefile

objs = main.o

ledc.bin : $(objs)
	arm-linux-gnueabihf-ld -Ttext 0x87800000 $^ -o ledc.elf -e main
	arm-linux-gnueabihf-objcopy -O binary -S ledc.elf $@
	arm-linux-gnueabihf-objdump -D -m arm ledc.elf > ledc.dis

%.o : %.c
	arm-linux-gnueabihf-gcc -Wall -nostdlib -c -o $@ $<

clean:
	rm -rf *.o ledc.bin ledc.elf ledc.dis

install:
	cp ledc.bin ~/tftp

编译

$ make clean && make && make install 
rm -rf *.o ledc.bin ledc.elf ledc.dis
arm-linux-gnueabihf-gcc -Wall -nostdlib -c -o main.o main.c
arm-linux-gnueabihf-ld -Ttext 0x87800000 main.o -o ledc.elf -e main
arm-linux-gnueabihf-objcopy -O binary -S ledc.elf ledc.bin
arm-linux-gnueabihf-objdump -D -m arm ledc.elf > ledc.dis
cp ledc.bin ~/tftp

i.MX6ULL 开发板进入 uboot 模式,tftp 下载 ledc.bin 到 0x87800000,并跳转到该地址运行,发现板子卡住了(LED 没有闪灯),应该是栈溢出,修改了不该修改的内存,导致程序崩溃了。

恢复带汇编 start.S 的版本。程序运行成功(LED 闪烁)。

延伸思考1

C 语言不能设置堆栈指针吗?

C 语言可以设置堆栈指针,但是运行 C 语言前就要设置好,不然无法运行 C 程序。

延伸思考2

bootloader 开始代码 arch/arm/lib/vectors.S

kernel 开始代码 arch/arm/kernel/head.S

都是从汇编开始

延伸思考3

C 语言比 Java 语言更底层,因为 C 语言可以直接操作内存地址

汇编语言比 C 语言更底层,因为汇编语言可以直接操作寄存器