u-boot-1.1.6源码分析

news/2024/11/29 20:45:55/
P { margin-bottom: 0.21cm; }A:link { }CODE.ctl { font-family: "Lohit Hindi",monospace; }

u-boot-1.1.6源码分析

15年11月1日15:22:59

想要分析一个大的程序是从哪一个文件开始执行的,首先是分析它的Makefile,当然也可以采取一个取巧的办法,将编译过的uboot.bin文件删除,然后从新make,从它的最后的输出信息里面找到 arm-linux-ld链接命令,如下所示:


cd /home/ybx/u-boot-1.1.6 && arm-linux-ld -Bstatic -T /home/ybx/u-boot-1.1.6/board/100ask24x0/u-boot.lds -Ttext 0x33F80000 $UNDEF_SYM cpu/arm920t/start.o .......


这样就可以轻松找到第一个执行文件,当然,对于uboot来说,没啥必要~~~


对于以下内容,所有的源代码都有灰色阴影,我所添加的注释没有阴影,对于2410里面配置的宏用红色字体标出,对于代码和注释,始终坚持代码在前,注释在后的潜规则(~.~!)严格执行~~~


1)那么就来分析这个文件:/cpu/arm920t/start.S


#include

#include


.globl _start

_start: b reset

ldr pc, _undefined_instruction

ldr pc, _software_interrupt

ldr pc, _prefetch_abort

ldr pc, _data_abort

ldr pc, _not_used

ldr pc, _irq

ldr pc, _fiq


_undefined_instruction: .word undefined_instruction

_software_interrupt: .word software_interrupt

_prefetch_abort: .word prefetch_abort

_data_abort: .word data_abort

_not_used: .word not_used

_irq: .word irq

_fiq: .word fiq


.balignl 16,0xdeadbeef


/*

*************************************************************************

*

* Startup Code (reset vector)

*

* do important init only if we don't start from memory!

* relocate armboot to ram

* setup stack

* jump to second stage

*

*************************************************************************

*/


_TEXT_BASE:

.word TEXT_BASE


.globl _armboot_start

_armboot_start:

.word _start


/*

* These are defined in the board-specific linker script.

*/

.globl _bss_start

_bss_start:

.word __bss_start


.globl _bss_end

_bss_end:

.word _end


/* 这一段代码是程序的起始阶段,设置程序的中断向量表。关于这个中断向量表,我有一篇博客里面仔细分析了。一般的uboot都会将中断关掉的。那我们就直接跳到reset这个标号那里继续执行。 */


2)这个resetstart.S中的一个标号,程序继续在/cpu/arm920t/start.S中执行:


/*

* the actual reset code

*/


reset:

/*

* set the cpu to SVC32 mode

*/

mrs r0,cpsr

bic r0,r0,#0x1f

orr r0,r0,#0xd3

msr cpsr,r0


/* cpu设置成管理模式,至于为什么设置成管理模式,可以参见《Ubootstart.S源码的指令级的详尽解析》 那篇文章。 */


/* turn off the watchdog */

#if defined(CONFIG_S3C2400)

# define pWTCON 0x15300000

# define INTMSK 0x14400008 /* Interupt-Controller base addresses */

# define CLKDIVN 0x14800014 /* clock divisor register */

#elif defined(CONFIG_S3C2410)

# define pWTCON 0x53000000

# define INTMSK 0x4A000008 /* Interupt-Controller base addresses */

# define INTSUBMSK 0x4A00001C

# define CLKDIVN 0x4C000014 /* clock divisor register */

#endif


/* 根据不同的宏来分别设置寄存器的地址,因为uboot兼容很多单板,这个技巧很常用。 */


#if defined(CONFIG_S3C2400) || defined(CONFIG_S3C2410)

ldr r0, =pWTCON

mov r1, #0x0

str r1, [r0]


/* 关闭看门狗,往寄存器里面写0即可。 */


/*

* mask all IRQs by setting all bits in the INTMR - default

*/

mov r1, #0xffffffff

ldr r0, =INTMSK

str r1, [r0]

# if defined(CONFIG_S3C2410)

ldr r1, =0x3ff

ldr r0, =INTSUBMSK

str r1, [r0]

# endif


/* 关闭所有中断,对于S3C2410来说,还把子中断寄存器INTSUBMSK关掉了,往寄存器里面写1即可,这个在用户手册里面有说明。 */


/* FCLK:HCLK:PCLK = 1:2:4 */

/* default FCLK is 120 MHz ! */

ldr r0, =CLKDIVN

mov r1, #3

str r1, [r0]

#endif /* CONFIG_S3C2400 || CONFIG_S3C2410 */


/* 设置分频系数 */


/*

* we do sys-critical inits only at reboot,

* not when booting from ram!

*/

#ifndef CONFIG_SKIP_LOWLEVEL_INIT

bl cpu_init_crit

#endif


/* 这段代码很有意思,C语言的关键字是#ifndef,意思是如果没有定义这个宏的话,那么就执行下面的语句,要仔细看清楚了,不能与#ifdef相混淆。很显然,2410中没有定义这个宏,那么就跳转到cpu_init_crit中去执行,在这里使用的是bl跳转指令,当程序执行完cpu_init_crit的时候,再返回到这里继续往下执行。先标记好这里,一会需要返回到这。 */


3cpu_init_crit也是start.S中的一个标号,以下代码仍在/cpu/arm920t/start.S中:


#ifndef CONFIG_SKIP_LOWLEVEL_INIT

cpu_init_crit:

/*

* flush v4 I/D caches

*/

mov r0, #0

mcr p15, 0, r0, c7, c7, 0 /* flush v3/v4 cache */

mcr p15, 0, r0, c8, c7, 0 /* flush v4 TLB */


/* 关掉I/D caches */


/*

* 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


/* 关掉MMU */


/*

* before relocating, we have to setup RAM timing

* because memory timing is board-dependend, you will

* find a lowlevel_init.S in your board directory.

*/

mov ip, lr

bl lowlevel_init

mov lr, ip

mov pc, lr

#endif /* CONFIG_SKIP_LOWLEVEL_INIT */


/* 这里又有一个lowlevel_init函数,跳到这个函数中去执行。可以仔细读读这个英文注释:在重定位代码前,需要先设置RAM的时钟,因为RAM时序是依赖于单板的,你能够在你的单板目录下找到一个 lowlevel_init.S文件。所以我们跳到SMDK2410的单板目录下的lowlevel_init.S文件。同时注意这个函数也是bl跳转的,执行完以后还需要跳转回来。 */


4)跳到/board/smdk2410/lowlevel_init.S中:


_TEXT_BASE:

.word TEXT_BASE


.globl lowlevel_init

lowlevel_init:

/* memory control configuration */

/* make r0 relative the current location so that it */

/* reads SMRDATA out of FLASH rather than memory ! */

ldr r0, =SMRDATA

ldr r1, _TEXT_BASE

sub r0, r0, r1

ldr r1, =BWSCON /* Bus Width Status Controller */

add r2, r0, #13*4

0:

ldr r3, [r0], #4

str r3, [r1], #4

cmp r2, r0

bne 0b


/* everything is fine now */

mov pc, lr


.ltorg

/* the literal pools origin */


SMRDATA:

.word (0+(B1_BWSCON<<4)+(B2_BWSCON<<8)+(B3_BWSCON<<12)+(B4_BWSCON<<16)+ (B5_BWSCON<<20)+(B6_BWSCON<<24)+(B7_BWSCON<<28))

.word ((B0_Tacs<<13)+(B0_Tcos<<11)+(B0_Tacc<<8)+(B0_Tcoh<<6)+(B0_Tah<<4)+ (B0_Tacp<<2)+(B0_PMC))

.word ((B1_Tacs<<13)+(B1_Tcos<<11)+(B1_Tacc<<8)+(B1_Tcoh<<6)+(B1_Tah<<4)+ (B1_Tacp<<2)+(B1_PMC))

.word ((B2_Tacs<<13)+(B2_Tcos<<11)+(B2_Tacc<<8)+(B2_Tcoh<<6)+(B2_Tah<<4)+ (B2_Tacp<<2)+(B2_PMC))

.word ((B3_Tacs<<13)+(B3_Tcos<<11)+(B3_Tacc<<8)+(B3_Tcoh<<6)+(B3_Tah<<4)+ (B3_Tacp<<2)+(B3_PMC))

.word ((B4_Tacs<<13)+(B4_Tcos<<11)+(B4_Tacc<<8)+(B4_Tcoh<<6)+(B4_Tah<<4)+ (B4_Tacp<<2)+(B4_PMC))

.word ((B5_Tacs<<13)+(B5_Tcos<<11)+(B5_Tacc<<8)+(B5_Tcoh<<6)+(B5_Tah<<4)+ (B5_Tacp<<2)+(B5_PMC))

.word ((B6_MT<<15)+(B6_Trcd<<2)+(B6_SCAN))

.word ((B7_MT<<15)+(B7_Trcd<<2)+(B7_SCAN))

.word ((REFEN<<23)+(TREFMD<<22)+(Trp<<20)+(Trc<<18)+(Tchr<<16)+REFCNT)

.word 0x32

.word 0x30

.word 0x30


/* 进行SDRAM的初始化,关于下面这一段汇编,

ldr r0, =SMRDATA

ldr r1, _TEXT_BASE

sub r0, r0, r1

ldr r1, =BWSCON /* Bus Width Status Controller */

add r2, r0, #13*4


为什么要写的这么奇怪呢?这就涉及到代码重定位的知识了。在重定位代码之前,SDRAM还没有初始化,只能使用相对地址来执行程序,同样必须使用相对跳转指令。我们的目的是将SMRDATA标号处的值一一存在以BWSCON开始的几个存储控制寄存器中,那么怎么定位SMRDATA的位置呢?先在前面用

_TEXT_BASE:

.word TEXT_BASE

来保存一个地址值,然后计算出SMRDATA_TEXT_BASE的相对偏移量,并保存在r0中,这时候r0中的值就是SMRDATA的地址值,然后将SMRDATA开始的各个值保存在 BWSCON开始的寄存器中。用的方法是汇编中经常用的循环保存值的方法。一共13个寄存器,每个寄存器占4个字节。*/


5)执行完lowlevel_init后,跳回到cpu_init_crit执行,从cpu_init_crit返回到/cpu/arm920t/start.S中继续执行:


#ifndef CONFIG_SKIP_RELOCATE_UBOOT

relocate: /* relocate U-Boot to RAM */

adr r0, _start /* r0 <- current position of code */

ldr r1, _TEXT_BASE /* test if we run from flash or RAM */

cmp r0, r1 /* don't reloc during debug */

beq stack_setup


/* 这一段程序的目的是判断 _start _TEXT_BASE 是否相同,如果程序开始执行的地方就位于程序的链接地址的话,就不用重定位了。如果是nor启动,_start即为代码的最开始,相对的0的位置;如果重新relocate代码之后,_start就是在/board/smdk2410/config.mk中定义的TEXT_BASE = 0x33F80000位置,即_start = TEXT_BASE = 0x33F80000,这样的话就不用重定位代码了。 */


ldr r2, _armboot_start

ldr r3, _bss_start

sub r2, r3, r2 /* r2 <- size of armboot */

add r2, r0, r2 /* r2 <- source end address */


/* 能执行到这,就说明_start _TEXT_BASE 是不相同的。重定位代码就是把代码从源地址拷贝到目的地址,拷贝多大的问题。源就是r0中的值,为_start,目的就是r1中的值,为_TEXT_BASE,程序拷贝多大呢?从链接脚本里面可以看出来,用(_bss_start - _armboot_start)即为程序的大小。 */


copy_loop:

ldmia r0!, {r3-r10} /* copy from source address [r0] */

stmia r1!, {r3-r10} /* copy to target address [r1] */

cmp r0, r2 /* until source end addreee [r2] */

ble copy_loop

#endif /* CONFIG_SKIP_RELOCATE_UBOOT */


/* 拷贝代码。 */


/* Set up the stack */

stack_setup:

ldr r0, _TEXT_BASE /* upper 128 KiB: relocated uboot */

sub r0, r0, #CFG_MALLOC_LEN /* malloc area */

sub r0, r0, #CFG_GBL_DATA_SIZE /* bdinfo */

#ifdef CONFIG_USE_IRQ

sub r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)

#endif

sub sp, r0, #12 /* leave 3 words for abort-stack */


/* 设置栈,在经过上面的重定位代码以后, r0 = TEXT_BASE = 0x33F80000,去除分给malloc堆的空间以后,再取出一部分空间给bdinfo结构体,再留出12字节给终止异常的空间,然后将这个值赋给sp,即设置好了栈。 */


clear_bss:

ldr r0, _bss_start /* find start of bss segment */

ldr r1, _bss_end /* stop here */

mov r2, #0x00000000 /* clear */


clbss_l:str r2, [r0] /* clear loop... */

add r0, r0, #4

cmp r0, r1

ble clbss_l


/* BSS段,BSS段的大小为(_bss_end - _bss_start),将这个空间都清为0*/


ldr pc, _start_armboot


_start_armboot: .word start_armboot


/* _start_armboot的值赋给pc,即调用start_armboot函数,即跳到uboot的第二阶段。 */


6)跳转到start_armboot函数中,它位于lib_arm/board.c 中:


void start_armboot (void)

{


init_fnc_t **init_fnc_ptr;

char *s;

#ifndef CFG_NO_FLASH

ulong size;

#endif


/* 在这个文件lib_arm/board.c的前面,有init_fnc_t的定义:

typedef int (init_fnc_t) (void);

其中 init_fnc_t 如下,它是一个函数类型,该类型的函数参数都为空(void),且都返回整型值(int)

init_fnc_t *init_sequence[] = {

cpu_init, /* basic cpu dependent setup */

board_init, /* basic board dependent setup */

interrupt_init, /* set up exceptions */

env_init, /* initialize environment */

init_baudrate, /* initialze baudrate settings */

serial_init, /* serial communications setup */

console_init_f, /* stage 1 init of console */

display_banner, /* say that we are here */

dram_init, /* configure available RAM banks */

display_dram_config,

NULL,

};


init_fnc_t **init_fnc_ptr;

那么这里就定义了变量init_fnc_ptr,它是一个指针变量,其中存的是另一个变量B的地址,而变量B也是一个指针变量,其中存的是init_fnc_t类型的函数的地址。*/


/* Pointer is writable since we allocated a register for it */

gd = (gd_t*)(_armboot_start - CFG_MALLOC_LEN – sizeof(gd_t));


/* 这个gd是啥?在本文件前面可以看到一个这样的声明:

DECLARE_GLOBAL_DATA_PTR;

这个宏在/include/asm-arm/global_data.h中是这样定义的:

#define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r8")

即这个gd就是 r8寄存器的值,每次调用gd都需要重新从r8寄存器中取值。至于为啥这样,我以后明白了再修改这。。。


_armboot_start 定义代码段的起始地址,定义在start.S
_armboot_start:
.word _start
可见其中保存的是第一条指令_start的地址。编译的时候指定了TEXT_BASE0x33F80000,所以
_armboot_start
中的值也为0x33F80000
CFG_MALLOC_LEN
也在/include/configs/smdk2410.h中定义了,如下所示:
#define CFG_MALLOC_LEN (CONFIG_ENV_SIZE + 128*1024)
#define CONFIG_ENV_SIZE 0x10000 /* Total Size of Environment Sector */
所以==> CFG_MALLOC_LEN = 0x10000 + 128*1024 = 0x10000 + 0x20000 = 0x30000,即192k
sizeof(gd_t) = 9 * 4 = 36 = 0x24 (
这里没有VDFLCD)*/


