嵌入式Linux——写jz2440BootLoader的第一阶段代码

2019-07-12 23:32发布

简介:

        本文主要介绍为JZ2440编写BootLoader来引导和启动Linux系统。本文为BootLoader的第一阶段,即对开发板硬件环境进行初始化来为第二阶段的引导内核和启动内核做准备。

声明:

        本文主要是看了韦东山老师的视频后,所写的课程总结。希望对你有所帮助。

介绍内存分配:

        我想很多人在看u-boot或者在了解BootLoader时,都会被内存地址问题搞得晕头转向,从而在了解BootLoader的路上越学越不明白,越学越没有意思,最后放弃对这部分的学习。其实我想说的是这部分并不是那么困难,这里我们只要分清楚nand flash中内存是怎么分配的,以及SDRAM中在哪里存放从nand flash中读到的数据这两个问题就可以了。当我们对这些知识了解了再去看BootLoader或者更确切的说是u-boot的源代码的时候,你就会发现其实u-boot也不是很难看懂。         下面我们就先看看nand中的分区:         从上图我们可以看出在nand中各个分区的大小,他们的起始地址,以及他们所代表的名称。而在我们烧写BootLoader程序到nand中时,kernel和rootfs中的内容应该已经烧好。同时kernel的大小应该不超过2M。         好了,上面我们规定好了nand中的分区,那么我们还要说明的就是在SDRAM中对于数据的安排。这里我们只设置一些基本的数据:         在SDRAM中我们并没有详细的规定一些参数的大小,例如TAG参数,这是因为这部分参数是随着我们对不同TAG数据的设置他的大小也不一定相同。所以我们看这里只有TAG数据的起始地址,然后在此基础上放数据,从而使得地址依次增加。而我们的2440会习惯性的将kernel的地址放到0x30008000的位置,而这里放的是kernel的真实数据。同时我们要说明的是很多时候我们在向内核或者向nand中烧写的是kernel的印象文件——uImage,而这个uImage是由一个kernel的头部和kernel的真实数据即kernel组成的(这样说可能有些绕口,希望大家可以理解)。 uImage结构:         而关于uImage的详细信息我会在后面用到时详细说明。

BootLoader第一阶段代码:

        其实我们都知道,最简单的BootLoader就是一个单片机程序,只不过这个单片机程序有点大,他集合了我们所学习的其他各个分离的功能,而将这些功能整合到这个整体中来实现启动内核的目的这就是BootLoader了。所以我们在写BootLoader程序的时候会用到其他已经完成的程序。同时我们要清楚BootLoader的目的是从nand中读出内核并启动内核,而我们要从nand中读写数据就要对nand进行初始化,所以我们第一阶段的代码主要做的就是对硬件进行初始化。同时CPU不同或者单板不同,对硬件初始化这部分的代码会有一些差异。而我们根据前面对2440单板的裸板操作程序将BootLoader的第一阶段分为以下几步(当然你可以加更多的步骤,因为步骤越多功能将会越多,那么这个BootLoader也会越好用,但是有优点就一定会有缺点,步骤多的缺点就是他的移植性很差,如果想移植到其他的单板将会做很多的修改),这里我们为了可以适合更多的板子,将一些必须的步骤写下来,而其他的复杂功能我们就不细说。下面我们BootLoader的基本步骤为: 1. 关看门狗 2. 设置CPU模式 3. 关中断,关mmu,关cache 4. 设置时钟 5. 初始化SDRAM 6. 初始化nand(由于我们在初始化nand时会使用到C函数,所以这里要先设置好栈) 7. 当BootLoader的代码比较大时(nand中的4K空间不足以放下整个BootLoader程序),我们会将BootLoader的程序重定位到SDRAM中 8. 最后我们还会清bss段。  

1. 关看门狗

        下面我将对上面的每一步进行说明,首先我们说明一下关看门狗,这里我为什么要将关看门狗放到第一个的位置。而在u-boot中这一步会相对靠后一些那?这要根据人的性格以及对看门狗的理解的不同,因此会在不同的位置安排关闭看门狗。而我为什么会选择在一开始就关闭看门狗那?这是一个比较保守的方式,因为我们从2440  的芯片手册中看到当单板在开机128个PCLK后还没有关闭看门狗的话,那么我们的单板将会重启。而为了防止这种事情发生,所以我在一开始的位置就关闭了看门狗,从而防止了这种可能性的发生。而关闭看门狗的汇编代码为: #define WTCON 0x53000000 ldr r0, =WTCON ldr r1, #0 str r1, [r0]         从上面看,其实就是向地址为0x53000000的寄存器中写入数值0。

