看Uboot时会遇到的不懂问题

目的:
1,通过看源码,知道如何初始化串口,知道如何让U-boot通过串口显示信息。
2,加强c语言基础。内联函数,typedef函数指针的用法,typedef与define的区别,static函数。gcc关键字weak及alias等。

预计学习+写文档时间:3小时。

上次初看了U-boot,知道先从start.S开始执行,那么汇编程序最后又跳到哪个c程序入口点呢?
见:ldr pc, _start_armboot
对,去找arch\arm\lib中的board.c文件中的start_armboot函数吧!

瞄了一眼,有难度。于是乎,从此文件的首行开始看,因为他都有注释说明功能,而且比较简单。没过多久被下面这段卡住了
void inline __coloured_LED_init (void) {}
void coloured_LED_init (void) __attribute__((weak, alias("__coloured_LED_init")));
__attribute__ weak, alias都是什么意思呢?我之前都没看到过。

于是在gcc手册中搜索到了(提示:不会不要紧,学会学习的方法才重要,有问题查help,绝对是官方标准答案。)

The keyword __attribute__ allows you to specify special attributes when making adeclaration. This keyword is followed by an attribute specification inside double parentheses.
The following attributes are currently defined for functions on all targets: noreturn,196 Using the GNU Compiler Collection (GCC) noinline, always_inline, pure, const, nothrow, format, format_arg, no_instrument_function, section, constructor, destructor, used, unused, deprecated, weak, malloc,alias, warn_unused_result and nonnull. Several other attributes are defined for functions on particular target systems. Other attributes, including section are supported for variables declarations (see Section 5.32 [Variable Attributes], page 210) and for types (see
Section 5.33 [Type Attributes], page 215).
原来 __attribute__ 不是单独使用的,要用((keyword))双括号括起来的,里面的keyword才是要看的重点。

weak The weak attribute causes the declaration to be emitted as a weak symbol rather than a global. This is primarily useful in defining library functions which can be overridden in user code, though it can also be used with non-function declarations. Weak symbols are supported for ELF targets, and also for a.out argets when using the GNU assembler and linker.
原来这是适合于库函数的变量类型。它能被重写。这个功能比全局函数功能还好哦!

alias ("target")The alias attribute causes the declaration to be emitted as an alias for another ymbol, which must be specified. For instance,
void __f () { ; }
void f () __attribute__ ((weak, alias ("__f")));
declares ‘f’ to be a weak alias for ‘__f’. In C++, the mangled name for the arget must be used.
Not all target machines support this attribute.
这个alias就是起别名的意思。你可以叫我小*,也可以叫我APPLE。

看到这里基本问题都解决了,顺道再仔细学学inline关键字吧,平时不太用,有映像,就是为了避免占内存,所以用inline把函数搞像宏定义一般。但是有个条件inline内部代码要少,而且不能用for循环。我目前能想到的就这些。到网上去搜索吧!我要了解的更细致。

inline关键字参考如下:

介绍内联函数之前,有必要介绍一下预处理宏。内联函数的功能和预处理宏的功能相似。相信大家都用过预处理宏,我们会经常定义一些宏,如

#define TABLE_COMP(x) ((x)>0?(x):0) 就定义了一个宏。

  为什么要使用宏呢?因为函数的调用必须要将程序执行的顺序转移到函数所存放在内存中的某个地

址,将函数的程序内容执行完后,再返回到转去执行该函数前的地方。这种转移操作要求在转去执行前

要保存现场并记忆执行的地址,转回后要恢复现场,并按原来保存地址继续执行。因此,函数调用要有

一定的时间和空间方面的开销,于是将影响其效率。而宏只是在预处理的地方把代码展开,不需要额外

的空间和时间方面的开销,所以调用一个宏比调用一个函数更有效率。

  但是宏也有很多的不尽人意的地方。

  1、.宏不能访问对象的私有成员。

  2、.宏的定义很容易产生二意性。

  我们举个例子:

#define TABLE_MULTI(x) (x*x)

  我们用一个数字去调用它,TABLE_MULTI(10),这样看上去没有什么错误,结果返回100,是正确的,

但是如果我们用TABLE_MULTI(10+10)去调用的话,我们期望的结果是400,而宏的调用结果是

(10+10*10+10),结果是120,这显然不是我们要得到的结果。避免这些错误的方法,一是给宏的参数都

加上括号。

