首页 > 代码库 > 自己学驱动9——uboot代码阅读四(start_armboot函数)
自己学驱动9——uboot代码阅读四(start_armboot函数)
前面分析过在start.S中执行完相关的一些操作之后,会跳转到C语言的部分来执行,跳转到的目标位置就是start_armboot函数,所以现在来看一下这个函数完成了一些什么工作。在这个函数的第一行定义了一个变量如下:
init_fnc_t **init_fnc_ptr;
通过查找uboot源码可以得到下面的类型重定义:
typedef int (init_fnc_t) (void);
typedef的使用方法非常灵活,这里这种定义方式就是定义了init_fnc_t代表一个接收参数为void类型返回值为int类型的函数,然后根据C语言定义变量的性质init_fnc_t **init_fnc_ptr中**init_fnc_ptr就是一个init_fnc_t类型的变量,这就好比int **p定义p之后,*p代表的是int类型的地址,**p代表的才是一个int类型的值。有了这个观点,就不难得出*init_fnc_ptr代表的是该类函数的地址,**init_fnc_ptr代表这种类型的一个实际的函数,所以(**init_fnc_ptr)();就是这种函数的一次调用过程了。而在uboot的源码中的使用有一些变化,下面具体来说一说,首先是:
init_fnc_t *init_sequence[] = {
cpu_init, /* basic cpu dependent setup */
board_init, /* basic board dependent setup */
...
这里的赋值操作,直接将函数名赋给了int_fnc_t *类型的数组,这个是由于函数名和数组名有相似之处,也就是函数名也就代表函数的地址,而且对函数名取地址的值也和函数名本身的值是相等的。这就类似于:
int a[5];
int *p = a;
另一个不同的地方是,上面说过了(**init_fnc_ptr)();才是完整的调用函数的方式,而源码中使用了(*init_fnc_ptr)();这种调用方式,这种调用方式也是编译器接受的方式,因为虽然(*init_fnc_ptr)只是代表函数的地址(也就相当于对函数名取地址之后的值),但是如果在其后加上()编译器就会将其提升为函数,这个类似于下面的操作:
int a[5];
int *p = a;
p[0] = 5;
这里的p也被隐形的进行了一次提升,理解这种操作的时候再去理解函数名的操作就容易很多。
紧接着下来是对gd的赋值操作:
gd = (gd_t*)(_armboot_start - CFG_MALLOC_LEN - sizeof(gd_t));
首先需要知道gd的定义为:
#define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r8")
//定义了一个结构体 gd_t类型的指针gd变量,并把该变量的地址存储在寄存器 r8 中。
其次需要知道gd代表的为global data数据结构定义,位于文件 include/asm-arm/global_data.h。其成员主要是一些全局的系统初始化参数。需要用到时用宏定义:DECLARE_GLOBAL_DATA_PTR,指定占用寄存器R8。最后从对gd的赋值操作也可以看出,这个结构体放在了代码段之前(因为_armboot_start代表的是代码段的起始地址)。
__asm__ __volatile__("": : :"memory");
这条语句memory强制gcc编译器假设RAM所有内存单元均被汇编指令修改,这样cpu中的registers和cache中已缓存的内存单元中的数据将作废。cpu将不得不在需要的时候重新读取内存中的数据。这就阻止了cpu又将registers,cache中的数据用于去优化指令,而避免去访问内存。
memset ((void*)gd, 0, sizeof (gd_t));
gd->bd = (bd_t*)((char*)gd - sizeof(bd_t));
memset (gd->bd, 0, sizeof (bd_t));
gd指向的gd_t单元用memset填充为0,然后将gd->bd指向gd单元之前的bd_t类型大小的一个单元,并且将这个bd_t单元也用0填充。
monitor_flash_len = _bss_start - _armboot_start;
monitor_flash_len中存放的是代码段的长度,因为前文已经说过代码段之后紧接着的就是.bss段,这里.bss段的起始地址减去代码段的起始地址得到的就是代码段的长度。
for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
if ((*init_fnc_ptr)() != 0) {
hang ();
}
}
这段代码完成初始化工作,会使用函数指针调用一系列的初始化函数,这些函数指针在init_sequence数组中存放。
后续的代码直到main_loop ();函数以前都是根据宏变量做一些相关的初始化,直到进入main_loop ();循环当中开始等待接受命令。在这个函数中,uboot会处于等待输入的状态,接收到合法的输入之后便解析命令并执行相关的操作。
init_fnc_t **init_fnc_ptr;
通过查找uboot源码可以得到下面的类型重定义:
typedef int (init_fnc_t) (void);
typedef的使用方法非常灵活,这里这种定义方式就是定义了init_fnc_t代表一个接收参数为void类型返回值为int类型的函数,然后根据C语言定义变量的性质init_fnc_t **init_fnc_ptr中**init_fnc_ptr就是一个init_fnc_t类型的变量,这就好比int **p定义p之后,*p代表的是int类型的地址,**p代表的才是一个int类型的值。有了这个观点,就不难得出*init_fnc_ptr代表的是该类函数的地址,**init_fnc_ptr代表这种类型的一个实际的函数,所以(**init_fnc_ptr)();就是这种函数的一次调用过程了。而在uboot的源码中的使用有一些变化,下面具体来说一说,首先是:
init_fnc_t *init_sequence[] = {
cpu_init, /* basic cpu dependent setup */
board_init, /* basic board dependent setup */
...
这里的赋值操作,直接将函数名赋给了int_fnc_t *类型的数组,这个是由于函数名和数组名有相似之处,也就是函数名也就代表函数的地址,而且对函数名取地址的值也和函数名本身的值是相等的。这就类似于:
int a[5];
int *p = a;
另一个不同的地方是,上面说过了(**init_fnc_ptr)();才是完整的调用函数的方式,而源码中使用了(*init_fnc_ptr)();这种调用方式,这种调用方式也是编译器接受的方式,因为虽然(*init_fnc_ptr)只是代表函数的地址(也就相当于对函数名取地址之后的值),但是如果在其后加上()编译器就会将其提升为函数,这个类似于下面的操作:
int a[5];
int *p = a;
p[0] = 5;
这里的p也被隐形的进行了一次提升,理解这种操作的时候再去理解函数名的操作就容易很多。
紧接着下来是对gd的赋值操作:
gd = (gd_t*)(_armboot_start - CFG_MALLOC_LEN - sizeof(gd_t));
首先需要知道gd的定义为:
#define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r8")
//定义了一个结构体 gd_t类型的指针gd变量,并把该变量的地址存储在寄存器 r8 中。
其次需要知道gd代表的为global data数据结构定义,位于文件 include/asm-arm/global_data.h。其成员主要是一些全局的系统初始化参数。需要用到时用宏定义:DECLARE_GLOBAL_DATA_PTR,指定占用寄存器R8。最后从对gd的赋值操作也可以看出,这个结构体放在了代码段之前(因为_armboot_start代表的是代码段的起始地址)。
__asm__ __volatile__("": : :"memory");
这条语句memory强制gcc编译器假设RAM所有内存单元均被汇编指令修改,这样cpu中的registers和cache中已缓存的内存单元中的数据将作废。cpu将不得不在需要的时候重新读取内存中的数据。这就阻止了cpu又将registers,cache中的数据用于去优化指令,而避免去访问内存。
memset ((void*)gd, 0, sizeof (gd_t));
gd->bd = (bd_t*)((char*)gd - sizeof(bd_t));
memset (gd->bd, 0, sizeof (bd_t));
gd指向的gd_t单元用memset填充为0,然后将gd->bd指向gd单元之前的bd_t类型大小的一个单元,并且将这个bd_t单元也用0填充。
monitor_flash_len = _bss_start - _armboot_start;
monitor_flash_len中存放的是代码段的长度,因为前文已经说过代码段之后紧接着的就是.bss段,这里.bss段的起始地址减去代码段的起始地址得到的就是代码段的长度。
for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
if ((*init_fnc_ptr)() != 0) {
hang ();
}
}
这段代码完成初始化工作,会使用函数指针调用一系列的初始化函数,这些函数指针在init_sequence数组中存放。
后续的代码直到main_loop ();函数以前都是根据宏变量做一些相关的初始化,直到进入main_loop ();循环当中开始等待接受命令。在这个函数中,uboot会处于等待输入的状态,接收到合法的输入之后便解析命令并执行相关的操作。
自己学驱动9——uboot代码阅读四(start_armboot函数)
声明:以上内容来自用户投稿及互联网公开渠道收集整理发布,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任,若内容有误或涉及侵权可进行投诉: 投诉/举报 工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。