uboot完全手册---14

摘要:
本文讲述的u-boot-1.2.0源码,是经笔者修改的代码。其中,每个异常中断的摆放次序,是事先规定的。值得一提的是,当发生异常时,都将执行u-boot-1.2.0cpuarm920tinterrupts.c中定义的中断函数。u-boot代码编写者把它放在CPU上电修改SVC模式后的第一个代码,是可以理解的。

1. u-boot介绍

本次移植采用的是U-Boot-1.2.0版本。

3. U-Boot源码分析

3.1 源码入口的解释

可能大多数的同学上网查资料后都了解到,stage1阶段的启动代码,主要就在start.s文件里。此start.s也是系统上电后执行的第一个代码。它全部由汇编编写。在讲述start.s之前,我们先来了解一下,系统怎么知道它要先去start.s里执行代码。

我们知道,每个可执行的映像Image,肯定会给编译器一个入口,而且是“有且只有一个全局的入口”。我们可以把这个入口放在flash的0x0地址上,然后让系统去找这个0x0即可。

实际上,我们可以通过编写链接文件(lds)和mk文件来告知编译器这些情况。Lds文件可以决定一个可执行代码的各个段的存储位置、入口地址等,详情请参考附录中的文章《u-boot lds文件详解》。这里来说的Mk文件,是在board/下对应开发板子目录中的mk文件。它指定了TEXT_BASE的地址。

3.2 stage1:启动分析

终于开始u-boot源代码的讲述了!本文讲述的u-boot-1.2.0源码,是经笔者修改的代码。不过,笔者也会将它与完整的源码包进行比较分析。首先是start.s文件,刚才说过了,这个是系统启动后运行的第一个代码,我们详细地分析如下:

3.2.1 中断向量表的设置

.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

Start.s文件一开始,就定义了_start的全局变量。也即,在别的文件,照样能引用这个_start变量。这段代码验证了我们之前学过的arm体系的理论知识:中断向量表放在从0x0开始的地方。其中,每个异常中断的摆放次序,是事先规定的。比如第一个必须是reset异常,第二个必须是未定义的指令异常等等。

需要注意的是,在这里,我们也可以理解:为何系统一上电,会自动运行代码。因为系统上电后,会从0x0地方取指令,而0x0处放置的是reset标签,直接就跳去reset标签处去启动系统了。

另外,这里使用了ldr指令。而ldr指令中的label,分别用一个.word伪操作来定义。比如:

_undefined_instruction: .word undefined_instruction

我们用source insight跟踪代码后,发现,undefined_instruction在start.s的后面给出了具体的操作,如下:

undefined_instruction:

get_bad_stack

bad_save_user_regs

bl do_undefined_instruction

在跳转到中断服务子程序之前,先有两个宏代码,一个是对stack的操作,一个是用户regs的保存。然后才能跳转如中断服务子程序中执行。请参考《ARM体系结构与编程》等相关书籍,自然能获得详细的答案。

值得一提的是,当发生异常时,都将执行u-boot-1.2.0cpuarm920t interrupts.c中定义的中断函数。也就是说,start.s中要跳转的这些中断子程序的代码,均在u-boot-1.2.0cpuarm920t interrupts.c中定义。

3.2.2 U-Boot存储器映射定义

该代码段主要是定义u-boot需要使用的一些映射区的label,比如用户堆区、用户栈区、全局数据结构区等。笔者在下页给出了一个图示,把整个u-boot映射的所有区都列出来了,这个图非常经典,网上找的,大家可以好好研究一把。

_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

#ifdef CONFIG_USE_IRQ

/* IRQ stack memory (calculated at run-time) */

.globl IRQ_STACK_START

IRQ_STACK_START:

.word 0x0badc0de

/* IRQ stack memory (calculated at run-time) */

.globl FIQ_STACK_START

FIQ_STACK_START:

.word 0x0badc0de

#endif

从上图也可以清晰地发现,堆和栈是有区别的。而且可以看到,用户栈区是向下递减的,即地址减少的方向生长。

3.2.3 上电后CPUSVC模式

reset:

/* set the cpu to SVC32 mode */

mrs r0,cpsr

bic r0,r0,#0x1f

orr r0,r0,#0xd3

msr cpsr,r0

这是系统复位后执行的“第一个代码段”(严格来说不是)。CPU复位后,系统会立即被设置成SVC模式。记得之前有网友发帖咨询这个问题,问系统复位后,cpu处于哪个处理器模式。这个代码,就回答了这个问题。

从这个代码中,我们也可以得到一个对寄存器操作的经验:读—修改--写。这里先把cpsr的值读到r0中,清除掉我们想修改的bit位,然后用orr指令来保证其他bit位不被改动,并达到修改寄存器低5位值的目的。最后用msr指令把r0的值给cpsr寄存器达到我们的修改目的。

3.2.4 关闭看门狗

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

ldr r0, =pWTCON

mov r1, #0x0

str r1, [r0]

根据S3C2440的datasheet文档,系统启动后,看门狗寄存器是被使能的,所以,如果不在预计的时间内“喂狗”,就有“被狗咬”的可能。别说啥了,赶紧先喂狗。上面这段代码即为喂狗代码。u-boot代码编写者把它放在CPU上电修改SVC模式后的第一个代码,是可以理解的。这个代码,也是修改寄存器的代码,它的思路依旧是:读—修改—写。

实际上,u-boot-1.2.0代码在喂狗代码之前,还有一段代码,如下:

#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

这是定义寄存器用的。比如根据S3C2440的datasheet文档,喂狗寄存器pWTCON的寄存器地址是0x15300000,需要定义后才能使用。同理,这里还定义了时钟除数寄存器CLKDIVN和中断掩码的INTMSK寄存器的地址。在后续代码中会陆续用到。

3.2.5 关掉中断

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

