资源描述
嵌入式系统开发基础—基于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,
展开阅读全文