欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

uboot-C语言环境初始化

程序员文章站 2024-02-25 11:02:46
...
1-栈初始化
栈是一种具有后进先出性质的数据组织方式,也就是说,后存放的先取出,先存放的后取出。
根据sp指针指向的位置,栈可以分为满栈和空栈。
满栈:当堆栈指针sp总是指向最后压入堆栈的数据。
空栈:当堆栈指针sp总是指向下一个将要放入数据的空位置。
ARM采用满栈!
 uboot-C语言环境初始化
根据SP指针移动方向,栈可以分为升栈和降栈。
升栈:随着数据的入栈,SP指针从低地址->高地址移动。
降栈:随着数据的入栈,SP指针从高地址->低地址移动。
ARM采用降栈。
ARM系统采用满降栈!!
栈帧:就是一个函数所使用的那部分栈,所有函数的栈帧串起来就组成了一个完整的栈。栈帧的两个边界分别由fp(r11)和sp(r13)来限定。
uboot-C语言环境初始化
栈的作用
1.保存局部变量
2.参数传递
3.保存寄存器值

通过汇编来说明局部变量保存在栈中。
范例解析 stack1.c --- 保存局部变量
#include <stdio.h>

int main()
{
	int a;
	a++;
	return a;	
}
arm-linux-gcc -g stack1.c -o stack1
arm-linux-objdump -D -S stack1 > dump1
打开反汇编文件,然后找到main函数这里,分析汇编。
 uboot-C语言环境初始化