从注释可以看出此段代码的作用:屏蔽掉所有的irq中断。为了屏蔽这些中断,我们只要把INTMSK的所有的bit位都置1即可。INTMSK寄存器共32bit位,每个bit对应着不同的中断源。事实上,笔者认为这个代码是多余的,只是为了“心里更踏实”而已。因为S3C2440的datasheet文档里明确指出,cpu在复位的时候,这个寄存器的值就是0XFFFFFFFF,以防止发生异常中断。

3.2.6 修改时钟除数寄存器

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

/* default FCLK is 120 MHz ! */

ldr r0, =CLKDIVN

mov r1, #0 /* 原先的值是3 ,现在是1:1:1*/

str r1, [r0]

在u-boot-1.2.0源码中,给CLKDIVN寄存器赋值的是#0x3,表示FCLK:HCLK:PCLK = 1:2:4,这里笔者将其比例改为1:1:1,没啥特殊的目的,调试代码的时候试验用的,后来调试完毕,就没有再修改了。

3.2.7 调用cpu_init_crit

#ifndef CONFIG_SKIP_LOWLEVEL_INIT

bl cpu_init_crit

#endif

此段代码指明:若未定义CONFIG_SKIP_LOWLEVEL_INIT,就执行cpu_init_crit。我们当然不会跳过底层的初始化。因为LOWLEVEL_INIT会对我们的SDRAM进行初始化,这对我们的cpu是必要的。根据source insight的索引,我们转到了cpu_init_crit的代码中:

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

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

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

非常符合我们的思维,我们无效掉了指令cache和数据cache,并禁止MMU与cache。为什么会有这一步呢?笔者曾经深受cache的伤害。在调试代码的时候,下载完修改的bin文件后,如果只按复位键,而不关掉板子重新上电,就会造成cache中可能残留之前对cache操作的数据。我们称之为“脏数据”,它会映像我们的调试结果,造成假象。

当然,在这里无效cache和MMU肯定还有别的原因。比如在初始化阶段,可以认为我们只有一个任务在跑,没有必要,也不允许使用地址变换。因此最好应该无效掉MMU。

由于在cpu_init_cri子程序中又一次调用子程序lowlevel_init,因此,需要事先保护好lr寄存器的内容。当返回时候,再恢复它。在进入lowlevel_init之前,有必要详细说一下mov ip, lr,这个语句的ip。

为了使单独编译的C语言程序和汇编程序之间能相互调用,必须为子程序间的调用规定一定的规则。这就是ATPCS规则。它规定了一些子程序间调用的基本规则。在寄存器的使用规则里,寄存器R12作用子程序间的scratch寄存器,记做ip。mov ip, lr语句的ip由此而来。笔者认为,这里使用别的通用寄存器来代替ip,实现的功能也是一样的。详情请参考《ARM体系结构与编程》第6章 ATPCS介绍。

3.2.8 调用lowlevel_init

这个函数在u-boot-1.2.0oardsmdk2410lowlevel_init.S文件中。这是对SDRAM的初始化。

_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控制器相关的寄存器赋值,赋值过程中,采用了一个巧妙的做法,把SDRAM控制器初始化需要用到的13个寄存器的值先保存在文字池(literal pools)中,然后通过LDR伪指令以及.ltorg来访问这个文字池,获取寄存器的值赋值到对应的寄存器地址中去。

很多同学对此代码的两个地址不理解:SMRDATA 与_TEXT_BASE。不理解这两个地址相减之后,到底是一个什么值。为什么要相减呢?

其实编译器进行编译,是按照链接文件进行的。也就是说,编译的时候所有的地址都是相对于这个TEXT_BASE计算出来的。而我们的程序是存放在Flash中的,ARM上电后,假设从nandflash模式启动,那么它会把Nandflash的前4K加载到内存中开始运行,当然是从0x0这个地址开始运行,所以要求我们的代码在还没有搬移到TEXT_BASE(0x38f00000)这个位置以前是不能使用这些label的,只能找到一个相对于0x0的地址出来,才能得到真正的数据。而且这时候,我们编译出来的bin文件是存放在0x0000000的,而不是存放在0x38f00000的。嘿嘿,说的有点乱,不知道有没有把笔者的意思表达出来。关于SDRAM初始化。

3.2.9 代码的搬移

#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

ldr r2, _armboot_start

ldr r3, _bss_start

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

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

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

在SDRAM初始化完毕后,我们开始搬移代码,把代码从原先的0x0开始的位置搬移到内存中的适当的位置继续执行。为啥要搬移代码?原因可能如下:

1、运行速度的考虑。

flash的读写速度远小于SDRAM的读写速度,搬移到SDRAM后,可提高运行效率。

2、空间的考虑。

如果是nandflash启动模式,那么只有4KB的空间供用户使用,实际的代码是永远大于4KB的,因此需要重新开辟空间来进行代码的运行工作。

有些版本的u-boot的代码搬移用C语言来实现:bl CopyCode2Ram,也是可以的。因为这时候,我们完全搭建好了C环境。

在这段代码中,还有一个子程序:beq stack_setup,用来设置栈空间的,我们在下节中讲解。

3.2.10 栈空间的设置

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

这段代码是用来分配各个栈空间的。包括分配动态内存区,全局数据区,IRQ和FIQ的栈空间等。

3.2.11 BSS段的清零

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段。至此,所有的cpu初始化工作(stage1阶段)已经全部结束了。后面的代码,将通过ldr pc, _start_armboot,进入C代码执行。这个C入口的函数,是在u-boot-1.1.6lib_armoard.c文件中。它标志着后续将全面启动C语言程序,同时它也是整个u-boot的主函数。

3.3 stage2:C代码分析

上节提到,start_armboot函数不仅标志着后续将全面启动C语言程序,同时它也是整个u-boot的主函数。那么该函数完成什么操作呢?