/* compiler optimization barrier needed for GCC >= 3.4 */

__asm__ __volatile__("": : :"memory");


/* __asm__ __volatile__("": : :"memory"); 是内嵌汇编的知识
__asm__
用于指示编译器在此插入汇编语句
__volatile__
用于告诉编译器,严禁将此处的汇编语句与其它的语句重组合优化。即:原原本本按原来的 样子处理这这里的汇编。
memory
强制gcc编译器假设RAM所有内存单元均被汇编指令修改,这样cpu中的registerscache中已缓存的内存单元中的数据将作废。cpu将不得不在需要的时候重新读取内存中的数据。这就阻止了cpu又将registerscache中的数据用于去优化指令,而避免去访问内存。
"":::
表示这是个空指令。barrier()不用在此插入一条串行化汇编指令。 */


memset ((void*)gd, 0, sizeof (gd_t));

gd->bd = (bd_t*)((char*)gd - sizeof(bd_t));

memset (gd->bd, 0, sizeof (bd_t));


/* 上面这些代码就是定义gd bd的位置,并将这两个结构体先清零,大致的布局如下

+------------+ -> 0x34000000 sp
| 512k | ->
这个位置为了放u-boot
+------------+ -> 0x33f80000 _armboot_start
| 192k | -> CFG_MALLOC_LEN = 192k
+------------+ -> 0x33f50000
| 24k | -> sizeof(gd_t) = 0x24,
uboot的配置信息
+------------+ <------- gd
就指向这里了。
| | -> sizeof(bd_t),
板级相关信息
+------------+ <------- gd->bd
就指向这里了。
| |
+------------+

*/


gd_t结构体在include/asm-arm/global_data.h中定义,如下所示:


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 */

void **jt; /* jump table */

} gd_t;


在这个结构体里面保存了uboot的配置信息,如波特率,flags等信息。


bd_t结构体在include/asm-arm/u-boot.h中定义,如下所示:

typedef struct bd_info {

int bi_baudrate; /* serial console baudrate */

unsigned long bi_ip_addr; /* IP Address */

unsigned char bi_enetaddr[6]; /* Ethernet adress */

struct environment_s *bi_env;

ulong bi_arch_number; /* unique id for this board */

ulong bi_boot_params; /* where this board expects params */

struct /* RAM configuration */

{

ulong start;

ulong size;

} bi_dram[CONFIG_NR_DRAM_BANKS];

} bd_t;

这个结构体保存了一些板子的信息,如IP地址,机器ID等信息。

这两个结构体贯穿了uboot的整体,很重要。


monitor_flash_len = _bss_start - _armboot_start;


/* 计算代码段的长度,根据链接脚本里面的计算的,bss段开始的地址减去程序开始的地址,就是代码段的长度。 */


for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {

if ((*init_fnc_ptr)() != 0) {

hang ();

}

}


/* 在上面说了,这个变量init_fnc_ptr,它是一个指针变量,其中存的是另一个变量B的地址,而变量B也是一个指针变量,其中存的是init_fnc_t类型的函数的地址,所以这个for循环可以表示为遍历init_sequence 这个数组里面所有的函数。那么就跳到这个数组里面,分析分析每个函数都干了什么事情。关于init_sequence这个函数数组已经在前面写出来了,在这就不阐述了。 */

<1> 首先是 cpu_init, /* basic cpu dependent setup */

这个函数在/cpu/arm920t/cpu.c里面,代码如下:

int cpu_init (void)

{

/*

* setup up stacks if necessary

*/

#ifdef CONFIG_USE_IRQ

IRQ_STACK_START = _armboot_start - CFG_MALLOC_LEN - CFG_GBL_DATA_SIZE - 4;

FIQ_STACK_START = IRQ_STACK_START - CONFIG_STACKSIZE_IRQ;

#endif

return 0;

}

并没有定义CONFIG_USE_IRQ这个宏,所以对于我们来说,什么都没做。

start.S中,对于这个宏有定义,如下所示:

.globl IRQ_STACK_START

IRQ_STACK_START:

.word 0x0badc0de

这个函数的目的就是在uboot使用中断的情况下,设置栈的位置等信息。


<2> board_init, /* basic board dependent setup */

这个函数在board/smdk2410/smdk2410.c中定义,代码如下:

int board_init (void)

{

S3C24X0_CLOCK_POWER * const clk_power = S3C24X0_GetBase_CLOCK_POWER();

S3C24X0_GPIO * const gpio = S3C24X0_GetBase_GPIO();


/* to reduce PLL lock time, adjust the LOCKTIME register */

clk_power->LOCKTIME = 0xFFFFFF;


/* configure MPLL */

clk_power->MPLLCON = ((M_MDIV << 12) + (M_PDIV << 4) + M_SDIV);


/* some delay between MPLL and UPLL */

delay (4000);


/* configure UPLL */

clk_power->UPLLCON = ((U_M_MDIV << 12) + (U_M_PDIV << 4) + U_M_SDIV);


/* some delay between MPLL and UPLL */

delay (8000);


/* set up the I/O ports */

gpio->GPACON = 0x007FFFFF;

gpio->GPBCON = 0x00044555;

gpio->GPBUP = 0x000007FF;

gpio->GPCCON = 0xAAAAAAAA;

gpio->GPCUP = 0x0000FFFF;

gpio->GPDCON = 0xAAAAAAAA;

gpio->GPDUP = 0x0000FFFF;

gpio->GPECON = 0xAAAAAAAA;

gpio->GPEUP = 0x0000FFFF;

gpio->GPFCON = 0x000055AA;

gpio->GPFUP = 0x000000FF;

gpio->GPGCON = 0xFF95FFBA;

gpio->GPGUP = 0x0000FFFF;

gpio->GPHCON = 0x002AFAAA;

gpio->GPHUP = 0x000007FF;


/* arch number of SMDK2410-Board */

gd->bd->bi_arch_number = MACH_TYPE_SMDK2410;


/* adress of boot parameters */

gd->bd->bi_boot_params = 0x30000100;


icache_enable();

dcache_enable();


return 0;

}

上面代码中S3C24X0_CLOCK_POWER * const clk_power = S3C24X0_GetBase_CLOCK_POWER();

这是uboot对寄存器访问的方式,对于底层的寄存器访问,uboot都采取了这种方式,好好体会:


1> 首先定义了S3C24X0_CLOCK_POWER 结构,在include/s3c24x0.h中:

typedef struct {

S3C24X0_REG32 LOCKTIME;

S3C24X0_REG32 MPLLCON;

S3C24X0_REG32 UPLLCON;

S3C24X0_REG32 CLKCON;

S3C24X0_REG32 CLKSLOW;

S3C24X0_REG32 CLKDIVN;

} /*__attribute__((__packed__))*/ S3C24X0_CLOCK_POWER;


2> 然后S3C24X0_GetBase_CLOCK_POWER()的定义在include/s3c2410.h中,如下所示,通过这个方法获取寄存器的基地址:

static inline S3C24X0_CLOCK_POWER * const S3C24X0_GetBase_CLOCK_POWER(void)

{

return (S3C24X0_CLOCK_POWER * const)S3C24X0_CLOCK_POWER_BASE;

}

3> S3C24X0_CLOCK_POWER_BASE的宏定义也在include/s3c2410.h中,为CLOCK首个寄存器的基地址:

#define S3C24X0_CLOCK_POWER_BASE 0x4C000000


4> 这样看也就好理解了,对于底层的每一个模块如clock模块,gpio模块,nand模块,uart模块等,uboot中都定义了相关的结构体,如 S3C24X0_CLOCK_POWERS3C24X0_GPIOS3C2410_NANDS3C24X0_UART等,然后用上面的方法调用相应模块,

S3C24X0_CLOCK_POWER * const clk_power = S3C24X0_GetBase_CLOCK_POWER();

再从相应的单板中取出相应的地址值,这种内核中常用的面向对象的思想。。。。。。


5> 这个函数中对于GPIO也采用了上述方法,我在这列举如下,自行体会吧:

其中 S3C24X0_GPIO这个结构体过于庞大,我就没有列举,去include/s3c24x0.h中查看。

static inline S3C24X0_GPIO * const S3C24X0_GetBase_GPIO(void)

{

return (S3C24X0_GPIO * const)S3C24X0_GPIO_BASE;

}


#define S3C24X0_GPIO_BASE 0x56000000


总结:这个函数依次完成了:设置LOCKTIMEMPLLCONUPLLCONGPIO管脚,并在最后通过

gd->bd->bi_arch_number = MACH_TYPE_SMDK2410;

gd->bd->bi_boot_params = 0x30000100;

设置了机器IDuboot向内核传递参数(tag)的地址信息,这两个信息用于启动内核的时候,传给theKernel函数:

theKernel (0, machid, bd->bi_boot_params)

然后激活了icachedcache,这两个函数就不列举了。


<3> interrupt_init, /* set up exceptions */


int interrupt_init (void)

{

S3C24X0_TIMERS * const timers = S3C24X0_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 15625 @ 50 MHz

*/

timer_load_val = get_PCLK()/(2 * 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 & ~0x0700000) | 0x600000;

/* auto load, start Timer 4 */

timers->TCON = (timers->TCON & ~0x0700000) | 0x500000;

timestamp = 0;


return (0);

}


这个函数实际上是设置timer定时器,2410总共有5个定时器,timer0timer4,这里使用timer 4原因解释的很清楚了, because it has no outputtimer0timer4有脉冲宽度调制(PWM)功能,因此这4个定时器也被称为PWM定时器。timer4是一个内部定时器,无外部输出引脚。
那么看上面的代码:
1> prescaler1
timers->TCFG0 = 0x0f00;
这里要使用timer4,所以设置其[15:8]0f,即prescaler1 = 16
2>
定时器初值计算
timer_load_val = get_PCLK()/(2 * 16 * 100);

get_PCLK()
位于cpu/arm920t/s3c24x0/speed.c 中,取得pclk的过程如下
FCLK => HCLK = FCLK / 2 => PCLK = HCLK / 2
即先通过MPLLCON的值计算得出FCLK,然后根据CLKDIVN的分频率比得出HCLK PCLK
要做一个10ms的定时器,所以这里计算初值。上面prescaler1 = 16, 且此处没有设置TCFG1,所以TCFG1为默认值0, divider选择了1/2; 1s = 10ms * 100;
于是,需要的三个参数全都出来了
分频后频率为 (clk /(2 * 16)),即每秒要做(clk/(2 * 16))次减计数,那么10ms就要做 clk / (2 * 16 * 100)次减计数。
3> TCNTB4
timers->TCNTB4 = timer_load_val
将值记录于TCNTB4中。在 auto reload 模式时,在减计数一次完成之后,会自动将 TCNTB4 的值赋给 TCNT4
4> TCON
最后 设置 TCON bit[22:20]
timers->TCON = (timers->TCON & ~0x0700000) | 0x600000; => bit[22:20] = 110
即选择 auto reload mode, update TCNTB4
timers->TCON = (timers->TCON & ~0x0700000) | 0x500000; => bit[22:20] = 101
auto reload mode, start for timer4 此时,真正开始定时器4


<4> env_init, /* initialize environment */

env_init函数在很多文件里面都有函数的定义,比如在env_dataflash.c, env_flash.c, env_nand.c等等,那么这个环境变量到底存在哪呢?随便打开一个文件,比如说env_nand.c中,查看的时候发现在这个文件的头部:#if defined(CFG_ENV_IS_IN_NAND),发现这是一个宏开关,肯定在2410的配置文件中定义了这些环境变量放在哪,于是又查看了env_flash.c文件,发现

#if defined(CFG_ENV_IS_IN_FLASH),追踪各个宏定义,发现在/include/config/smdk2410.h中定义了这些宏开关,发现在2410的默认代码中,环境变量是放在flash中的。于是下面的这个函数就是common/env_flash.c中的函数:

(但是发现env_flash.c中有两个env_init函数啊。。。。真是日了dog了。。。)

分析代码,发现是这样的:

#ifdef CFG_ENV_ADDR_REDUND

int env_init(void)

{

...

}

else

{

int env_init(void)

{

...

}

}


CFG_ENV_ADDR_REDUND是在/include/environment.h中定义的,所以就用第一个env_init函数吧,因为还不知道

/include/environment.h这个头文件是怎么使用的,所以暂且这样分析。。。

int env_init(void)

{

int crc1_ok = 0, crc2_ok = 0;


uchar flag1 = flash_addr->flags;

uchar flag2 = flash_addr_new->flags;


ulong addr_default = (ulong)&default_environment[0];

ulong addr1 = (ulong)&(flash_addr->data);

ulong addr2 = (ulong)&(flash_addr_new->data);

crc1_ok = (crc32(0, flash_addr->data, ENV_SIZE) == flash_addr->crc);

crc2_ok = (crc32(0, flash_addr_new->data, ENV_SIZE) == flash_addr_new->crc);


if (crc1_ok && ! crc2_ok) {

gd->env_addr = addr1;

gd->env_valid = 1;

} else if (! crc1_ok && crc2_ok) {

gd->env_addr = addr2;

gd->env_valid = 1;

} else if (! crc1_ok && ! crc2_ok) {

gd->env_addr = addr_default;

gd->env_valid = 0;

} else if (flag1 == ACTIVE_FLAG && flag2 == OBSOLETE_FLAG) {

gd->env_addr = addr1;

gd->env_valid = 1;

} else if (flag1 == OBSOLETE_FLAG && flag2 == ACTIVE_FLAG) {

gd->env_addr = addr2;

gd->env_valid = 1;

} else if (flag1 == flag2) {

gd->env_addr = addr1;

gd->env_valid = 2;

} else if (flag1 == 0xFF) {

gd->env_addr = addr1;

gd->env_valid = 2;

} else if (flag2 == 0xFF) {

gd->env_addr = addr2;

gd->env_valid = 2;

}

return (0);

}

用了那么多ifelse语句,目的就是为了设置gd->env_addrgd->env_valid这两个变量的值,并且其中还用到了 default_environment这个数组,这个数组在common/env_common.c中定义的,

uchar default_environment[] = {

#ifdef CONFIG_BOOTCOMMAND

"bootcmd=" CONFIG_BOOTCOMMAND "\0"

#endif

#if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >= 0)

"bootdelay=" MK_STR(CONFIG_BOOTDELAY) "\0"

#endif

#if defined(CONFIG_BAUDRATE) && (CONFIG_BAUDRATE >= 0)

"baudrate=" MK_STR(CONFIG_BAUDRATE) "\0"

#endif

#ifdef CONFIG_IPADDR

"ipaddr=" MK_STR(CONFIG_IPADDR) "\0"

#endif

#ifdef CONFIG_SERVERIP

"serverip=" MK_STR(CONFIG_SERVERIP) "\0"

#endif

#ifdef CONFIG_NETMASK

"netmask=" MK_STR(CONFIG_NETMASK) "\0"

#endif

"\0"

};


如果定义了CFG_ENV_IS_IN_NAND的话,就说明一些环境变量存在nand中,这里env_init就是设置了:

gd->env_addr = (ulong)&default_environment[0];

gd->env_valid = 1;

这个函数暂时先写到这里,具体就不深入分析了,具体的关系到uboot环境变量的实现分析里面了,我在网上找了一篇文章:

uboot环境变量实现分析:http://blog.csdn.net/skyflying2012/article/details/39005705


这个环境变量的实现分析以后再继续深入研究。


<5> init_baudrate, /* initialze baudrate settings */


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);

}