2. 设置CPU模式为管理模式

        下面我们就要讲第二步设置CPU模式,我们通过设置2440程序状态寄存器(CPSR)中的第0~4位来决定CPU在什么样的模式下工作,而不同的模式会有不同的权限。而要设置CPU为管理模式,也相当于是特权模式。 M[4:0] 处理器模式 ARM模式可访问的寄存器 THUMB模式可访问的寄存器 0b10000 用户模式 PC,CPSR,R0~R14 PC,CPSR,R0~R7,LR,SP 0b10001 FIQ模式 PC,CPSR,SPSR_fiq,R14_fiq~R8_fiq,R0~R7 PC,CPSR,SPSR_fiq,LR_fiq,SP_fiq,R0~R7 0b10010 IRQ模式 PC,CPSR,SPSR_irq,R14_irq~R13_irq,R0~R12 PC,CPSR,SPSR_irq,LR_irq,SP_irq,R0~R7 0b10011 管理模式 PC,CPSR,SPSR_svc,R14_svc~R13_svc,R0~R12 PC,CPSR,SPSR_svc,LR_svc,SP_svc,R0~R7 0b10111 中止模式 PC,CPSR,SPSR_abt,R14_abt~R13_abt,R0~R12 PC,CPSR,SPSR_abt,LR_abt,SP_abt,R0~R7 0b11011 未定义模式 PC,CPSR,SPSR_und,R14_und~R13_und,R0~R12 PC,CPSR,SPSR_und,LR_und,SP_und,R0~R7 0b11111 系统模式 PC,CPSR,R0~R14 PC,CPSR,LR,SP,R0~R74         同时2440 的程序状态寄存器为: 31 30 29 28     27 ~ 8   7 6 5 4 3 2 1 0 N Z C V 保留 I F T M4 M3 M2 M1 M0                                     N Negative/Less Than           I   IRQ disable Z Zero                   F   FIQ disable C Carry/Borrow/Extend           T   State bit   V Overflow                 M0~4 Mode bits           如果你要了解更多关于2440程序状态寄存器的信息,你可以读2440 的芯片手册第二章关于程序模型的描述。如果你不想了解这么详细,你也可以在网上搜程序状态寄存器,一定有你想要的答案。同样你可能会问我为什么要这样的安排。为什么要将设置CPU模式放到第二位?这个其实也是与我的性格相关,我喜欢先将单板正式运行前的准备工作做好。做好这些准备工作再做其他的工作的时候你就会更放心了。同时也为后面程序出错时检查错误打好基础(我们谁都不能保证自己写的程序不会有错误)。而细心的人就会发现,其实我BootLoader的前几步都是在做准备工作,来为单板在后面可以正常的工作做铺垫。而设置CPU为系管理模式的汇编代码为: //1. 进入管理模式 mrs r0, cpsr bic r0, r0, #0x1f orr r0, r0, #0xd3 msr cpsr, r0         这里用到了ARM汇编中的mrs指令,即从程序状态寄存器中读出数值到某个寄存器中。同时使用bic指令来对指定位清零,用orr指令将向对应的位写入1 。最后再使用msr指令将寄存器中的值写入到程序状态寄存器中。其实说的更明白一点就是对程序状态寄存器中的指定位进行操作。

