ARM Linux对中断的处理
- 格式:doc
- 大小:322.00 KB
- 文档页数:43
arm版本linux系统的启动流程ARM架构是一种常见的处理器架构,被广泛应用于嵌入式设备和移动设备中。
在ARM版本的Linux系统中,启动流程是非常重要的,它决定了系统如何从开机到正常运行。
本文将详细介绍ARM版本Linux系统的启动流程。
一、引导加载程序(Bootloader)引导加载程序是系统启动的第一阶段,它位于系统的固化存储器中,比如ROM或Flash。
在ARM版本的Linux系统中,常用的引导加载程序有U-Boot和GRUB等。
引导加载程序的主要功能是加载内核镜像到内存中,并将控制权转交给内核。
二、内核初始化引导加载程序将内核镜像加载到内存后,控制权被转交给内核。
内核初始化是系统启动的第二阶段,它主要完成以下几个步骤:1. 设置异常向量表:ARM架构中,异常是指硬件产生的中断或故障,比如系统调用、中断请求等。
内核需要设置异常向量表,以便正确处理异常。
2. 初始化处理器:内核对处理器进行初始化,包括设置页表、启用缓存、初始化中断控制器等。
3. 启动第一个进程:内核创建第一个用户进程(一般是init进程),并将控制权转交给它。
init进程是系统中所有其他进程的父进程,负责系统的初始化工作。
三、设备树(Device Tree)设备树是ARM版本Linux系统中的一种机制,用于描述硬件设备的相关信息。
在内核初始化过程中,内核会解析设备树,并建立设备树对象,以便后续的设备驱动程序使用。
设备树描述了硬件设备的类型、地址、中断等信息,以及设备之间的连接关系。
它使得内核能够在运行时自动识别和配置硬件设备,大大提高了系统的可移植性和灵活性。
四、启动初始化(Init)启动初始化是系统启动的第三阶段,它是用户空间的第一个进程(init进程)接管系统控制权后的操作。
启动初始化主要完成以下几个任务:1. 挂载根文件系统:启动初始化会挂载根文件系统,使得用户可以访问文件系统中的文件和目录。
2. 加载系统服务:启动初始化会加载并启动系统服务,比如网络服务、日志服务、时间同步服务等。
arm-linux-gcc常用参数讲解gcc编译器使用方法我们需要编译出运行在ARM平台上的代码,所使用的交叉编译器为arm-linux-gcc。
下面将arm-linux-gcc编译工具的一些常用命令参数介绍给大家。
在此之前首先介绍下编译器的工作过程,在使用GCC编译程序时,编译过程分为四个阶段:1. 预处理(Pre-Processing)2. 编译(Compiling)3. 汇编(Assembling)4. 链接(Linking)Linux程序员可以根据自己的需要让GCC在编译的任何阶段结束,以便检查或使用编译器在该阶段的输出信息,或者对最后生成的二进制文件进行控制,以便通过加入不同数量和种类的调试代码来为今后的调试做好准备。
和其它常用的编译器一样,GCC也提供了灵活而强大的代码优化功能,利用它可以生成执行效率更高的代码。
以文件example.c为例说明它的用法0. arm-linux-gcc -o example example.c不加-c、-S、-E参数,编译器将执行预处理、编译、汇编、连接操作直接生成可执行代码。
-o参数用于指定输出的文件,输出文件名为example,如果不指定输出文件,则默认输出a.out1. arm-linux-gcc -c -o example.oexample.c-c参数将对源程序example.c进行预处理、编译、汇编操作,生成example.0文件去掉指定输出选项"-o example.o"自动输出为example.o,所以说在这里-o加不加都可以2.arm-linux-gcc -S -o example.sexample.c-S参数将对源程序example.c进行预处理、编译,生成example.s文件-o选项同上3.arm-linux-gcc -E -o example.iexample.c-E参数将对源程序example.c进行预处理,生成example.i文件(不同版本不一样,有的将预处理后的内容打印到屏幕上)就是将#include,#define等进行文件插入及宏扩展等操作。
第 1 部分目的、知识与技能1.1 引言看到课程的名字,“嵌入式软件设计”,同学们能体会到,本课程所关注的是如何设计和实现基于ARM和Linux 的嵌入式系统。
那么,正确理解什么是嵌入式系统,为什么要学习嵌入式系统是学习本课程的重要前提。
在此基础之上,才能进一步学习和探讨有关嵌入式系统的设计与实现的问题,进而深入到基于ARM处理器架构,以及Linux操作系统的嵌入式系统的原理、设计及实现等一系列问题。
本章将首先讲解嵌入式系统的定义、为什么学习嵌入式系统,进而比较嵌入式系统与PC机及单片机的区别与联系,描述嵌入式系统的应用,以期给学生一个关于嵌入式系统的概念及直观的应用实例;然后,概述嵌入式系统研发的基本过程;最后,简明扼要地阐述研发嵌入式系统所要具备的知识和所要掌握的技能。
1.2 定义及学习目的“嵌入式系统”的全称应为“嵌入式计算机系统”。
所谓“嵌入”是指将一物“置于”另一物中,所以,嵌入式系统可以理解为“一个成为其他产品构成成分的、为特殊目的而个性化设计的计算机软、硬件的组合”。
要学习和了解嵌入式系统就必须首先正确理解计算机的内涵与外延。
什么是计算机?这是一个即容易、又很难回答的问题。
而且,不同背景的人会给出不同的答案。
但无论答案如何,有一点是共同的,即:中央处理单元(CPU)是计算机的基本的、不可缺少的组成成分。
而有资料表明,世界范围内每100个CPU中,只有不到10个用于台式机(包括各种PC机和台式工作站),其他90多个用于各种各样的产品中,见图1-1。
由此可见,“计算机= PC机”是一种不正确的陈述,更不是事实。
但正是这一不正确的陈述却被很多人接受“接受”为事实,并影响着高等学校计算机及相关院系的“教”与“学”。
目前,高等学校几乎都设有计算机科学与技术或相关院系,所开设的课程都围绕着WinTel(Windows + Intel), 即:绝大多数软件课程追随微软(Microsoft)Windows的走向;绝大多数硬件课程基于英特尔(Intel)的CPU 体系结构。
关于ARM7与ARM9,uCOS与Linux的对比详解arm7简介ARM7系列处理器是英国ARM公司设计的主流嵌入式处理器ARM7内核是0.9MIPS/MHz的三级流水线和冯诺伊曼结构;ARM9内核是5级流水线,提供1.1MIPS/MHz的哈佛结构。
ARM7没有MMU。
ARM7系列包括ARM7TDMI、ARM7TDMI-S、带有高速缓存处理器宏单元的ARM720T。
该系列处理器提供Thumb16位压缩指令集和EmbededICE软件调试方式,适用于更大规模的SoC设计中。
ARM7TDMI基于ARM体系结构V4版本,是目前低端的ARM核。
ARM7TDMI处理器是ARM通用32位微处理器家族的成员之一。
它具有优异的性能,但功耗却很低,使用门的数量也很少。
它属于精简指令集计算机(RISC),比复杂指令集计算机(CISC)要简单得多。
这样的简化实现了:高的指令吞吐量;出色的实时中断响应;小的、高性价比的处理器宏单元。
三级流水线:ARM7TDMI处理器使用流水线来增加处理器指令流的速度。
这样可使几个操作同时进行,并使处理和存储器系统连续操作,能提供0.9MIPS/MHz的指令执行速度。
ARM7TDMI的流水线分3令从存储器中取出。
内同时有5个指令在执行。
在同样的加工工艺下,ARM9TDMI处理器的时钟频率是ARM7TDMI的1.8~2.2倍。
ARM9简介ARM9系列处理器是英国ARM公司设计的主流嵌入式处理器,主要包括ARM9TDMI和ARM9E-S等系列。
ARM9采用哈佛体系结构,指令和数据分属不同的总线,可以并行处理。
在流水线上,ARM7是三级流水线,ARM9是五级流水线。
由于结构不同,ARM7的执行效率低于ARM9。
平时所说的ARM7、ARM9实际上指的是ARM7TDMI、ARM9TDMI软核,这种处理器软核并不带有MMU和cache,不能够运行诸如linux这样的嵌入式操作系统。
而ARM公司对这种架构进行了扩展,所以有了ARM710T、ARM720T、ARM920T、ARM922T等带有MMU和cache的处理器内核。
一、软件方面这应该是最大的区别了。
引入了操作系统。
为何引入操作系统?有什么益处嘛?1)方便。
主要表此刻后期的开发,即在操作系统上直接开发应用程序。
不像单片机一样一切都要从头写。
前期的操作系统移植工作,仍是要专业人士来做。
2)安全。
这是LINUX的一个特点。
LINUX的内核与用户空间的内存管理分开,不会因为用户的单个程序错误而引发系统死掉。
这在单片机的软件开发中没见到过。
3)高效。
引入进程的管理调度系统,使系统运行加倍高效。
在传统的单片机开发中大多是基于中断的前后台技术,对多任务的管理有局限性。
二、硬件方面此刻的8位单片机技术硬件发展的也超级得快,也出现了许多功能超级壮大的单片机。
可是与32ARM相较仍是有些差距吧。
ARM芯片大多把SDRAM,LCD等控制器集成到片子当中。
在8位机,大多要进行外扩。
总的来讲,单片机是个微控制器,ARM显然已是个微处置器了。
arm是单片机的一种,51也是,但arm的ROM和RAM远大于51,而且IO口功能和处置速度也是两个级别的,arm能上很多操作系统,51只能勉强上极为简单的实时操作系统,所以arm常常利用来开发等多媒体产品,51只能完成有限的实时控制功能,形象一点说,51和arm的品级不同就像和个人电脑的品级不同。
arm此刻分为A R M三个系列,A面向高端,M主要面向低端,或说白了就是单片机,此刻比较流行的cortex-m3,区别的话,应该在于高端低端,和性能上面;stm32(cortex-m3内核)的低端的价格可以低到10块钱以下,价格非常的低廉,性能很强,而且有库函数的支持,开发的流程简化很多,理论上应该会替代,但是其他的单片机也有自己的优势,比如:51也有自己的优势,简单,使用的人多,价格更低,开发工具更多,MSP430的低功耗,各有各的优势,取不取代还要看市场,公司的推广ARM与单片机其实没有什么区别的,ARM就是集成了各类模块,可以不像51单片机那样还要买什么芯片所需要的功能,ARM自身就集成了,直接写寄放器就哦了~~当然ARM的功耗低,实现的功能强大就不说了~~我感觉ARM最大的优点就是移植操作系统了!!这个是学习ARM的大方向吧,呵呵~~第一,处置速度块、处置能力强、贮存容量大、给用户带来便利传统的51单片机为8为处置器,而ARM芯片为32位处置器,简单的说,51单片机就是前期咱们应用的“赛扬”式电脑,而ARM芯片就是我们此刻应用的“双核”式电脑。
深入分析request_irq的dev_id参数作用上一篇/ 下一篇 2010-07-21 22:06:44 / 个人分类:Linux移植查看( 358 ) / 评论( 0 ) / 评分( 0 / 0 )注:若对kernel中断处理模型不是很清楚的话(如:irqaction的作用)可以先参考一下这篇文档:/u2/60011/showart.php?id=1079281这里主要讲request_irq的参数dev_id的作用,内容会涉及到少许上面文档提到的内容。
Request_irq的作用是申请使用IRQ并注册中断处理程序。
request_irq()函数的原型如下:我们知道,当使用内核共享中断时,request_irq必须要提供dev_id参数,并且dev_id的值必须唯一。
那么这里提供唯一的dev_id值的究竟是做什么用的?起先我以为dev_id的值是提供给kernel进行判断共享中断线上的哪一个设备产生了中断(即哪个irqaction 产生中断),然后执行相应的中断处理函数(irqaction->handler)。
实际上不是的,我们来看看《Linux Kernel Development – Second Edition》第六章中Shared Handlers这一节,其中有段总结性的文字如下:When the kernel receives an interrupt, it invokes sequentially each registered handler on the line. Therefore, it is important that the handler be capable of distinguishing whether it generated a giveninterrupt. The handler must quickly exit if its associated device did not generate the interrupt. This requires the hardware device to have a status register (or similar mechanism) that the handler can check. Most hardware does indeed have such a feature.这段话的大概意思是,发生中断时,内核并不判断究竟是共享中断线上的哪个设备产生了中断,它会循环执行所有该中断线上注册的中断处理函数(即irqaction->handler函数)。
arm 嵌入式 linux 系统分区升级的方法嵌入式 Linux 系统分区升级的方法在嵌入式Linux 系统中,升级系统分区是一个重要的任务。
通过升级系统分区,我们可以更新操作系统的版本、修复漏洞、改进系统性能等。
以下是一种常用的arm 嵌入式Linux 系统分区升级的方法:1. 确定升级方式:根据嵌入式设备的具体情况,确定升级方式。
常见的升级方式包括通过 SD 卡、USB 存储设备、网络等途径进行升级。
2. 准备升级包:根据需要升级的系统版本,准备相应的升级包。
确保升级包的完整性和正确性。
3. 备份关键数据:在进行系统分区升级之前,务必备份嵌入式设备中的关键数据。
这样可以在升级失败或出现问题时恢复到原始状态。
4. 下载升级包:将准备好的升级包下载到嵌入式设备的存储介质,如 SD 卡或 USB 存储设备中。
5. 进入升级模式:根据设备的要求,进入相应的升级模式。
这通常涉及按下特定的硬件按键或通过命令行等方式触发设备进入升级模式。
6. 执行升级命令:通过终端或命令行界面,执行相应的升级命令。
这通常是运行特定的脚本或命令,来实现系统分区的升级。
7. 等待升级完成:升级过程可能需要一段时间,请耐心等待。
确保升级过程中设备保持稳定的电源供应,以免升级中断或导致设备损坏。
8. 验证升级结果:升级完成后,通过检查系统版本、功能验证等方式,确认升级是否成功。
同时,重新安装或恢复之前备份的关键数据。
需要注意的是,进行系统分区升级前,请确保充分了解设备的硬件和系统要求,并在参考相关文档和指南的基础上操作,以避免可能的风险和损坏。
这是一种常用的 arm 嵌入式 Linux 系统分区升级的方法,你可以根据具体情况进行调整和实施。
一、选择题1.数字信号处理器( B )。
A. ADSB. DSPC.CPUD.GPP2.嵌入式微处理器(C)。
A. MCUB. DSPC. MPUD.SOC3.精简指令系统(C)。
A.CISCB.MIPSC.RISCD.CPLA4.可编程片上系统(A)。
A.SOPCB.SOCC. PDAD. OMAP5.复杂指令系统(A)。
A.CISCB.MIPSC.RISCD.CPLA 6.片上系统(B)。
A.SOPCB.SOCC. PDAD. OMAP 7.静态存储器(A)。
A. SRAMB. DRAMC. SDRAMD. RAM 8.动态随机存储器(B)。
A.SRAMB.DRAMC.RAMD.ROM 9.板级支持包(A)。
A. BSPB. DSPC. EDSPD.MCU10.采用冯﹒诺伊曼存储结构的ARM处理器( A)A.ARM7B.ARM9C.ARM10D.ARM1111.同CISC相比,下面哪一项不属于RISC处理器的特征_DA、采用固定长度的指令格式,指令规整、简单、基本寻址方式有2~3种。
B、减少指令数和寻址方式,使控制部件简化,加快执行速度。
C、数据处理指令只对寄存器进行操作,只有加载/存储指令可以访问存储器,以提高指令的执行效率,同时简化处理器的设计。
D、RISC处理器都采用哈佛结构12.实时系统是指( B)A 响应快的系统B 时间约束的系统C 单任务系统D 内核小的系统13、关于RISC指令系统描述不正确的是(A)。
A、指令条数多B、指令长度固定C、指令格式种类少D、寻址方式种类少14、通常所说的32位微处理器是指。
(C)A) 地址总线的宽度为32位B) 处理的数据长度只能为32位C) CPU 字长为32位 D) 通用寄存器数目为32个电脑技术中对CPU在单位时间内(同一时间)能一次处理的二进制数的位数叫字长。
15、嵌入式微控制器相比嵌入式微处理器的的最大特点(B)。
A、体积大大减小B、单片化C、功耗低D、成本高16、(B)不是嵌入式系统的三要素。
一、1、某文件属性显示为 drwxr-xr-x,则该文件是( A )。
A、目录文件B、普通文件C、链接文件D、管道文件2、在linux中,通常作为用户工作目录的是( C )。
A、 /bootB、/etcC、 /homeD、/bin3、下列命令中,用于显示系统进程列表的命令是( D )。
A、 locateB、mvC、 catD、ps4、表示目标文件的扩展名一般是( B )。
A、.cB、 .oC、 .hD、 .i5、在Makefile的工程管理中,( C )表示第一个依赖文件的名称。
A、 $*B、$+C、$<D、 $?6、以下不属于嵌入式系统特点的是 B 。
A 、不具备二次开发能力B 、面向通用应用 C、软硬件裁剪 D、软件固化于芯片7、对嵌入式板进行在线交叉调试(ICD方式),所使用的连接接口方式为( D )。
A、USBB、网络接口C、串口D、 JTAG8、linux与开发板串行调试方式中,所使用到的工具软件是( A )A、 minicomB、超级终端C、arm-linux-gccD、 gdb9、在Linux内核源代码中,与处理器体系结构有关的子目录是( C )。
A、/includeB、/initC、/archD、drivers10、下列文件系统,不是嵌入式系统的文件系统格式的是( B )A、cramfsB、ntfsC、romfsD、jffs11、以下属于Linux文件系统格式的是( A )A、EXT3B、FATC、FAT32D、NTFS12、某文件属性显示为–rwxr-xr-x,则该文件是( B )。
A、目录文件B、普通文件C、链接文件D、管道文件13、在linux中,通常作为存放系统配置文件的目录是( B )。
A、 /bootB、/etcC、 /homeD、/bin14、下列命令中,用于给特定进程发送信号的命令是( C )。
A、 locateB、mvC、 killD、 cat15、在Makefile的工程管理中,( D )表示目标文件的完整名称。
arm-linux 启动代码分析——stage1 (1)本文针对arm linux, 从kernel的第一条指令开始分析,一直分析到进入start_kernel()函数.我们当前以linux-2.6.19内核版本作为范例来分析,本文中所有的代码,前面都会加上行号以便于和源码进行对照.例:在文件init/main.c中:00478: asmlinkage void __init start_kernel(void)前面的"00478:" 表示478行,冒号后面的内容就是源码了.在分析代码的过程中,我们使用缩进来表示各个代码的调用层次.由于启动部分有一些代码是平台特定的,虽然大部分的平台所实现的功能都比较类似,但是为了更好的对code进行说明,对于平台相关的代码,我们选择at91(ARM926EJS)平台进行分析.另外,本文是以uncompressed kernel开始讲解的.对于内核解压缩部分的code,在arch/arm/boot/compressed中,本文不做讨论.一. 启动条件通常从系统上电到执行到linux kenel这部分的任务是由bootloader来完成.关于boot loader的内容,本文就不做过多介绍.这里只讨论进入到linux kernel的时候的一些限制条件,这一般是bootloader在最后跳转到kernel之前要完成的:1.CPU必须处于SVC(supervisor)模式,并且IRQ和FIQ中断都是禁止的;2. MMU(内存管理单元)必须是关闭的, 此时虚拟地址对物理地址;3. 数据cache(Data cache)必须是关闭的4. 指令cache(Instructioncache)可以是打开的,也可以是关闭的,这个没有强制要求;5. CPU 通用寄存器0 (r0)必须是0;6. CPU 通用寄存器1 (r1)必须是ARM Linux machinetype (关于machine type, 我们后面会有讲解)7. CPU 通用寄存器2 (r2) 必须是kernel parameterlist 的物理地址(parameter list 是由bootloader传递给kernel,用来描述设备信息属性的列表,详细内容可参考"Booting ARM Linux"文档).二. starting kernel首先,我们先对几个重要的宏进行说明(我们针对有MMU的情况): 宏 位置 默认值 说明KERNEL_RAM_ADDR arch/arm/kernel/head.S+26 0xc0008000 kernel在RAM中的的虚拟地址PAGE_OFFSET include/asm-arm/memeory.h+50 0xc0000000 内核空间的起始虚拟地址TEXT_OFFSET arch/arm/Makefile+137 0x00008000 内核相对于存储空间的偏移TEXTADDR arch/arm/kernel/head.S+49 0xc0008000 kernel的起始虚拟地址PHYS_OFFSET include/asm-arm/arch-xxx/memory.h 平台相关 RAM的起始物理地址内核的入口是stext,这是在arch/arm/kernel/vmlinux.lds.S中定义的:00011: ENTRY(stext)对于vmlinux.lds.S,这是ldscript文件,此文件的格式和汇编及C程序都不同,本文不对ldscript作过多的介绍,只对内核中用到的内容进行讲解,关于ld的详细内容可以参考这里的ENTRY(stext) 表示程序的入口是在符号stext.而符号stext是在arch/arm/kernel/head.S中定义的:下面我们将arm linuxboot的主要代码列出来进行一个概括的介绍,然后,我们会逐个的进行详细的讲解.在arch/arm/kernel/head.S中72 - 94 行,是armlinux boot的主代码:00072:ENTRY(stext) 00073: msr cpsr_c,#PSR_F_BIT | PSR_I_BIT | SVC_MODE @ ensure svc mode00074: @and irqsdisabled 00075: mrc p15, 0,r9, c0, c0 @ getprocessorid 00076:bl __lookup_processor_type @r5=procinfor9=cpuid 00077: movs r10,r5 @invalid processor (r5=0)?00078:beq __error_p @yes, error‘p’ 00079:bl __lookup_machine_type @r5=machinfo 00080: movs r8,r5 @invalid machine (r5=0)?00081:beq __error_a @yes, error‘a’ 00082:bl __create_page_tables 00083: 00084: 00091: ldr r13,__switch_data @address to jump to after00092: @mmu has beenenabled 00093: adr lr,__enable_mmu @return (PIC)address 00094: add pc,r10,#PROCINFO_INITFUNC 其中,73行是确保kernel运行在SVC模式下,并且IRQ和FIRQ中断已经关闭,这样做是很谨慎的.arm linux boot的主线可以概括为以下几个步骤:1. 确定processortype (75 - 78行)2. 确定machinetype (79 - 81行)3.创建页表 (82行) 4.调用平台特定的__cpu_flush函数 (在struct proc_info_list中) (94行) 5.开启mmu (93行)6. 切换数据 (91行)最终跳转到start_kernel (在__switch_data的结束的时候,调用了b start_kernel)下面,我们按照这个主线,逐步的分析Code.1. 确定processor type arch/arm/kernel/head.S中:00075: mrc p15, 0,r9, c0, c0 @ getprocessorid 00076:bl __lookup_processor_type @r5=procinfor9=cpuid 00077: movs r10,r5 @invalid processor (r5=0)?00078:beq __error_p @yes, error‘p’ 75行: 通过cp15协处理器的c0寄存器来获得processor id的指令.关于cp15的详细内容可参考相关的arm手册76行:跳转到__lookup_processor_type.在__lookup_processor_type中,会把processortype 存储在r5中77,78行: 判断r5中的processor type是否是0,如果是0,说明是无效的processortype,跳转到__error_p(出错)__lookup_processor_type 函数主要是根据从cpu中获得的processorid和系统中的proc_info进行匹配,将匹配到的proc_info_list的基地址存到r5中,0表示没有找到对应的processor type.下面我们分析__lookup_processor_type函数arch/arm/kernel/head-common.S中:00145:.type __lookup_processor_type,%function00146: __lookup_processor_type:00147: adr r3,3f00148: ldmda r3,{r5 - r7}00149: sub r3, r3,r7 @get offset between virt&phys00150: add r5, r5,r3 @convert virt addresses to00151: add r6, r6,r3 @physical address space00152: 1: ldmia r5,{r3,r4} @value, mask00153: and r4, r4,r9 @mask wanted bits00154: teq r3,r400155:beq 2f00156: add r5, r5,#PROC_INFO_SZ @sizeof(proc_info_list)00157: cmp r5,r600158:blo 1b00159: mov r5,#0 @unknown processor00160: 2: mov pc,lr00161:00162:00165: ENTRY(lookup_processor_type)00166: stmfd sp!,{r4 - r7, r9, lr}00167: mov r9,r000168:bl __lookup_processor_type00169: mov r0,r500170: ldmfd sp!,{r4 - r7, r9, pc}00171:00172:00176:.long __proc_info_begin00177:.long __proc_info_end00178:3: .long .00179:.long __arch_info_begin00180:.long __arch_info_end 145, 146行是函数定义147行: 取地址指令,这里的3f是向前symbol名称是3的位置,即第178行,将该地址存入r3. 这里需要注意的是,adr指令取址,获得的是基于pc的一个地址,要格外注意,这个地址是3f 处的"运行时地址",由于此时MMU还没有打开,也可以理解成物理地址(实地址).(详细内容可参考arm指令手册) 148行: 因为r3中的地址是178行的位置的地址,因而执行完后: r5存的是176行符号__proc_info_begin的地址; r6存的是177行符号__proc_info_end的地址; r7存的是3f处的地址. 这里需要注意链接地址和运行时地址的区别. r3存储的是运行时地址(物理地址),而r7中存储的是链接地址(虚拟地址). __proc_info_begin和__proc_info_end是在arch/arm/kernel/vmlinux.lds.S中: 00031: __proc_info_begin= .; 00032: *(.init) 00033: __proc_info_end= .; 这里是声明了两个变量:__proc_info_begin 和__proc_info_end,其中等号后面的"."是locationcounter(详细内容请参考) 这三行的意思是: __proc_info_begin 的位置上,放置所有文件中的".init"段的内容,然后紧接着是__proc_info_end 的位置. kernel 使用struct proc_info_list来描述processor type. 在include/asm-arm/procinfo.h 中: 00029: struct proc_info_list { 00030: unsignedint cpu_val; 00031: unsignedint cpu_mask; 00032: unsignedlong __cpu_mm_mmu_flags; 00033: unsignedlong __cpu_io_mmu_flags; 00034: unsignedlong __cpu_flush; 00035: constchar *arch_name; 00036: constchar *elf_name; 00037: unsignedint elf_hwcap; 00038: constchar *cpu_name; 00039: structprocessor *proc; 00040: structcpu_tlb_fns *tlb; 00041: structcpu_user_fns *user; 00042: structcpu_cache_fns *cache; 00043: }; 我们当前以at91为例,其processor是926的. 在arch/arm/mm/proc-arm926.S 中: 00464: .section ".init", #alloc,#execinstr 00465: 00466:.type __arm926_proc_info,#object 00467: __arm926_proc_info: 00468:.long 0x41069260 @ARM926EJ-S (v5TEJ) 00469:.long 0xff0ffff0 00470:.long PMD_TYPE_SECT | \ 00471: PMD_SECT_BUFFERABLE| \ 00472: PMD_SECT_CACHEABLE| \ 00473: PMD_BIT4 |\ 00474: PMD_SECT_AP_WRITE| \ 00475: PMD_SECT_AP_READ 00476:.long PMD_TYPE_SECT | \ 00477: PMD_BIT4 |\ 00478: PMD_SECT_AP_WRITE| \ 00479: PMD_SECT_AP_READ 00480:b __arm926_setup 00481:.long cpu_arch_name 00482:.long cpu_elf_name 00483:.long HWCAP_SWP|HWCAP_HALF|HWCAP_THUMB|HWCAP_FAST_MULT|HWCAP_VFP|HW CAP_EDSP|HWCAP_JA V A 00484:.long cpu_arm926_name 00485:.long arm926_processor_functions 00486:.long v4wbi_tlb_fns 00487:.long v4wb_user_fns 00488:.long arm926_cache_fns 00489:.size __arm926_proc_info,. - __arm926_proc_info 从464行,我们可以看到__arm926_proc_info 被放到了".init"段中. 对照struct proc_info_list,我们可以看到__cpu_flush的定义是在480行,即__arm926_setup.(我们将在"4.调用平台特定的__cpu_flush函数"一节中详细分析这部分的内容.) 从以上的内容我们可以看出: r5中的__proc_info_begin是proc_info_list的起始地址,r6中的__proc_info_end是proc_info_list的结束地址.149行:从上面的分析我们可以知道r3中存储的是3f处的物理地址,而r7存储的是3f处的虚拟地址,这一行是计算当前程序运行的物理地址和虚拟地址的差值,将其保存到r3中.150行: 将r5存储的虚拟地址(__proc_info_begin)转换成物理地址151行: 将r6存储的虚拟地址(__proc_info_end)转换成物理地址152行: 对照structproc_info_list,可以得知,这句是将当前proc_info的cpu_val和cpu_mask分别存r3,r4中153行: r9中存储了processorid(arch/arm/kernel/head.S中的75行),与r4的cpu_mask进行逻辑与操作,得到我们需要的值154行: 将153行中得到的值与r3中的cpu_val进行比较155行: 如果相等,说明我们找到了对应的processor type,跳到160行,返回156行: (如果不相等) , 将r5指向下一个proc_info,157行: 和r6比较,检查是否到了__proc_info_end.158行: 如果没有到__proc_info_end,表明还有proc_info配置,返回152行继续查找159行: 执行到这里,说明所有的proc_info都匹配过了,但是没有找到匹配的,将r5设置成0(unknownprocessor)160行: 返回2. 确定machine type arch/arm/kernel/head.S中:00079:bl __lookup_machine_type @r5=machinfo 00080: movs r8,r5 @invalid machine (r5=0)?00081:beq __error_a @yes, error ‘a’79行: 跳转到__lookup_machine_type函数,在__lookup_machine_type中,会把structmachine_desc的基地址(machine type)存储在r5中80,81行: 将r5中的machine_desc的基地址存储到r8中,并判断r5是否是0,如果是0,说明是无效的machinetype,跳转到__error_a(出错)__lookup_machine_type 函数下面我们分析__lookup_machine_type 函数: arch/arm/kernel/head-common.S中: 00176:.long __proc_info_begin00177:.long __proc_info_end00178:3: .long .00179:.long __arch_info_begin00180:.long __arch_info_end00181:00182: 00193:.type __lookup_machine_type,%function00194: __lookup_machine_type:00195: adr r3,3b00196: ldmia r3,{r4, r5, r6}00197: sub r3, r3,r4 @get offset between virt&phys00198: add r5, r5,r3 @convert virt addresses to00199: add r6, r6,r3 @physical address space00200: 1: ldr r3,[r5, #MACHINFO_TYPE] @ get machinetype00201: teq r3,r1 @matches loader number?00202:beq 2f @found00203: add r5, r5,#SIZEOF_MACHINE_DESC @ nextmachine_desc00204: cmp r5,r600205:blo 1b00206: mov r5,#0 @unknown machine00207: 2: mov pc,lr193, 194行: 函数声明195行: 取地址指令,这里的3b是向后symbol名称是3的位置,即第178行,将该地址存入r3. 和上面我们对__lookup_processor_type 函数的分析相同,r3中存放的是3b处物理地址.196行: r3是3b处的地址,因而执行完后: r4存的是3b处的地址 r5存的是__arch_info_begin 的地址 r6存的是__arch_info_end 的地址 __arch_info_begin 和__arch_info_end是在arch/arm/kernel/vmlinux.lds.S中: 00034: __arch_info_begin= .; 00035: *(.init) 00036: __arch_info_end= .; 这里是声明了两个变量:__arch_info_begin 和__arch_info_end,其中等号后面的"."是locationcounter(详细内容请参考) 这三行的意思是: __arch_info_begin 的位置上,放置所有文件中的".init"段的内容,然后紧接着是__arch_info_end 的位置. kernel 使用struct machine_desc 来描述machine type. 在include/asm-arm/mach/arch.h 中: 00017: struct machine_desc { 00018: 00022: unsignedint nr; 00023: unsignedint phys_io; 00024: unsignedint io_pg_offst; 00026: 00027: constchar *name; 00028: unsignedlong boot_params; 00029: 00030: unsignedint video_start; 00031: unsignedint video_end; 00032: 00033: unsignedint reserve_lp0:1; 00034: unsignedint reserve_lp1:1; 00035: unsignedint reserve_lp2:1; 00036: unsignedint soft_reboot:1; 00037:void (*fixup)(structmachine_desc *, 00038: struct tag *, char **, 00039: struct meminfo *); 00040:void (*map_io)(void); 00041:void (*init_irq)(void); 00042: structsys_timer *timer; 00043:void (*init_machine)(void); 00044: }; 00045: 00046: 00050: #defineMACHINE_START(_type,_name) \ 00051: static const struct machine_desc__mach_desc_##_type \ 00052:__attribute_used__ \ 00053:__attribute__((__section__(".init"))) ={ \ 00054:.nr =MACH_TYPE_##_type, \ 00055:.name =_name, 00056: 00057: #defineMACHINE_END \ 00058:}; 内核中,一般使用宏MACHINE_START来定义machine type. 对于at91, 在arch/arm/mach-at91rm9200/board-ek.c 中: 00137: MACHINE_START(AT91RM9200EK, "Atmel AT91RM9200-EK") 00138: 00139: .phys_io =AT91_BASE_SYS, 00140:.io_pg_offst =(AT91_VA_BASE_SYS >> 18)& 0xfffc, 00141:.boot_params =AT91_SDRAM_BASE + 0x100, 00142:.timer =&at91rm9200_timer, 00143:.map_io =ek_map_io, 00144: .init_irq =ek_init_irq, 00145:.init_machine =ek_board_init, 00146: MACHINE_END197行:r3中存储的是3b处的物理地址,而r4中存储的是3b处的虚拟地址,这里计算处物理地址和虚拟地址的差值,保存到r3中198行: 将r5存储的虚拟地址(__arch_info_begin)转换成物理地址 199行:将r6存储的虚拟地址(__arch_info_end)转换成物理地址 200行: MACHINFO_TYPE 在arch/arm/kernel/asm-offset.c 101行定义, 这里是取struct machine_desc中的nr(architecture number) 到r3中201行: 将r3中取到的machine type 和r1中的machine type(见前面的"启动条件")进行比较202行: 如果相同,说明找到了对应的machine type,跳转到207行的2f处,此时r5中存储了对应的structmachine_desc的基地址203行: (不相同), 取下一个machine_desc的地址204行: 和r6进行比较,检查是否到了__arch_info_end.205行: 如果不相同,说明还有machine_desc,返回200行继续查找.206行: 执行到这里,说明所有的machind_desc都查找完了,并且没有找到匹配的, 将r5设置成0(unknownmachine).207行: 返回3. 创建页表通过前面的两步,我们已经确定了processor type 和machine type.此时,一些特定寄存器的值如下所示:r8 = machineinfo (struct machine_desc的基地址)r9 = cpuid (通过cp15协处理器获得的cpu id)r10 =procinfo (struct proc_info_list的基地址)创建页表是通过函数__create_page_tables 来实现的.这里,我们使用的是arm的L1主页表,L1主页表也称为段页表(section page table)L1 主页表将4 GB 的地址空间分成若干个1 MB的段(section),因此L1页表包含4096个页表项(sectionentry). 每个页表项是32 bits(4 bytes)因而L1主页表占用4096 *4 = 16k的内存空间. 对于ARM926,其L1 section entry的格式为:(可参考arm926EJS TRM): 。