3.3.1 gdbd分配空间

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

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

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

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

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之前,我们需要先设置它的地址,并用memset函数为它分配合适的空间。u-boot的注释告知我们,gd和bd是一个可写的指针,实际上不过是一个地址而已。

代码中的这句话:__asm__ __volatile__("": : :"memory");目的就是告诉编译器内存被修改过了。更详细的关于C程序中内嵌汇编的文档,请参考附录中的文献《ARM GCC 内嵌(inline)汇编手册》。

3.3.2 执行初始化列表函数

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

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

hang (); }

}

这是一个for语句,却完成了板子初始化列表函数的功能。我们先来看一下for语句的初始值:init_sequence。用source insight跟踪后发现,它是一个指针数组:

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

#if defined(CONFIG_DISPLAY_CPUINFO)

print_cpuinfo, /* display cpu info (and speed) */

#endif

#if defined(CONFIG_DISPLAY_BOARDINFO)

checkboard, /* display board info */

#endif

dram_init, /* configure available RAM banks */

display_dram_config,

NULL,

};

指针数组的每个成员都对应着一个函数名(函数指针),指向的是init_fnc_t类型的函数。For语句每次都会判断当前的函数是不是NULL,如果是,则跳出for语句,完成当前的板子初始化列表函数的功能。

可能大家都注意到了一个类型:init_fnc_t,它表示什么意思呢?我们看到了在初始化列表函数之前,有一个新的数据类型,它是个typedef语句:

typedef int (init_fnc_t) (void);

可能有的同学对此不太理解,为啥非得用一个typedef呢?笔者认为,可以不用typedef,但是用了init_fnc_t后,团队中别的成员来看代码的时候,会很轻松地知道,这是一个初始化(init)的函数(fnc),增加了代码的可读性。如果您对typedef用法还不是很理解,那就赶紧咯,复习下typedef的用法。我们在附录C中给出了《typedef用法小结》,附录D中给出了《u-boot中typedef应用解析》,以上两篇文档均摘自互联网资料,可供参考。

现在,我们对每个初始化列表函数,都进行分析,由于代码量太大,我们不一一列出代码,大家可以参考u-boot-1.2.0的源码包。

Cpu_init函数,并没有做实质性的工作,而且我们现在暂时没有定义CONFIG_USE_IRQ,因此,代码执行到这里,直接就return 0;

Board_init函数,是初始化与硬件平台有关的函数。它的工作很明显:时钟的设置,引脚IO口的设置,并且在这里把数据cache和指令cache也打开了。以上工作的完成,标志着板子已经准备好工作了。当然,考虑到可能系统会发生意外中断,所以我们还需要初始化中断,让中断也准备好工作,因此u-boot代码中下一步就开始了中断的初始化。

interrupt_init函数,这实际上是定时器的中断初始化。和我们之前的培训课程相符的是,我们看到了中断初始化中的那几个熟悉的寄存器,首先是两个配置寄存器TCFG0和TCFG1。晕倒,怎么代码中只有TCFG0的设置,没有TCFG1的设置?很明显,TCFG1采用的是默认值。然后配置寄存器的下载值,最后打开启动开关,启动定时器工作。

env_init函数,这是对我们板子的环境做出初始化配置。顺便提一下,我们修改的配置文件里,用的是nand flash来存放环境变量的值。

#define CFG_ENV_IS_IN_NAND 1

#define CFG_ENV_OFFSET 0x40000

#define CFG_ENV_SIZE64 0xc000 /* Total Size of Environment Sector */

#define CFG_ENV_SIZE 0x20000 /* Total Size of Environment Sector */

因此,我们在进入u-boot命令行之后,运行的关于环境变量的操作,只要它被保存,saveenv,肯定是save在nandflash中的某个位置。

init_baudrate函数,初始化波特率。我们心里要很明确,初始化波特率,目的只有一个:让串口打印调试信息。因此,下一个函数,肯定是串口的初始化函数。所以,我们可以在调试的时候,先算好波特率的值,直接赋值给gd->bd->bi_baudrate,注释掉该函数中的其他代码。调试完毕,再恢复出原先的代码。这样,我们可以不用考虑别的因素导致串口打印不出信息。

serial_init函数,串口的初始化函数。这里调用了另一个函数来配置串口寄存器:serial_setbrg();在这个函数中,我们看到了关于串口的5个寄存器的配置。关于每个寄存器的更详细的配置信息,请参考ARM技术交流网推出的串口课程讲解部分。

console_init_f函数,这个函数的功能只有一个,就是指出我们目前是使用串口的,因此有此句:gd->have_console = 1;然后直接返回0。

display_banner函数。OK,现在串口初始化完毕,我们可以打印信息了。这是u-boot代码中第一次打印信息。我们可以在这里加入我们自己的代码,比如笔者移植的u-boot代码中,就加入了如下“欢迎”的代码信息:

printf (" ");

printf("************************************************* ");

printf("* * ");

printf("* ARM技术交流网欢迎您! * ");

printf("* www.arm79.com * ");

printf("* * ");

printf("************************************************* ");

出现打印信息后,可以说,u-boot移植已经成功了一半。有了打印信息,我们可以随时用打印信息来调试。初始化列表函数中,还有几个函数,比较简单,我这里就不说了。随后开始的是一系列外设的初始化。

3.3.3 配置可用的flash区:flash_init

当您跟踪到flash_init函数的时候,您会发现,这里只兼容AMD系列的flash芯片,比如LV400及LV800。如果您的开发板上刚好就是AMD的芯片,那么恭喜,您可能就不需要修改flash ID号了。可惜,笔者用的开发板上用的是EON生产的flash芯片。笔者只好把AMD的所有代码,都改成EON的代码。比如,笔者嫌麻烦,直接补上