#define TABLE_MULTI(x) ((x)*(x))

  这样可以确保不会出错,但是,即使使用了这种定义,这个宏依然有可能出错,例如使用

TABLE_MULTI(a++)调用它,他们本意是希望得到(a+1)*(a+1)的结果,而实际上呢?我们可以看看宏的

展开结果: (a++)*(a++),如果a的值是4,我们得到的结果是5*6=30。而我们期望的结果是5*5=25,这

又出现了问题。事实上,在一些C的库函数中也有这些问题。例如: Toupper(*pChar++)就会对pChar执

行两次++操作,因为Toupper实际上也是一个宏。

  我们可以看到宏有一些难以避免的问题,怎么解决呢?

  下面就是用我要介绍的内联函数来解决这些问题,我们可以使用内联函数来取代宏的定义。而且事

实上我们可以用内联函数完全取代预处理宏。

  内联函数和宏的区别在于,宏是由预处理器对宏进行替代,而内联函数是通过编译器控制来实现的

。而且内联函数是真正的函数,只是在需要用到的时候,内联函数像宏一样的展开,所以取消了函数的

参数压栈,减少了调用的开销。你可以象调用函数一样来调用内联函数,而不必担心会产生于处理宏的

一些问题。

  我们可以用Inline来定义内联函数,不过,任何在类的说明部分定义的函数都会被自动的认为是内

联函数。

  下面我们来介绍一下内联函数的用法。

  内联函数必须是和函数体申明在一起,才有效。像这样的申明Inline Tablefunction(int I)是没

有效果的,编译器只是把函数作为普通的函数申明,我们必须定义函数体。

Inline tablefunction(int I) {return I*I};

  这样我们才算定义了一个内联函数。我们可以把它作为一般的函数一样调用。但是执行速度确比一

般函数的执行速度要快。

看到这里原来宏定义也会有问题,怪不得要用内联函数呢!

继续往下看代码
#if defined(CONFIG_ARM_DCC) && !defined(CONFIG_BAUDRATE)
#define CONFIG_BAUDRATE 115200
#endif
CONFIG_ARM_DCC我的source insight没有链接到应该是没定义,CONFIG_BAUDRATE链接到了超级多。不知道是哪个?看U-boot的这样比较大的工程应该还是有点技巧的。我需要在这方面突破一下找到方法。记录下,这是一个问题.

往下看
从static int init_baudrate (void)函数中的getenv_r ("baudrate", tmp, sizeof (tmp));调转到int getenv_r (char *name, char *buf, unsigned len)函数,具体看。接着看到for (i=0; env_get_char(i) != '\0'; i=nxt+1) 又跳转,跳了几层,没看懂,要全看懂看来要花时间的。暂时知道个大概,就是设置波特率。

往下看
static int display_banner (void)
{
 printf ("\n\n%s\n\n", version_string);
 debug ("U-Boot code: lX -> lX  BSS: -> lX\n",
        _armboot_start, _bss_start, _bss_end);
...}
_armboot_start, _bss_start, _bss_end好像很熟悉,原来是在u-boot.lds文件中定义的值。

往下看
static int display_dram_config (void)函数

看到此,增加static函数及static变量的复习
一、内部函数

  如果在一个源文件中定义的函数只能被本文件中的函数调用,而不能被同一源程序其它文件中的函

数调用,   这种函数称为内部函数。定义内部函数的一般形式是:   static   类型说明符   函数名

(形参表)   例如: static   int   f(int   a,int   b)   内部函数也称为静态函数。但此处静态

static   的含义已不是指存储方式,而是指对函数的调用范围只局限于本文件。   因此在不同的源文

件中定义同名的静态函数不会引起混淆。
一、静态变量(c和c++中都差不多)
static 是C++中很常用的修饰符,它被用来控制变量的存储方式和可见性,下面我将从 static 修饰符

的产生原因、作用谈起,全面分析static 修饰符的实质。 static 的两大作用: 一、控制存储方式:

   static被引入以告知编译器,将变量存储在程序的静态存储区而非栈上空间。    1、引出原因

:函数内部定义的变量,在程序执行到它的定义处时,编译器为它在栈上分配空间,大家知道,函数在

栈上分配的空间在此函数执行结束时会释放掉,这样就产生了一个问题: 如果想将函数中此变量的值保

存至下一次调用时,如何实现? 最容易想到的方法是定义一个全局的变量,但定义为一个全局变量有

