bootloader的移植4339.pptx
第第10课课 bootloader介绍(含介绍(含VIVI)VIVI课程内容介绍nPARTI:Bootloader概述nPARTII:VIVInPARTIII:VIVI基本命令介绍nPARTIV:本章小结PARTIBootloader概述1.1 Bootloader简介简介n引导加载程序(Bootloader)是系统加电后运行的第一段软件代码。回忆一下PC的体系结构我们可以知道,PC机中的引导加载程序由BIOS(其本质就是一段固件程序)和位于硬盘MBR(masterbootrecord)中的OSBootLoader(比如,LILO和GRUB等)一起组成。BIOS在完成硬件检测和资源分配后,将硬盘MBR中的BootLoader读到系统的RAM中,然后将控制权交给OSBootLoader。BootLoader的主要运行任务就是将内核映像从硬盘上读到RAM中,然后跳转到内核的入口点去运行,也即开始启动操作系统。(用图来演示PC的启动过程)1.1 Bootloader简介简介n而在嵌入式系统中,通常并没有像BIOS那样的固件程序(注:有的嵌入式CPU也会内嵌一段短小的启动程序),因此整个系统的加载启动任务就完全由BootLoader来完成。比如在一个基于ARMcore的嵌入式系统中,系统在上电或复位时通常都从地址0 x00000000处开始执行,而在这个地址处安排的通常就是系统的BootLoader程序。(演示通信系统的一般系统启动过程)n本节将从BootLoader的概念和启动过程来讨论嵌入式系统的BootLoader。1.1 Bootloader简介简介1.1.1 BootLoader的概念的概念n简单地说,BootLoader就是在操作系统内核运行之前运行的一段小程序。通过这段小程序,我们可以初始化硬件设备、建立内存空间的映射图,从而将系统的软硬件环境带到一个合适的状态,以便为最终调用操作系统内核准备好正确的环境。n通常,BootLoader是严重地依赖于硬件而实现的,特别是在嵌入式世界。因此,在嵌入式世界里建立一个通用的BootLoader几乎是不可能的。尽管如此,我们仍然可以对BootLoader归纳出一些通用的概念来,以指导用户特定的BootLoader设计与实现。1.1 Bootloader简介简介1、BootLoader所支持的CPU和嵌入式单板每种不同的CPU体系结构都有不同的BootLoader。有些BootLoader也支持多种体系结构的CPU,比如U-Boot就同时支持ARM、PPC和MIPS等一系列体系结构。除了依赖于CPU的体系结构外,BootLoader实际上也依赖于具体的嵌入式板级设备的配置。也就是说,对于两块不同的嵌入式板而言,即使它们是基于同一种CPU构建的,要想让运行在一块板子上的BootLoader程序也能运行在另一块板子上,通常也都需要修改BootLoader的源程序。1.1 Bootloader简介简介2、BootLoader的安装系统加电或复位后,所有的CPU通常都从CPU制造商预先安排的地址上取指令。比如,ARM系列CPU在复位后都从地址0 x00000000取它的第一条指令。而嵌入式系统通常都有某种类型的固态存储设备(比如:ROM、EEPROM或FLASH等)被安排在这个起始地址上,因此在系统加电后,CPU将首先执行BootLoader程序。1.1 Bootloader简介简介n一个同时装有BootLoader、内核的启动参数、内核映像和根文件系统映像的固态存储设备的典型空间分配结构图(这个图是Linux的典型配置,不同的系统配置可以不同):1.1 Bootloader简介简介3、用来控制BootLoader的设备或机制串口通讯是最简单也是最廉价的一种双机通讯设备,所以往往在BootLoader中主机和目标机之间都通过串口建立连接,BootLoader程序在执行时通常会通过串口来进行I/O,比如:输出打印信息到串口,从串口读取用户控制字符等。当然如果认为串口通讯速度不够,也可以采用网络或者USB通讯,那么相应的在BootLoader中就需要编写各自的驱动。1.1 Bootloader简介简介4、BootLoader的启动过程BootLoader的启动过程分为单阶段(SingleStage)和多阶段(Multi-Stage)两种。通常多阶段的BootLoader能提供更为复杂的功能,以及更好的可移植性。从固态存储设备上启动的BootLoader大多都是2阶段的启动过程,也即启动过程可以分为stage1和stage2两部分。至于在stage1和stage2中具体完成哪些任务,将在下一小节中具体讨论。1.1 Bootloader简介简介5、BootLoader的操作模式大多数BootLoader都包含两种不同的操作模式:“启动加载”模式和“下载”模式。这种区别仅对于开发人员才有意义。但从最终用户的角度看,BootLoader的作用就是用来加载操作系统的,而并不存在所谓的启动加载模式与下载工作模式的区别。1.1 Bootloader简介简介n启动加载(Bootloading)模式:这种模式也称为“自主”(Autonomous)模式。即BootLoader从目标机上的某个固态存储设备上将操作系统加载到RAM中运行,整个过程并没有用户的介入。这种模式是BootLoader的正常工作模式,因此在嵌入式产品发布的时侯,BootLoader显然必须工作在这种模式下。n下载(Downloading)模式:在这种模式下,目标机上的BootLoader将通过串口连接或网络连接等通信手段从主机(Host)下载文件,比如:下载内核映像和根文件系统映像等。从主机下载的文件通常首先被BootLoader保存到目标机的RAM中,然后再被BootLoader写到目标机上的FLASH类固态存储设备中。BootLoader的这种模式通常在第一次安装内核与根文件系统时被使用;此外,以后的系统更新也会使用BootLoader的这种工作模式。工作于这种模式下的BootLoader通常都会向它的终端用户提供一个简单的命令行接口。1.1 Bootloader简介简介6、BootLoader与主机之间进行文件传输所用的通信设备及协议n最常见的情况就是,目标机上的BootLoader通过串口与主机之间进行文件传输,传输协议通常是xmodemymodemzmodemkermit协议中的一种。但是,串口传输的速度是有限的,因此通过以太网连接并借助TFTP协议来下载文件是个更好的选择。(问题:TFTP协议是在第几层?它是基于那一个协议的?)n此外,在论及这个话题时,主机方所用的软件也要考虑。比如,在通过以太网连接和TFTP协议来下载文件时,主机方必须有一个软件用来提供TFTP服务(tftpserver)。1.1 Bootloader简介简介1.1.2 BootLoader 的启动过程的启动过程BootLoader的stage1通常包括以下步骤(以执行的先后顺序):n硬件设备初始化。n为加载BootLoader的stage2准备RAM空间。n拷贝BootLoader的stage2到RAM空间中。n设置好堆栈。n跳转到stage2的C入口点。1.1 Bootloader简介简介BootLoader的stage2通常包括以下步骤(以执行的先后顺序):n初始化本阶段要使用到的硬件设备(比如网络和串口)。n检测系统内存映射(memorymap),得知内存的大小等信息。n将kernel映像和根文件系统映像从flash上读到RAM空间中。n为内核设置启动参数。(这个后面会讲)n调用内核。1.1 Bootloader简介简介1、BootLoader的stage1(通常用汇编来写)a)基本的硬件初始化n这是BootLoader一开始就执行的操作,其目的是为stage2的执行以及随后的kernel的执行准备好一些基本的硬件环境。它通常包括以下步骤(以执行的先后顺序):n屏蔽所有的中断。为中断提供服务通常是OS设备驱动程序的责任,因此在BootLoader的执行全过程中可以不必响应任何中断。中断屏蔽可以通过写CPU的中断屏蔽寄存器或状态寄存器(比如ARM的CPSR寄存器)来完成。1.1 Bootloader简介简介n设置CPU的速度和时钟频率。nRAM初始化。包括正确地设置系统的内存控制器的功能寄存器以及各内存库控制寄存器等。n初始化LED。典型地,通过GPIO来驱动LED,其目的是表明系统的状态是OK还是Error。如果板子上没有LED,那么也可以通过初始化UART向串口打印BootLoader的Logo字符信息来完成这一点。n关闭CPU内部指令数据cache。1.1 Bootloader简介简介b)为加载stage2准备RAM空间n为了获得更快的执行速度,通常把stage2加载到RAM空间中来执行,因此必须为加载BootLoader的stage2准备好一段可用的RAM空间范围。n由于stage2通常是C语言执行代码,因此在考虑空间大小时,除了stage2可执行映像的大小外,还必须把堆栈空间也考虑进来。此外,空间大小最好是memorypage大小(通常是4KB)的倍数。一般而言,1M的RAM空间已经足够了。1.1 Bootloader简介简介n另外,还必须确保所安排的地址范围的的确确是可读写的RAM空间,因此,必须对你所安排的地址范围进行测试。具体的测试方法可以采用类似于blob的方法,也即:以memorypage为被测试单位,测试每个memorypage开始的两个字是否是可读写的。为了后面叙述的方便,我们记这个检测算法为:test_mempage,其具体步骤如下:n先保存memorypage一开始两个字的内容。n向这两个字中写入任意的数字。比如:向第一个字写入0 x55,第2个字写入0 xaa。1.1 Bootloader简介简介n然后,立即将这两个字的内容读回。显然,我们读到的内容应该分别是0 x55和0 xaa。如果不是,则说明这个memorypage所占据的地址范围不是一段有效的RAM空间。n再向这两个字中写入任意的数字。比如:向第一个字写入0 xaa,第2个字中写入0 x55。n然后,立即将这两个字的内容立即读回。显然,我们读到的内容应该分别是0 xaa和0 x55。如果不是,则说明这个memorypage所占据的地址范围不是一段有效的RAM空间。n恢复这两个字的原始内容。测试完毕。n为了得到一段干净的RAM空间范围,我们也可以将所安排的RAM空间范围进行清零操作。1.1 Bootloader简介简介c)拷贝stage2到RAM中n拷贝时要确定两点:(1)stage2的可执行映像在固态存储设备的存放起始地址和终止地址;(2)RAM空间的起始地址。d)设置堆栈指针spn堆栈指针的设置是为了执行C语言代码作好准备。通常我们可以把sp的值设置为(stage2_end-4),也即在b)中所安排的那个1MB的RAM空间的最顶端(最大处)(堆栈向下生长)。n此外,在设置堆栈指针sp之前,也可以关闭led灯,以提示用户我们准备跳转到stage2。1.1 Bootloader简介简介e)跳转到stage2的C入口点n在上述一切都就绪后,就可以跳转到BootLoader的stage2去执行了。比如,在ARM系统中,这可以通过修改PC寄存器为合适的地址来实现。1.1 Bootloader简介简介n执行完Stage_1以后的内存情况:1.1 Bootloader简介简介2、BootLoader的stage2n正如前面所说,stage2的代码通常用C语言来实现,以便于实现更复杂的功能和取得更好的代码可读性和可移植性。但是与普通C语言应用程序不同的是,在编译和链接BootLoader这样的程序时,我们不能使用glibc库中的任何支持函数。其原因是显而易见的。n这就给我们带来一个问题,那就是从那里跳转进main()函数呢?(问题:为什么无法跳进main函数?)直接把main()函数的起始地址作为整个stage2执行映像的入口点或许是最直接的想法。但是这样做有两个缺点:1)无法通过main()函数传递函数参数;2)无法处理main()函数返回的情况。1.1 Bootloader简介简介n解决方法:.text.globl_trampoline_trampoline:blmain/*ifmaineverreturnswejustcallitagain*/b_trampolinen还可以通过给寄存器赋值来传递参数,具体要看编译器的规定,一般会使用r0,r1来传递参数1.1 Bootloader简介简介a)初始化本阶段要使用到的硬件设备n这通常包括:(1)初始化至少一个串口,以便和终端用户进行I/O输出信息;(2)初始化计时器等。n在初始化这些设备之前,也可以重新把LED灯点亮,以表明我们已经进入main()函数执行。n设备初始化完成后,可以输出一些打印信息,程序名字字符串、版本号等。1.1 Bootloader简介简介b)检测系统的内存映射(memorymap)n所谓内存映射就是指在整个4GB物理地址空间中有哪些地址范围被分配用来寻址系统的RAM单元。比如,在SA-1100CPU中,从0 xC000,0000开始的512M地址空间被用作系统的RAM地址空间,而在SamsungS3C44B0XCPU中,从0 x0c00,0000到0 x1000,0000之间的64M地址空间被用作系统的RAM地址空间。1.1 Bootloader简介简介n虽然CPU通常预留出一大段足够的地址空间给系统RAM,但是在搭建具体的嵌入式系统时却不一定会实现CPU预留的全部RAM地址空间。也就是说,具体的嵌入式系统往往只把CPU预留的全部RAM地址空间中的一部分映射到RAM单元上,而让剩下的那部分预留RAM地址空间处于未使用状态。由于上述这个事实,因此BootLoader的stage2必须在它想干点什么(比如,将存储在flash上的内核映像读到RAM空间中)之前检测整个系统的内存映射情况,也即它必须知道CPU预留的全部RAM地址空间中的哪些被真正映射到RAM地址单元,哪些是处于“unused”状态的。1.1 Bootloader简介简介nc)加载内核映像和根文件系统映像n规划内存占用的布局n这里包括两个方面:(1)内核映像所占用的内存范围;(2)根文件系统所占用的内存范围。在规划内存占用的布局时,主要考虑基地址和映像的大小两个方面。1.1 Bootloader简介简介n对于内核映像,一般将其拷贝到从(MEM_START0 x8000)这个基地址开始的大约1MB大小的内存范围内(嵌入式Linux的内核一般都不操过1MB)。为什么要把从MEM_START到MEM_START0 x8000这段32KB大小的内存空出来呢?这是因为Linux内核要在这段内存中放置一些全局数据结构,如:启动参数和内核页表等信息。n而对于根文件系统映像,则一般将其拷贝到MEM_START+0 x0010,0000开始的地方。如果用Ramdisk作为根文件系统映像,则其解压后的大小一般是1MB。1.1 Bootloader简介简介从Flash上拷贝n由于像ARM这样的嵌入式CPU通常都是在统一的内存地址空间中寻址Flash等固态存储设备的,因此从Flash上读取数据与从RAM单元中读取数据并没有什么不同。用一个简单的循环就可以完成从Flash设备上拷贝映像的工作:while(count)*dest+=*src+;/*theyareallalignedwithwordboundary*/count-=4;/*bytenumber*/;1.1 Bootloader简介简介d)设置内核的启动参数n应该说,在将内核映像和根文件系统映像拷贝到RAM空间中后,就可以准备启动Linux内核了。但是在调用内核之前,应该作一步准备工作,即:设置Linux内核的启动参数(比如文件系统放在哪里,内存多大等)。1.1 Bootloader简介简介e)调用内核nBootLoader调用Linux内核的方法是直接跳转到内核的第一条指令处,也即直接跳转到MEM_START0 x8000地址处。在跳转时,下列条件要满足:nCPU寄存器的设置:R00;R1机器类型ID;R2启动参数标记列表在RAM中起始基地址。nCPU模式:必须禁止中断(IRQs和FIQs);CPU必须处于SVC模式。nCache和MMU的设置:MMU必须关闭;指令Cache可以打开也可以关闭;数据Cache必须关闭。PARTIIVIVI2.1 vivi概述概述nvivi是韩国Mizi公司开发的Bootloader,适用于ARM9处理器。vivi也有上一节提到的两种工作模式。启动加载模式可以在一定时间后(时间长短可以修改)自行启动Linux内核,这是vivi的默认模式。而在下载模式下,vivi为用户提供了一个命令行接口,通过该接口可以使用vivi的一些主要命令:load,part,param,boot,flash.2.2 vivi的配置和编译的配置和编译n下载vivi压缩包后,使用如下命令解压缩:$tarjxvfvivi.tar.bz2进入vivi目录n我们使用如下命令配置vivi:演示:$makemenuconfign我们可以自行配置,也可以使用默认的配置文件进行自动配置。这些默认的配置文件放在vivi/arch/def-configs目录下。nA)选择“LoadonAlternateConfigurationFile”菜单,在其中填上“arch/def-configs/smdk2410”or“arch/def-configs/s3c2410-tk”来选择对应的单板配置文件nB)在配置完vivi之后,则可以使用下面的命令进行编译:$makenC)编译成功之后,就可以在vivi目录下看到vivi生成的二进制文件。nD)编译成功后,会在vivi目录下产生vivi.map文件。n解释:n做完A)以后,会在根目录下生成.config文件(演示:lsa),这个文件是很多的宏,比如(演示):CONFIG_MTD_NAND=y,这样就决定了哪些代码(功能)将会被编译。n做完B)以后,将根据Makefile的第一个目标(do-it-all)来生成vivi目标。n做完C)以后,系统将会调用$(OBJCOPY)-Obinary-Svivi-elfvivi$(OBJCOPYFLAGS)这句话生成二进制的vivi文件。n做完D)后,系统会调用$(NM)-v-lvivi-elfvivi.map,生成map文件(演示),供调试使用。nvivi的link文件:1)link文件是ld工具要调用的输入。它在链接时其作用。它在运行时才起到效果。注意它和链接不是一个过程,它只是链接的一个过程,叫做运行导入过程。2)vivi的link文件如下:SECTIONS.=0 x33f00000;.text:*(.text).dataALIGN(4):*(.data).bssALIGN(4):*(.bss)*(COMMON)3)以上内容的含义是:a)它内存的视图。b)它告诉ld,在装载时,vivi在内存的基地址是0 x33f00000,以后依次存放.text,.data和.bss。c).data和.bss都是4位对齐(word对齐)。d)我们可以看到这个配置文件没有指定.rodata的load情况,也就是说它将由elf文件自身决定。4)LINKFLAGS=-Tarch/vivi.ldsBstatic表明:使用的导入内存的配置文件是vivi.lds,使用的是静态的链接。2.3 vivi代码导读代码导读2.3.1 阶段阶段1:arch/s3c2410/head.Sn沿着代码执行的顺序,head.S完成如下几件事情:1、关WATCHDOG:上电后,WATCHDOG默认是开着的。(P435,手册)2、禁止所有中断:vivi中没用到中断(不过这段代码实在多余,上电后中断默认是关闭的)。(P358)2.3 vivi代码导读代码导读3、初始化系统时钟:启动MPLL(machinePLL),FCLK=200MHz(ARM920T),HCLK=100MHz(AHB bus),PCLK=50MHz(APB bus),“CPUbusmode”改为“Asynchronousbusmode”(使得FCLK和HCLK不一样,也就是说CPU工作在200Mhz,AHB总线工作在100Mhz)。(P226,总线仲裁器和CPU的工作频率说明见ARM内核技术手册P108)。注意:这里没有提到UPLL(也就是USB的PLL),在以后要使用USB设备时会提到。4、初始化内存控制寄存器。(注意:ENTRY(memsetup)表示这个是一个内部符号,在本文件可以找到,和GLOBLE相对)mem_cfg_val是一张表,表示内存访问的控制,主要包括bank的属性和时序2.3 vivi代码导读代码导读5、检查是否从掉电模式唤醒,若是,则调用WakeupStart函数进行处理这是一段没用上的代码,vivi不可能进入掉电模式。6、点亮所有LED。7、初始化UART0:na设置GPIO,选择UART0使用的引脚;nb初始化UART0,设置工作方式(不使用FIFO)(P313,UART工作模式)、波特率115200(P309,波特律)8N1(P312,UART的线控)、无流控等。8、将vivi所有代码(包括阶段1和阶段2)从nandflash复制到SDRAM中:na设置nandflash控制寄存器;nb设置堆栈指针调用C函数时必须先设置堆栈;nc设置即将调用的函数nand_read_ll的参数:r0=目的地址(SDRAM的地址),r1=源地址(nandflash的地址),r2=复制的长度(以字节为单位);注意,这里我们要看一下S3C2410Ausersmanual的P215,我们采用Nandflashbooting方式,nandflash的范围是0-0 x40000002.3 vivi代码导读代码导读nd.调用nand_read_ll进行复制;ne进行一些检查工作:上电后nandflash最开始的4K代码被自动复制到一个称为“Steppingstone”的内部RAM中(地址为0 x00000000-0 x00001000);在执行nand_read_ll之后,这4K代码同样被复制到SDRAM中(地址为0 x33f00000-0 x33f01000)。比较这两处的4K代码,如果不同则表示出错。跳到bootloader的阶段2运行就是调用init/main.c中的main函数na重新设置堆栈;nb设置main函数的参数;nc调用main函数。2.3 vivi代码导读代码导读当执行完head.S的代码后,内存的使用情况如下:2.3 vivi代码导读代码导读4.4.2阶段阶段2:init/main.c本阶段从init/main.c中的main函数开始执行,它可以分为8个步骤。下面先把main函数的代码罗列如下,然后逐个分析:1、Step 1:reset_handler()reset_handler用于将内存清零,代码在lib/reset_handle.c中。2.3 vivi代码导读代码导读2、Step 2:board_init()nboard_init调用2个函数用于初始化定时器和设置各GPIO引脚功能,代码在arch/s3c2410/smdk.c中:ninit_time()只是简单的令寄存器TCFG0=0 xf00,vivi未使用定时器,这个函数可以忽略。nset_gpios()用于选择GPA-GPH端口各引脚的功能及是否使用各引脚的内部上拉电阻,并设置外部中断源寄存器EXTINT0-2(vivi中未使用外部中断)。2.3 vivi代码导读代码导读3、Step 3:建立页表和启动:建立页表和启动MMU nmem_map_init函数用于建立页表,vivi使用段式页表,只需要一级页表。它调用3个函数,代码在arch/s3c2410/mmu.c中:n第9、10行的两个函数可以不用管它,他们做的事情在下面的mmu_init函数里又重复了一遍。对于本例,在.config中定义了CONFIG_S3C2410_NAND_BOOT。mem_map_nand_boot()函数调用mem_mapping_linear()函数来最终完成建立页表的工作。页表存放在SDRAM物理地址0 x33dfc000开始处,共16K:一个页表项4字节,共有4096个页表项;每个页表项对应1M地址空间,共4G。mem_map_init先将4G虚拟地址映射到相同的物理地址上(不使用cache,不使用writebuffer)这样,对寄存器的操作跟未启动MMU时是一样的;再将SDRAM对应的64M空间的页表项修改为使用cache。2.3 vivi代码导读代码导读nmmu_init()函数用于启动MMU,它直接调用arm920_setup()函数。arm920_setup()的代码在arch/s3c2410/mmu.c中2.3 vivi代码导读代码导读n到此为止,内存的状态是:2.3 vivi代码导读代码导读4、Step 4:heap_init()nheap堆,内存动态分配函数mmalloc就是从heap中划出一块空闲内存的,mfree则将动态分配的某块内存释放回heap中。nheap_init函数在SDRAM中指定了一块1M大小的内存作为heap(起始地址HEAP_BASE=0 x33e00000),并在heap的开头定义了一个数据结构blockhead事实上,heap就是使用一系列的blockhead数据结构来描述和操作的。每个blockhead数据结构对应着一块heap内存,假设一个blockhead数据结构的存放位置为A,则它对应的可分配内存地址为“A+sizeof(blockhead)”到“A+sizeof(blockhead)+size1”。blockhead数据结构在lib/heap.c中定义2.3 vivi代码导读代码导读现在来看看heap是如何运作的。vivi对heap的操作比较简单,vivi中有一个全局变量staticblockhead*gHeapBase,它是heap的链表头指针,通过它可以遍历所有blockhead数据结构。假设需要动态申请一块sizeA大小的内存,则mmalloc函数从gHeapBase开始搜索blockhead数据结构,如果发现某个blockhead满足:na.allocated=0/表示未分配nb.sizesizeA,n则找到了合适的blockhead,于是进行如下操作:naallocated设为1nb如果sizesizeAsizeof(blockhead),则将剩下的内存组织成一个新的blockhead,放入链表中nc返回分配的内存的首地址2.3 vivi代码导读代码导读n释放内存的操作更简单,直接将要释放的内存对应的blockhead数据结构的allocated设为0即可。(对应的是mfree函数)2.3 vivi代码导读代码导读n下面简单演示先分配1K内存,再分配2K内存的过程:2.3 vivi代码导读代码导读nheap_init函数直接调用mmalloc_init函数进行初始化,此函数代码在lib/heap.c中,比较简单,初始化gHeapBase即可2.3 vivi代码导读代码导读n分配heap区域后,内存划分情况如下:2.3 vivi代码导读代码导读5、Step 5:mtd_dev_init()在分析代码前先介绍一下MTD(MemoryTechnologyDevice)相关的技术。在linux系统中,我们通常会用到不同的存储设备,特别是FLASH设备。为了在使用新的存储设备时,我们能更简便地提供它的驱动程序,在上层应用和硬件驱动的中间,抽象出MTD设备层。驱动层不必关心存储的数据格式如何,比如是FAT32、ETX2还是FFS2或其它。它仅仅提供一些简单的接口,比如读写、擦除及查询。如何组织数据,则是上层应用的事情。MTD层将驱动层提供的函数封装起来,向上层提供统一的接口。这样,上层即可专注于文件系统的实现,而不必关心存储设备的具体操作。2.3 vivi代码导读代码导读n在我们即将看到的代码中,使用mtd_info数据结构表示一个MTD设备,使用nand_chip数据结构表示一个nandflash芯片。n在mtd_info结构中,对nand_flash结构作了封装,向上层提供统一的接口。比如,它根据nand_flash提供的read_data(读一个字节)、read_addr(发送要读的扇区的地址)等函数,构造了一个通用的读函数read,将此函数的指针作为自己的一个成员。而上层要读写flash时,执行mtd_info中的read、write函数即可。n在nand_chip结构中,使用this指针对自己进行一些和Nandflash特性有关的特殊操作,很有特色。n问题:在nand.h中,我们有如下结构:structnand_chip#ifdefCONFIG_MTD_NANDYvoid(*hwcontrol)(intcmd);void(*write_cmd)(u_charval);void(*write_addr)(u_charval);请问:void(*hwcontrol)(intcmd);占有多少空间?2.3 vivi代码导读代码导读nmtd_dev_init()用来扫描所使用的NANDFlash的型号,构造MTD设备,即构造一个mtd_info的数据结构。对于本例,它直接调用mtd_init(),mtd_init又调用smc_init(),此函数在drivers/mtd/maps/s3c2410_flash.c中,smc_init()是最重要的函数,其中的this指针是它最精妙的使用之处(详细分析)nMTD设备的树形结构:MTDmtd1structone|mtd2structtwo|mtd3structthreen每一个MTD设备的this指针都是自己nand_chip结构的地址。2.3 vivi代码导读代码导读6、Step 6:init_priv_data()n此函数将启动内核的命令参数取出,存放在内存特定的位置中。这些参数来源有两个:vivi预设的默认参数,用户设置的参数(存放在nandflash上)。init_priv_data先读出默认参数,存放在“VIVI_PRIV_RAM_BASE”开始的内存上;然后读取用户参数,若成功则用用户参数覆盖默认参数,否则使用默认参数。ninit_priv_data函数分别调用get_default_priv_data函数和load_saved_priv_data函数来读取默认参数和用户参数。这些参数分为3类:avivi自身使用的一些参数,比如传输文件时的使用的协议等;blinux启动命令;cnandflash的分区参数。2.3 vivi代码导读代码导读get_default_priv_data函数比较简单,它将vivi中存储这些默认参数的变量,复制到指定内存中。此函数执行完毕后,内存使用情况如下:2.3 vivi代码导读代码导读n7、Step 7:misc()和和init_builtin_cmds()n这两个函数都是简单地调用add_command函数,给一些命令增加相应的处理函数。在vivi启动后,可以进去操作界面,这些命令,就是供用户使用的。它们增加了如下命令:na.add_command(&cpu_cmd)nb.add_command(&bon_cmd)nc.add_command(&reset_cmd)nd.add_command(¶m_cmd)2.3 vivi代码导读代码导读ne.add_command(&part_cmd)nf.add_command(&mem_cmd)ng.add_command(&load_cmd)nh.add_command(&go_cmd)ni.add_command(&dump_cmd)nj.add_command(&call_cmd)nk.add_command(&boot_cmd)nl.add_command(&help_cmd)2.3 vivi代码导读代码导读8、Step 8:boot_or_vivi()n此函数根据情况,或者启动“vivi_shell”,进入与用户进行交互的界面,或者直接启动linux内核。n(见129页)第10行等待键盘输入,如果在一段时间内键盘无输入,或者输入了回车键,则调用run_autoboot启动内核;否则调用vivi_shell进入交互界面。nvivi_shell等待用户输入命令(等待串口数据),然后根据命令查找“Step7:misc()和init_builtin_cmds()”设置的命令链表,运行找到的命令函数。vivi_shell函数是通过调用serial_term函数来实现的。2.3 vivi代码导读代码导读vivi执行完毕后,内存使用情况如下:2.3 vivi代码导读代码导读至此,linux内核终于开始运行了!PARTIIIVIVI基本命令介绍3.1命令介绍ncpucmds-Managecpuclocksnboncmds-Managethebonfilesystemnreset-Resetthesystemnparamset|show|save|reset-set/getparameternpartadd|del|show|reset-ManageMTDpartitionsnmemcmds-ManageMemorynload.-LoadafiletoRAM/Flashngo-jumptondump-Display(hexdump)arangeofmemory.ncall-jump_with_returntonbootcmds-Bootinglinuxkernelnhelpcmds-Helpabouthelp?3.2命令使用n3.2.1cpu命令n演示:vivicpuUsage:cpuinfo-Displaycpuinformatincpuset-ChangecpuclockandbusclockvivicpuinfonProcessorInformation(Revision:0 x41129200)n-nProcessorclock:200000000HznAHBbusclock:100000000HznAPBbusclock:50000000HznRegistervaluesnMPLLCON:0 x0005c040(MDIV:0 x005c,PDIV:0 x04,SDIV:0 x00)nCLKDIVN:0 x00000003说明:1)MDIV,PDIV,SDIV是MPLLCON寄存器的内容(S3C2410Ausersmanual,P237),它们是用来计算PLL的值的,用计算得出的PLL值,可以知道PLL要稳定在哪一个频率(S3C2410Ausersmanual,P238)2)CLKDIVN是0 x00000003表示分频比是1:2:4(外部倍频稳定时钟:总线频率:CPU频率)。3)cpuset命令其实是通过改变寄存器来改变CPU和总线运行频率。n3.2.2bon命令n演示:vivibonnUsage:nbonpartinfonbonpartnvivibonpartinfonBONinfo.(3partitions)nNo:offsetsizeflagsbadn-n0:0 x000000000 x00030000000000000192kn1:0 x000300000 x000d0000000000000832kn2:0 x001000000 x03efc00000000000062M+1008kn演示:nvivibonpart0192k1mndoingpartitionnoffset=0nflag=0noffset=196608nflag=0noffset=104857