push    {fp}            ; (str fp, [sp, #-4]!)  把fp指针保存到堆栈中,push {fp}翻译过来就是分号后面的汇编代码。ARM采用的是满栈的方式,所以SP指针指向的数据,一定是有效的数据。将fp数据保存在sp-4 的位置。在修改fp之前,先保存。[sp, #-4]! “!”表示要将sp-4 的值再赋值给sp,是需要改变sp值。没有!是不改变sp值。
add     fp, sp, #0      ; 0x0  将sp的数据保存在fp中,也就是上一行代码sp-4之后的值。此时的fp正好是这个程序的栈开始地方。
sub     sp, sp, #12     ; 0xc   sp-12,
ldr     r3, [fp, #-8]     将fp-8的值存进r3寄存器   也就是说a存放在fp-8的这个位置  
add     r3, r3, #1      ; 0x1  执行a++
str     r3, [fp, #-8]     将计算结果存进fp-8位置,也就是放在变量a中。

stack2.c --- 传递参数
#include <stdio.h>
void func1(int a,int b,int c,int d,int e,int f)
{
	int k;
	k=e+f;
}


int main()
{
    func1(1,2,3,4,5,6);
    return 0;
}

反汇编之后来分析汇编代码,
 uboot-C语言环境初始化
函数传递参数时,当参数<=4时,是利用r0-r3寄存器来传递。
当大于4个时候,采用栈来传递。
 
stack3.c --- 保存寄存器的值
#include <stdio.h>
void func2(int a,int b)
{
    int k;
    k=a+b;
}
void func1(int a,int b)
{
	int c;
	func2(3,4);
	c=a+b;
}
int main()
{
    func1(1,2);
    return 0;
}
uboot-C语言环境初始化uboot-C语言环境初始化
 
在函数调用的过程中,栈保存寄存器的值。main中,把func1的两个参数保存在r0、r1中,func1又调用func2,func2的两个参数也是需要保存在r0、r1中的,这样的话就会修改原来r0、r1的值,所以,就要先把r0 r1的值保存在栈中。
代码编写:6410内存的起始地址为50000000,然后我们规定,64MB的地方为栈的起始地址。
init_stack:
	ldr sp, =0x54000000
	mov pc ,lr

2-初始化BSS段

初始化的全局变量:数据段
局部变量:栈

malloc:堆

未初始化的全局变量:bss段

bss.c

#include <stdio.h>
int year;
int main()
{
	year = 2017;
	return year;
}

arm-linux-gcc bss.c -o bss
arm-linux-readelf -a bss > dump
 uboot-C语言环境初始化
year的地址在bss开始和结束之间,所以为初始化的全局变量是放在bss段。
为什么要初始化bss段呢?因为有可能在写程序的时候,全局变量没有初始化就使用了。我们希望bss段中的值开始就是0的。
bss_start bss_end在连接器脚本当中定义了,代码编写如下:
clean_bss:
	ldr r0, =bss_start
	ldr r1, =bss_end
	cmp r0, r1
	moveq pc, lr
clean_loop:
	mov r2, #0
	str r2, [r0], #4
	cmp r0, r1
	bne clean_loop
	mov pc, lr

3-汇编语言跳到c语言运行

跳转有相对跳转和绝对跳转,
相对:b bl指令  利用两个标号之间的差值,将差值加到pc指针中完成跳转。
绝对:ldr pc, =main
在这之前,所有的代码都是运行在SRAM(垫脚石中,6410为4k),所有之前的b bl都是在SRAM中运行的。现在的板子中,垫脚石和内存中都有一份代码,跳转到c语言我们希望pc指针指向内存中的main,所以用ldr pc,=main

4-c与汇编混合编程
汇编语言:执行效率高,但编写繁琐
c语言:可读性强,移植性好,调试方便

为什么需要混合编程?
1.执行效率

2.能够更直接地控制处理器 访问MSR MRS CPSR寄存器等。

一、汇编调用C函数
ldr pc,=main   这就是汇编调用C函数。

二、c调用汇编函数

假设在start.s中用汇编实现了light_led 标号。

int gboot_main()
{
	light_led(); 
	return 0;
}


编译的时候会出错,没有定义light_led。汇编语言中所实现的标号(函数),想让其它文件引用,需要将标号声明为.global属性。

三、c内嵌汇编

格式:
__asm__(
	汇编语句部分
	:输出部分 -- 可能修改了c语言中变量的值修改了,这些变量就放在输出部分
	:输入部分 -- 可能需要从c语言中拿到一些参数作为汇编的操作数,这些参数就是放在输入部分
	:破坏描述部分 -- mov r0,#0 这样把r0原来的值破坏了,所以要放到这个地方来。  
);
C内嵌汇编以关键字”__asm__”或”asm”开始,下辖四个部分,各部分之间使用":"分开, 第一部分是必须写的,
后面三部分是可以省略,但是冒号:不能省略!

1.汇编语句部分:汇编语句的集合,可以包含多条汇编
语句,每条语句之间需要使用换行符 “\n”隔开或
使用分号“ ; ”隔开。
2.输出部分:在汇编中被修改的C变量列表
3.输入部分: 作为参数输入到汇编中的变量列表

4.破坏描述部分: 执行汇编指令会破坏的寄存器描述

void write_p15_c1(unsigned long value)
{
	__asm__(
		"mcr p15, 0, %0, c1,c0, 0\n"
		:
		: "r" (value)  @编译器选择一个rx寄存器。"r"表示是一个通用寄存器。值从value中来。
	);
}

凡是要去读的参数,都是放在输入部分,凡是要是写进去的参数,都是放在输出部分。

unsigned long read_p15_c1(void)
{
	unsigned long value;
	__asm__(
		"mcr p15, 0, %0, c1,c0, 0\n"
		: "=r" (value) @ "=" 表示只写操作数,用于输出部。r0中的值要存放到value变量中。
		:
		:  "memory"); //value变量放在栈中的,也就是内存中的值被修改了,通过“memory”通知系统有语句去修改内存。
	return value;
}

使用volatile来告诉编译器,不要对接下来的这部分代码进行优化

unsigned long old;
unsigned long temp;
__asm__ volatile(
	"mrs %0, cpsr \n"
	"orr %1, %0, #128 \n“
	"msr cpsr_c, %1\n"
	: "=r“ (old), "=r“ (temp)
	:
	:"memory");

使用内嵌汇编点亮LED

#define GPKCON 0x7f008800
#define GPKDAT 0x7f008808
int gboot_main()
{
	__asm__(
		"ldr r1, =0x11110000"
		"str r1, [%0]\n"
		"ldr r1, =0xa0\n"
		"str r1, [%1]\n"
		:
		:"r" (GPKCON), "r" (GPKDAT)
		:"r1"
	);
}