4、如何造就完美效率
4.1、话说效率
效率(efficiency)是指有用功率对驱动功率的比值,同时也引申出了多种含义。效率也分为很多种,比如机械效率(mechanical efficiency)、热效率(thermal efficiency )等。效率与做功的快慢没有直接关系。
对计算机领域而言,效率就是我们就最少的时间周期实现最大化的功能,比如说我们现在的处理器主频是2G的,那每条指令需要的时间为1/2G秒,如果我们在要实现拷贝功能的函数,两条指令就能完成肯定比三条指令的效率高。以上是针对2G处理器的,现在有2.5G的处理器,同样是两条指令完成的拷贝,那么很明显2.5G的效率要高了,实现同样的功能时间明显就少了吧,这就是所谓的高频率。
4.2、处理器的频率
如我们常见的嵌入式处理器,ARM9(S3C2410、S3C2440),ARM11(S3C6410),Cortex-A8,Cortex-A9都不是只有一个固件的频率,如ARM1176的主频传闻可达1G,但我们际应用的是667MHz左右,S3C2440主频可达400+MHz,但在代码中却没看到这样的设置(详见下小节),其实目的很简单,就是在保证性能的同时,尽可能提高运行效率。
但实际应用中,采用高主频、特别有超频性质的获取更高频率,会导致功耗的直线上升,现在明白了各位“机”友们手机为什么会那么热了吧,特别是山寨机友们体会很明显吧。发热倒是小事,大不了冬天暖手,夏天晚上喝夜啤酒的时候把手机拿出来烤烤烧烤吧,但最大的问题这可是直接影响使用寿命的,想想咱花了白花花的银子买来的,两天就给坏了肯定不好受吧,所以这里得取一个平衡点(可以通过变频技术实现,以后再发一文弥补这里的空缺),在不影响系统效率的情况下,适当地降低频率可减少功耗,提高电池的使用寿命。
4.3、返璞归真
说了那么多的废话,现在又到看代码的时候了,接上一篇的地方说时钟频率的设置,代码如下:
/* FCLK:HCLK:PCLK = 1:2:4 */ /* default FCLK is 120 MHz ! */ ldr r0, =CLKDIVN mov r1, #3 str r1, [r0] mrc p15, 0, r1, c1, c0, 0 orr r1, r1, #0xc0000000 mcr p15, 0, r1, c1, c0, 0
提前先解释几个名字的意思,即使写到这里,得介绍本节的主演。
FCLK is used by ARM920T,内核时钟,主频。
HCLK is used for AHB bus, which is used by the ARM920T, the memory controller, the interrupt controller, the LCD controller, the DMA and USB host block. 也就是总线时钟,包括USB时钟。AHB主要用于高性能模块(如CPU、DMA和DSP等)之间的连接。
PCLK is used for APB bus, which is used by the peripherals such as WDT, IIS, I2C, PWM timer, MMC interface,ADC, UART, GPIO, RTC and SPI.即IO接口时钟,例如串口的时钟设置就是从PCLK来的; APB主要用于低带宽的周边外设之间的连接。
这三个时钟通常设置为1:4:8,1:3:6,1:2:4的分频关系,也就说如果主频FLCK是400MHz,按照1:4:8的设置,那么HLCK是100MHz,PLCK是50MHz。通过对寄存器CLKDIVN设置三个时钟的关系,CLKDIVN=0x05,那么设置比例即为1:4:8,前提是CAMDIVN[9]为0;这里采用的默认设置为1:2:4,CLKDIVN=0x03.
最后还有一个特殊人物,P15协处理器:
协处理器(coprocessor),一种芯片,用于减轻系统微处理器的特定处理任务。例如,数学协处理器可以控制数字处理;图形协处理器可以处理视频绘制。例如,intel pentium 微处理器就包括内置的数学协处理器。这里使用的协处理器15(CP15),ARM处理器使用协处理器15的寄存器来控制cache、TCM和存储器管理(详见ARM完美解读——协处理器)。 既然有单独的介绍,这里就不再描述了。
5、移民
5.1、先固其根本
何谓根本,比如说修楼房,我们得先把根基修好吧,虽然传说中有一空中楼阁,但相信我们这些IT苦逼们还没达到能建成那种楼阁的境界,所以在将代码拷贝到RAM之前,需要老老实实的对cpu进行初始化,否则,系统还没有完成启动就已经崩溃了,其代码部分如下:
#ifndef CONFIG_SKIP_LOWLEVEL_INIT bl cpu_init_crit #endif
这里实现仅了一个跳转,调用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)
第二步:关闭MMU功能
关闭MMU和缓存,设备上电后,系统都没来有启动,所使用的地址都是物理地址,使用没必要打开MMU实现地址的映射,当然uCLinux之所以比通用的Linux更小巧,也正是因为在MMU作了处理。这里通过代码可能不容易理解,因为使用到了协处理器P15,关于协处理器的内容(详见ARM完美解读——协处理器)。
第三步:跳转lowlevel_init进一步初始化
在lowlevel_init中,主要是初始化存储控制器,如S3C2410/S3c2440的是Bank0-bank6,这里的配置需要根据使用情况而定,与具体的外围硬件相关,如网卡放在第几个bank,位宽多少,SDRAM放在哪个bank,多大等都通过这里进行配置。
5.2、长远而计之
在调试阶段:代码直接从RAM中运行,而在使用过程中,需要把代码固化到Flash中,但U-Boot在启动后,需要将代码从Flash拷贝到RAM中运行,就如同我们的PC机,在运行程序时先把要对应的程序调入内存一样,在U-Boot中引入重定向实现类似的功能。先来看看代码部分:
/***************** CHECK_CODE_POSITION ******************************************/ 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 /***************** CHECK_CODE_POSITION ******************************************/ /***************** CHECK_BOOT_FLASH ******************************************/ #define BWSCON 0x48000000 ldr r0, =BWSCON ldr r0, [r0] ands r0, r0, #6 bne relocate /***************** CHECK_BOOT_FLASH ******************************************/
前一部分主要是检查代码当前的位置,通过adr指令得到当前代码的地址信息:如果U-boot是从RAM开始运行,则从adr,r0,_start得到的地址信息为:
r0 =_start = _TEXT_BASE = TEXT_BASE = 0xa3000000
如果U-boot从Flash开始运行,即从处理器对应的地址运行,则r0=0x0000,这时将会执行代码搬运工作,当然在执行搬运前需要建立堆栈区域。
/* Set up the stack */ stack_setup: ldr r0, _TEXT_BASE /* upper 128 KiB: relocated uboot */ sub r0, r0, #CONFIG_SYS_MALLOC_LEN /* malloc area */ sub r0, r0, #CONFIG_SYS_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 */
这里建立的堆栈区域,主要是预留空间供U-Boot的代码拷贝,如果需要使用IRQ功能,而需要预留3word的空间用于中断堆栈。需要说明的是,这里分配的区域大小需要与实现的u-boot而定,分配过小则会失败,过多是空间浪费。初始化好堆栈空间后就可以进行代码搬运工作了。
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 */ 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
在进行U-Boot的搬运时,首先计算代码空间的大小,计算方式可直观的表现为:
代码大小:_bss_start - _armboot_start
结束位置:_bss_start - _armboot_start + _start
拷贝的原理,是完成一次拷贝后进行地址比较,如果已经是结束位置了,则表示拷贝完成,否则继续拷贝。完成拷贝后,所谓U-Boot的第一阶段也就完成八九了,最后就是跳转,通过一行代码:
ldr pc, _start_armboot
将顺利跳转到主函数中进一步执行,至于主函数要做什么工作,且听下回分解。