这里面用到了getenv_r这个函数,这个函数在common/cmd_nvedit.c中定义:


int getenv_r (char *name, char *buf, unsigned len)

{

int i, nxt;


for (i=0; env_get_char(i) != '\0'; i=nxt+1) {

int val, n;


for (nxt=i; env_get_char(nxt) != '\0'; ++nxt) {

if (nxt >= CFG_ENV_SIZE) {

return (-1);

}

}

if ((val=envmatch((uchar *)name, i)) < 0)

continue;

/* found; copy out */

n = 0;

while ((len > n++) && (*buf++ = env_get_char(val++)) != '\0')

;

if (len == n)

*buf = '\0';

return (n);

}

return (-1);

}


这个函数又用到了env_get_char这个函数数组,

uchar (*env_get_char)(int) = env_get_char_init;


static uchar env_get_char_init (int index)

{

uchar c;


/* if crc was bad, use the default environment */

if (gd->env_valid)

{

c = env_get_char_spec(index);

} else {

c = default_environment[index];

}


return (c);

}


阅读完代码后,我们返回 getenv_r这个函数,它的大致意思是根据名字name来从 env_get_char_init中找找有没有设置baudrate,如果存在的话,就保存在buf里面返回,如果没有的话,就返回0

那么我们再回到 init_baudrate这个函数中,如果getenv_r函数返回波特率的话,就把值存入gd->bd->bi_baudrate中,如果getenv_r函数返回0的话,就使用默认的波特率,即115200


<6> serial_init, /* serial communications setup */

这个函数在cpu/arm920t/s3c24x0/serial.c中:

int serial_init (void)

{

serial_setbrg ();

return (0);

}


void serial_setbrg (void)

{

S3C24X0_UART * const uart = S3C24X0_GetBase_UART(UART_NR);

int i;

unsigned int reg = 0;


/* value is calculated so : (int)(PCLK/16./baudrate) -1 */

reg = get_PCLK() / (16 * gd->baudrate) - 1;


/* FIFO enable, Tx/Rx FIFO clear */

uart->UFCON = 0x07;

uart->UMCON = 0x0;

/* Normal,No parity,1 stop,8 bit */

uart->ULCON = 0x3;

/*

* tx=level,rx=edge,disable timeout int.,enable rx error int.,

* normal,interrupt or polling

*/

uart->UCON = 0x245;

uart->UBRDIV = reg;


for (i = 0; i < 100; i++);

}

就是完成串口的初始化,但是这里开了FIFO,这个只是uboot源码,用在特殊的板子上的话肯定还需要修改。


<7> console_init_f, /* stage 1 init of console */


int console_init_f (void)

{

gd->have_console = 1;


#ifdef CONFIG_SILENT_CONSOLE

if (getenv("silent") != NULL)

gd->flags |= GD_FLG_SILENT;

#endif


return (0);

}


这里只是将gd->have_console1,相当于设置了一个标识,表示将会启用uboot的命令终端。


<8> display_banner, /* say that we are here */


此函数在lib_arm/board.c里面定义:


static int display_banner (void)

{

printf ("\n\n%s\n\n", version_string);

debug ("U-Boot code: %08lX -> %08lX BSS: -> %08lX\n",

_armboot_start, _bss_start, _bss_end);


return (0);

}

这个函数只执行了两句话,printf ("\n\n%s\n\n", version_string);就是打印出uboot的一些信息。

const char version_string[] =

U_BOOT_VERSION" (" __DATE__ " - " __TIME__ ")"CONFIG_IDENT_STRING;


#ifndef CONFIG_IDENT_STRING

#define CONFIG_IDENT_STRING ""

#endif


下面分析一下这个printf函数:

printf函数在common/console.c中定义:

void printf (const char *fmt, ...)

{

va_list args;

uint i;

char printbuffer[CFG_PBSIZE];


va_start (args, fmt);


/* For this to work, printbuffer must be larger than

* anything we ever want to print.

*/

i = vsprintf (printbuffer, fmt, args);

va_end (args);


/* Print the string */

puts (printbuffer);

}


CFG_PBSIZE宏在include/config/smdk2410.h中定义:

#define CFG_PROMPT "SMDK2410 # " /* Monitor Command Prompt */

#define CFG_CBSIZE 256 /* Console I/O Buffer Size */

#define CFG_PBSIZE (CFG_CBSIZE+sizeof(CFG_PROMPT)+16) /* Print Buffer Size */


可以看出,printf函数中,先用vsprintf函数对字符串进行了处理,然后用puts打印出来。

vsprintf函数在lib_generic/vsprintf.c里面定义,但是我没看太懂,代码就不粘贴在这里了。

Puts函数在common/console.c中定义:


void puts (const char *s)

{

if (gd->flags & GD_FLG_DEVINIT) {

/* Send to the standard output */

fputs (stdout, s);

} else {

/* Send directly to the handler */

serial_puts (s);

}

}


#define GD_FLG_RELOC 0x00001 /* Code was relocated to RAM */

#define GD_FLG_DEVINIT 0x00002 /* Devices have been initialized */

#define GD_FLG_SILENT 0x00004 /* Silent mode */


这里使用到了gd->flags,代码的意思是根据这一位,选择从哪里输出打印信息,如果定义了GD_FLG_DEVINIT的话,就从标准输出中输出,如果没有定义的话,就从串口中打印。


<9> dram_init, /* configure available RAM banks */

dram_init函数在board/smdk2410/smdk2410.c中定义:

int dram_init (void)

{

gd->bd->bi_dram[0].start = PHYS_SDRAM_1;

gd->bd->bi_dram[0].size = PHYS_SDRAM_1_SIZE;

return 0;

}

那两个宏定义在smdk2410.h中,如下所示:

#define CONFIG_NR_DRAM_BANKS 1 /* we have 1 bank of DRAM */

#define PHYS_SDRAM_1 0x30000000 /* SDRAM Bank #1 */

#define PHYS_SDRAM_1_SIZE 0x04000000 /* 64 MB */


这个函数主要用来填充gd结构体里面的bd_t结构,bi_dram[0]表示这是板子的第一个sdram

gd->bd->bi_dram[0].start = PHYS_SDRAM_1;表示这个sdram的起始地址,

gd->bd->bi_dram[0].size = PHYS_SDRAM_1_SIZE;表示sdram的大小。


<10> display_dram_config,

display_dram_config这个函数在lib_arm/board.c中定义,如下所示:

static int display_dram_config (void)

{

int i;


#ifdef DEBUG

puts ("RAM Configuration:\n");


for(i=0; i

printf ("Bank #%d: %08lx ", i, gd->bd->bi_dram[i].start);

print_size (gd->bd->bi_dram[i].size, "\n");

}

#else

ulong size = 0;


for (i=0; i

size += gd->bd->bi_dram[i].size;

}

puts("DRAM: ");

print_size(size, "\n");

#endif


return (0);

}


这个函数的作用就是打印出这个板子的RAM的一些信息,如大小等。


7)终于讲完了init_sequence这个函数数组,它完成了一部分的初始化,让我们继续回到start_armboot这个函数中:

#ifndef CFG_NO_FLASH

/* configure available FLASH banks */

size = flash_init ();

display_flash_config (size);

#endif /* CFG_NO_FLASH */


确实没有定义CFG_NO_FLASH这个宏,所以执行下面的语句,


/* armboot_start is defined in the board-specific linker script */

mem_malloc_init (_armboot_start – CFG_MALLOC_LEN);


/* 将堆的内存空间清零。

mem_malloc_init这个函数在lib_arm/board.c中:

void mem_malloc_init (ulong dest_addr)

{

mem_malloc_start = dest_addr;

mem_malloc_end = dest_addr + CFG_MALLOC_LEN;

mem_malloc_brk = mem_malloc_start;


memset ((void *) mem_malloc_start, 0,

mem_malloc_end - mem_malloc_start);

}


memset的定义如下:

void * memset(void * s,int c,size_t count)

{

char *xs = (char *) s;


while (count--)

*xs++ = c;


return s;

}


在这个程序中,_armboot_start = 0x33f80000CFG_MALLOC_LEN是定义的堆的内存大小,将这一段空间清零。*/


8)继续在start_armboot中执行:

#if (CONFIG_COMMANDS & CFG_CMD_NAND)

puts ("NAND: ");

nand_init(); /* go init the NAND */

#endif


/* nand_init函数在drivers/nand/nand.c中定义:

void nand_init(void)

{

int i;

unsigned int size = 0;

for (i = 0; i < CFG_MAX_NAND_DEVICE; i++) {

nand_init_chip(&nand_info[i], &nand_chip[i], base_address[i]);

size += nand_info[i].size;

if (nand_curr_device == -1)

nand_curr_device = i;

}

printf("%lu MiB\n", size / (1024 * 1024));

}


我们看到这里有一个for循环,有一个CFG_MAX_NAND_DEVICE宏,表示板子有几个nand flash,所以如果要移植这个uboot的话,需要在配置文件中定义这个宏,这个函数里面包含了一个nand_init_chip函数,除此之外,计算出nand flash的总大小,保存在这个nand_info结构体中,nand_curr_device表示当前nand flash设备编号,初始值为-1(在文件前面定义初始化的),因为我们只有一个nand flash 设备,所以这个值应该为0,之后打印出来这个nand flash的大小。

之后我们再进入nand_init_chip这个函数,

static void nand_init_chip(struct mtd_info *mtd, struct nand_chip *nand,

ulong base_addr)

{

mtd->priv = nand;


nand->IO_ADDR_R = nand->IO_ADDR_W = (void __iomem *)base_addr;

board_nand_init(nand);


if (nand_scan(mtd, 1) == 0) {

if (!mtd->name)

mtd->name = (char *)default_nand_name;

} else

mtd->name = NULL;

}


在看这个函数之前,我们要看传递给这个函数的三个参数,nand_infonand_chipbase_address。这三个参数它们都是定义在nand.c中的三个全局变量,用于保存nand flash的相关信息,这就是初始化要的关键。nand_info主要和芯片本身相关,比如记录nand flash的大小等等。nand_chip这个结构主要记录nand flash它的操作相关,比如readwirte等等。而base_address是记录的nand flash主控制器的寄存器基地址。它是这样定义的。

#ifndef CONFIG_SYS_NAND_BASE_LIST
#define CONFIG_SYS_NAND_BASE_LIST { CONFIG_SYS_NAND_BASE }
#endif

nand_info_t nand_info[CFG_MAX_NAND_DEVICE];

static struct nand_chip nand_chip[CFG_MAX_NAND_DEVICE];

static ulong base_address[CONFIG_SYS_MAX_NAND_DEVICE] = CONFIG_SYS_NAND_BASE_LIST;


如果没有定义CONFIG_SYS_NAND_BASE_LIST,那么寄存器基地址就是CONFIG_SYS_NAND_BASE,对于S3C2410呢这个值就为0x4E000000,所以在移植的时候又需要在配置文件中定义这个宏。

这个函数的核心就是两个函数,board_nand_init()函数和 nand_scan()函数。

这个board_nand_init()函数一看就是与板子相关的函数,我们搜遍uboot源码都没有找到这个函数,所以这个函数是需要我们实现的函数,现在参考网上的资料,也可能是在以后的uboot版本中支持了,在这列举一些这个函数的大概框架:

int board_nand_init(struct nand_chip *nand)
{
        u_int32_t cfg;
        u_int8_t tacls, twrph0, twrph1;
        S3C24X0_CLOCK_POWER * const clk_power = S3C24X0_GetBase_CLOCK_POWER();

        clk_power->CLKCON |= (1 << 4);

        /* initialize hardware */
        twrph0 = 3; twrph1 = 0; tacls = 0;

        cfg = S3C2410_NFCONF_EN;
        cfg |= S3C2410_NFCONF_TACLS(tacls - 1);
        cfg |= S3C2410_NFCONF_TWRPH0(twrph0 - 1);
        cfg |= S3C2410_NFCONF_TWRPH1(twrph1 - 1);

        NFCONF = cfg;

        /* initialize nand_chip data structure */
        nand->IO_ADDR_R = nand->IO_ADDR_W = (void *)0x4e00000c;

        /* read_buf and write_buf are default */
        /* read_byte and write_byte are default */

        /* hwcontrol always must be implemented */
        nand->cmd_ctrl = s3c2410_hwcontrol;

        nand->dev_ready = s3c2410_dev_ready;

#ifdef CONFIG_S3C2410_NAND_HWECC
        nand->ecc.hwctl = s3c2410_nand_enable_hwecc;
        nand->ecc.calculate = s3c2410_nand_calculate_ecc;

        nand->ecc.correct = s3c2410_nand_correct_data;
        nand->ecc.mode = NAND_ECC_HW3_512;
#else
        nand->ecc.mode = NAND_ECC_SOFT;
#endif

        nand->options = 0;

        DEBUGN("end of nand_init\n");

        return 0;
}

这个函数首先置CLKCON第5位,给nand控制器供电,上电默认是供电的,然后配置NFCONF寄存器,设置nand flash读写基地址为0x4e00000c,然后填充nand_chip结构体。

nand_scan()这个函数位于drivers/nand/nand_base.c这个文件中,这段代码很长,但是不难分析,主要的目的是填充nand_chip结构体,我在后面列出了这个结构体(也是很大。。。)简单分析如下:

int nand_scan (struct mtd_info *mtd, int maxchips)