3. 关中断,关mmu,关cache

        第三步,继续做我们的准备工作,我先来说为什么要关中断。我相信大家都知道中断的功能就是暂停当前正在进行的程序去执行中断处理函数中的程序,当处理完中断函数后再回到原来的程序中去完成当前的程序。而中断函数在我们平时的程序应用中非常的普遍,也非常有用。但是我想说的是在BootLoader中是不需要中断的。我们只想让单板将kernel映像文件读入到内存中,并跳到kernel的入口地址中去启动内核。因为我们谁都不想程序正在很好运行的时候因为某些原因而进入到中断函数中。所以在这里我们要关中断。关中断的代码为: #define INTMSK (*(volatile unsigned long *)0x4A000008) #define INTSUBMSK (*(volatile unsigned long *)0x4A00001c) ldr r0, =INTMSK ldr r1, =0xffffffff str r1, [r0] ldr r0, =INTSUBMSK ldr r1, =0xFFFF str r1, [r0]         上面程序中我们关中断,其实就是向中断屏蔽寄存器中写1来屏蔽各个中断。上面程序中我们也屏蔽了子中断,个人感觉这部分代码有没有都可以,因为我们从下图中可以看出:         如果中断屏蔽后,子中断无法到达中断服务程序中所以也就被屏蔽了。不过加上这段代码也可以更加的保险。所以我还是留下他了。         而关闭mmu是因为我们现在用到的是物理地址还没有实现代码的重映射,同时我们现在也不需要使用虚拟地址来实现对内存的扩充。我们使用MMU确实是想用到页表来实现对内存地址的扩充。但是我们在BootLoader中只使用物理地址,并用不到虚拟地址。所以我们要关闭mmu。         至于关cache,其实这里有两种cache,一种是指令cache即Icache,而另一种是数据cache即Dcache。这里其实我们所要关的是Dcache,同时我想先介绍一下cache的功能。在我们的单板上分别有16k的Icache和16k的Dcache。当然了我们可以将他们同样的理解为一块内存,只不过CPU从这块内存上读写的速度会比其他的SDRAM或者flash要快的多。我们先在脑中有这样的映像。         我在这里打个比方CPU要从内存中读数据,假如我们内存中有100条数据,而每条数据从内存读到CPU要一秒钟,我们CPU从内存中读数据是一条一条的读数据,那么读上述数据一共使用了100秒的时间。而当我们使用Dcache就不一样了。Dcache从内存中读数据是一块一块的数,也就是说很多条数据一起读,那么我们假设每块有十条数据,而Dcache每读一块的时间为3秒钟,同时由于从Dcache到CPU的数据传输很快,那么传输完一块所用的时间也就是2秒钟,这样传输完100条数据所用的时间为50秒。这样我们就可以省下50秒的时间了。而在现实中,我们的Dcache要比我所描述的速度更快,所以我们就会省下更多的时间了。         上面说的cache这么好,那么我们为什么要关掉Dcache那?我想细心的读者已经看出,我们上面所说的Dcache是帮助CPU与内存(或者说的具体点就是SDRAM)之间实现数据的快速传输的。但是在这里我们还没有初始化内存,同样我们也没有设置栈,以及代码的重定位。所以在这里使用Dcache,是会出错的。         既然这里我们不能开Dcache,那么我们是否可以开Icache那?是的这里我们是可以开Icache的。我们可以理解为这样。当我们没有开Icache时,我们的CPU寻找指令是要到内存中寻找的。而当我们开了Icache后,他是先从Icache中寻找是否有这个指令。如果有则从Icache中直接调用这个命令。而如果没有则从内存中找这个命令调用,同时将这个命令放到Icache中。同样从Icache中读取指令要比内存中读取指令要快的多。所以开启Icache可以让我们的程序运行的更快。这也是我们在使用Icache提升BootLoader运行速度的一个有效方法。而关于关mmu,关Dcache和开Icache的代码为: /* * disable MMU stuff and caches */ mrc p15, 0, r0, c1, c0, 0 bic r0, r0, #0x00002300 @ clear bits 13, 9:8 (--V- --RS) bic r0, r0, #0x00000087 @ clear bits 7, 2:0 (B--- -CAM) orr r0, r0, #0x00000002 @ set bit 2 (A) Align orr r0, r0, #0x00001000 @ set bit 12 (I) I-Cache mcr p15, 0, r0, c1, c0, 0         在上面我们知道mrs是读取程序状态寄存器的值并将其存到寄存器中,而这里的mrc就是读取协处理器中指定位置的指令。而具体关于该指令的操作就要看2440的芯片手册或者去网上搜一下了。同样mcr就是将指定寄存器中的值写入到协处理器中。

