本文使用的开发板是九鼎创展的X210 iNand版本。
一、预备知识
1、C语言运行时和栈
(1)C语言运行时需要
C语言运行时(runtime)需要一定的条件,这些条件由汇编来提供。C语言运行时主要是需要栈。
(2)C语言与栈的关系
栈是一种遵循后进先出(LIFO)原则的数据结构,在C语言中广泛应用于函数调用、局部变量存储和表达式求值等场景。栈由操作系统或程序运行时自动管理,通常分为系统栈(调用栈)和手动实现的栈(数据结构)。
// 系统栈的典型示例:函数调用
void func1() { int x = 10; }
void func2() { func1(); }
int main() { func2(); return 0; }
系统栈(调用栈)
系统栈在函数调用时存储以下内容:
- 函数返回地址
- 局部变量
- 函数参数
- 寄存器状态
栈指针(SP)和基址指针(BP)寄存器跟踪栈帧位置。栈溢出可能发生在递归过深或局部变量过大时。
// 递归导致的栈溢出示例
void infinite_recursion() {
int x[1000]; // 大量局部变量
infinite_recursion();
}
手动实现栈数据结构
C语言可通过数组或链表手动实现栈,常用于算法开发:
#define MAX_SIZE 100
typedef struct {
int data[MAX_SIZE];
int top;
} Stack;
void push(Stack *s, int item) {
if (s->top < MAX_SIZE)
s->data[s->top++] = item;
}
int pop(Stack *s) {
if (s->top > 0)
return s->data[--s->top];
return -1; // 错误处理
}
栈的应用场景
- 括号匹配检查:通过栈验证嵌套结构的正确性
- 逆波兰表达式:栈用于后缀表达式求值
- 深度优先搜索(DFS):替代递归实现
- 撤销操作:保存操作历史记录
// 括号匹配示例
bool isValid(char *s) {
char stack[10000];
int top = 0;
for (int i = 0; s[i]; i++) {
if (s[i] == '(' || s[i] == '[' || s[i] == '{')
stack[top++] = s[i];
else if (--top < 0 ||
(s[i] == ')' && stack[top] != '(') ||
(s[i] == ']' && stack[top] != '[') ||
(s[i] == '}' && stack[top] != '{'))
return false;
}
return top == 0;
}
栈的注意事项
- 系统栈空间有限(通常只有几MB),需避免深度递归或大局部变量
- 手动实现栈需处理边界条件(空栈/满栈)
- 多线程环境下需考虑线程安全栈的实现
C语言中的局部变量都是用栈来实现的,如果汇编部分没有给C语言预先设置合理合法的栈地址,那么C代码中定义的局部变量就会落空,整个C程序就不能运行。
2、CPU模式和各种模式下的栈
(1)在ARM的37个寄存器中,每种模式下都有自己的独立的SP寄存器(r13)。
(2)我们要设置栈,不可能而且也没有必要去设置所有的栈。我们先要找到当前的模式,然后设置该模式下的栈到合理合法的位置即可。
(3)系统在复位后默认是进入SVC模式。
(4)我们先把模式设置为SVC,再直接操作SP,即可访问SVC模式下的SP。因为复位后就已经是SVC模式了,所以直接设置SP即可。
3、设置栈指针
(1)栈必须是当前一段可用的内存,这个内存必须是被初始化过可以访问的内存,而且这个内存只会被用作栈,不会被其他程序占用。
(2)当前CPU刚复位,外部的DRAM尚未初始化,目前可用的内存只有内部的SRAM(不需初始化即可使用),因此只能在SRAM中找一段内存来作为SVC的栈。
(3)在ARM中,ATPCS要求使用满减栈
二、查阅iROM Application Note文档
由上图可知SVC栈应该设置为0xD0037D80。
三、代码实现
#define SVC_STACK 0xD0037D80
.global _start
_start:
ldr sp, =SVC_STACK
b .
四、汇编调用C函数,实现LED闪烁效果
1、led.c
#define GPJ0CON 0xE0200240
#define GPJ0DAT 0xE0200244
#define rGPJ0CON *((volatile unsigned int *)GPJ0CON)
#define rGPJ0DAT *((volatile unsigned int *)GPJ0DAT)
void delay(void);
void led_blink(void) {
rGPJ0CON = 0x11111111;
while(1) {
// led亮
rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));
// 延时
delay();
// led灭
rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));
// 延时
delay();
}
}
void delay(void) {
volatile unsigned int i = 900000;
while (i--);
}
2、Makefile
led.bin: start.o led.o
arm-linux-ld -Ttext 0x0 -o led.elf $^
arm-linux-objcopy -O binary led.elf led.bin
arm-linux-objdump -D led.elf > led_elf.dis
gcc mkv210_image.c -o mkx210
./mkx210 led.bin 210.bin
%.o : %.S
arm-linux-gcc -o $@ $< -c -nostdlib
%.o : %.c
arm-linux-gcc -o $@ $< -c -nostdlib
clean:
rm *.o *.elf *.bin *.dis mkx210 -f
3、start.S
#define SVC_STACK 0xD0037D80
.global _start
_start:
ldr sp, =SVC_STACK
bl led_blink
b .