{

int i, j, nand_maf_id, nand_dev_id, busw;

struct nand_chip *this = mtd->priv;


/* Get buswidth to select the correct functions*/

busw = this->options & NAND_BUSWIDTH_16; /* 设置位宽 */


/* check for proper chip_delay setup, set 20us if not */

if (!this->chip_delay)

this->chip_delay = 20;


/* check, if a user supplied command function given */

if (this->cmdfunc == NULL)

this->cmdfunc = nand_command;


/* check, if a user supplied wait function given */

if (this->waitfunc == NULL)

this->waitfunc = nand_wait;


if (!this->select_chip)

this->select_chip = nand_select_chip;

if (!this->write_byte)

this->write_byte = busw ? nand_write_byte16 : nand_write_byte;

if (!this->read_byte)

this->read_byte = busw ? nand_read_byte16 : nand_read_byte;

if (!this->write_word)

this->write_word = nand_write_word;

if (!this->read_word)

this->read_word = nand_read_word;

if (!this->block_bad)

this->block_bad = nand_block_bad;

if (!this->block_markbad)

this->block_markbad = nand_default_block_markbad;

if (!this->write_buf)

this->write_buf = busw ? nand_write_buf16 : nand_write_buf;

if (!this->read_buf)

this->read_buf = busw ? nand_read_buf16 : nand_read_buf;

if (!this->verify_buf)

this->verify_buf = busw ? nand_verify_buf16 : nand_verify_buf;

if (!this->scan_bbt)

this->scan_bbt = nand_default_bbt;

/* 以上代码就是判断这些nand_chip成员是否初始化,如果没有的话,就给它们赋值 */

/* Select the device */

this->select_chip(mtd, 0); /* 选中芯片 */


/* Send the command for reading device ID */

this->cmdfunc (mtd, NAND_CMD_READID, 0x00, -1); /* 这个一个发送命令的函数 */


/* Read manufacturer and device IDs */

nand_maf_id = this->read_byte(mtd); /* 对于三星来说,厂商ID0xec */

nand_dev_id = this->read_byte(mtd); /* 这个是设备ID,以k9f1208为例是0x76 */


/* Print and store flash device information */

for (i = 0; nand_flash_ids[i].name != NULL; i++) {


if (nand_dev_id != nand_flash_ids[i].id)

continue;

/* 根据这个设备ID:nand_dev_id从这个nand_flash_ids[i]数组中找出匹配的一项,该项上含有芯片的一些信息,这个数组在drivers/nand/nand_ids.c中,列举部分如下:

struct nand_flash_dev nand_flash_ids[] = {

{"NAND 1MiB 5V 8-bit", 0x6e, 256, 1, 0x1000, 0},

{"NAND 2MiB 5V 8-bit", 0x64, 256, 2, 0x1000, 0},

{"NAND 4MiB 5V 8-bit", 0x6b, 512, 4, 0x2000, 0},

{"NAND 1MiB 3,3V 8-bit", 0xe8, 256, 1, 0x1000, 0},

{"NAND 1MiB 3,3V 8-bit", 0xec, 256, 1, 0x1000, 0},

{"NAND 2MiB 3,3V 8-bit", 0xea, 256, 2, 0x1000, 0},

。。。

{NULL,}

};

对于此块nand,读出nand_dev_id0x76后,则在nand_flash_ids[]中匹配到下面这一项:{"NAND 64MiB 3,3V 8-bit", 0x76, 512, 64, 0x4000, 0},由此可知,页面大小为512bytes, 芯片容量为64M, 擦写页面大小为16k等。 */

if (!mtd->name) mtd->name = nand_flash_ids[i].name;

/* mtd->name就指向字符串"NAND 64MiB 3,3V 8-bit" */

this->chipsize = nand_flash_ids[i].chipsize << 20;

/* 芯片大小为64M,即0x4000000 */

/* New devices have all the information in additional id bytes */

if (!nand_flash_ids[i].pagesize) {

。。。。/* 这个if语句不会执行,会执行else分支,所以我把代码删了 */

} else {

/* Old devices have this data hardcoded in the

* device id table */

mtd->erasesize = nand_flash_ids[i].erasesize; /*擦除页大小为16k*/

mtd->oobblock = nand_flash_ids[i].pagesize;/*页大小为512bytes*/

mtd->oobsize = mtd->oobblock / 32; /*oob大小为16bytes*/

busw = nand_flash_ids[i].options & NAND_BUSWIDTH_16; /*busw=0*/

} /* 上述这些信息都是刚才从nand_flash_ids[]中找到的,将他们赋给mtd结构体 */


/* Check, if buswidth is correct. Hardware drivers should set

* this correct ! */

if (busw != (this->options & NAND_BUSWIDTH_16)) {

printk (KERN_INFO "NAND device: Manufacturer ID:"

" 0x%02x, Chip ID: 0x%02x (%s %s)\n", nand_maf_id, nand_dev_id,

nand_manuf_ids[i].name , mtd->name);

printk (KERN_WARNING

"NAND bus width %d instead %d bit\n",

(this->options & NAND_BUSWIDTH_16) ? 16 : 8,

busw ? 16 : 8);

this->select_chip(mtd, -1);

return 1;

}


/* Calculate the address shift from the page size */

this->page_shift = ffs(mtd->oobblock) - 1;

this->bbt_erase_shift = this->phys_erase_shift = ffs(mtd->erasesize) - 1;

this->chip_shift = ffs(this->chipsize) – 1;

/* #define ffs(x) generic_ffs(x)
/* Return the position of the first bit set in 1, or 0 if none are set. The least-significant bit is position 1, the most-significant 32. */
找出第一个为1的位,最低位为1
0x1000 0000 0000 0000 0000 0000 0000 0000 返回32
0x0001
返回1
所以这里 ffs(mtd->oobblock) 10 , this->page_shift = 9
this->bbt_erase_shift = this->phys_erase_shift = 13
this->chip_shift = 26
*/


/* Set the bad block position */

this->badblockpos = mtd->oobblock > 512 ?

NAND_LARGE_BADBLOCK_POS : NAND_SMALL_BADBLOCK_POS;

/* 判断是大页还是小页,来确定坏块的标记位置, this->badblockpos大页的话=0,小页=5*/

/* Get chip options, preserve non chip based options */

this->options &= ~NAND_CHIPOPTIONS_MSK;

this->options |= nand_flash_ids[i].options & NAND_CHIPOPTIONS_MSK;

/* Set this as a default. Board drivers can override it, if neccecary */

this->options |= NAND_NO_AUTOINCR;

/* 应该是设置nand flash的一些属性类的东西,没有深入研究。 */

/* Check if this is a not a samsung device. Do not clear the options

* for chips which are not having an extended id.

*/

if (nand_maf_id != NAND_MFR_SAMSUNG && !nand_flash_ids[i].pagesize)

this->options &= ~NAND_SAMSUNG_LP_OPTIONS;


/* Check for AND chips with 4 page planes */

if (this->options & NAND_4PAGE_ARRAY)

this->erase_cmd = multi_erase_cmd;

else

this->erase_cmd = single_erase_cmd;


/* Do not replace user supplied command function ! */

if (mtd->oobblock > 512 && this->cmdfunc == nand_command)

this->cmdfunc = nand_command_lp;


/* Try to identify manufacturer */

for (j = 0; nand_manuf_ids[j].id != 0x0; j++) {

if (nand_manuf_ids[j].id == nand_maf_id)

break;

}

break;

}


if (!nand_flash_ids[i].name) {

printk (KERN_WARNING "No NAND device found!!!\n");

this->select_chip(mtd, -1);

return 1;

}


for (i=1; i < maxchips; i++) {

this->select_chip(mtd, i);


/* Send the command for reading device ID */

this->cmdfunc (mtd, NAND_CMD_READID, 0x00, -1);


/* Read manufacturer and device IDs */

if (nand_maf_id != this->read_byte(mtd) ||

nand_dev_id != this->read_byte(mtd))

break;

} /* 如果有多个nand flash的话,一次去读他们的ID,但是,nand_scan的调用为 * nand_scan(mtd, 1), 即maxchips == 1,所以这一段代码不会执行。 */

if (i > 1)

printk(KERN_INFO "%d NAND chips detected\n", i);


/* Allocate buffers, if neccecary */

if (!this->oob_buf) {

size_t len;

len = mtd->oobsize << (this->phys_erase_shift – this->page_shift);

/* len = 16 << (13 - 9) = 256 */

this->oob_buf = kmalloc (len, GFP_KERNEL);

if (!this->oob_buf) {

printk (KERN_ERR "nand_scan(): Cannot allocate oob_buf\n");

return -ENOMEM;

}

this->options |= NAND_OOBBUF_ALLOC;

}


if (!this->data_buf) {

size_t len;

len = mtd->oobblock + mtd->oobsize; /* len = 512 + 16 \ 528 */

this->data_buf = kmalloc (len, GFP_KERNEL);

if (!this->data_buf) {

if (this->options & NAND_OOBBUF_ALLOC)

kfree (this->oob_buf);

printk (KERN_ERR "nand_scan(): Cannot allocate data_buf\n");

return -ENOMEM;

}

this->options |= NAND_DATABUF_ALLOC;

}


/* Store the number of chips and calc total size for mtd */

this->numchips = i; /* 芯片数目 */

mtd->size = i * this->chipsize; /* 总大小 */

/* Convert chipsize to number of pages per chip -1. */

this->pagemask = (this->chipsize >> this->page_shift) - 1;

/* Preset the internal oob buffer */

memset(this->oob_buf, 0xff, mtd->oobsize << (this->phys_erase_shift - this->page_shift));


/* If no default placement scheme is given, select an

* appropriate one */

if (!this->autooob) {

/* Select the appropriate default oob placement scheme for

* placement agnostic filesystems */

switch (mtd->oobsize) {

case 8:

this->autooob = &nand_oob_8;

break;

case 16:

this->autooob = &nand_oob_16;

break;

case 64:

this->autooob = &nand_oob_64;

break;

default:

printk (KERN_WARNING "No oob scheme defined for oobsize %d\n",

mtd->oobsize);

/* BUG(); */

}

}


/* The number of bytes available for the filesystem to place fs dependend

* oob data */

if (this->options & NAND_BUSWIDTH_16) {

mtd->oobavail = mtd->oobsize - (this->autooob->eccbytes + 2);

if (this->autooob->eccbytes & 0x01)

mtd->oobavail--;

} else

mtd->oobavail = mtd->oobsize - (this->autooob->eccbytes + 1);

/* 上面的代码是填充oob的一些东西。 */

/*

* check ECC mode, default to software

* if 3byte/512byte hardware ECC is selected and we have 256 byte pagesize

* fallback to software ECC

*/

this->eccsize = 256; /* set default eccsize */

this->eccbytes = 3;


switch (this->eccmode) {

case NAND_ECC_HW12_2048:

if (mtd->oobblock < 2048) {

printk(KERN_WARNING "2048 byte HW ECC not possible on %d byte page size, fallback to SW ECC\n",

mtd->oobblock);

this->eccmode = NAND_ECC_SOFT;

this->calculate_ecc = nand_calculate_ecc;

this->correct_data = nand_correct_data;

} else

this->eccsize = 2048;

break;


case NAND_ECC_HW3_512:

case NAND_ECC_HW6_512:

case NAND_ECC_HW8_512:

if (mtd->oobblock == 256) {

printk (KERN_WARNING "512 byte HW ECC not possible on 256 Byte pagesize, fallback to SW ECC \n");

this->eccmode = NAND_ECC_SOFT;

this->calculate_ecc = nand_calculate_ecc;

this->correct_data = nand_correct_data;

} else

this->eccsize = 512; /* set eccsize to 512 */

break;


case NAND_ECC_HW3_256:

break;


case NAND_ECC_NONE:

printk (KERN_WARNING "NAND_ECC_NONE selected by board driver. This is not recommended !!\n");

this->eccmode = NAND_ECC_NONE;

break;


case NAND_ECC_SOFT:

this->calculate_ecc = nand_calculate_ecc;

this->correct_data = nand_correct_data;

break;


default:

printk (KERN_WARNING "Invalid NAND_ECC_MODE %d\n", this->eccmode);

/* BUG(); */

}


/* Check hardware ecc function availability and adjust number of ecc bytes per

* calculation step

*/

switch (this->eccmode) {

case NAND_ECC_HW12_2048:

this->eccbytes += 4;

case NAND_ECC_HW8_512:

this->eccbytes += 2;

case NAND_ECC_HW6_512:

this->eccbytes += 3;

case NAND_ECC_HW3_512:

case NAND_ECC_HW3_256:

if (this->calculate_ecc && this->correct_data && this->enable_hwecc)

break;

printk (KERN_WARNING "No ECC functions supplied, Hardware ECC not possible\n");

/* BUG(); */

}


mtd->eccsize = this->eccsize;


/* Set the number of read / write steps for one page to ensure ECC generation */

switch (this->eccmode) {

case NAND_ECC_HW12_2048:

this->eccsteps = mtd->oobblock / 2048;

break;

case NAND_ECC_HW3_512:

case NAND_ECC_HW6_512:

case NAND_ECC_HW8_512:

this->eccsteps = mtd->oobblock / 512;

break;

case NAND_ECC_HW3_256:

case NAND_ECC_SOFT:

this->eccsteps = mtd->oobblock / 256;

break;


case NAND_ECC_NONE:

this->eccsteps = 1;

break;

}

/* 上面的代码是设置ECC一类的东西,比如说硬件ECC或者软件ECC等。 */

/* De-select the device */

this->select_chip(mtd, -1); /* 取消片选芯片 */


/* Invalidate the pagebuffer reference */

this->pagebuf = -1;


/* Fill in remaining MTD driver data */

mtd->type = MTD_NANDFLASH;

mtd->flags = MTD_CAP_NANDFLASH | MTD_ECC;

mtd->ecctype = MTD_ECC_SW;

mtd->erase = nand_erase;

mtd->point = NULL;

mtd->unpoint = NULL;

mtd->read = nand_read;

mtd->write = nand_write;

mtd->read_ecc = nand_read_ecc;

mtd->write_ecc = nand_write_ecc;

mtd->read_oob = nand_read_oob;

mtd->write_oob = nand_write_oob;

mtd->sync = nand_sync;

mtd->block_isbad = nand_block_isbad;

mtd->block_markbad = nand_block_markbad;

/* 以上的代码就是填充mtd结构体 */

/* and make the autooob the default one */

memcpy(&mtd->oobinfo, this->autooob, sizeof(mtd->oobinfo));


/* Build bad block table */

return this->scan_bbt (mtd); /* 建立坏块列表 */

}

终于分析完了这两个函数,可以看出来里面最重要的就是nand_chipmtd_info这两个结构体,由于这两个结构体成员众多,下面就不粘贴出来这两个结构体了,mtd_info结构体位于include/linux/mtd/mtd.h中,nand_chip结构体位于include/linux/mtd/nand.h中。*/


9)执行完nand_init函数以后,我们继续返回到start_armboot函数中往下执行:


/* initialize environment */

env_relocate ();


/* 这个函数位于common/env_common.c文件中:

void env_relocate (void)

{

DEBUGF ("%s[%d] offset = 0x%lx\n", __FUNCTION__,__LINE__,

gd->reloc_off);


/*

* We must allocate a buffer for the environment

*/

env_ptr = (env_t *)malloc (CFG_ENV_SIZE);

DEBUGF ("%s[%d] malloced ENV at %p\n", __FUNCTION__,__LINE__,env_ptr);


/* CFG_ENV_SIZE这个宏在include/config/smdk2410.h中定义,为0x10000。这里从malloc堆里面分配出来0x10000这么大的空间,并将 env_ptr指向它。 */


/*

* After relocation to RAM, we can always use the "memory" functions

*/

env_get_char = env_get_char_memory; /* 这个函数没看懂。。。 */


if (gd->env_valid == 0) {

#if defined(CONFIG_GTH) || defined(CFG_ENV_IS_NOWHERE) /* Environment not changable */

puts ("Using default environment\n\n");

#else

puts ("*** Warning - bad CRC, using default environment\n\n");

SHOW_BOOT_PROGRESS (-1);

#endif


if (sizeof(default_environment) > ENV_SIZE)

{

puts ("*** Error - default environment is too large\n\n");

return;

}


memset (env_ptr, 0, sizeof(env_t));

memcpy (env_ptr->data,

default_environment,

sizeof(default_environment));

#ifdef CFG_REDUNDAND_ENVIRONMENT

env_ptr->flags = 0xFF;

#endif

env_crc_update ();

gd->env_valid = 1;

}

else {

env_relocate_spec ();

}

gd->env_addr = (ulong)&(env_ptr->data);

}


/* 对于这个if语句,在前面那个init_sequence数组里面的env_init函数,它已经设置了

gd->env_addr = (ulong)&default_environment[0];

gd->env_valid = 1;

所以这个if语句不会执行,会执行它的else分支,我在上面用红色字体标出来了。

关于这个env_relocate_spec()函数,也是单板相关的函数,在common/env_nand.c中:

void env_relocate_spec (void)

{

#if !defined(ENV_IS_EMBEDDED)

ulong total;

int ret;


total = CFG_ENV_SIZE;

ret = nand_read(&nand_info[0], CFG_ENV_OFFSET, &total, (u_char*)env_ptr);

if (ret || total != CFG_ENV_SIZE)

return use_default();


if (crc32(0, env_ptr->data, ENV_SIZE) != env_ptr->crc)

return use_default();

#endif /* ! ENV_IS_EMBEDDED */

}