4. 设置时钟

        我们的单板刚上电时的速率为晶振的速率即12MHz,而我们要想让单板运行的更快就要设置时钟了。我们可以通过设置MPLL寄存器来控制输出的速度。通过提速,CPU的速率可以达到400MHz,而芯片手册中也为我们详细的介绍了要提速到400MHz所要设置的参数,如下图:         这里特别要强调的一点是,在设置时钟时,由于我们的HDIVN设置为非0,所以我们要修改协处理器,将默认的fast bus mode改为asynchronous bus mode(这个是芯片厂商规定的,我也不知道为什么要这么做)。而他相应的汇编代码为: mrc p15,0,r0,c1,c0,0 orr r0,r0,#R1_nF:OR:R1_iA mcr p15,0,r0,c1,c0,0         而我们整个的设置时钟的汇编代码为: #define CLKDIVN 0x4c000014 #define MPLLCON 0x4c000004 #define S3C2440_MPLL_200MHZ ((0x5c<<12)|(0x01<<4)|(0x02)) //3. 设置时钟 ldr r0, =CLKDIVN ldr r1, =0x03 str r1, [r0] mrc p15, 0, r1, c1, c0, 0 /* 读出控制寄存器 */ orr r1, r1, #0xc0000000 /* 设置为“asynchronous bus mode” */ mcr p15, 0, r1, c1, c0, 0 /* 写入控制寄存器 */ ldr r0, =MPLLCON ldr r1, =S3C2440_MPLL_200MHZ str r1, [r0]         在这里我们首先要设置CPU中FCLK,HCLK以及PCLK的预分频系数。上面程序中他们的比例为4:2:1 。即FCLK为200MHz,HCLK为100MHz,而PCLK为50MHz。这之后我们就要设置MPLLCON寄存器来将FCLK提升到200MHz了。而具体提升个公式为:         所以我们这里的代码主要是设置MPLLCON寄存器中MDIV,PDIV和SDIV的值就可以了。

5. 初始化SDRAM

        前面的那么多铺垫工作都做好了之后,这里就要讲到我们的重点了,因为我们知道要想启动内核就要先将内核从flash中读到内存,而内核是在内存中运行的。所以这里我们一定要设置内存,让他为下面的工作做好准备,即我们要初始化内存。而在初始化内存之前我们要先介绍一下板子上的内存是什么,也就是说板子上的内存的特性我们要明白。         首先看我们开发板上SDRAM的电路图:         从上图中我们看出,这两个分离的SDRAM组合成一个SDRAM,这个合成的SDRAM的数据宽度为32位。他有2*13条地址线,因为我们知道CPU是可以直接对SDRAM进行读写操作的。所以CPU要通过这些地址线访问到SDRAM中的每一位地址。所以通过这里的数据线的条数我们知道这个SDRAM的内存大小为2^26=64M。下面我们就要讨论另一个十分重要的问题:我们的SDRAM接到了那里?         从2440的芯片手册中我们了解到:         从上面我们知道SDRAM接到不同的内存映射上,内存的基地址就会不一样。那么我们的SDRAM接到了那里那?我们仔细看2440的电路图会发现在片选引脚LnGCS6上接有SDRAM的片选引脚,所以我们的起始引脚为0x30000000 。         而我们知道SDRAM的内存大小为64M,所以内存的起始地址和终止地址分别是:         好了,有了上面这些知识我们再来看初始化代码就简单了很多,初始化SDRAM的汇编代码为: #define MEM_CTL_BASE 0x48000000 //4. 设置存储控制器,初始化SDRAM ldr r0, =MEM_CTL_BASE adrl r1, mem_cfg_val add r2, r0, #(13*4) 1: ldr r3, [r1], #4 str r3, [r0], #4 cmp r0, r2 bne 1b mem_cfg_val: @ 存储控制器13个寄存器的设置值 .long 0x22011110 @ BWSCON .long 0x00000700 @ BANKCON0 .long 0x00000700 @ BANKCON1 .long 0x00000700 @ BANKCON2 .long 0x00000700 @ BANKCON3 .long 0x00000700 @ BANKCON4 .long 0x00000700 @ BANKCON5 .long 0x00018005 @ BANKCON6 .long 0x00018005 @ BANKCON7 .long 0x008C07A3 @ REFRESH .long 0x000000B1 @ BANKSIZE .long 0x00000030 @ MRSRB6 .long 0x00000030 @ MRSRB7         而从上面看我们这里主要就是将13个寄存器中的值分别写入到对应的寄存器中,从而实现对整个单板中内核的控制。