#define EN29LV160AB_ID 0x2249001c

再来一个:

#define CONFIG_EON_29LV160AB 1

后面再修改FLASH_BANK_SIZE、CFG_MAX_FLASH_SECT、PHYS_FLASH_1等信息,来配置笔者的板子上可用的flash区域。

3.3.4 初始化内存分配函数

mem_malloc_init函数,这是非常关键的一步,请大家引起注意。我们必须配置好内存的起始地址和结束地址,然后把这块区域清零,以便后续来使用它。

3.3.5 nand flash的初始化

这部分代码,可能隐含是不执行的。如果您想使用它,需要自行打开,然后添加自己的nand flash驱动的代码。笔者自己没有写nand flash的代码,而是直接copy别人的代码,拿过来改一改。如果想验证自己修改或者自己写的nand flash的驱动是否正确,可以试着从nand flash中读取或写入一个数据,并用串口打印出来(笔者修改的nand flash驱动代码,将在ARM技术交流网上公布,需要的可以随时下载)。后面的代码,一直到main_loop函数,我们都不需要修改。main_loop函数是进入命令循环的函数,它接受用户从串口输入的命令,然后执行相应的工作,这也是整个u-boot的工作循环。

注意,它并没有使用中断来触发命令的操作,而是用循环来做这部分的工作:

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

for (;;) {

main_loop ();

}

至此,u-boot代码的分析接近尾声。

4. U-Boot移植过程参考

4.1 移植准备

我们采用的是u-boot-1.2.0版本。

4.2 U-Boot移植过程分析

本章节将详细给出整个u-boot移植的过程,您只需要按照此过程操作,即可轻松地移植,并定制属于您自己的u-boot-1.2.0版本到您的开发板上!

说明:交叉编译工具的制作,请自行完成!事实上,许多开发板厂商都给出了详细的制作过程供用户参考。

4.2.1 修改Makefile文件

我们建议,除非您只是体验一次u-boot,而非研究u-boot。否则,请抽时间浏览一下u-boot根目录下的readme文档。这将对您理解u-boot大有帮助。

请点击您的鼠标,打开makefile文件。如果您是在linux环境下开发,使用vi makefile命令可打开该文件。使用ctrl + F键,查找“smdk2400_config”,找到后,您会看到如下代码:

smdk2400_config : unconfig

@$(MKCONFIG) $(@:_config=) arm arm920t smdk2400 NULL s3c24x0

我们解释一下代码:

arm,就表示现在用的是CPU的架构是arm体系结构。

arm920t,指明这是cpu的内核类型,它对应于cpu/arm920t目录。

Smdk2400,这是开发板的型号,它的目录在board/smdk2400目录下。您也可以自己命名您的开发板。比如:ARM79。

NULL,表示开发者或者经销商是谁(vender)。

S3c24x0,表示开发板上的cpu是啥。对于我们的开发板,当然是S3C2440了。

根据以上的解释,我们可以自己模仿着建立自己的编译项:

arm79_config : unconfig

@$(MKCONFIG) $(@:_config=) arm arm920t arm79 NULL s3c24x0

OK,修改完毕,可以保存、退出makefile。

4.2.2 建立自己的开发板文件

为了使得u-boot具有自己的特征,我们需要在board目录下建立自己的文件:

1、复制board/smdk2410,并更名为board/arm79。

2、复制board/smdk2410/smdk2410.c,并更名为board/arm79/arm79.c

OK,我们的开发板是自己花钱买的,现在开发板上面跑的u-boot,我们也可以假装是自己写的代码了。

4.2.3 建立自己的配置文件

配置文件在:include/configs/smdk2410.h。大家还希望用别人的配置文件吗?当然不想!所以,改过来!复制include/configs/smdk2410.h,并更名为:include/configs/arm79.h。这时候,可以暂时保留arm79.h中的配置信息。一会再来修改它。我们现在有更重要的事情要做。

4.2.4 修改交叉编译工具的路径

交叉编译工具,您可以使用开发板公司为您提供的制作包即可。修改交叉编译工具的路径,请参考每个开发板公司的用户手册。这里无法给出一个定性的答案。一般都是在/etc/profile文件下修改,增加一个.bin目录。

4.2.5 测试编译u-boot-1.2.0版本

其实,u-boot虽然号称经典,但是有些版本在某些特定的arm平台或者powerpc平台是编译不通过的。笔者在实习时候,在公司产品上移植了一个u-boot版本,就是不行的。换成u-boot-1.2.0版本,可以编译通过。因此,笔者本次移植也采用了u-boot-1.2.0版本。

cd u-boot-1.2.0 /* 切换到u-boot目录下 */

make arm79_config

这时候,命令行界面上会显示:Configuring for arm79 board…然后您再敲入make,回车。如果您的交叉编译工具安装正确的话,这时候就开始编译了,大约几分钟后,您就会看到窗口中出现了.bin文件的打印信息,回到您的u-bot根目录下,您就会发现,那里多出了一个u-boot.bin文件。

当然,当您在调试的时候,或许您还想得到u-boot的反汇编代码,那么,请再次打开makefile文件,用ctrl + F键,查找到“u-boot.bin”所在的行,大约在第239行(如果您之前没有在makefile中修改别的信息的话):

ALL = $(obj)u-boot.srec $(obj)u-boot.bin $(obj)System.map $(U_BOOT_NAND)

这行的代码,是指定编译后,输出啥文件的。可以看到,编译结果,会输出u-boot.srec文件,u-boot.bin文件,system.map文件,等等。这时候,您如果想让它输出u-boot的反汇编文件,只要这样做:

ALL = $(obj)u-boot.srec $(obj)u-boot.bin $(obj)System.map $(obj)u-boot.dis $(U_BOOT_NAND)

对比一下,发现我们现在增加了“$(obj)u-boot.dis”。对了,这就是指定编译结果,要输出u-boot反汇编文件。