环境变量存在nand中,将其从nand中读出,并填入env_ptr指向的env_t结构里,从nand读出时,配置项中有 CFG_ENV_OFFSET,即环境变量在nand中存储的起始地址。数据读错或数据校验出错,都会使用默认的环境变量配置use_default(), 第一次运行uboot时板子会打印如下信息:
*** Warning - bad CRC or NAND, using default environment
就是因为读出的数据经过crc32校验出错(此时读出的数据不是环境变量),进而调用use_default(),在
use_default()
中会打印该信息。use_default()函数同样在common/env_nand.c中,如下所示:

static void use_default()

{

puts ("*** Warning - bad CRC or NAND, using default environment\n\n");


if (default_environment_size > CFG_ENV_SIZE){

puts ("*** Error - default environment is too large\n\n");

return;

}


memset (env_ptr, 0, sizeof(env_t));

memcpy (env_ptr->data,

default_environment,

default_environment_size);

env_ptr->crc = crc32(0, env_ptr->data, ENV_SIZE);

gd->env_valid = 1;

} */


env_relocate 函数的最后一行又将gd->env_addr指向env_ptr->data

小结;env_relocate()函数所做的事情有3件:
1.
heap中分配一段空间,用于env_t结构
2.
找到环境变量(或从内存中找或从nand中找),填充env_t结构
3.
gd->env_addr指向env_ptr->data, 这个也就是这里的relocate所在吧。

*/


10)继续在start_armboot函数中执行:


/* IP Address */

gd->bd->bi_ip_addr = getenv_IPaddr ("ipaddr");


/* 将环境变量设置完之后,紧接着就是从环境变量的相应项中获取信息,环境变量是用户与u-boot的一个交互方式,有了它之后,用户即可通过修改环境变量来修改板子的一些信息配置。这里的ip地址和网卡地址即是其中的一个典型例子。来看上面的代码, 通过getenv_IPaddr函数来获取ip地址,然后将它保存在gd->bd->bi_ip_addr里面,注意gd->bd->bi_ip_addrungisned long 类型,而ip地址是类似于"192.168.1.111"的字符串,我们继续往下看代码,getenv_IPaddr()函数net/net.c中:

IPaddr_t getenv_IPaddr (char *var)

{

return (string_to_ip(getenv(var)));

}


继续追踪,getenv()函数在common/cmd_nvedit.c中定义,如下所示:

char *getenv (char *name)

{

int i, nxt;


WATCHDOG_RESET();


for (i=0; env_get_char(i) != '\0'; i=nxt+1) {

int val;


for (nxt=i; env_get_char(nxt) != '\0'; ++nxt) {

if (nxt >= CFG_ENV_SIZE) {

return (NULL);

}

}

if ((val=envmatch((uchar *)name, i)) < 0)

continue;

return ((char *)env_get_addr(val));

}


return (NULL);

}


getenv("ipaddr")即在环境变量中找到ipaddr这一项对应的字符串,假设这里为"192.168.1.111"
"192.168.1.111"传入string_to_ipIPaddr_t 类型是unsigned long 的一个typedef

IPaddr_t string_to_ip(char *s)

{

IPaddr_t addr;

char *e;

int i;


if (s == NULL)

return(0);


for (addr=0, i=0; i<4; ++i) {

ulong val = s ? simple_strtoul(s, &e, 10) : 0;

addr <<= 8;

addr |= (val & 0xFF);

if (s) {

s = (*e) ? e+1 : e;

}

}


return (htonl(addr));

}


192.168.1.111 分为四个段,也就是要做4simple_strtoul()转换成10进制的整型
4次转换后的值赋给valaddrunsigned long型,32位的,将其分为4段,每8位存储ip地址中的一个段,最后addr = (((((192 << 8) | 168) << 8) | 1) << 8 ) | 111 = 0xc0a8016f

最后一行,htonl(addr)表示主机字节顺序转换为网络字节顺序返回。那么什么叫做网络字节顺序呢?

CPU为小端模式时,addr如下存储:


31 24 23 16 15 8 7 0
+--------------+---------------+----------------+-----------------+
| 192 = 0xc0 | 168 = 0xa8 | 1 = 0x01 | 111 = 0x6f |
+--------------+---------------+----------------+-----------------+
3 2 1 0


CPU为大端模式时,addr如下存储:


31 24 23 16 15 8 7 0
+--------------+---------------+----------------+-----------------+
| 111 = 0x6f | 1 = 0x01 | 168 = 0xa8 | 192 = 0xc0 |
+--------------+---------------+----------------+-----------------+
3 2 1 0


当与另一台计算机通信时,通常不知道对方存储数据时是先存放最高位字节 (MSB)还是最低位字节 (LSB)
恰恰网络字节顺序跟大端模式时相同,htonl函数就是将主机字节顺序转为网络字节顺序,在最高位字节
(MSB)-
最前的系统上,这个函数什么都不做。在最低位字节(LSB)-最前的系统上它们将值转换为正确的
顺序。
最后将值返回给了gd->bd->bi_ip_addr, 所以其值应该是0x6f01800a*/


11)继续在start_armboot函数中执行:


/* MAC Address */

{

int i;

ulong reg;

char *s, *e;

char tmp[64];


i = getenv_r ("ethaddr", tmp, sizeof (tmp));

s = (i > 0) ? tmp : NULL;


for (reg = 0; reg < 6; ++reg) {

gd->bd->bi_enetaddr[reg] = s ? simple_strtoul (s, &e, 16) : 0;

if (s)

s = (*e) ? e + 1 : e;

}

}


/* 网卡地址以十六进制的形式存于gd->bd->bi_enetaddr[]数组中 */


12) 继续在start_armboot函数中执行:

devices_init (); /* get the devices list going. */


/* start_armboot简单的一句话,它的底层函数很多。。。对于这个设备初始化函数同样是这样,下面慢慢来分析这个函数,这个函数在common/devices.c


int devices_init (void)

{

#ifndef CONFIG_ARM /* already relocated for current ARM implementation */

ulong relocation_offset = gd->reloc_off;

int i;


/* relocate device name pointers */

for (i = 0; i < (sizeof (stdio_names) / sizeof (char *)); ++i) {

stdio_names[i] = (char *) (((ulong) stdio_names[i]) +

relocation_offset);

}

#endif


/* Initialize the list */

devlist = ListCreate (sizeof (device_t));


if (devlist == NULL) {

eputs ("Cannot initialize the list of devices!\n");

return -1;

}

#if defined(CONFIG_HARD_I2C) || defined(CONFIG_SOFT_I2C)

i2c_init (CFG_I2C_SPEED, CFG_I2C_SLAVE);

#endif

#ifdef CONFIG_LCD

drv_lcd_init ();

#endif

#if defined(CONFIG_VIDEO) || defined(CONFIG_CFB_CONSOLE)

drv_video_init ();

#endif

#ifdef CONFIG_KEYBOARD

drv_keyboard_init ();

#endif

#ifdef CONFIG_LOGBUFFER

drv_logbuff_init ();

#endif

drv_system_init ();

#ifdef CONFIG_SERIAL_MULTI

serial_devices_init ();

#endif

#ifdef CONFIG_USB_TTY

drv_usbtty_init ();

#endif

#ifdef CONFIG_NETCONSOLE

drv_nc_init ();

#endif


return (0);

}


首先并没有定义这个CONFIG_ARM宏,所以执行这个#ifndef这个语句,在本文件的开头有这样的声明:

char *stdio_names[MAX_FILES] = { "stdin", "stdout", "stderr" };

首先将gd->reloc_off的值赋给relocation_offset,然后重定位stdio_names[]这个数组里面的每一项。

然后就是这个ListCreate函数了,我们先分析了这个函数整体以后再分析这个ListCreate函数。这个函数的作用就是创建一个设备列表,列表里面包含所有用到的设备。

然后这个函数后面,可以看出来有很多宏,比如CONFIG_KEYBOARDCONFIG_SERIAL_MULTICONFIG_USB_TTY等,如果想使用键盘,串口,和USB设备等的话,就定义这些宏。


接下来分析这个ListCreate函数。

list_t ListCreate (int elementSize)

{

list_t list;


list = (list_t) (NewHandle (sizeof (ListStruct))); /* create empty list */

if (list) {

(*list)->signature = LIST_SIGNATURE;

(*list)->numItems = 0;

(*list)->listSize = 0;

(*list)->itemSize = elementSize;

(*list)->percentIncrease = kDefaultAllocationPercentIncrease;

(*list)->minNumItemsIncrease =

kDefaultAllocationminNumItemsIncrease;

}


return list;

}

这个函数传进来的参数是sizeof (device_t),调用 NewHandle函数创建一个空的list,并将这个函数的非0返回值强制类型转换成list_t类型,然后填充这个list_t结构体。


NewHandle函数位于common/list.c中:

Handle NewHandle (unsigned int numBytes)

{

void *memPtr;

HandleRecord *hanPtr;


memPtr = calloc (numBytes, 1);

hanPtr = (HandleRecord *) calloc (sizeof (HandleRecord), 1);

if (hanPtr && (memPtr || numBytes == 0)) {

hanPtr->ptr = memPtr;

hanPtr->size = numBytes;

return (Handle) hanPtr;

} else {

free (memPtr);

free (hanPtr);

return NULL;

}

}


#define calloc(size,num) malloc(size*num)


这个NewHandle函数传进来的参数是sizeof (ListStruct),给 memPtrhanPtr分配了内存,如果分配成功的话,就填充 hanPtr这个结构体。

一段用到了很多结构体,如list_t, HandleRecord等等,我就不一一列举了。


分析完底层的函数,我们继续返回到devices_init这个函数,在u-boot-1.1.6中,默认没有定义任何一个宏,但是我们肯定是需要用到这些设备中的一个或几个的,现在挑一个常用的串口设备来分析一个这个serial_devices_init()函数。这个函数在common/serial.c中:

void serial_devices_init (void)

{

device_t dev;

struct serial_device *s = serial_devices;


while (s) {

memset (&dev, 0, sizeof (dev));


strcpy (dev.name, s->name);

dev.flags = DEV_FLAGS_OUTPUT | DEV_FLAGS_INPUT;


dev.start = s->init;

dev.putc = s->putc;

dev.puts = s->puts;

dev.getc = s->getc;

dev.tstc = s->tstc;


device_register (&dev);


s = s->next;

}

}


分析代码发现,在这个文件的开头,首先定义了static struct serial_device *serial_devices = NULL;然后将这个serial_devices作为链表头,遍历这个链表中的每一项,分别为这个链表中的每一个串口设备初始化,即填充device_t这个结构体,这个结构体肯定是设备专属的结构体。既然有遍历列表的操作,那么肯定有往列表里面注册设备的操作,果然在函数的最后,有这个device_register (&dev)函数,下面分析这个注册函数。一路跟踪下去:

int device_register (device_t * dev)

{

ListInsertItem (devlist, dev, LIST_END);

return 0;

}


int ListInsertItem (list_t list, void *ptrToItem, int itemPosition)

{

return ListInsertItems (list, ptrToItem, itemPosition, 1);

}


int ListInsertItems (list_t list, void *ptrToItems, int firstItemPosition,

int numItemsToInsert)

{

int numItems = (*list)->numItems;


if (firstItemPosition == numItems + 1)

firstItemPosition = LIST_END;

else if (firstItemPosition > numItems)

return 0;


if ((*list)->numItems >= (*list)->listSize) {

if (!ExpandListSpace (list, -numItemsToInsert))

return 0;

}


if (firstItemPosition == LIST_START) {

if (numItems == 0) {

/* special case for empty list */

firstItemPosition = LIST_END;

} else {

firstItemPosition = 1;

}

}


if (firstItemPosition == LIST_END) { /* add at the end of the list */

if (ptrToItems)

memcpy (ITEMPTR (list, numItems), ptrToItems,

(*list)->itemSize * numItemsToInsert);

else

memset (ITEMPTR (list, numItems), 0,

(*list)->itemSize * numItemsToInsert);


(*list)->numItems += numItemsToInsert;

} else { /* move part of list up to make room for new item */

memmove (ITEMPTR (list, firstItemPosition - 1 + numItemsToInsert),

ITEMPTR (list, firstItemPosition - 1),

(numItems + 1 - firstItemPosition) * (*list)->itemSize);


if (ptrToItems)

memmove (ITEMPTR (list, firstItemPosition - 1), ptrToItems,

(*list)->itemSize * numItemsToInsert);

else

memset (ITEMPTR (list, firstItemPosition - 1), 0,

(*list)->itemSize * numItemsToInsert);


(*list)->numItems += numItemsToInsert;

}


return 1;

}

这些函数做的功能大致就是往设备列表list中注册第一个设备“serial”,为这个设备分配空间,然后这个链表中的设备数目增加。具体怎么执行就不分析了。

*/


13)继续在start_armboot函数中执行:


jumptable_init ();


/* 这个 jumptable_init函数位于common/exports.c里面:

void jumptable_init (void)

{

int i;


gd->jt = (void **) malloc (XF_MAX * sizeof (void *));

for (i = 0; i < XF_MAX; i++)

gd->jt[i] = (void *) dummy;


gd->jt[XF_get_version] = (void *) get_version;

gd->jt[XF_malloc] = (void *) malloc;

gd->jt[XF_free] = (void *) free;

gd->jt[XF_getenv] = (void *) getenv;

gd->jt[XF_setenv] = (void *) setenv;

gd->jt[XF_get_timer] = (void *) get_timer;

gd->jt[XF_simple_strtoul] = (void *) simple_strtoul;

gd->jt[XF_udelay] = (void *) udelay;

#if defined(CONFIG_I386) || defined(CONFIG_PPC)

gd->jt[XF_install_hdlr] = (void *) irq_install_handler;

gd->jt[XF_free_hdlr] = (void *) irq_free_handler;

#endif /* I386 || PPC */

#if (CONFIG_COMMANDS & CFG_CMD_I2C)

gd->jt[XF_i2c_write] = (void *) i2c_write;

gd->jt[XF_i2c_read] = (void *) i2c_read;

#endif /* CFG_CMD_I2C */

}


分析这个函数,发现它就是填充gd_t结构体里面的jt成员,具体这个成员是干嘛的,还不太了解。*/


14)继续在start_armboot函数中执行:


console_init_r (); /* fully init console as a device */


/* console_init_r这个函数在common/console.c中,这个函数也是有两个,根据CFG_CONSOLE_IS_IN_ENV这个宏选择用哪个函数,在2410中没有定义这个宏,所以我们选择第二个console_init_r函数,如下所示:

/* Called after the relocation - use desired console functions */

int console_init_r (void)