6. 初始化nand

        我在初始介绍第一阶段步骤的时候就说了,由于初始化nand 相对于前面的步骤来说太过的复杂,同时调用的函数很多,如果我们用汇编代码来写这部分,那么势必会使得代码过于的庞大,且不容易理解。最主要的是这样做会花费太多的精力,所以这是得不偿失的。因此我们用C语言来完成这部分代码。同时我们也知道在嵌入式中写C语言的函数之前是要先初始化栈的。不然我们都不知道自己的程序在哪里运行,这是十分危险的事情。所以在对nand初始化前,我们先设置栈指针。我们知道栈是向下生长的。所以我们要设置栈顶地址尽量的高。这里我们选择SDRAM的顶端0x34000000作为栈顶。         接下来我们就要对nand进行初始化了。同样我们在初始化nand之前要先对板子上的nand有所了解,才能对其进行初始化。我们先看在电路图中nand的连接方式:         从图中可以看出他只有8条数据线,所以我们就知道了nand是地址,命令与数据公用这8条线。而要区分是命令,地址还是数据,就要看ALE和CLE引脚电平的高低了。不过2440中集成了nand控制器,所以相对来说这部分代码会简单些,因为我们只要对nand控制器的寄存器进行控制就可以了。同样我们也知道nand的初始化以及读写函数比较复杂,所以我们如果要详细的了解单板的nand就要看他的芯片手册了。而我之前在写nand驱动的时候对nand硬件做了介绍。你可以通过下面文章了解: 嵌入式Linux——nand flash 驱动开发(一):硬件介绍         而关于nand初始化的代码为: //5. nand flash 初始化 ldr sp, =0x34000000 @ 设置堆栈 bl nand_init /* 初始化NAND Flash */ void nand_init(void) { #define TACLS 0 #define TWRPH0 3 #define TWRPH1 0 /* 设置时序 */ s3c2440nand->NFCONF = (TACLS<<12)|(TWRPH0<<8)|(TWRPH1<<4); /* 使能NAND Flash控制器, 初始化ECC, 禁止片选 */ s3c2440nand->NFCONT = (1<<4)|(1<<1)|(1<<0); } /* 复位NAND Flash */ nand_reset(); } /* 复位 */ static void nand_reset(void) { s3c2440_nand_select_chip(); s3c2440_write_cmd(0xff); // 复位命令 s3c2440_wait_idle(); s3c2440_nand_deselect_chip(); }         由于初始化nand的代码太多,放到这里会影响文章的可读性。所以我省略了一些。如果你想要关于nand初始化的代码,或者BootLoader的代码,你可以私信我。