4.2.6 修改配置文件

之前已经提到,笔者的配置文件已经改为arm79.h,目录在include/configs/arm79.h。由于配置文件修改较多,而且是根据具体开发板进行配置的,因此笔者直接给出了修改完的配置文件,并作出详细的注释,希望对您有所帮助!

#ifndef __CONFIG_H

#define __CONFIG_H

/* High Level Configuration Options (easy to change) */

#define CONFIG_ARM920T 1 /* This is an ARM920T Core */

#define CONFIG_S3C2410 1 /* in a SAMSUNG S3C2410 SoC */

#define CONFIG_SMDK2410 1 /* on a SAMSUNG SMDK2410 Board */ /* input clock of PLL */

#define CONFIG_SYS_CLK_FREQ 12000000 /* 输入时钟12M */

#define USE_920T_MMU 1

#undef CONFIG_USE_IRQ /* 暂时不使用IRQ */

/* Size of malloc() pool */

#define CFG_MALLOC_LEN (CFG_ENV_SIZE + 128*1024)

#define CFG_GBL_DATA_SIZE 128 /* size in bytes reserved for initial data */

/*网卡的配置信息 */

#define CONFIG_DRIVER_DM9000 1

#define CONFIG_DM9000_BASE 0x20000300

#define DM9000_IO CONFIG_DM9000_BASE

#define DM9000_DATA (CONFIG_DM9000_BASE + 4)

#define CONFIG_DM9000_USE_16BIT

/* select serial console configuration */

#define CONFIG_SERIAL1 1 /* 使用串口 */

/****RTC *****/

#define CONFIG_RTC_S3C24X0 1

/* allow to overwrite serial and ethaddr */

#define CONFIG_ENV_OVERWRITE

#define CONFIG_BAUDRATE 38400 /* 波特率使用38400 */

/********* Command definition *********/

#define CONFIG_COMMANDS

(CONFIG_CMD_DFL |

CFG_CMD_LOADS |

CFG_CMD_LOADB |

CFG_CMD_CACHE |

CFG_CMD_NAND |

CFG_CMD_FLASH |

CFG_CMD_PING |

/*CFG_CMD_EEPROM |*/

/*CFG_CMD_I2C |*/

/*CFG_CMD_USB |*/

CFG_CMD_REGINFO |

CFG_CMD_DATE |

CFG_CMD_ELF)

/* this must be included AFTER the definition of CONFIG_COMMANDS (if any) */

#include <cmd_confdefs.h>

#define CONFIG_BOOTDELAY 3 /* 进入命令行的等待时间3s */

/*#define CONFIG_BOOTARGS "root=ramfs devfs=mount console=ttySA0,9600" */

/*#define CONFIG_ETHADDR 08:00:3e:26:0a:5b */

#define CONFIG_NETMASK 255.255.255.0

#define CONFIG_IPADDR 10.0.0.110

#define CONFIG_SERVERIP 10.0.0.1

/*#define CONFIG_BOOTFILE "elinos-lart" */

/*#define CONFIG_BOOTCOMMAND "tftp; bootm" */

#if (CONFIG_COMMANDS & CFG_CMD_KGDB)

#define CONFIG_KGDB_BAUDRATE 9600 /* speed to run kgdb serial port */

/* what's this ? it's not used anywhere */

#define CONFIG_KGDB_SER_INDEX 1 /* which serial port to use */

#endif

/* Miscellaneous configurable options */

#define CFG_LONGHELP /* undef to save memory */

#define CFG_PROMPT "[arm79-uboot-1.2.0]# " /* Monitor Command Prompt */

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

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

#define CFG_MAXARGS 16 /* max number of command args */

#define CFG_BARGSIZE CFG_CBSIZE /* Boot Argument Buffer Size */

#define CFG_MEMTEST_START0x30000000 /* memtest works on */

#define CFG_MEMTEST_END 0x33F00000 /* 63 MB in DRAM */

#undef CFG_CLKS_IN_HZ /* everything, incl board info, in Hz */

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

/* the PWM TImer 4 uses a counter of 15625 for 10 ms, so we need */

/* it to wrap 100 times (total 1562500) to get 1 sec. */

#define CFG_HZ 1562500

/* valid baudrates */

#define CFG_BAUDRATE_TABLE { 9600, 19200, 38400, 57600, 115200 }

/The stack sizes are set up in start.S using the settings below */

#define CONFIG_STACKSIZE (128*1024) /* regular stack */

#ifdef CONFIG_USE_IRQ

#define CONFIG_STACKSIZE_IRQ (4*1024) /* IRQ stack */

#define CONFIG_STACKSIZE_FIQ (4*1024) /* FIQ stack */

#endif

/* Physical Memory Map */

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

#define PHYS_FLASH_1 0x00000000 /* Flash Bank #1 */

#define CFG_FLASH_BASE PHYS_FLASH_1

/*FLASH and environment organization */

#if 0

#define CONFIG_AMD_LV400 1 /* uncomment this if you have a LV400 flash */

#define CONFIG_AMD_LV800 1 /* uncomment this if you have a LV800 flash

#endif

#define CONFIG_EON_29LV160AB 1

/*added by www.arm79.con */

#define CFG_MAX_FLASH_BANKS 1 /* max number of memory banks */

#ifdef CONFIG_EON_29LV160AB

#define PHYS_FLASH_SIZE 0x00200000 /* 2MB */

#define CFG_MAX_FLASH_SECT (35) /* max number of sectors on one chip */

#define CFG_ENV_ADDR (CFG_FLASH_BASE + 0x1F0000) /* addr of environment */

#endif

#ifdef CONFIG_AMD_LV800

#define PHYS_FLASH_SIZE 0x00200000 /* 1MB */

