文章目录
1 函数类型的使用场景
1.1 第一层理解
通常使用定义函数类型的时候,我们习惯定义成函数指针来使用,如
typedef int(*fuc_ptr_t)(int,int);
int add(int a, int b)
{
return a+b;
}
int main(void)
{
fuc_ptr_t my_fuc = NULL;
my_fuc = add;
my_fuc(10,20);
}
上面定义了一个函数指针类型,指向了add函数
- 类型定义:
typedef int(*fuc_ptr_t)(int,int);
注意fuc_ptr_t
前面有*
- 定义:
fuc_ptr_t my_fuc = NULL;
注意fuc_ptr_t
后面没有*
- 赋值:
my_fuc = add
1.2 第二层理解
还可以这样来定义
typedef int(fuc_t)(int,int);
int add(int a, int b)
{
return a+b;
}
int main(void)
{
fuc_t* my_fuc = NULL;
my_fuc = add;
my_fuc(10,20);
}
上面定义了一个函数类型,不是函数指针类型。
- 类型定义:
tyepdef int(fuc_t)(int,int);
注意fuc_t
前面没有*
- 定义:
fuc_t* my_fuc
,注意fuc_t
后面有*
- 赋值:
my_fuc = add
1.3 第三层理解
如果有个数组是函数指针数组,那么前两种理解中的函数类型或者函数指针类型应该如何定义变量并指向函数指针数组?
1.3.1 第一种
typedef int(*fuc_ptr_t)(void);
int fuc1(void)
{
...
}
...
int fuc10(void)
{
...
}
fuc_ptr_t fuc_array[10] =
{
fuc1,...,fuc10
};
int main(void)
{
fuc_ptr_t* p = fuc_array;
p[0]();
retrun 0;
}
这样定义的好处就是fuc_array
怎么操作元素,p
就怎么操作元素。
例如,如果想使用fuc1
函数,可以fuc1();
,可以fuc_array[0]()
,还可以p[0]()
。
1.3.2 第二种
typedef int(fuc_t)(void);
int fuc1(void)
{
...
}
...
int fuc10(void)
{
...
}
fuc_t* fuc_array[10] =
{
fuc1,...,fuc10
};
int main(void)
{
fuc_t** pp = fuc_array;
(*pp)();
retrun 0;
}
这种情况下,fuc_array
等价于*pp
例如:想要调用fuc1
,可以fuc1()
,可以fuc_array[0]()
,还可以(*pp)[0]()
2 二级指针遍历指针数组
2.1 源码理解
在uboot
启动的第二阶段中,lib_arm/board.c
的start_armboot
函数里有这样一段代码,
整理如下:
/*board.c*/
typedef int(init_fnc_t)(void);
init_fnc_t *init_sequence[] = {
cpu_init, /* basic cpu dependent setup */
...
NULL,/*遍历终止条件*/
};
void start_armboot (void)
{
init_fnc_t **init_fnc_ptr;/*二级指针*/
...
for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
if ((*init_fnc_ptr)() != 0) {
hang ();
}
}
...
}
函数类型定义:typedef int(init_fnc_t)(void)
,定义了一个函数类型,不是函数指针类型。
函数注册:init_fnc_t *init_sequence[] = {...}
,元素类型init_fnc_t *
。
迭代指针定义:init_fnc_t **init_fnc_ptr
,指针数组的迭代指针是一个二级指针。
通过迭代指针遍历:for(init_fnc_ptr = init_sequence; *init_fnc_ptr != NULL; ++init_fnc_ptr)
。
迭代器解释:(*init_fnc_ptr)()
。
一些理解:
init_fnc_ptr
指向的是init_sequence
数组的首元素地址,所以++init_fnc_ptr
指向了下一个元素的地址。*init_fnc_ptr
对其进行解引用。
2.2 另一种形式
定义了函数类型还是函数指针类型,决定了后面的遍历方式,如果定义的是函数指针应该怎么遍历呢?
typedef int(*init_fnc_ptr_t)(void);/*注意这里定义的是函数指针类型*/
/*注意:init_fnc_ptr_t后面没有*
*/
init_fnc_ptr_t init_sequence[] = {
cpu_init, /* basic cpu dependent setup */
...
NULL,/*遍历终止条件*/
};
void start_armboot (void)
{
init_fnc_ptr_t *init_fnc_ptr;/*一级指针*/
...
for (init_fnc_ptr = init_sequence; (*init_fnc_ptr) != NULL; ++init_fnc_ptr) {
if ((*init_fnc_ptr)() != 0) {
hang ();
}
}
...
}
函数指针类型定义:typedef int(*init_fnc_ptr_t)(void)
。
函数指针数组定义:init_fnc_ptr_t init_sequence[]
。
迭代器定义:init_fnc_ptr_t *init_fnc_ptr
遍历:for (init_fnc_ptr = init_sequence; (*init_fnc_ptr) != NULL; ++init_fnc_ptr)
解释:(*init_fnc_ptr)()
3 uboot中全局变量的处理及内存分配方法
在uboot
启动第二阶段,所有的全局变量组织在了一起,并不是散落在各个文件中。
代码整理如下:
#define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r8")
typedef struct global_data {
bd_t *bd;
unsigned long flags;
unsigned long baudrate;
unsigned long have_console; /* serial_init() was called */
unsigned long reloc_off; /* Relocation Offset */
unsigned long env_addr; /* Address of Environment struct */
unsigned long env_valid; /* Checksum of Environment valid? */
unsigned long fb_base; /* base address of frame buffer */
#ifdef CONFIG_VFD
unsigned char vfd_type; /* display type */
#endif
#if 0
unsigned long cpu_clk; /* CPU clock in Hz! */
unsigned long bus_clk;
phys_size_t ram_size; /* RAM size */
unsigned long reset_status; /* reset status register at boot */
#endif
void **jt; /* jump table */
} gd_t;
DECLARE_GLOBAL_DATA_PTR;
void start_armboot (void)
{
ulong gd_base;
gd_base = CFG_UBOOT_BASE + CFG_UBOOT_SIZE -\
CFG_MALLOC_LEN - CFG_STACK_SIZE -\
sizeof(gd_t);
gd = (gd_t*)gd_base;
memset ((void*)gd, 0, sizeof (gd_t));/*做一个负责任的人,将内存清0后再使用*/
gd->bd = (bd_t*)((char*)gd - sizeof(bd_t));/*c语言知识点:结构体中的指针变量要手动分配内存*/
memset (gd->bd, 0, sizeof (bd_t));/*做一个负责任的人,将内存清0后再使用*/
}
- 以结构体
global_data
的形式组织uboot
中的全局变量。 - 在
start_armboot
中分配内存,uboot
中不能使用malloc
分配内存,因为没有OS
对内存进行管理,直接使用malloc
会将内存打乱,也是不安全的,后面的程序可能将gd
中的内容污染。 ulong gd_base = xxxx
,xxxx
是uboot
提前规划好的内存分配的地址。gd = (gd_t*)gd_base
,gd
全局变量起始地址,将内存进行实例化。memset ((void*)gd, 0, sizeof (gd_t))
,清空内存内容,避免脏内存。gd->bd = (bd_t*)((char*)gd - sizeof(bd_t))
,这个很重要,结构体元素如果是指针,一定要分配内存。memset (gd->bd, 0, sizeof (bd_t))
,避免脏内存。
不理解的地方:
程序只分析到这里其实不能理解为什么不直接定义一个全局变量,例如gd_t gd
,这样gd
就直接分配了内存了,虽然这样gd
地址是变化的,但是可以通过&gd
得到。
而uboot
中是定义了一个全局的指针变量gd_t *gd
,然后手动分配了内存空间,且空间的地址是提前规划好的。
这样做的好处是什么?必须这样处理吗?
4 uboot中的board_init函数
int board_init(void)
{
DECLARE_GLOBAL_DATA_PTR;/*根据宏定义展开,这里是声明了gd*/
#ifdef CONFIG_DRIVER_SMC911X
smc9115_pre_init();
#endif
#ifdef CONFIG_DRIVER_DM9000
dm9000_pre_init();/*网卡配置,这个函数是网卡的GPIO和端口的配置,而不是驱动。网卡驱动是现成的,移植的时候不需要更改*/
#endif
/*需要移植的其他网卡的配置可以在这里实现*/
gd->bd->bi_arch_number = MACH_TYPE;
gd->bd->bi_boot_params = (PHYS_SDRAM_1+0x100);/*记录ddr的在总线上的地址,用户自动配置该信息,启动时读取使用*/
/*board的信息,也可以通过读取硬件的信息,来自动识别一些信息,这里使用的不是这种方式,而是通过用户自己配置(登记)
*登记的信息,都在include/configs/*.h中定义,通过宏定义进行配置值得更改
*bi_boot_params变量是kernel启动时,uboot传参的参数地址。内核启动后,从这个地址处获得传参的值。
*/
return 0;
}
DECLARE_GLOBAL_DATA_PTR
,宏定义的展开值为:#define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r8")
,作用:对gd
进行声明,供后面使用。这样就不用通过包含头文件来访问gd
了,这样是一种访问全局变量的方法,平时在项目开发中用的不多,但是有奇效。- 网卡配置,这个函数是网卡的GPIO和端口的配置,而不是驱动。网卡驱动是现成的,移植的时候不需要更改
#ifdef CONFIG_DRIVER_SMC911X
smc9115_pre_init();
#endif
#ifdef CONFIG_DRIVER_DM9000
dm9000_pre_init();
#endif
通过宏定义开关选择不同的网卡进行配置
gd->bd->bi_arch_number = MACH_TYPE;
,设置开发板的机器码。机器码是开发板的唯一办法。理论上是linux官方发布的,开发板厂家向linux官方申请。作用:针对开发板移植好的uboot
和kernal
中都要定义这个机器码,在启动的时候,通过这个机器码进行适配,保证镜像是针对该开发板移植的。MACH_TYPE
这个机器码是2456
,没有特殊的含义,原理上是uboot
和kernal
中的机器码相同就行。gd->bd->bi_boot_params = (PHYS_SDRAM_1+0x100);
,kernal
启动时,uboot
给kernal
传递的参数之一,bi_boot_params
是个地址,kernal
程序从该地址处查询需要的变量的值。
5 uboot中interrupt_init函数
int interrupt_init(void)
{
S5PC11X_TIMERS *const timers = S5PC11X_GetBase_TIMERS();
/* use PWM Timer 4 because it has no output */
/* prescaler for Timer 4 is 16 */
timers->TCFG0 = 0x0f00;
if (timer_load_val == 0) {
/*
* for 10 ms clock period @ PCLK with 4 bit divider = 1/2
* (default) and prescaler = 16. Should be 10390
* @33.25MHz and @ 66 MHz
*/
timer_load_val = get_PCLK() / (16 * 100);
}
/* load value for 10 ms timeout */
lastdec = timers->TCNTB4 = timer_load_val;
/* auto load, manual update of Timer 4 */
timers->TCON = (timers->TCON & ~0x00700000) | TCON_4_AUTO | TCON_4_UPDATE;
/* auto load, start Timer 4 */
timers->TCON = (timers->TCON & ~0x00700000) | TCON_4_AUTO | COUNT_4_ON;
timestamp = 0;
return (0);
}
interrupt_init
是用来设置定时器的,用来产生tick时钟的。并不是设置cpu的中断的。不要被其名字误导。- 为
uboot
提供delay
函数的。 - 将
timer
设置成了定时10ms
。 uboot
启动的最后有一个bootdelay
阶段,就是用这个timer
实现的。
6 uboot中env_init函数
在uboot
中,env_init
函数有很多个,原因是环境变量相关的初始化和uboot
的启动介质有关。env相关的信息,是存放在启动介质中的。所以针对不同的启动介质分别实现了各自的env_init
函数。
7 uboot中的init_baudrate函数
static int init_baudrate (void)
{
char tmp[64]; /* long enough for environment variables */
int i = getenv_r ("baudrate", tmp, sizeof (tmp));
gd->bd->bi_baudrate = gd->baudrate = (i > 0)
? (int) simple_strtoul (tmp, NULL, 10)
: CONFIG_BAUDRATE;
return (0);
}
tmp[64]
:定义一个临时字符数组,用来存放读取的环境变量中baudrate
的值。getenv_r
:该函数用来读取环境变量,分析在下面。第一个参数:要读取的环境变量,第二个参数:读取到的环境变量的值,第三个参数:第二个参数的长度。返回值:>0表示读取成功,<0表示读取失败。(int) simple_strtoul (tmp, NULL, 10)
:将字符串转换成数字。tmp
:要转化的字符串,NULL
:不读会处理剩下的字符串,10
:十进制转换。CONFIG_BAUDRATE
:uboot
配置是定义的波特率的大小。(i > 0)? (int) simple_strtoul (tmp, NULL, 10): CONFIG_BAUDRATE;
:如果i>0
(读取环境变量成功)则返回环境变量中的波特率,否则返回uboot
配置的波特率。- 读取的环境变量的值在哪里?这里指的是在DDR中的那份环境变量。刚启动后,DDR中的环境变量与Flash(启动介质)中的环境变量是相同的。用户通过
uboot
的控制台更改了环境变量后,DDR中的环境变量就与Flash中的环境变量不一样了。
8 uboot中的serial_init函数
这里该函数什么都没有做,因为在start.S
中uboot
启动第一阶段打印OK
时就初始化了串口驱动,所以这里就不用初始化了。
下一节见:uboot中的知识点记录-2