{

device_t *inputdev = NULL, *outputdev = NULL;

int i, items = ListNumItems (devlist);


/* 通过这个 ListNumItems函数,获取list链表中的设备个数。

int ListNumItems (list_t list)

{

return (*list)->numItems;

} */


#ifdef CONFIG_SPLASH_SCREEN /* 没有定义 */

/* suppress all output if splash screen is enabled and we have

a bmp to display */

if (getenv("splashimage") != NULL)

outputdev = search_device (DEV_FLAGS_OUTPUT, "nulldev");

#endif


#ifdef CONFIG_SILENT_CONSOLE /* 没有定义 */

/* Suppress all output if "silent" mode requested */

if (gd->flags & GD_FLG_SILENT)

outputdev = search_device (DEV_FLAGS_OUTPUT, "nulldev");

#endif


/* Scan devices looking for input and output devices */

for (i = 1;

(i <= items) && ((inputdev == NULL) || (outputdev == NULL));

i++

) {

device_t *dev = ListGetPtrToItem (devlist, i);


if ((dev->flags & DEV_FLAGS_INPUT) && (inputdev == NULL)) {

inputdev = dev;

}

if ((dev->flags & DEV_FLAGS_OUTPUT) && (outputdev == NULL)) {

outputdev = dev;

}

}


/* 遍历list链表,根据每个设备对应的device_t结构体中的flags属性来选择出输入输出设备。

ListGetPtrToItem函数就是把devlist链表中的每一项都赋给dev,然后分别将 dev->flagsDEV_FLAGS_INPUTDEV_FLAGS_OUTPUT所对比,以此来挑选出输入输出设备。这些都是链表的基本操作。 */


/* Initializes output console first */

if (outputdev != NULL) {

console_setfile (stdout, outputdev);

console_setfile (stderr, outputdev);

}


/* Initializes input console */

if (inputdev != NULL) {

console_setfile (stdin, inputdev);

}


/* 这里用到了console_setfile函数,这个函数在common/console.c函数中, 如下所示:


static int console_setfile (int file, device_t * dev)

{

int error = 0;


if (dev == NULL)

return -1;


switch (file) {

case stdin:

case stdout:

case stderr:

/* Start new device */

if (dev->start) {

error = dev->start ();

/* If it's not started dont use it */

if (error < 0)

break;

}


/* Assign the new device (leaving the existing one started) */

stdio_devices[file] = dev;


/*

* Update monitor functions

* (to use the console stuff by other applications)

*/

switch (file) {

case stdin:

gd->jt[XF_getc] = dev->getc;

gd->jt[XF_tstc] = dev->tstc;

break;

case stdout:

gd->jt[XF_putc] = dev->putc;

gd->jt[XF_puts] = dev->puts;

gd->jt[XF_printf] = printf;

break;

}

break;


default: /* Invalid file ID */

error = -1;

}

return error;

}

*/

先来分析这个console_setfile函数,如果在初始化设备的时候,定义了输入输出设备的话,就调用device_t函数里面的start函数,来开启该设备,如果没有定义这些设备的话,通过stdio_devices[file] = dev根据console_init_r函数来指定标准输入,标准输出,标准错误设备,这个stdio_devicescommon/devices.c中是这样定义的:device_t *stdio_devices[] = { NULL, NULL, NULL };可以看到他们没有指定这些设备,所以通过

console_setfile (stdout, outputdev);

console_setfile (stderr, outputdev);

console_setfile (stdin, inputdev);

3句话就指定好标准输入,标准输出,标准错误设备,并且将标准输出和标准错误设备指定为同一个设备,这样就可以通过输出设备来输出错误信息了。 */


gd->flags |= GD_FLG_DEVINIT; /* device initialization completed */


/* 设置好这些设备以后,将gd_t结构体里面的flags添加GD_FLG_DEVINIT属性,表示设备的初始化已经完成。*/


#ifndef CFG_CONSOLE_INFO_QUIET

/* Print information */

puts ("In: ");

if (stdio_devices[stdin] == NULL) {

puts ("No input devices available!\n");

} else {

printf ("%s\n", stdio_devices[stdin]->name);

}


puts ("Out: ");

if (stdio_devices[stdout] == NULL) {

puts ("No output devices available!\n");

} else {

printf ("%s\n", stdio_devices[stdout]->name);

}


puts ("Err: ");

if (stdio_devices[stderr] == NULL) {

puts ("No error devices available!\n");

} else {

printf ("%s\n", stdio_devices[stderr]->name);

}

#endif /* CFG_CONSOLE_INFO_QUIET */


/* 将标准输入,标准输出,标准错误这些设备的信息打印出来。 */


/* Setting environment variables */

for (i = 0; i < 3; i++) {

setenv (stdio_names[i], stdio_devices[i]->name);

}


/* 将信息写到环境变量中去char *stdio_names[MAX_FILES] = { "stdin", "stdout", "stderr" }; */


return (0);

}

/* 这样就完成了console_init_r函数的分析。 */


15)继续在start_armboot函数中执行:


/* enable exceptions */

enable_interrupts ();


/* 跟踪这个函数,同样是两个函数,如果定义CONFIG_USE_IRQ宏的话,就使用第一个,但是我们没有定义这个宏,使用第二个,第二个函数是一个空函数,所以忽略它。。。 */


/* Perform network card initialisation if necessary */

#ifdef CONFIG_DRIVER_CS8900

cs8900_get_enetaddr (gd->bd->bi_enetaddr);

#endif


/* 2410中定义了CONFIG_DRIVER_CS8900这个宏,但是我们用的是DM9000网卡,所以暂时先不分析这个函数。 */


/* Initialize from environment */

if ((s = getenv ("loadaddr")) != NULL) {

load_addr = simple_strtoul (s, NULL, 16);

}


#if (CONFIG_COMMANDS & CFG_CMD_NET)

if ((s = getenv ("bootfile")) != NULL) {

copy_filename (BootFile, s, sizeof (BootFile));

}

#endif /* CFG_CMD_NET */


#if (CONFIG_COMMANDS & CFG_CMD_NET)

#if defined(CONFIG_NET_MULTI)

puts ("Net: ");

#endif

eth_initialize(gd->bd);

#endif


/* 上面的函数看看就能理解了,基本也没做什么。。。 */


/* main_loop() can return to retry autoboot, if so just run it again. */

for (;;) {

main_loop ();

}


/* 到这,start_armboot函数就执行完了,可以看到,这个函数的最后是一个死循环,联想uboot的界面,可以理解,uboot一直在等待用户输入命令信息,然后解析这些命令,根据不用的命令来作出不用的操作。所以下面我们来分析这个main_loop()函数。 */


16main_loop()函数:


void main_loop (void)

{

#ifndef CFG_HUSH_PARSER

static char lastcommand[CFG_CBSIZE] = { 0, };

int len;

int rc = 1;

int flag;

#endif


#if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >= 0)

char *s;

int bootdelay;

#endif


#if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >= 0)

s = getenv ("bootdelay");

bootdelay = s ? (int)simple_strtol(s, NULL, 10) : CONFIG_BOOTDELAY;


/* 通过getenv函数,去环境变量中取bootdelay的值,如果存在的话,就采用环境变量中设置的值,如果不存在的话,就使用配置中的值CONFIG_BOOTDELAY*/


debug ("### main_loop entered: bootdelay=%d\n\n", bootdelay);


s = getenv ("bootcmd"); /* 到环境变量中去取启动命令字符串。 */


debug ("### main_loop: bootcmd=\"%s\"\n", s ? s : " ");


if (bootdelay >= 0 && s && !abortboot (bootdelay)) {

run_command (s, 0);

}

#endif /* CONFIG_BOOTDELAY */


/* 注意这个if语句,必须这三者同时满足条件的话才往下进行这个run_command()函数,否则跳出这个if语句,进入下面的for循环。其中用到了延时函数(abortboot),若在延时期间没有按下任何键的话,则返回0,经过!abortboot,变成1,这样函数就能继续往下执行。 */


/*

* Main Loop for Monitor Command Processing

*/

for (;;)

{

len = readline (CFG_PROMPT);


flag = 0; /* assume no special flags for now */


if (len > 0)

strcpy (lastcommand, console_buffer);

else if (len == 0)

flag |= CMD_FLAG_REPEAT;


if (len == -2)

{

/* -2 means timed out, retry autoboot

*/

puts ("\nTimed out waiting for command\n");

return; /* retry autoboot */

}


if (len == -1)

puts (" \n");

else

rc = run_command (lastcommand, flag);


if (rc <= 0)

{

/* invalid command or not repeatable, forget it */

lastcommand[0] = 0;

}

}

}


/* 这个主循环中,重点是len = readline (CFG_PROMPT)函数,后面的程序都是解析这个len来分别作出不同的反应:

1> 如果readline函数返回0的话,说明输入了enter键,通过flag |= CMD_FLAG_REPEAT来添加属性,这个flags在后面作为run_command函数的一个参数。

2> 如果readline函数返回-1的话,说明按下了ctrl+c,打印出" \n" 这句话。

3> 如果readline函数返回-2的话,说明超时了,打印出"\nTimed out waiting for command\n"这句话。

4> 如果readline函数返回值len>0的话,通过strcpy (lastcommand, console_buffer);console_buffer里面的值拷贝到 lastcommand中,最后执行run_command (lastcommand, flag)这个语句。


这个CFG_PROMPTinclude/config/smdk2410.h中定义:

#define CFG_PROMPT "SMDK2410 # " /* Monitor Command Prompt */

下面来分析这个readline()函数:

int readline (const char *const prompt)

{

char *p = console_buffer;

int n = 0; /* buffer index */

int plen = 0; /* prompt length */

int col; /* output column cnt */

char c;


/* console_buffer在文件开头的定义如下:

char console_buffer[CFG_CBSIZE]; /* console I/O buffer */

#define CFG_CBSIZE 256 /* Console I/O Buffer Size */

所以这个数组实际上就是一个char类型的数组: console_buffer[256]

*/


/* print prompt */

if (prompt) {

plen = strlen (prompt);

puts (prompt);

}

col = plen;


/* 先打印出来这个 prompt,在smdk2410.h我们定义它为"SMDK2410 # ",我们在使用uboot的时候,可以看到这个提示符,所以我们想要修改提示符的话,可以修改这个宏, */


for (;;) {

WATCHDOG_RESET(); /* Trigger watchdog, if needed */


c = getc();


/*

* Special character handling

*/

switch (c) {

case '\r': /* Enter */

case '\n':

*p = '\0';

puts ("\r\n");

return (p – console_buffer);


/* 判断输入了什么,如果输入enter的话,*p = '\0'p – console_buffer = 0,所以这个readline函数返回0*/


case '\0': /* nul */

continue;


case 0x03: /* ^C – break */

console_buffer[0] = '\0'; /* discard input */

return (-1);


/* 输入ctrl+c的话,返回-1*/


case 0x15: /* ^U - erase line */

while (col > plen) {

puts (erase_seq);

--col;

}

p = console_buffer;

n = 0;

continue;


case 0x17: /* ^W - erase word */

p=delete_char(console_buffer, p, &col, &n, plen);

while ((n > 0) && (*p != ' ')) {

p=delete_char(console_buffer, p, &col, &n, plen);

}

continue;


case 0x08: /* ^H - backspace */

case 0x7F: /* DEL – backspace */

p=delete_char(console_buffer, p, &col, &n, plen);

continue;


default:

/*

* Must be a normal character then

*/

if (n < CFG_CBSIZE-2) {

if (c == '\t') { /* expand TABs */


puts (tab_seq+(col&07));

col += 8 - (col&07);

} else {

++col; /* echo input */

putc (c);

}

*p++ = c;

++n;

} else { /* Buffer full */

putc ('\a');

}

}

}

}


可以看出来,这个main_loop函数的核心就是run_command函数,它根据输入的不同命令,来辨别不同的操作,最后执行这个操作,所以下面分析这个run_command函数。*/


  1. 17run_command函数分析和uboot中命令实现方式。


    run_command函数位于common/main.c中:

int run_command (const char *cmd, int flag)

{

cmd_tbl_t *cmdtp;

char cmdbuf[CFG_CBSIZE]; /* working copy of cmd */

char *token; /* start of token in cmdbuf */

char *sep; /* end of token (separator) in cmdbuf */

char finaltoken[CFG_CBSIZE];

char *str = cmdbuf;

char *argv[CFG_MAXARGS + 1]; /* NULL terminated */

int argc, inquotes;

int repeatable = 1;

int rc = 0;


clear_ctrlc(); /* forget any previous Control C */


if (!cmd || !*cmd) {

return -1; /* empty command */

}

/* 空命令 */


if (strlen(cmd) >= CFG_CBSIZE) {

puts ("## Command too long!\n");

return -1;

}

/* 判断命令的长度是否过长。 */


strcpy (cmdbuf, cmd);


while (*str) {


/*

* Find separator, or string end

* Allow simple escape of ';' by writing "\;"

*/

for (inquotes = 0, sep = str; *sep; sep++) {

if ((*sep=='\'') &&

(*(sep-1) != '\\'))

inquotes=!inquotes;


if (!inquotes &&

(*sep == ';') && /* separator */

( sep != str) && /* past string start */

(*(sep-1) != '\\')) /* and NOT escaped */

break;

}

/* 对于多个命令的处理,uboot中允许用;隔开两个命令一起输入,这处理的就是这种情况。 */


/*

* Limit the token to data between separators

*/

token = str;

if (*sep) {

str = sep + 1; /* start of command for next pass */

*sep = '\0';

}

else

str = sep; /* no more commands for next pass */


/* find macros in this token and replace them */

process_macros (token, finaltoken);

/* 对宏的一些处理。 */


/* Extract arguments */

if ((argc = parse_line (finaltoken, argv)) == 0) {

rc = -1; /* no command at all */

continue;

}

/* 对命令的分析处理,一会在下面继续分析这个函数。 */


/* Look up command in command table */

if ((cmdtp = find_cmd(argv[0])) == NULL) {

printf ("Unknown command '%s' - try 'help'\n", argv[0]);

rc = -1; /* give up after bad command */

continue;

}

/* 寻找命令函数,从保存的命令中找到与输入命令所匹配的,同样,一会分析它。 */


/* found - check max args */

if (argc > cmdtp->maxargs) {

printf ("Usage:\n%s\n", cmdtp->usage);

rc = -1;

continue;

}


#if (CONFIG_COMMANDS & CFG_CMD_BOOTD)

/* avoid "bootd" recursion */

if (cmdtp->cmd == do_bootd) {


if (flag & CMD_FLAG_BOOTD) {

puts ("'bootd' recursion detected\n");

rc = -1;

continue;

} else {

flag |= CMD_FLAG_BOOTD;

}

}

#endif /* CFG_CMD_BOOTD */


/* run_command函数中传入的flags参数,与所定义的宏进行比对,然后执行相关的操作,这两个宏定义如下:#define CMD_FLAG_BOOTD 0x0002 /* command is from bootd */

*/


/* OK - call function to do the command */

if ((cmdtp->cmd) (cmdtp, flag, argc, argv) != 0) {

rc = -1;

}


repeatable &= cmdtp->repeatable;


/* Did the user stop this? */

if (had_ctrlc ())

return 0; /* if stopped then not repeatable */

}


return rc ? rc : repeatable;

}


在这里,有一个很重要的结构体,cmd_tbl_t,它贯穿了命令实现的始终,在include/command.h中定义:


struct cmd_tbl_s {

char *name; /* Command Name */

int maxargs; /* maximum number of arguments */

int repeatable; /* autorepeat allowed? */

/* Implementation function */

int (*cmd)(struct cmd_tbl_s *, int, int, char *[]);

char *usage; /* Usage message (short) */

char *help; /* Help message (long) */

};


typedef struct cmd_tbl_s cmd_tbl_t;


这个结构体里面包含命令的名字,最大参数数目,是否可重复,usagehelp信息,以及一个执行函数。对于这个是否可重复性是指,在uboot等待用户输入状态下,直接敲回车键,自动执行刚执行过的上一条命令,这个在uboot的使用过程中应该可以发现。

可以分析这个run_command函数,大体过程是这样的:

1> 对输入的命令进行一些简单的处理。

2> 有一个while (*str)循环,在这个循环里面,首先对命令分析,看它是否是两个命令一起输入的。

3> 然后是对命令中的宏进行处理,通过这个process_macros函数。

4> 对命令中的参数进行分析,比如说有的命令只有一个参数,如help,有的命令有2个参数,比如bootm 30000000,对于这两种不同的情况,通过这个parse_line函数进行处理,将命令分别保存在argv[0],argv[1]....中。