7.  重定位代码

        这里我们就要讲解代码的重定位了。但是我要强调的一点是,代码重定位并不是必须要做的,只要你的BootLoader代码小于nand中4k的stepingstone,那么你就可以不用代码重定位。我们都知道代码的重定位其实也是费时费力的。所以有时候我们会尽量的将代码的体积缩小,这样既少了代码重定位这一步,同时也符合嵌入式系统对短小简练的要求。而这里我们还是要介绍这步,因为他很重要,同时也是因为我们的BootLoader代码很容易就超出了4k。下面我们就要问了,既然我们要将代码重定位,要将代码定位到哪里哪?同时可能还要问我们从哪里开始复制代码那?这确实是我们想要知道的答案。同时我们要记住一点,如果我们要做与数据转移相关的操作,我们就要问: 1. 我们从哪里转移? 2. 转移到哪里去? 3. 转移的数据长度?         现在我们来一一回答。代码执行过程是这样的,CPU会将nand的前4k代码读到SDRAM中,然后从SDRAM中一条一条的获得指令来执行。而当BootLoader的代码超过4k的时候从nand读到SDRAM中的代码就不完整了。所以这个时候我们才会进行代码的重定位工作。既然我们知道了CPU会将代码读到SDRAM中,那么我们重定位的时候同样要将代码复制到SDRAM中,至于是SDRAM中的那个地址,这就可以根据个人的情况和喜好了,你可以放到任意一个位置,只要他不影响你后面对SDRAM内存的安排,而我们将他放到了SDRAM的顶端,我们在顶端位置留下1M的空间用于存放BootLoader。如下面中红 {MOD}方框所示:         而在代码中我们的位置并不是上面表示的0x33f00000,而是0位置,这是为什么那?这就要看我们的连接文件了,我们在连接文件中可以看到: SECTIONS { . = 0x33f00000; .text : { *(.text) } . = ALIGN(4); .rodata : {*(.rodata*)} . = ALIGN(4); .data : { *(.data) } . = ALIGN(4); __bss_start = .; .bss : { *(.bss) *(COMMON) } __bss_end = .; }         从上面我们知道连接地址为0x33f00000,那么我们的程序就应该位于0x33f00000这个位置。而我们在代码中的0就是一个相对0x33f00000的一个偏移量。所以我们其实就是将程序重新定位到了0x33f00000这个位置。我想现在我们就知道了代码要复制到哪里了吧。         而从哪里复制代码我在上面已经说得很清楚了——从nand的开始位置。现在我们已经设置好了栈并将当前程序的指针放到了SDRAM中,所以我们现在再要找到nand的开始地址似乎不是一个容易的事情,那么现在我们应该去哪里找这个开始复制代码的位置那?这里我们就要说明一个很巧的方法,我们会在代码开始的位置使用代码: .text .global _start _start:         标识出代码开始的位置,既然我们找不到nand的开始位置,那么我们是否可以从这个_start的位置开始复制代码那?是的,我们就是使用这种方法来确定代码开始位置的。         前面两个问题我们都讲完了,下面我们来讲解第三个问题:转移的数据长度。这里其实我们要转移的就是整个代码段的长度,我们在第二个问题的时候已经知道了我们代码段开始的位置是_start处,那么我们知道代码段的结束位置不就知道了整个代码段的长度了吗。现在我们要问的就是代码段结束的位置在哪里?这个确实不好判断。这时我们就要再看一下我们的连接文件了,连接文件中我们可以看到bss段的开始位置为__bss_start,而我们的代码段,只读数据段和数据段就在__bss_start前面,而__bss_start后的bss段是将静态变量和全局变量初始化为0的位置。所以不包括在我们重定位的范围。所以在这里我们就知道了代码结束的位置其实就是__bss_start处。所以转移的数据长度为__bss_start - _start。         而重定位的代码以及相关的函数为: //6. 重定位代码 ldr r0, #0 @ 1. 目标地址 = 0x30000000,这是SDRAM的起始地址 ldr r1, _start @ 2. 源地址 ldr r2, =__bss_start @ 3. 复制长度 = 1M,对于本实验,这是足够了 sub r2, r2, r1 bl CopyCode2SDRAM @ 调用C函数CopyCode2SDRAM int CopyCode2SDRAM(unsigned char *buf, unsigned long start_addr, int size) { s3c2440_nand_reset(); if(is_nand_flash()){ nand_read(buf, start_addr, size); }else{ memcpy(buf,start_addr,size); } return 0; } /* 读函数 */ void nand_read(unsigned char *buf, unsigned long start_addr, int size) { int i, j; #ifdef LARGER_NAND_PAGE if ((start_addr & NAND_BLOCK_MASK_LP) || (size & NAND_BLOCK_MASK_LP)) { return ; /* 地址或长度不对齐 */ } #else if ((start_addr & NAND_BLOCK_MASK) || (size & NAND_BLOCK_MASK)) { return ; /* 地址或长度不对齐 */ } #endif /* 选中芯片 */ nand_select_chip(); for(i=start_addr; i < (start_addr + size);) { /* 发出READ0命令 */ write_cmd(0); /* Write Address */ write_addr(i); #ifdef LARGER_NAND_PAGE write_cmd(0x30); #endif wait_idle(); #ifdef LARGER_NAND_PAGE for(j=0; j < NAND_SECTOR_SIZE_LP; j++, i++) { #else for(j=0; j < NAND_SECTOR_SIZE; j++, i++) { #endif *buf = read_data(); buf++; } } /* 取消片选信号 */ nand_deselect_chip(); return ; }         这里我们需要各位读者确认的一点是,你的nand是大页还是小页,如果你是大页那么你的nand每页有2k的数据空间。而如果你的nand为小页那么你的nand每页只有512Byte的数据空间。所以我们在上面的程序中用宏来标识自己是大页还是小页。

8. 清bss段。

        最后就要清bss段了,关于这部分的知识我们在上面已经说了,即我们在程序中会有没有初始化的静态变量和全局变量,这里我们就会将这些变量初始化为0。所以我们知道了,如果我们在程序中没有对静态变量或者全局变量进行初始化,那么不用担心。编译器会帮我们将这些变量设置为0 。         清bss的代码为: bl clear_bss void clean_bss(void) { extern int __bss_start, __bss_end; int *p = &__bss_start; for (; p < &__bss_end; p++) *p = 0; }         上面程序中的__bss_start和__bss_end就是bss段的起始和终止地址了。         好了讲到这里我们BootLoader的第一阶段代码就讲解完成了,大家可以看出这里主要都是对2440的一些硬件的初始化和设计。而至于跳转到内核就要到第二部分了。