许多缺点,最明显的缺点是破坏了此变量的访问范围(使得在此函数中定义的变量,不仅仅受此函数控

制)。    2、 解决方案:因此C++ 中引入了static,用它来修饰变量,它能够指示编译器将此变量

在程序的静态存储区分配空间保存,这样即实现了目的,又使得此变量的存取范围不变。 二、控制可

见性与连接类型 :    static还有一个作用,它会把变量的可见范围限制在编译单元中,使它成为一

个内部连接,这时,它的反义词为”extern”.    Static作用分析总结:static总是使得变量或对

象的存储形式变成静态存储,连接方式变成内部连接,对于局部变量(已经是内部连接了),它仅改变

其存储方式;对于全局变量(已经是静态存储了),它仅改变其连接类型。 类中的static成员: 一、

出现原因及作用:    1、需要在一个类的各个对象间交互,即需要一个数据对象为整个类而非某个

对象服务。    2、同时又力求不破坏类的封装性,即要求此成员隐藏在类的内部,对外不可见。  

  类的static成员满足了上述的要求,因为它具有如下特征:有独立的存储区,属于整个类。

往下看,看到一段有用的注释
 * The requirements for any new initalization function is simple: it
 * receives a pointer to the "global data" structure as it's only
 * argument, and returns an integer return code, where 0 means
 * "continue" and != 0 means "fatal error, hang the system".
 说明了下面用的一些数据结构是global data"。

往下看
typedef int (init_fnc_t) (void);
如何typedef A B就是用B代替A。但是这里面好像没有B?
网上查了下该定义是声明init_fnc_t是函数类型,该函数返回整型值。突然想起了函数指针。于是再仔

细复习下。参考如下:
typedef 行为有点像 #define 宏,用其实际类型替代同义字。

 不同点:typedef 在编译时被解释,因此让编译器来应付超越预处理器能力的文本替换。

用法一:

typedef int (*MYFUN)(int, int);
这种用法一般用在给函数定义别名的时候
上面的例子定义MYFUN 是一个函数指针, 函数类型是带两个int 参数, 返回一个int

在分析这种形式的定义的时候可以用下面的方法:
先去掉typedef 和别名, 剩下的就是原变量的类型.
去掉typedef和MYFUN以后就剩:
int (*)(int, int)

用法二:

typedef给变量类型定义一个别名.

typedef struct{
int a;
int b;
}MY_TYPE;

这里把一个未命名结构直接取了一个叫MY_TYPE的别名, 这样如果你想定义结构的实例的时候就可以这

样:
MY_TYPE tmp;

第二种用法:typedef 原变量类型 别名

typedef补充内容:

例如:

  typedef int (*PF) (const char *, const char *);
  这个声明引入了 PF 类型作为函数指针的同义字,该函数有两个 const char * 类型的参数以及一

个 int 类型的返回值。

简单的函数指针的用法

//形式1:返回类型(*函数名)(参数表)

char(*pFun)(int);

char glFun(int a){return;}

void main()

{

pFun =glFun;

(*pFun)(2);

}

第一行定义了一个指针变量pFun.它是一个指向某种函数的指针,这种函数参数是一个int类型,返回值是char类型。只有第一句我们还无法使用这个指针,因为我们还未对它进行赋值。

第二行定义了一个函数glFun().该函数正好是一个以int为参数返回char的函数。我们要从指针的层次上理解函数-函数的函数名实际上就是一个指针,函数名指向该函数的代码在内存中的首地址。

使用typedef更直接

typedef char(*PTRFUN)(int)

PTRFUN pFun;

char glFun(int a){return;}

void main()

{

pFun = glFun;

(*pFun)(2);

}

typedef的功能是定义新的类型。第一句就是定义了一种PTRFUN的类型,并定义这种类型为指向某种函数的指针,这种函数以一个int为参数并返回char类型。

下面是个例子:

在typedef的使用中,最麻烦的是指向函数的指针,如果没有下面的函数,你知道下面这个表达式的定义以及如何使用它吗?

 int (*s_calc_func(char op))(int, int);

如果不知道,请看下面的程序,里面有比较详细的说明

 // 定义四个函数

int add(int, int);

int sub(int, int);

int mul(int, int);

int div(int, int);

// 定义指向这类函数的指针

typedef int (*FP_CALC)(int, int);

// 我先不介绍,大家能看懂下一行的内容吗?

int (*s_calc_func(char op))(int, int);

// 下一行的内容与上一行完全相同,

