收藏 分销(赏)

嵌入式系统开发基础—基于ARM微处理器和Linux操作系统的课后答案.doc

上传人:精**** 文档编号:9997722 上传时间:2025-04-16 格式:DOC 页数:79 大小:1.84MB
下载 相关 举报
嵌入式系统开发基础—基于ARM微处理器和Linux操作系统的课后答案.doc_第1页
第1页 / 共79页
嵌入式系统开发基础—基于ARM微处理器和Linux操作系统的课后答案.doc_第2页
第2页 / 共79页
点击查看更多>>
资源描述
嵌入式系统开发基础—基于ARM微处理器和Linux操作系统的课后答案(全面完整版) (可以直接使用,可编辑 全面完整版资料,欢迎下载) 1-1 什么是嵌入式系统?嵌入式系统和普通计算机系统的区别是什么?举例说明。 答: 问题一:嵌入式系统是以应用为中心,以计算机为基础,其软硬件可裁剪配置,对功能、可靠性、成本、体积、功耗有严格约束的一种专用计算机系统。 问题二: 比较项目 嵌入式系统 普通计算机系统 引导代码 BootLoader引导,针对不同电路进行移植 主板的BIOS引导 OS WindowsCE、VxWorks、Linux等,需要移植 Windows、Linux,不移植 驱动程序 每个设备都必须针对电路板进行开发 OS中含有大多数,直接下载 协议栈 移植 OS或者第三方供应商提供 开发环境 借助服务器进行交叉编译 在本机可开发调试 仿真器 需要 不需要 1-2 简述嵌入式系统的构成 答: 硬件 微处理器 嵌入式系统的控制核心 外围电路 嵌入式系统的内存、I/O端口、复位电路、电源等 外设 USB、LCD、键盘等 软件 设备驱动接口 负责嵌入式系统与外设的信息交互 实时操作系统 包括与硬件相关的底层软件、系统内核、设备驱动接口、通信协议、图形界面、标准化浏览器等 可编程应用接口 为编制应用程序提供各种编程接口库 应用软件 1-3 答:R13:也记作SP,在ARM指令集中虽然没有强制,但是通常用于堆栈指针SP;在Thumb指令集中强制其作为堆栈指针。 R14:也记作程序连接寄存器LR(Link Register),用于保存子程序调用或异常中断处理返回时程序的返回地址。 R15:也记作程序计数器PC,用于标示下一条将要执行的指令地址。 CPSR:程序状态寄存器,包含条件标识位、中断标识位、当前处理器模式等状态和控制位。 SPSR:备份的程序状态寄存器。在异常中断处理过程中,用于保存被中断处理程序的执行现场和处理器状态。 1-4 答:(1)复位异常中断:当系统上电、复位、软件复位时产生该类型中断。 (2)未定义指令异常中断:当ARM处理器或系统中的协处理器认为当前指令未定义时,产生该中断。通常利用该中断模拟浮点向量运算。 (3)软件中断:可用于用户模式下特权操作的调用,既可以是系统功能,也可以是用户自定义的功能。 (4)指令预取中止异常中断:如果处理器预取的指令地址不存在,或者该地址不允许当前指令访问,产生该类型的中断。 (5)数据访问中止异常中断:如果数据访问的目标地址不存在,或者该地址不允许当前指令访问,则产生该中断。 (6)外部中断:当处理器的外部中断请求引脚有效,而且CPSR的I控制位被清除时,产生该类型的中断 (7)快速外部中断:当处理器的快速中断请求引脚有效,而且CPSR的F控制位被清除时,产生该中断。 1-5 答:(1)ADR R0, TABLE (2) ADR R1, DATA LDR R0, [R1] (3) LDR R0, =DATA (4) TABLE EQU 800 MOV R0, #TABLE (5) TABLE SPACE 20 1-6 答: R0=DATA1这组数据在存储器中所存放的起始地址,由编译器分配; R1=0x0C0D0E0F; R2=0xF; R3=0x8 ; [0x8 ]=R1 1-7 答: AREA SWITCH, CODE, READONLY ENTRY AND R2, R0, 0x3 ;R2ß R0的低两位 MOV R2, R2, LSL #30 ;将低两位移动到高两位 BIC R0, R0, 0x3 ;将R0的低两位清0 AND R3, R1, 0xC0000000 ;R3ßR1的高两位 MOV R3, R3, LSR #30 ;将高两位移动到低两位 BIC R1, R1, 0xC0000000 ;将R1的高两位清0 ORR R0, R0,R3 ;R1的高两位写入到R0的低两位 ORR R1, R1, R2 ;R0的低两位写入到R1的高两位 END 1-8 答: // main.c Include “stdio.h” extern int sum (int num[], int n); main(){ int array[10]={20, 30, 23, 5,15,64,6,15,72,73 }; int HE=sum(array, 10); printf(“The sum of array is %d”, HE); } //huibian.s AREA ASM, CODE, READONLY EXPORT sum sum MOV R2, #0 LOOP LDR R3, [R0], #4 ADD R2, R2, R3 SUB R1, R1, 1 CMP R1, 0 BNE LOOP MOV R0, R2 MOV PC, LR END 1-9 答:(1)要求很强的实时性,支持快速而明确的上下文切换 (2)具有高度的可裁剪性,支持动态链接,能够通过装卸某些模块来达到系统所需要的功能 (3)具有快速有效的中断和异常处理能力 (4)具有优化的浮点支持 (5)能够进行动态的内存管理 2-1 略。 2-2 略 2-3略 2-4按照要求完成以下操作。 (1)创建文件夹test。 mkdir test (2)进入test目录。 cd test (3)在test目录下用Vi编辑一个新文件test.c,其内容如下: #include <stdio.h> intmain() { int a,i=0; a=0; while(i<20) { a=a+3; printf("the value of a=%d \n",a); sleep(1); i=i+1; return 0; } } vi test.c (4)保存退出test.c。 (5)按照下面的要求编译test.c。 使用gcc -o test.o test.c编译,生成test.o。 使用gcc -g -o gtest.o test.c编译,生成gtest.o。 比较gtest.o 与 test.o的大小,哪个大?为什么? gtest.o 比 test.o 大,因为前者加入了一些调试信息。 (6)执行gtest.o与test.o。 2-5使用GDB调试上面的程序gtest.o。 (1)进入GDB调试环境,读入调试程序。 gdb gtest.o (2)列出源文件内容。 list (3)在程序a=0;处设置一个断点。 breakpoint (4)在程序printf("the value of a=%d \n",a);处设置一个断点。 (5)执行该程序。 run (6)查看变量a的值。 print a (7)查看变量a的类型。 (8)执行下一个源程序行,从而执行其整体中的一个函数。 (9)从断点开始继续执行到下一个断点。 (10)查看变量a的值,看看a是否有变化? (11)不停地执行continue,直到程序结束。 (12)退出GDB。 2-6根据要求编写Makefile文件。 五个文件分别是main.c、display1.h、display1.c、display2.h、display2.c,具体的代码如下: #include "stdio.h" int main(int argc,char **argv) { display1 ("hello");   display2("hello"); } display1.h void display1 (char *print_str); display2.h void display2 (char *print_str); display1.c #include "display1.h" void display1(char *print_str) { printf("This is display1 print %sn",print_str); } display2.c #include "display2.h" void display2 (char *print_str) { printf("This is display2 print %sn",print_str); } (1)如果上述文件在同一个目录,请编写Makefile文件。 (2)如果按照下面的目录结构存放文件,请编写Makefile文件。 |---bin 存放生成的可执行文件 |---obj 存放.o文件 |---include 存放display1.h和display2.h文件 |---src 存放main.c、display1.c、display2.c和Makefile (3)如果按照下面的目录结构存放文件,请编写Makefile文件。 |---bin 存放生成的可执行文件 |---obj 存放.o文件 |---include 存放display1.h和display2.h文件 |---src 存放main.c和Makefile |---src/display1 存放display1.c和Makefile |---src/display2 存放display2.c和Makefile src下面的makefile SRC_DIR=. INC_DIR=../include OBJ_DIR=../obj BIN_DIR=../bin include $(SRC_DIR)/test/makefile include $(SRC_DIR)/test1/makefile display1下面的makefile all:$(OBJ_DIR)/display1.o $(OBJ_DIR)/display1.o gcc -o $(BIN_DIR)/display1 $^ display2下面的makefile $(OBJ_DIR)/display2.o:$(SRC_DIR)/display2/display.c gcc -c $< -o $@ 3-1 答: 1.建立宿主机开发环境,包括操作系统及编译器等 2.配置宿主机相关服务及软件,如minicom、网络等 3.建立引导加载程序BootLoader 4.移植内核kernel 5.建立根文件系统root 6.建立应用程序的Flash磁盘分区 7.开发应用程序 8.烧写内核、根文件系统和应用程序 9.发布产品 3-2 答: Stage1: (1)硬件设备初始化。 (2)为加载BootLoader的Stage2准备RAM空间。 (3)复制BootLoader的Stage2到RAM空间中。 (4)设置好堆栈。堆栈指针的设置是为执行C语言代码做好准备。 Stage2: (1)初始化本阶段要使用到的硬件设备。 (2)检测系统内存映射(Memory Map)。 (3)将内核映像和根文件系统映像从Flash存储器上读到RAM空间中。 (4)为内核设置启动参数。 (5)调用内核。 3-3 答: 1. 数据结构file_operations 2.设备注册:驱动程序模块通过函数register_chrdev来完成内核的注册。 3.设备卸载:驱动程序模块通过函数unregister_chrdev来完成内核的卸载。 4.打开/释放设备:驱动程序通过函数open来完成设备的打开。驱动程序通过函数release来完成设备的释放 5.读写设备:read函数将数据从内核复制到应用程序空间,write函数则将数据从应用程序空间复制到内核。 6.读写以外的I/O操作:驱动程序模块通过ioctl函数来完成读写以外的I/O操作,如锁设备等 3-4 答: (1)register_chrdev(0,”demo”,&demo_fops); (2)mknod /dev/demo c 220 0 (3)insmod demo.o 4-1参见exam4-1 4-2参见exam4-2 4-3参见exam4-3 5-1 略 5-2 略 5-3 C:\Qt\>sqlite3 exam.db SQLite version 3.5.4 Enter ".help" for instructions Enter SQL statements terminated with a ";" sqlite> create table book(ID Integer primarykey,Name varchar(20),Type varchar(10 ),Count Integer); sqlite> insert into book values(1,'SQLite3','Database',10); sqlite> insert into book values(2,'Qt4','GUI',20); sqlite> select * from book; 1|SQLite3|Database|10 2|Qt4|GUI|20 sqlite> 5-4 #include<stdio.h> #include<sqlite3.h> int main() { sqlite3 *db=NULL; int rc; char *Errormsg; int nrow; int ncol; char **Result; int i=0; rc=sqlite3_open("exam.db",&db); if(rc){ fprintf(stderr,"can't open database:%s\n", sqlite3_errmsg(db)); sqlite3_close(db); return 1; }else printf("open database successly!\n"); char *sql="create table book(ID Integer primary key,Name varchar(20),Type varchar(10),Count Integer)"; sqlite3_exec(db,sql,0,0,&Errormsg); sql="insert into book values(1,'SQLite3','Database',10)"; sqlite3_exec(db,sql,0,0,&Errormsg); sql="insert into book values(2,'Qt4','GUI',20);"; sqlite3_exec(db,sql,0,0,&Errormsg); sql="select * from book"; sqlite3_get_table(db,sql,&Result,&nrow,&ncol,&Errormsg); printf("row=%d column=%d\n",nrow,ncol); printf("the result is:\n"); for( i=0;i<(nrow+1)*ncol;i++) printf("Result[%d]=%s\n",i,Result[i]); sqlite3_free(Errormsg); sqlite3_free_table(Result); sqlite3_close(db); return 0; } 5-5 参见案例 6-1略 6-2略 6-3参见exam6-3。 6-4参见案例 Linux内核构成 (国嵌) Linux/arch/arm/boot/compressed/head.s 1.解压缩 2.初始化 3.启动应用程序 1 arch/arm/boot/compressed/Makefile arch/arm/boot/compressed/vmlinux.lds 2. arch/arm/kernel/vmlinux.lds Linux内核启动流程 (国嵌) arch/arm/boot/compressed/start.S(head.s—负责解压缩) Start: .type start,#function .rept 8 mov r0, r0 .endr b 1f .word 0x016f2818 @ Magic numbers to help the loader .word start @ absolute load/run zImage address .word _edata @ zImage end address 1: mov r7, r1 @ save architecture ID mov r8, r2 @ save atags pointer 这也标志着u-boot将系统完全的交给了OS,bootloader生命终止。之后代码在133行会读取cpsr并判断是否处理器处于supervisor模式——从u-boot进入kernel,系统已经处于SVC32模式;而利用angel进入则处于user模式,还需要额外两条指令。之后是再次确认中断关闭,并完成cpsr写入 mrs r2, cpsr @ get current mode tst r2, #3 @ not user? bne not_angel mov r0, #0x17 @ angel_SWIreason_EnterSVC swi 0x123456 @ angel_SWI_ARM not_angel: mrs r2, cpsr @ turn off interrupts to orr r2, r2, #0xc0 @ prevent angel from running msr cpsr_c, r2 然后在LC0地址处将分段信息导入r0-r6、ip、sp等寄存器,并检查代码是否运行在与链接时相同的目标地址,以决定是否进行处理。由于现在很少有人不使用loader和tags,将zImage烧写到rom直接从0x0位置执行,所以这个处理是必须的(但是zImage的头现在也保留了不用loader也可启动的能力)。arm架构下自解压头一般是链接在0x0地址而被加载到0x30008000运行,所以要修正这个变化。涉及到 r5寄存器存放的zImage基地址 r6和r12(即ip寄存器)存放的got(global offset table) r2和r3存放的bss段起止地址 sp栈指针地址 很简单,这些寄存器统统被加上一个你也能猜到的偏移地址 0x30008000。该地址是s3c2410相关的,其他的ARM处理器可以参考下表 PXA2xx是0xa0008000 IXP2x00和IXP4xx是0x00008000 Freescale i.MX31/37是0x80008000 TI davinci DM64xx是0x80008000 TI omap系列是0x80008000 AT91RM/SAM92xx系列是0x20008000 Cirrus EP93xx是0x00008000 这些操作发生在代码172行开始的地方,下面只粘贴一部分 add r5, r5, r0 add r6, r6, r0 add ip, ip, r0 后面在211行进行bss段的清零工作 not_relocated: mov r0, #0 1: str r0, [r2], #4 @ clear bss str r0, [r2], #4 str r0, [r2], #4 str r0, [r2], #4 cmp r2, r3 blo 1b 然后224行,打开cache,并为后面解压缩设置64KB的临时malloc空间 bl cache_on mov r1, sp @ malloc space above stack add r2, sp, #0x10000 @ 64k max 接下来238行进行检查,确定内核解压缩后的Image目标地址是否会覆盖到zImage头,如果是则准备将zImage头转移到解压出来的内核后面 cmp r4, r2 bhs wont_overwrite sub r3, sp, r5 @ > compressed kernel size add r0, r4, r3, lsl #2 @ allow for 4x expansion cmp r0, r5 bls wont_overwrite mov r5, r2 @ decompress after malloc space mov r0, r5 mov r3, r7 bl decompress_kernel 真实情况——在大多数的应用中,内核编译都会把压缩的zImage和非压缩的Image链接到同样的地址,s3c2410平台下即是0x30008000。这样做的好处是,人们不用关心内核是Image还是zImage,放到这个位置执行就OK,所以在解压缩后zImage头必须为真正的内核让路。 在250行解压完毕,内核长度返回值存放在r0寄存器里。在内核末尾空出128字节的栈空间用,并且使其长度128字节对齐。 add r0, r0, #127 + 128 @ alignment + stack bic r0, r0, #127 @ align the kernel length 算出搬移代码的参数:计算内核末尾地址并存放于r1寄存器,需要搬移代码原来地址放在r2,需要搬移的长度放在r3。然后执行搬移,并设置好sp指针指向新的栈(原来的栈也会被内核覆盖掉) add r1, r5, r0 @ end of decompressed kernel adr r2, reloc_start ldr r3, LC1 add r3, r2, r3 1: ldmia r2!, {r9 - r14} @ copy relocation code stmia r1!, {r9 - r14} ldmia r2!, {r9 - r14} stmia r1!, {r9 - r14} cmp r2, r3 blo 1b add sp, r1, #128 @ relocate the stack 搬移完成后刷新cache,因为代码地址变化了不能让cache再命中被内核覆盖的老地址。然后跳转到新的地址继续执行 bl cache_clean_flush add pc, r5, r0 @ call relocation code 注意——zImage在解压后的搬移和跳转会给gdb调试内核带来麻烦。因为用来调试的符号表是在编译是生成的,并不知道以后会被搬移到何处去,只有在内核解压缩完成之后,根据计算出来的参数“告诉”调试器这个变化。以撰写本文时使用的zImage为例,内核自解压头重定向后,reloc_start地址由0x30008360变为0x30533e60。故我们要把vmlinux的符号表也相应的从0x30008000后移到0x30533b00开始,这样gdb就可以正确的对应源代码和机器指令。 随着头部代码移动到新的位置,不会再和内核的目标地址冲突,可以开始内核自身的搬移了。此时r0寄存器存放的是内核长度(严格的说是长度外加128Byte的栈),r4存放的是内核的目的地址0x30008000,r5是目前内核存放地址,r6是CPU ID,r7是machine ID,r8是atags地址。代码从501行开始 reloc_start: add r9, r5, r0 sub r9, r9, #128 @ do not copy the stack debug_reloc_start mov r1, r4 1: .rept 4 ldmia r5!, {r0, r2, r3, r10 - r14} @ relocate kernel stmia r1!, {r0, r2, r3, r10 - r14} .endr cmp r5, r9 blo 1b add sp, r1, #128 @ relocate the stack 接下来在516行清除并关闭cache,清零r0,将machine ID存入r1,atags指针存入r2,再跳入0x30008000执行真正的内核Image call_kernel: bl cache_clean_flush bl cache_off mov r0, #0 @ must be zero mov r1, r7 @ restore architecture number mov r2, r8 @ restore atags pointer mov pc, r4 @ call kernel 内核代码入口在arch/arm/kernel/head.S文件的83行。首先进入SVC32模式,并查询CPU ID,检查合法性 msr cpsr_c, #PSR_F_BIT | PSR_I_BIT | SVC_MODE @ ensure svc mode @ and irqs disabled mrc p15, 0, r9, c0, c0 @ get processor id bl __lookup_processor_type @ r5=procinfo r9=cpuid movs r10, r5 @ invalid processor (r5=0)? beq __error_p @ yes, error 'p' 接着在87行进一步查询machine ID并检查合法性 bl __lookup_machine_type @ r5=machinfo movs r8, r5 @ invalid machine (r5=0)? beq __error_a @ yes, error 'a' 其中__lookup_processor_type在linux-2.6.24-moko-linuxbj/arch/arm/kernel/head-common.S文件的149行,该函数首将标号3的实际地址加载到r3,然后将编译时生成的__proc_info_begin虚拟地址载入到r5,__proc_info_end虚拟地址载入到r6,标号3的虚拟地址载入到r7。由于adr伪指令和标号3的使用,以及__proc_info_begin等符号在linux-2.6.24-moko-linuxbj/arch/arm/kernel/vmlinux.lds而不是代码中被定义,此处代码不是非常直观,想弄清楚代码缘由的读者请耐心阅读这两个文件和adr伪指令的说明。 r3和r7分别存储的是同一位置标号3的物理地址(由于没有启用mmu,所以当前肯定是物理地址)和虚拟地址,所以儿者相减即得到虚拟地址和物理地址之间的offset。利用此offset,将r5和r6中保存的虚拟地址转变为物理地址 __lookup_processor_type: adr r3, 3f ldmda r3, {r5 - r7} sub r3, r3, r7 @ get offset between virt&phys add r5, r5, r3 @ convert virt addresses to add r6, r6, r3 @ physical address space 然后从proc_info中读出内核编译时写入的processor ID和之前从cpsr中读到的processor ID对比,查看代码和CPU硬件是否匹配(想在arm920t上运行为cortex-a8编译的内核?不让!)。如果编译了多种处理器支持,如versatile板,则会循环每种type依次检验,如果硬件读出的ID在内核中找不到匹配,则r5置0返回 1: ldmia r5, {r3, r4} @ value, mask and r4, r4, r9 @ mask wanted bits teq r3, r4 beq 2f add r5, r5, #PROC_INFO_SZ @ sizeof(proc_info_list) cmp r5, r6 blo 1b mov r5, #0 @ unknown processor 2: mov pc, lr __lookup_machine_type在文件的197行,编码方法与检查processor ID完全一样,请参考前段 __lookup_machine_type: adr r3, 3b ldmia r3, {r4, r5, r6} sub r3, r3, r4 @ get offset between virt&phys add r5, r5, r3 @ convert virt addresses to add r6, r6, r3 @ physical address space 1: ldr r3, [r5, #MACHINFO_TYPE] @ get machine type teq r3, r1 @ matches loader number? beq 2f @ found add r5, r5, #SIZEOF_MACHINE_DESC @ next machine_desc cmp r5, r6 blo 1b mov r5, #0 @ unknown machine 2: mov pc, lr 代码回到head.S第92行,检查atags合法性,然后创建初始页表 bl __vet_atags bl __create_page_tables 创建页表的代码在218行,首先将内核起始地址-0x4000到内核起始地址之间的16K存储器清0 __create_page_tables: pgtbl r4 @ page table address /* * Clear the 16K level 1 swapper page table */ mov r0, r4 mov r3, #0 add r6, r0, #0x4000 1: str r3,
展开阅读全文

开通  VIP会员、SVIP会员  优惠大
下载10份以上建议开通VIP会员
下载20份以上建议开通SVIP会员


开通VIP      成为共赢上传
相似文档                                   自信AI助手自信AI助手

当前位置:首页 > 学术论文 > 其他

移动网页_全站_页脚广告1

关于我们      便捷服务       自信AI       AI导航        抽奖活动

©2010-2025 宁波自信网络信息技术有限公司  版权所有

客服电话:4009-655-100  投诉/维权电话:18658249818

gongan.png浙公网安备33021202000488号   

icp.png浙ICP备2021020529号-1  |  浙B2-20240490  

关注我们 :微信公众号    抖音    微博    LOFTER 

客服