#define CFG_MAX_FLASH_SECT (19) /* max number of sectors on one chip */

#define CFG_ENV_ADDR (CFG_FLASH_BASE + 0x1F0000) /* addr of environment */

#endif

#ifdef CONFIG_AMD_LV400

#define PHYS_FLASH_SIZE 0x00080000 /* 512KB */

#define CFG_MAX_FLASH_SECT (11) /* max number of sectors on one chip */

#define CFG_ENV_ADDR (CFG_FLASH_BASE + 0x070000) /* addr of environment */

#endif

/* timeout values are in ticks */

#define CFG_FLASH_ERASE_TOUT (5*CFG_HZ) /* Timeout for Flash Erase */

#define CFG_FLASH_WRITE_TOUT (5*CFG_HZ) /* Timeout for Flash Write */

//#define CFG_ENV_IS_IN_FLASH 1

#define CFG_ENV_IS_IN_NAND 1

#define CFG_ENV_OFFSET 0x40000

#define CFG_ENV_SIZE64 0xc000 /* Total Size of Environment Sector */

#define CFG_ENV_SIZE 0x20000 /* Total Size of Environment Sector */

#define CFG_NAND_BASE 0

#define CFG_MAX_NAND_DEVICE 1

#define NAND_MAX_CHIPS 1

#endif /* __CONFIG_H */

4.2.7 修改start.s文件

这是系统启动运行的第一个文件。大部分代码是不需要修改的,毕竟S3C2410和S3C2440的启动时差别不大的。笔者修改了下时钟:

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

/* default FCLK is 120 MHz ! */

ldr r0, =CLKDIVN

mov r1, #0 /* 原先的值是3 ,现在是1:1:1*/

str r1, [r0]

事实上,没有必要修改这个。笔者也是调试的时候修改的,调试结束,也就没有再改回去。其他地方就不需要修改了:SDRAM初始化部分,代码搬移部分,都可以直接用。

4.2.8 修改board/arm79/arm79.c

这个文件是由原来的board/smdk2410/smdk2410.c来的。笔者修改了这段:

#if FCLK_SPEED==0 /* Fout = 203MHz, Fin = 12MHz for Audio */

#define M_MDIV 0xC3

#define M_PDIV 0x4

#define M_SDIV 0x1

#elif FCLK_SPEED==1 /* Fout = 75MHz */

#define M_MDIV 42 /* 42*/

#define M_PDIV 0x2 /* 0x3 */

#define M_SDIV 0x2

#endif

这段代码修改了MPLL的时钟。它是为了迎合波特率计算公式的设置的。然后在该文件里的board_init函数,笔者把UPLLCON的配置和MPLLCON的配置顺序颠倒下。可能这是2410与2440的区别。S3C2440的datasheet文档中明确规定,必须先初始化UPLLCON,然后延迟一段时间后才能初始化MPLLCON。代码如下:

/* configure UPLL */

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

/* some delay between MPLL and UPLL */

delay(0xffff);

delay(0xffff);

delay(0xffff);

/* configure MPLL */

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

/* some delay between MPLL and UPLL */

delay(0xffff);

delay(0xffff);

delay(0xffff);

另外,笔者修改了该函数里的IO口的初始化配置部分,这是根据笔者开发板上面的硬件结构修改的代码:

/* set up the I/O ports */

gpio->GPACON = 0x007FFFFF;

gpio->GPBCON = 0x00055555;

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 = 0xFF94FFBA;

gpio->GPGUP = 0x0000FFEF;

gpio->GPGDAT = gpio->GPGDAT & (~(1<<4)) | (1<<4) ;

gpio->GPHCON = 0x002AFAAA;

gpio->GPHUP = 0x000007FF;

4.2.9 修改cpu/arm920t/s3c24x0/speed.c

修改该文件,是因为u-boot版本中没有S3C2440对应的版本,只有2410的版本。而2410与2440在计算MPLL的公式上有区别。2440芯片的MPLL计算公式中,多了一个“乘以2”。代码修改的是get_PLLCLK函数:

static ulong get_PLLCLK(int pllreg)

{

S3C24X0_CLOCK_POWER * const clk_power = S3C24X0_GetBase_CLOCK_POWER();

ulong r, m, p, s;

if (pllreg == MPLL)

r = clk_power->MPLLCON;

else if (pllreg == UPLL)

r = clk_power->UPLLCON;

else

hang();

if (pllreg == MPLL)

m = 2*(((r & 0xFF000) >> 12) + 8);

else if (pllreg == UPLL)

m = ((r & 0xFF000) >> 12) + 8;

else

hang();

p = ((r & 0x003F0) >> 4) + 2;

s = r & 0x3;

return((CONFIG_SYS_CLK_FREQ * m) / (p << s));

}

笔者承认,这个代码修改的很不成功。大家可以看到,笔者只是增加了:

if (pllreg == MPLL)

m = 2*(((r & 0xFF000) >> 12) + 8);

而这段代码,根本不具移植性。假设以后出了新的产品,升级版,那么这个代码无法移植,需要重新修改。最好的代码修改思路应该是,在return语句上修改:如果当前是2440的芯片,就return乘以2的时钟;如果是2410芯片,就不乘以2;或者2442的芯片等等。这样,有几个版本的CPU,只要增加这里的代码兼容性即可。

4.2.10 修改board.c文件

由于在修改的时候,还未编写nand flash驱动的代码,所以这时候最好屏蔽掉nand_init函数。本文件中的其他函数不需要修改。

4.2.11 重新编译u-boot

现在,我们可以试一下之前修改的u-boot是否可行。我们执行命令:cd u-boot-1.2.0进入u-boot根目录,然后make一下,执行编译。当生成u-boot.bin文件后,把它用JTAG软件烧到nor flash或者nand flash中,启动开发板,如果之前的修改工作正确的话,就会出现如下界面:

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