5> 通过find_cmd函数来找到与输入命令所匹配的命令操作。

6> 最后通过cmdtp->cmd来调用操作函数。


下面来分析parse_line函数,它在common/main.c中:

int parse_line (char *line, char *argv[])

{

int nargs = 0;

while (nargs < CFG_MAXARGS) {


/* skip any white space */

while ((*line == ' ') || (*line == '\t')) {

++line;

} /* 跳过空格 */


if (*line == '\0') { /* end of line, no more args */

argv[nargs] = NULL;

return (nargs);

}


/* 如果为空命令的话,直接让这个函数返回0,并且argv[0]=NULL*/


argv[nargs++] = line; /* begin of argument string */


/* find end of string */

while (*line && (*line != ' ') && (*line != '\t')) {

++line;

}


if (*line == '\0') { /* end of line, no more args */

argv[nargs] = NULL;

return (nargs);

}


/* line填写进argv[0],argv[1]...中,并且使最后一个argv[nargs]=NULL,最后返回argv[]的个数 nargs*/


*line++ = '\0'; /* terminate current arg */

}

/* end of while (nargs < CFG_MAXARGS) */


printf ("** Too many args (max. %d) **\n", CFG_MAXARGS);

return (nargs);

}


这个parse_line的返回值为argv[]的个数,这样run_command函数就可以根据这些返回值进行操作了。


下面分析这个find_cmd函数:

这个函数同样在common/command.c中:

cmd_tbl_t *find_cmd (const char *cmd)

{

cmd_tbl_t *cmdtp;

cmd_tbl_t *cmdtp_temp = &__u_boot_cmd_start; /*Init value */

const char *p;

int len;

int n_found = 0;


/*

* Some commands allow length modifiers (like "cp.b");

* compare command name only until first dot.

*/

len = ((p = strchr(cmd, '.')) == NULL) ? strlen (cmd) : (p - cmd);


for (cmdtp = &__u_boot_cmd_start;

cmdtp != &__u_boot_cmd_end;

cmdtp++) {

if (strncmp (cmd, cmdtp->name, len) == 0) {

if (len == strlen (cmdtp->name))

return cmdtp; /* full match */


cmdtp_temp = cmdtp; /* abbreviated command ? */

n_found++;

}

}

if (n_found == 1) { /* exactly one match */

return cmdtp_temp;

}


return NULL; /* not found or ambiguous command */

}


先大致浏览这个函数,它通过从__u_boot_cmd_start__u_boot_cmd_end之间的区域去搜索匹配的命令,我们跟踪__u_boot_cmd_start的定义,可以在include/command.h中看到有这样的定义:

extern cmd_tbl_t __u_boot_cmd_start;

extern cmd_tbl_t __u_boot_cmd_end;

但是搜索完源码都没有发现这两个变量的定义,它们其实是在board/smdk2410/uboot.lds中定义的:

SECTIONS

{

. = 0x00000000;


. = ALIGN(4);

.text :

{

cpu/arm920t/start.o (.text)

*(.text)

}


. = ALIGN(4);

.rodata : { *(.rodata) }


. = ALIGN(4);

.data : { *(.data) }


. = ALIGN(4);

.got : { *(.got) }


. = .;

__u_boot_cmd_start = .;

.u_boot_cmd : { *(.u_boot_cmd) }

__u_boot_cmd_end = .;


. = ALIGN(4);

__bss_start = .;

.bss : { *(.bss) }

_end = .;

}


他们是怎么跑到链接脚本里面了?链接脚本中位于__u_boot_cmd_start__u_boot_cmd_end之间是.u_boot_cmd段,那么这个段究竟里面是什么?我们在源码中搜索这个,可以发现如下的内容:


#define Struct_Section __attribute__ ((unused,section (".u_boot_cmd")))


bootm 30000000为例,尽管搜索到了如上所示的内容,但是仍然没法知道调用了哪个函数,于是我们继续搜索bootm,在common/cmd_bootm.c中发现如下内容:


U_BOOT_CMD(

bootm, CFG_MAXARGS, 1, do_bootm,

"bootm - boot application image from memory\n",

"[addr [arg ...]]\n - boot application image stored in memory\n"

"\tpassing arguments 'arg ...'; when booting a Linux kernel,\n"

"\t'arg' can be the address of an initrd image\n"

#ifdef CONFIG_OF_FLAT_TREE

"\tWhen booting a Linux kernel which requires a flat device-tree\n"

"\ta third argument is required which is the address of the of the\n"

"\tdevice-tree blob. To boot that kernel without an initrd image,\n"

"\tuse a '-' for the second argument. If you do not pass a third\n"

"\ta bd_info struct will be passed instead\n"

#endif

);


继续搜索U_BOOT_CMD这个宏,可以在include/command.h中发现如下定义:


#define U_BOOT_CMD(name,maxargs,rep,cmd,usage,help) \

cmd_tbl_t __u_boot_cmd_##name Struct_Section = {#name, maxargs, rep, cmd, usage, help}


继续以bootm为例来分析这个宏,将U_BOOT_CMD宏的内容填充进去,其中##是连词符:

cmd_tbl_t __u_boot_cmd_bootm __attribute__ ((unused,section (".u_boot_cmd"))) = {bootm, CFG_MAXARGS, 1, do_bootm, usage, help}

usage = "bootm - boot application image from memory\n"

help = "[addr [arg ...]]\n - boot application image stored in memory\n"

"\tpassing arguments 'arg ...'; when booting a Linux kernel,\n"

"\t'arg' can be the address of an initrd image\n"


可以看出来,这个宏定义了一个cmd_tbl_t类型的变量__u_boot_cmd_bootm,这个变量带有一个属性__attribute__ ((unused,section (".u_boot_cmd"))),即将这个变量的段属性设置成.u_boot_cmd类型的,这样,所有的命令都以这种形式存在链接脚本指定的位置了。开始地址就是__u_boot_cmd_start,结束地址是__u_boot_cmd_end。所以,我们想要给uboot中添加啊一个命令的话,肯定也是构建这样的一个宏,宏里面包含命令的一些参数。


这样再返回来看这个find_cmd函数,就很好理解了。


run_command函数从find_cmd函数处继续执行,最后通过

(cmdtp->cmd) (cmdtp, flag, argc, argv)这个命令来执行与输入命令所匹配的函数。


至此,我们就分析完了run_command函数。


  1. 最后分析do_bootm函数。


do_bootm函数位于common/cmd_bootm.c中,在分析这个函数之前,我们先将image_header_t这个结构体分析一下,因为在这个do_bootm中会一直用到这个结构体中的变量,这个结构体位于include/image.h中,如下所示:

typedef struct image_header {

uint32_t ih_magic; /* Image Header Magic Number */

uint32_t ih_hcrc; /* Image Header CRC Checksum */

uint32_t ih_time; /* Image Creation Timestamp */

uint32_t ih_size; /* Image Data Size */

uint32_t ih_load; /* Data Load Address */

uint32_t ih_ep; /* Entry Point Address */

uint32_t ih_dcrc; /* Image Data CRC Checksum */

uint8_t ih_os; /* Operating System */

uint8_t ih_arch; /* CPU architecture */

uint8_t ih_type; /* Image Type */

uint8_t ih_comp; /* Compression Type */

uint8_t ih_name[IH_NMLEN]; /* Image Name */

} image_header_t;

简单分析一下他的成员, ih_magic是幻数, ih_hcrc是头信息校验, ih_load是加载地址,是Image所放的位置,我们比较关心这个成员, ih_ep是入口地址,也是我们关心的一个成员, ih_dcrc是数据校验, ih_osOperating System,即操作系统的类型,ih_arch是架构相关的信息, ih_type是 映像的类型。


下面继续分析do_bootm函数:

bootm这个命令用于启动一个操作系统映像,它会从映像文件的头部取得一些信息,这些信息包括:
映像文件的基于的cpu架构、其操作系统类型、映像的类型、压缩方式、映像文件在内存中的加载地址、
映像文件运行的入口地址、映像文件名等。
紧接着bootm将映像加载到指定的地址,如果需要的话,还会解压映像并传递必要有参数给内核,最后
跳到入口地址进入内核。

int do_bootm (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])

{

ulong iflag;

ulong addr;

ulong data, len, checksum;

ulong *len_ptr;

uint unc_len = CFG_BOOTM_LEN;

int i, verify;

char *name, *s;

int (*appl)(int, char *[]);

image_header_t *hdr = &header;


s = getenv ("verify");

verify = (s && (*s == 'n')) ? 0 : 1;

/* 从环境变量中获取"verify",最后这个信息需要传到do_bootm_linux函数中。 */


if (argc < 2) {

addr = load_addr;

} else {

addr = simple_strtoul(argv[1], NULL, 16);

}


/* 如果输入的命令的argc < 2的话,即直接输入bootm命令,此时映像存储地址使用默认的load_addr

ulong load_addr = CFG_LOAD_ADDR; /* Default Load Address */

#define CFG_LOAD_ADDR 0x33000000 /* default load address */

在前面是这样定义的,有一个默认的映像存储地址。但是在uboot运行期间,load_addr的值会被环境变量所更改。如果bootm后面还带有一个参数,如bootm 30000000,将这个30000000存到addr中。 */


SHOW_BOOT_PROGRESS (1);

printf ("## Booting image at %08lx ...\n", addr);


/* Copy header so we can blank CRC field for re-calculation */

#ifdef CONFIG_HAS_DATAFLASH

if (addr_dataflash(addr)){

read_dataflash(addr, sizeof(image_header_t), (char *)&header);

} else

#endif


/* 上面这个宏没有定义,所以不会执行。 */


memmove (&header, (char *)addr, sizeof(image_header_t));


/* 将我们输入的这个addr的值存到header中,这样就可以使用这个hdr了。 */


if (ntohl(hdr->ih_magic) != IH_MAGIC) {

{

puts ("Bad Magic Number\n");

SHOW_BOOT_PROGRESS (-1);

return 1;

}

}

SHOW_BOOT_PROGRESS (2);


/* 如果hdr里面的ih_magic不等于预定义的幻数的话,就打印出“Bad Magic Number”并返回。

#define IH_MAGIC 0x27051956 /* Image Magic Number */

*/


data = (ulong)&header;

len = sizeof(image_header_t);


/* 现在这个data里面保存的就是我们所输入的这个addr地址。lenimage_header_t 结构体的长度。 */


checksum = ntohl(hdr->ih_hcrc);

hdr->ih_hcrc = 0;


if (crc32 (0, (uchar *)data, len) != checksum) {

puts ("Bad Header Checksum\n");

SHOW_BOOT_PROGRESS (-2);

return 1;

}

SHOW_BOOT_PROGRESS (3);


/* hdr->ih_hcrc里面保存的是头信息校验信息,用crc32校验它是否正确。 */


#ifdef CONFIG_HAS_DATAFLASH

if (addr_dataflash(addr)){

len = ntohl(hdr->ih_size) + sizeof(image_header_t);

read_dataflash(addr, len, (char *)CFG_LOAD_ADDR);

addr = CFG_LOAD_ADDR;

}

#endif

/* 没有定义这个宏,不会执行。 */


/* for multi-file images we need the data part, too */

print_image_hdr ((image_header_t *)addr);

/* 打印出一些信息,如Image类型,创建时间,加载地址等。 */


data = addr + sizeof(image_header_t);

len = ntohl(hdr->ih_size);


if (verify) {

puts (" Verifying Checksum ... ");

if (crc32 (0, (uchar *)data, len) != ntohl(hdr->ih_dcrc)) {

printf ("Bad Data CRC\n");

SHOW_BOOT_PROGRESS (-3);

return 1;

}

puts ("OK\n");

}

SHOW_BOOT_PROGRESS (4);


len_ptr = (ulong *)data;


#if defined(__PPC__)

if (hdr->ih_arch != IH_CPU_PPC)

#elif defined(__ARM__)

if (hdr->ih_arch != IH_CPU_ARM)

#elif defined(__I386__)

if (hdr->ih_arch != IH_CPU_I386)

#elif defined(__mips__)

if (hdr->ih_arch != IH_CPU_MIPS)

#elif defined(__nios__)

if (hdr->ih_arch != IH_CPU_NIOS)

#elif defined(__M68K__)

if (hdr->ih_arch != IH_CPU_M68K)

#elif defined(__microblaze__)

if (hdr->ih_arch != IH_CPU_MICROBLAZE)

#elif defined(__nios2__)

if (hdr->ih_arch != IH_CPU_NIOS2)

#elif defined(__blackfin__)

if (hdr->ih_arch != IH_CPU_BLACKFIN)

#elif defined(__avr32__)

if (hdr->ih_arch != IH_CPU_AVR32)

#else

# error Unknown CPU type

#endif

{

printf ("Unsupported Architecture 0x%x\n", hdr->ih_arch);

SHOW_BOOT_PROGRESS (-4);

return 1;

}

SHOW_BOOT_PROGRESS (5);

/* 上面这一段代码没看懂想要检查什么,这些宏都没有定义。 */


switch (hdr->ih_type) {

case IH_TYPE_STANDALONE:

name = "Standalone Application";

/* A second argument overwrites the load address */

if (argc > 2) {

hdr->ih_load = htonl(simple_strtoul(argv[2], NULL, 16));

}

break;

case IH_TYPE_KERNEL:

name = "Kernel Image";

break;

case IH_TYPE_MULTI:

name = "Multi-File Image";

len = ntohl(len_ptr[0]);

/* OS kernel is always the first image */

data += 8; /* kernel_len + terminator */

for (i=1; len_ptr[i]; ++i)

data += 4;

break;

default: printf ("Wrong Image Type for %s command\n", cmdtp->name);

SHOW_BOOT_PROGRESS (-5);

return 1;

}

SHOW_BOOT_PROGRESS (6);


/* hdr->ih_type里面保存的是映像的类型,这里是先检查映像的类型,有kernel,ramdisk,multi,firmware等类型,根据不同的类型来获得真正的Image data(即不包括头信息的image)的起始地址(data)和大小(len)。 */


/*

* We have reached the point of no return: we are going to

* overwrite all exception vector code, so we cannot easily

* recover from any failures any more...

*/


iflag = disable_interrupts(); /* 关中断 */


#ifdef CONFIG_AMIGAONEG3SE

/*

* We've possible left the caches enabled during

* bios emulation, so turn them off again

*/

icache_disable();

invalidate_l1_instruction_cache();

flush_data_cache();

dcache_disable();

#endif

/* 未定义的宏,不会执行。 */


switch (hdr->ih_comp) {

case IH_COMP_NONE:

if(ntohl(hdr->ih_load) == addr) {

printf (" XIP %s ... ", name);

} else {

#if defined(CONFIG_HW_WATCHDOG) || defined(CONFIG_WATCHDOG)

size_t l = len;

void *to = (void *)ntohl(hdr->ih_load);

void *from = (void *)data;


printf (" Loading %s ... ", name);


while (l > 0) {

size_t tail = (l > CHUNKSZ) ? CHUNKSZ : l;

WATCHDOG_RESET();

memmove (to, from, tail);

to += tail;

from += tail;

l -= tail;

}

#else /* !(CONFIG_HW_WATCHDOG || CONFIG_WATCHDOG) */

memmove ((void *) ntohl(hdr->ih_load), (uchar *)data, len);

#endif /* CONFIG_HW_WATCHDOG || CONFIG_WATCHDOG */

}

break;

case IH_COMP_GZIP:

printf (" Uncompressing %s ... ", name);

if (gunzip ((void *)ntohl(hdr->ih_load), unc_len,

(uchar *)data, &len) != 0) {

puts ("GUNZIP ERROR - must RESET board to recover\n");

SHOW_BOOT_PROGRESS (-6);

do_reset (cmdtp, flag, argc, argv);

}

break;

#ifdef CONFIG_BZIP2

case IH_COMP_BZIP2:

printf (" Uncompressing %s ... ", name);

/*

* If we've got less than 4 MB of malloc() space,

* use slower decompression algorithm which requires

* at most 2300 KB of memory.

*/

i = BZ2_bzBuffToBuffDecompress ((char*)ntohl(hdr->ih_load),

&unc_len, (char *)data, len,

CFG_MALLOC_LEN < (4096 * 1024), 0);

if (i != BZ_OK) {

printf ("BUNZIP2 ERROR %d - must RESET board to recover\n", i);

SHOW_BOOT_PROGRESS (-6);

udelay(100000);

do_reset (cmdtp, flag, argc, argv);

}

break;

#endif /* CONFIG_BZIP2 */

default:

if (iflag)

enable_interrupts();

printf ("Unimplemented compression type %d\n", hdr->ih_comp);

SHOW_BOOT_PROGRESS (-7);

return 1;

}

puts ("OK\n");

SHOW_BOOT_PROGRESS (7);


/* hdr->ih_comp里面保存的是image的压缩方式,IH_COMP_NONE 表示未压缩的imageIH_COMP_GZIP 表示gzip的压缩方式,IH_COMP_BZIP2 表示gzip2的压缩方式,根据压缩方式来选择将要执行的动作,对于 IH_COMP_NONE类型的,判断它的加载地址是否与它头信息里面的加载地址相同,如果相同打印出"XIP %s ... ",如果不同,则调用 memmove函数,将image移动到头信息执行的加载地址。 */


switch (hdr->ih_type) {

case IH_TYPE_STANDALONE:

if (iflag)

enable_interrupts();


/* load (and uncompress), but don't start if "autostart"

* is set to "no"

*/

if (((s = getenv("autostart")) != NULL) && (strcmp(s,"no") == 0)) {

char buf[32];

sprintf(buf, "%lX", len);

setenv("filesize", buf);

return 0;

}

appl = (int (*)(int, char *[]))ntohl(hdr->ih_ep);

(*appl)(argc-1, &argv[1]);

return 0;

case IH_TYPE_KERNEL:

case IH_TYPE_MULTI:

/* handled below */

break;

default:

if (iflag)

enable_interrupts();

printf ("Can't boot image type %d\n", hdr->ih_type);

SHOW_BOOT_PROGRESS (-8);

return 1;

}

SHOW_BOOT_PROGRESS (8);

/* hdr->ih_type里面保存的是映像的类型,在前面已经设置过一部分内容,现在这里再设置一些内容。 */


switch (hdr->ih_os) {

default: /* handled by (original) Linux case */

case IH_OS_LINUX:

#ifdef CONFIG_SILENT_CONSOLE

fixup_silent_linux();

#endif

do_bootm_linux (cmdtp, flag, argc, argv,

addr, len_ptr, verify);

break;

case IH_OS_NETBSD:

do_bootm_netbsd (cmdtp, flag, argc, argv,

addr, len_ptr, verify);

break;


#ifdef CONFIG_LYNXKDI

case IH_OS_LYNXOS:

do_bootm_lynxkdi (cmdtp, flag, argc, argv,

addr, len_ptr, verify);

break;

#endif


case IH_OS_RTEMS:

do_bootm_rtems (cmdtp, flag, argc, argv,

addr, len_ptr, verify);

break;


#if (CONFIG_COMMANDS & CFG_CMD_ELF)

case IH_OS_VXWORKS:

do_bootm_vxworks (cmdtp, flag, argc, argv,

addr, len_ptr, verify);

break;

case IH_OS_QNX:

do_bootm_qnxelf (cmdtp, flag, argc, argv,

addr, len_ptr, verify);

break;

#endif /* CFG_CMD_ELF */

#ifdef CONFIG_ARTOS

case IH_OS_ARTOS:

do_bootm_artos (cmdtp, flag, argc, argv,

addr, len_ptr, verify);

break;

#endif

}


SHOW_BOOT_PROGRESS (-9);

#ifdef DEBUG

puts ("\n## Control returned to monitor - resetting...\n");

do_reset (cmdtp, flag, argc, argv);

#endif

return 1;

}