// 定义一个函数calc_func,它根据操作字符 op 返回指向相应的计算函数的指针

FP_CALC calc_func(char op);

// 根据 op 返回相应的计算结果值

int calc(int a, int b, char op);

int add(int a, int b)

{

    return a + b;

}

int sub(int a, int b)

{

    return a - b;

}

int mul(int a, int b)

{

    return a * b;

}

int div(int a, int b)

{

    return b? a/b : -1;

}

// 这个函数的用途与下一个函数作业和调用方式的完全相同,

// 参数为op,而不是最后的两个整形

int (*s_calc_func(char op)) (int, int)

{

    return calc_func(op);

}

FP_CALC calc_func(char op)

{

    switch (op)

    {

    case '+': return add;

    case '-': return sub;

    case '*': return mul;

    case '/': return div;

    default:

        return NULL;

    }

    return NULL;

}

int calc(int a, int b, char op)

{

     FP_CALC fp = calc_func(op); // 下面是类似的直接定义指向函数指针变量

       // 下面这行是不用typedef,来实现指向函数的指针的例子,麻烦!

        int (*s_fp)(int, int) = s_calc_func(op);

        // ASSERT(fp == s_fp); // 可以断言这俩是相等的

      if (fp) return fp(a, b);

     else return -1;

}

void test_fun()

{

    int a = 100, b = 20;

    printf("calc(%d, %d, %c) = %d\n", a, b, '+', calc(a, b, '+'));

    printf("calc(%d, %d, %c) = %d\n", a, b, '-', calc(a, b, '-'));

    printf("calc(%d, %d, %c) = %d\n", a, b, '*', calc(a, b, '*'));

    printf("calc(%d, %d, %c) = %d\n", a, b, '/', calc(a, b, '/'));

}

运行结果

   calc(100, 20, +) = 120

   calc(100, 20, -) = 80

   calc(100, 20, *) = 2000

   calc(100, 20, /) = 5
 这本文章写的不错。我有空要实践一下。

往下看。终于,看到重点了。
init_fnc_t *init_sequence[] = {
#if defined(CONFIG_ARCH_CPU_INIT)
 arch_cpu_init,  
#endif
 board_init,  
#if defined(CONFIG_USE_IRQ)
 interrupt_init,  
#endif
 timer_init,  
#ifdef CONFIG_FSL_ESDHC
 get_clocks,
#endif
 env_init,  
 init_baudrate,  
 serial_init,  
 console_init_f,  
 display_banner,  
#if defined(CONFIG_DISPLAY_CPUINFO)
 print_cpuinfo,  
#endif
#if defined(CONFIG_DISPLAY_BOARDINFO)
 checkboard,  
#endif
#if defined(CONFIG_HARD_I2C) || defined(CONFIG_SOFT_I2C)
 init_func_i2c,
#endif
 dram_init,  
#if defined(CONFIG_CMD_PCI) || defined (CONFIG_PCI)
 arm_pci_init,
#endif
 display_dram_config,
 NULL,
};
里面的一些CONFIG_都是在自己的开发板.h中定义的,也就是include/config中自己的开发板.h文件中

。用宏定义做开关,我又学到了一点,用起来应该是很方便的。
其中timer_init是在\arm\cpu\arm920t\s3c24x0文件夹下的timer.c中。
其中interrupt_init 是在\arch\arm\lib文件夹下的interrupts.c中。
其中board_init是在\board\samsung\开发板名文件夹下的开发板名.c中。
其中serial_init是在drivers\serial文件夹下的serial_s3c24x0.c中。
但都是我自己对号入座的,他有很多文件都包含这些函数,我不知道他真正的调用情况,我要怎么才能搞清楚他的调用情况?是不是它有什么flag,我遗漏了。这是问题

往下看
#if !defined(CONFIG_SERIAL_MULTI)

int serial_init(void)
{
 return serial_init_dev(UART_NR);
}
#endif

往下看
void start_armboot (void)函数,是汇编跳到c的主函数。里面都是什么环境变量,不太懂。先跳过,不仔细看了。重点就在这里
 for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
  if ((*init_fnc_ptr)() != 0) {
   hang ();
  }
 }
对应之前记录的数据结构,执行各种初始化的函数。

我今天要初始化串口。那么就先看到这里就够了。

今天还有一个问题,就是我对于连接.h头文件不太清楚。我总觉得此文件的中包含的头文件太少了。先记录下。

接着要开始根据自己对代码的理解动手修改源码了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值