* * ARM技术交流网欢迎您!

* *www.arm79.com

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

U-Boot 1.2.0 (Dec 2 2009 - 16:51:34)

U-Boot code: 33F80000 -> 33FA0A4C BSS: -> 33FA5DB4

DRAM: 64 MB

Nor Flash: 2 MB

Nand Flash: 256 MiB

In: serial

Out: serial

Err: serial

[arm79-uboot-1.2.0]#

[arm79-uboot-1.2.0]#

说明您的u-boot移植工作基本完成。但是,我们还需要验证一下它是否可以执行我们需要的命令。所以,我们在这里一边介绍u-boot命令,一边演示。

附A、U-Boot的lds文件详解

对于.lds文件,决定一个可执行程序的各个段的存储位置,以及入口地址,这也是链接定位的作用。这里以u-boot的lds为例说明uboot的链接过程。

首先看一下GNU官方网站上对.lds文件形式的完整描述:

SECTIONS {

...

secname start BLOCK(align) (NOLOAD) : AT ( ldadr )

{ contents } >region :phdr =fill

...

}

secname和contents是必须的,前者用来命名这个段,后者用来确定代码中的什么部分放在这个段,以下是对这个描述中的一些关键字的解释。

1、secname:段名

2、contents:决定哪些内容放在本段,可以是整个目标文件,也可以是目标文件中的某段(代码段、数据段等)

3、start:是段的重定位地址,本段连接(运行)的地址,如果代码中有位置无关指令,程序运行时这个段必须放在这个地址上。start可以用任意一种描述地址的符号来描述。

4、AT(ldadr):定义本段存储(加载)的地址,如果不使用这个选项,则加载地址等于运行地址,通过这个选项可以控制各段分别保存于输出文件中不同的位置。

例:

/* nand.lds */

SECTIONS {

firtst 0x00000000 : { head.o init.o }

second 0x30000000 : AT(4096) { main.o }

}

以上,head.o放在0x00000000地址开始处,init.o放在head.o后面,他们的运行地址也是0x00000000,即连接和存储地址相同(没有AT指定);main.o放在4096(0x1000,是AT指定的,存储地址)开始处,但它的运行地址在0x30000000,运行之前需要从0x1000(加载地址处)复制到0x30000000(运行地址处),此过程也就需要读取 flash,把程序拷贝到相应位置才能运行。这就是存储地址和运行地址的不同,称为加载时域和运行时域,可以在.lds连接脚本文件中分别指定。

ARM 技术交流网 版权所有 请勿用于商业用途 违者必究 45帮助客户成功!

编写好的.lds文件,在用arm-linux-ld连接命令时带-Tfilename来调用执行,如

arm-linux-ld –Tnand.lds x.o y.o –o xy.o。也用-Ttext参数直接指定连接地址,如

arm-linux-ld –Ttext 0x30000000 x.o y.o –o xy.o。

既然程序有了两种地址,就涉及到一些跳转指令的区别。

ARM汇编中,常有两种跳转方法:b跳转指令、ldr指令向PC赋值。

要特别注意这两条指令的意思:

(1)b step:b跳转指令是相对跳转,依赖当前PC的值,偏移量是通过该指令本身的bit[23:0]算出来的,这使得使用b指令的程序不依赖于要跳到的代码的位置,只看指令本身。

(2)ldr pc, =step :该指令是一个伪指令编译后会生成以下代码:

ldr pc, 0x30008000

<0x30008000>

step

是从内存中的某个位置(step)读出数据并赋给PC,同样依赖当前PC的值,但是偏移量是step的连接地址(运行时的地址),所以可以用它实现从Flash到RAM的程序跳转。

(3) 此外,有必要回味一下adr伪指令,U-boot中那段relocate代码就是通过adr实现当前程序是在RAM中还是flash中:

relocate: /* 把U-Boot重新定位到RAM */

adr r0, _start /* r0是代码的当前位置 */

/* adr伪指令,汇编器自动通过当前PC的值算出这条指令中“_start"的值,执行到_start时PC的值放到r0中:

当此段在flash中执行时r0 = _start = 0;当此段在RAM中执行时_start = _TEXT_BASE(在board/smdk2410/config.mk中指定的值为0x33F80000,即u-boot在把代码拷贝到RAM中去执行的代码段的开始) */

ldr r1, _TEXT_BASE /* 测试判断是从Flash启动,还是RAM */

/* 此句执行的结果r1始终是0x33FF80000,因为此值是链接指定的 */

cmp r0, r1 /* 比较r0和r1,调试的时候不要执行重定位 */

结合u-boot.lds谈谈连接脚本

OUTPUT_FORMAT("elf32&shy;littlearm", "elf32&shy;littlearm", "elf32&shy;littlearm")

;指定输出可执行文件是elf格式,32位ARM指令,小端

OUTPUT_ARCH(arm)

;指定输出可执行文件的平台为ARM

ENTRY(_start)

;指定输出可执行文件的起始代码段为_start.

SECTIONS

{

. = 0x00000000 ; 定位当前地址为0地址

. = ALIGN(4) ; 代码以4字节对齐

ARM 技术交流网 版权所有 请勿用于商业用途 违者必究 46帮助客户成功!

.text : ; 指定代码段

{

cpu/arm920t/start.o (.text) ; 代码的第一个代码部分

*(.text) ; 其它代码部分

}

. = ALIGN(4)

.rodata : { *(.rodata) } ; 指定只读数据段

. = ALIGN(4);

.data : { *(.data) } ; 指定读/写数据段

. = ALIGN(4);

.got : { *(.got) } ; 指定got段, got段式是uboot自定义的一个段, 非标准段

__u_boot_cmd_start = . ; 把__u_boot_cmd_start赋值为当前位置, 即起始位置

.u_boot_cmd : { *(.u_boot_cmd) } ;指定u_boot_cmd段, uboot把所有的uboot命令放在该段.

__u_boot_cmd_end = . ;把__u_boot_cmd_end赋值为当前位置,即结束位置

. = ALIGN(4);

__bss_start = . ; 把__bss_start赋值为当前位置,即bss段的开始位置

.bss : { *(.bss) } ; 指定bss段

_end = . ; 把_end赋值为当前位置,即bss段的结束位置

}