/* hdr->ih_os里面保存的是操作系统的类型,根据不同的操作系统类型进去不同的入口中,对于linux,就进入了相应的do_bootm_linux函数中。 */


  1. 下面进入这个do_bootm_linux函数中,注意这个函数在很多文件里面都有定义,它是架构相关的,我们需要进入的是lib_arm/armlinux.c中的这个:


void do_bootm_linux (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[],

ulong addr, ulong *len_ptr, int verify)

{

ulong len = 0, checksum;

ulong initrd_start, initrd_end;

ulong data;

void (*theKernel)(int zero, int arch, uint params);

image_header_t *hdr = &header;

bd_t *bd = gd->bd;


#ifdef CONFIG_CMDLINE_TAG

char *commandline = getenv ("bootargs");

#endif


theKernel = (void (*)(int, int, uint))ntohl(hdr->ih_ep);

/* 将这个函数指针指向入口地址。 */


/*

* Check if there is an initrd image

*/

if (argc >= 3) {

SHOW_BOOT_PROGRESS (9);


addr = simple_strtoul (argv[2], NULL, 16);


printf ("## Loading Ramdisk Image at %08lx ...\n", addr);


/* Copy header so we can blank CRC field for re-calculation */

#ifdef CONFIG_HAS_DATAFLASH

if (addr_dataflash (addr)) {

read_dataflash (addr, sizeof (image_header_t),

(char *) &header);

} else

#endif

memcpy (&header, (char *) addr, sizeof (image_header_t));


if (ntohl (hdr->ih_magic) != IH_MAGIC) {

printf ("Bad Magic Number\n");

SHOW_BOOT_PROGRESS (-10);

do_reset (cmdtp, flag, argc, argv);

}

/* 检验 bootargs里面是否有initrd的信息,如果有的话,对这些信息进行处理。 */


data = (ulong) & header;

len = sizeof (image_header_t);


checksum = ntohl (hdr->ih_hcrc);

hdr->ih_hcrc = 0;


if (crc32 (0, (unsigned char *) data, len) != checksum) {

printf ("Bad Header Checksum\n");

SHOW_BOOT_PROGRESS (-11);

do_reset (cmdtp, flag, argc, argv);

}


SHOW_BOOT_PROGRESS (10);


print_image_hdr (hdr);


data = addr + sizeof (image_header_t);

len = ntohl (hdr->ih_size);


#ifdef CONFIG_HAS_DATAFLASH

if (addr_dataflash (addr)) {

read_dataflash (data, len, (char *) CFG_LOAD_ADDR);

data = CFG_LOAD_ADDR;

}

#endif


/* 再次做一些检验等信息。。。之前在do_bootm中做过一些。 */


if (verify) {

ulong csum = 0;


printf (" Verifying Checksum ... ");

csum = crc32 (0, (unsigned char *) data, len);

if (csum != ntohl (hdr->ih_dcrc)) {

printf ("Bad Data CRC\n");

SHOW_BOOT_PROGRESS (-12);

do_reset (cmdtp, flag, argc, argv);

}

printf ("OK\n");

}


SHOW_BOOT_PROGRESS (11);


if ((hdr->ih_os != IH_OS_LINUX) ||

(hdr->ih_arch != IH_CPU_ARM) ||

(hdr->ih_type != IH_TYPE_RAMDISK)) {

printf ("No Linux ARM Ramdisk Image\n");

SHOW_BOOT_PROGRESS (-13);

do_reset (cmdtp, flag, argc, argv);

}


#if defined(CONFIG_B2) || defined(CONFIG_EVB4510) || defined(CONFIG_ARMADILLO)

/*

*we need to copy the ramdisk to SRAM to let Linux boot

*/

memmove ((void *) ntohl(hdr->ih_load), (uchar *)data, len);

data = ntohl(hdr->ih_load);

#endif /* CONFIG_B2 || CONFIG_EVB4510 */

/* 没有定义这些宏,不会执行。 */


/*

* Now check if we have a multifile image

*/

} /* end if (argc >= 3) */

else if ((hdr->ih_type == IH_TYPE_MULTI) && (len_ptr[1])) {

ulong tail = ntohl (len_ptr[0]) % 4;

int i;


SHOW_BOOT_PROGRESS (13);


/* skip kernel length and terminator */

data = (ulong) (&len_ptr[2]);

/* skip any additional image length fields */

for (i = 1; len_ptr[i]; ++i)

data += 4;

/* add kernel length, and align */

data += ntohl (len_ptr[0]);

if (tail) {

data += 4 - tail;

}


len = ntohl (len_ptr[1]);


} else {

/*

* no initrd image

*/

SHOW_BOOT_PROGRESS (14);


len = data = 0;

}


#ifdef DEBUG

if (!data) {

printf ("No initrd\n");

}

#endif


if (data) {

initrd_start = data;

initrd_end = initrd_start + len;

} else {

initrd_start = 0;

initrd_end = 0;

}


SHOW_BOOT_PROGRESS (15);


debug ("## Transferring control to Linux (at address %08lx) ...\n",

(ulong) theKernel);


#if defined (CONFIG_SETUP_MEMORY_TAGS) || \

defined (CONFIG_CMDLINE_TAG) || \

defined (CONFIG_INITRD_TAG) || \

defined (CONFIG_SERIAL_TAG) || \

defined (CONFIG_REVISION_TAG) || \

defined (CONFIG_LCD) || \

defined (CONFIG_VFD)

setup_start_tag (bd);

#ifdef CONFIG_SERIAL_TAG

setup_serial_tag (&params);

#endif

#ifdef CONFIG_REVISION_TAG

setup_revision_tag (&params);

#endif

#ifdef CONFIG_SETUP_MEMORY_TAGS

setup_memory_tags (bd);

#endif

#ifdef CONFIG_CMDLINE_TAG

setup_commandline_tag (bd, commandline);

#endif

#ifdef CONFIG_INITRD_TAG

if (initrd_start && initrd_end)

setup_initrd_tag (bd, initrd_start, initrd_end);

#endif

#if defined (CONFIG_VFD) || defined (CONFIG_LCD)

setup_videolfb_tag ((gd_t *) gd);

#endif

setup_end_tag (bd);

#endif


/* 上面就是设置taglist,这些参数是要传给内核的,关于这些tag的设置,在自己写bootloader中写的很清楚了,在这就不写了。 */


/* we assume that the kernel is in place */

printf ("\nStarting kernel ...\n\n");


#ifdef CONFIG_USB_DEVICE

{

extern void udc_disconnect (void);

udc_disconnect ();

}

#endif


cleanup_before_linux ();

/* 进入内核前,需要关中断,关i/d-cache*/


theKernel (0, bd->bi_arch_number, bd->bi_boot_params);

}


在这个函数中,重要的就是为内核执行入口地址,设置taglist,最后调用这个theKernel函数,我在程序中用黄色背景标出了。这样,就可以启动内核了。

<script>window._bd_share_config={"common":{"bdSnsKey":{},"bdText":"","bdMini":"2","bdMiniList":false,"bdPic":"","bdStyle":"0","bdSize":"16"},"share":{}};with(document)0[(getElementsByTagName('head')[0]||body).appendChild(createElement('script')).src='http://bdimg.share.baidu.com/static/api/js/share.js?v=89860593.js?cdnversion='+~(-new Date()/36e5)];</script>
阅读(286) | 评论(0) | 转发(0) |
0

上一篇:自己动手写一个简单的bootloader

下一篇:driver: Linux设备模型之input子系统详解

相关热门文章
  • u-boot下mkconfig脚本具体含义...
  • SHTML是什么_SSI有什么用...
  • 卡尔曼滤波的原理说明...
  • shell中字符串操作
  • 关于java中的“错误:找不到或...
  • linux设备驱动归纳总结...
  • linux dhcp peizhi roc
  • 关于Unix文件的软链接
  • 求教这个命令什么意思,我是新...
  • sed -e "/grep/d" 是什么意思...
  • 谁能够帮我解决LINUX 2.6 10...
给主人留下些什么吧!~~
评论热议

http://www.ppmy.cn/news/334830.html

相关文章

Interventional Contrastive Learning with Meta Semantic Regularizer

1. 摘要 基于对比学习(CL)的自我监督学习模型以成对的方式学习视觉表征。虽然目前流行的CL模型已经取得了很大的进展&#xff0c;但在本文中&#xff0c;我们发现了一个一直被忽视的现象&#xff1a;当用完整图像训练CL模型时&#xff0c;在完整图像中测试的性能比在前景区域测…

opencv图像算法

图像的对比度增强 一&#xff1a; 绘制直方图 就是把各个像素值所含有的个数统计出来&#xff0c;然后画图表示。 可以看到在当前图像中&#xff0c;哪个像素值的个数最多。 同时&#xff0c;可以看当前图像总体的像素值大小在哪些范围。。靠近0的话&#xff0c;说明图像偏暗…

Linux Interrupt

在面试的时候我们常常问或者被问一个问题&#xff1a;几种中断下半部机制softirq、tasklet、workqueue有什么区别&#xff1f;linux为什么要设计这几种机制&#xff1f;真正能够回答清楚的人还是少数的。下面我们就详细分析一下这其中的区别。 本文的代码分析基于linux kernel …

gem5 arm架构 fullsystem spec2017 benchmark 仿真

gem5 system emulation 模式&#xff0c;内部实现了对system call的模拟&#xff0c;使用了一段时间后&#xff0c;有一些发现: 如果使用spec2017 X86编译&#xff0c;那么会存在对intel比较新的指令不支持的问题&#xff1b;后来使用gcc march K6 m32来解决&#xff0c;即使用…

内存分配存在性能瓶颈怎么办?

title: 性能优化-使用高效内存分配器 date: 2020-12-20 22:00:00 comments: true categories: 性能优化 tags: [性能优化] 性能优化是一个常有的事情&#xff0c;通常来说 不要过早优化-当你没有性能问题时&#xff0c;不需要过早考虑优化&#xff0c;当然对于一些代价很小&a…

通过fork来剖析Linux内核的内存管理和进程管理(下)

上一篇文章我们讲到fork的时候内存管理相关的内容&#xff0c;时间大概隔了快一周了&#xff0c;发布下篇文章&#xff0c;写文章确实费时费力&#xff0c;需要仔细推敲&#xff0c;原创不易&#xff0c;希望大家多多支持吧。本文讲解fork的时候进程管理相关的内容&#xff0c;…

H3C SE 教程笔记——构建安全优化的广域网(上)

第1篇 广域网安全和优化概述 第1章 企业网模型 随着应用的发展,各种需求不断出现。作为企业IT系统基础的计算机网络,其未来的发展适应企业业务和应用对IT系统越来越高的要求。 1.2 趋势和挑战 信息技术发展至今,包括企业在内的各种组织几乎都已部署了各种各…

06-如何选购电脑内存条?小白装机通俗易懂的电脑内存选购知识指南

内存是电脑中重要的硬件之一&#xff0c;它是与CPU进行沟通的桥梁&#xff0c;无论是电脑还是手机都有内存的&#xff0c;手机运存相当于电脑中的内存。我们在选购电脑内存的时候&#xff0c;通常我们只看内存的品牌和容量以及频率&#xff0c;对内存其它的参数不是太了解&…