E: Ping命令使用的ARP协议

如果您详细查看u-boot代码的net.c文件,您会发现,里面的ping命令,使用了arp协议。所以我们简单地来介绍下arp协议。如需深入研究,请查看网络协议IEEE 802.3的官方文档。

ARP协议原理简述

ARP协议(Address Resolution Protocol 地址解析协议),在局域网中,网络中实际传输的是“帧”,帧里面有目标主机的MAC地址。在以太网中,一个注意要和另一个主机进行直接通信,必须要知道目标主机的MAC地址。这个MAC地址就是标识我们的网卡芯片唯一性的地址。但这个目标MAC地址是如何获得的呢?这就用到了我们这里讲到的地址解析协议。所有“地址解析”,就是主机在发送帧前将目标IP地址转换成MAC地址的过程。ARP协议的基本功能就是通过目标设备的IP地址,查询目标设备的MAC地址,以保证通信的顺利进行。所以在第一次通信前,我们知道目标机的IP地址,想要获知目标机的MAC地址,就要发送ARP报文(即ARP数据包)。它的传输过程简单的说就是:我知道目标机的IP地址,那么我就向网络中所有的机器发送一个ARP请求,请求中有目标机的IP地址,请求的意思是目标机要是收到了此请求,就把你的MAC地址告诉我。如果目标机不存在,那么此请求自然不会有人回应。若目标机接收到了此请求,它就会发送一个ARP应答,这个应答是明确发给请求者的,应答中有MAC地址。我接到了这个应答,我就知道了目标机的MAC地址,就可以进行以后的通信了。因为每次通信都要用到MAC地址。

ARP报文被封装在以太网帧头部中传输,如图为ARP请求报文的头部格式。

注意,以太网的传输存储是“大端格式”,即先发送高字节后发送低字节。例如,两个字节的数据,先发送高8位后发送低8位。所以接收数据的时候要注意存储顺序。

整个报文分成两部分,以太网首部和ARP请求/应答。下面挑重点讲述。

“以太网目的地址”字段:若是发送ARP请求,应填写广播类型的MAC地址FF-FF-FF-FF-FF-FF,意思是让网络上的所有机器接收到;

“帧类型”字段:填写08-06表示次报文是ARP协议;

“硬件类型”字段:填写00-01表示以太网地址,即MAC地址;

“协议类型”字段:填写08-00表示IP,即通过IP地址查询MAC地址;

“硬件地址长度”字段:MAC地址长度为6(以字节为单位);

“协议地址长度”字段:IP地址长度为4(以字节为单位);

“操作类型”字段:ARP数据包类型,0表示ARP请求,1表示ARP应答;

“目的以太网地址”字段:若是发送ARP请求,这里是需要目标机填充的。

免责声明:文章转载自《uboot完全手册---14》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇pycharm 快捷键[Qt]自定义QStyle——实现QProgressBar自定义样式下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

Go第六篇之结构体剖析

Go 语言通过用自定义的方式形成新的类型,结构体是类型中带有成员的复合类型。Go 语言使用结构体和结构体成员来描述真实世界的实体和实体对应的各种属性。 Go 语言中的类型可以被实例化,使用new或&构造的类型实例的类型是类型的指针。 结构体成员是由一系列的成员变量构成,这些成员变量也被称为“字段”。字段有以下特性: 字段拥有自己的类型和值。...

Linux服务器性能检测命令集锦

uptime $ uptime 23:51:26 up 21:31, 1 user, load average: 30.02, 26.43, 19.02 这个命令可以快速查看机器的负载情况。在Linux系统中,这些数据表示等待CPU资源的进程和阻塞在不可中断IO进程(进程状态为D)的数量。这些数据可以让我们对系统资源使用有一个宏观的了解。 命令的输出...

Linux学习 : 总线-设备-驱动模型

  platform总线是一种虚拟的总线,相应的设备则为platform_device,而驱动则为platform_driver。Linux 2.6的设备驱动模型中,把I2C、RTC、LCD等都归纳为platform_device。总线将设备和驱动绑定,在系统每注册一个设备的时候,会寻找与之匹配的驱动;相反的,在系统每注册一个驱动的时候,会寻找与之匹配的设...

Mac电脑mds_store进程占用cpu过高

  mds、mds_stores、mdworker占用大量的CPU,是因为系统在建立索引,开机后的一段时间比较明显 解决方案1: sudo mdutil -a -i off    # 关闭sudo mdutil -a -i on # 还原 解决方案二: 关闭控制聚焦参数文件的加载: sudo launchctl unload -w /System/L...

OpenGL代码学习(22)--绘制通道

注意:需要在配置好OpenGL的编程环境中运行下列代码,环境配置文章可参考: OpenGL在Mac项目上的配置下面的代码,直接放置在main.cpp文件中即可: #include "GLTools.h" #include "GLShaderManager.h" #include "GLFrustum.h" #include "GLBatch.h" #inc...

C# 类类型

1、一旦定义了自定义构造函数,默认的构造函数就会被移除2、可以使用this设计构造函数链,将核心部分代码由主构造函数完成,其余的构造函数调用主构造函数就可以了3、静态构造函数适合初始化静态数据成员(这个初始化发生在编译时)4、防止创建类对象的方式: 定义静态类 将构造函数声明为私有的(private) 将类声明为抽象的(abstract) 5、OOP...