笔试题
![](https://i-blog.csdnimg.cn/blog_migrate/fa6c6deb83f37c59031b180c469e027c.png)
1. ARM指令和中B和BLX的区别?
B:
跳转执行 B 指令的格式为:
B{条件} 目标地址
B 指令是最简单的跳转指令。一旦遇到一个B 指令,ARM 处理器将立即跳转到给定的目标地址,从那里继续执行。
注意存储在跳转指令中的实际值是相对当前PC 值的一个偏移量,而不是一个绝对地址,它的值由汇编器来计算(参考寻址方式中的相对寻址)。
它是 24 位有符号数,左移两位后有符号扩展为 32 位,表示的有效偏移为 26 位(前后32MB 的地址空间)。
举例:
B Label ; 程序无条件跳转到标号 Label 处执行
CMP R1 ,# 0 ; 当 CPSR 寄存器中的 Z 条件码置位时,程序跳转到标号 Label 处执行
BEQ Label
BLX
带链接和状态切换的跳转。结合了BX与BL功能。BLX 指令的格式为:
BLX 目标地址
BLX 指令从ARM 指令集跳转到指令中所指定的目标地址,并将处理器的工作状态由ARM 状态切换到Thumb 状态,该指令同时将PC 的当前内容保存到寄存器R14 中。
因此,当子程序使用Thumb 指令集,而调用者使用ARM 指令集时,可以通过BLX 指令实现子程序的调用和处理器工作状态的切换。
总结
1、BL 和 BLX 指令可将下一个指令的地址复制到lr(r14,链接寄存器)中。2、BX 和 BLX 指令可将处理器的状态从 ARM 更改为 Thumb,或从 Thumb 更改为ARM。3、BLX label 无论何种情况,始终会更改处理器的状态。4、BX Rm 和 BLX Rm 可从 Rm 的位 [0]推算出目标状态。5、如果 Rm 的位 [0] 为 0,则处理器的状态会更改为(或保持在)ARM 状态。如果 Rm 的位 [0] 为 1,则处理器的状态会更改为(或保持在)Thumb 状态。
2. ARM处理器体系结构中IRQ发生时,处理器将执行哪些操作?
中断发生后,ARM核的操作步骤可以总结为4大步3小步。
1. 4大步3小步
保存执行状态:将CPSR复制到发生的异常模式下SPSR中;
模式切换:
CPSR模式位强制设置为与异常类型相对应的值,
处理器进入到ARM执行模式,
禁止所有IRQ中断,当进入FIQ快速中断模式时禁止FIQ中断;
保存返回地址:将下一条指令的地址(被打断程序)保存在LR(异常模式下LR_excep)中。
跳入异常向量表:强制设置PC的值为相应异常向量地址,跳转到异常处理程序中。
2. 步骤详解
保存执行状态
当前程序的执行状态是保存在CPSR里面的,异常发生时,要保存当前的CPSR里的执行状态到异常模式里的SPSR里,将来异常返回时,恢复回CPSR,恢复执行状态。
模式切换
硬件自动根据当前的异常类型,将异常码写入CPSR里的M[4:0]模式位,这样CPU就进入了对应异常模式下。不管是在ARM状态下还是在THUMB状态下发生异常,都会自动切换到ARM状态下进行异常的处理,这是由硬件自动完成的,将CPSR[5] 设置为 0。同时,CPU会关闭中断IRQ(设置CPSR 寄存器I位),防止中断进入,如果当前是快速中断FIQ异常,关闭快速中断(设置CPSR寄存器F位)。
保存返回地址
当前程序被异常打断,切换到异常处理程序里,异常处理完之后,返回当前被打断模式继续执行,因此必须要保存当前执行指令的下一条指令的地址到LR_excep(异常模式下LR,并不存在LR_excep寄存器,为方便读者理解加上_excep,以下道理相同),由于异常模式不同以及ARM内核采用流水线技术,异常处理程序里要根据异常模式计算返回地址。
跳入异常向量表
该操作是CPU硬件自动完成的,当异常发生时,CPU强制将PC的值修改为一个固定内存地址,这个固定地址叫做异常向量。
3. ARM状态与Thumb状态的切换指令是什么?举例说明
概念
ARM状态 arm处理器工作于32位指令的状态,所有指令均为32位
thumb状态 arm执行16位指令的状态,即16位状态
切换指令
由arm状态切换到thumb 状态将寄存器的最低位设置为1
BX指令:R0[0]=1,则执行BX R0指令将进入thumb状态
由thumb状态切换到ARM状态 寄存器最低位设置为0
BX指令:R0[0]=0,则执行BX R0指令将进入arm状态
举例
AREA Arm_to_Thumb,CODE, READONLY
ENTRY
CODE32
start
ldr r0,=aaa+1
mov r3,#18
bx r0
CODE16
aaa
mov r1,#12
mov r2,#10
END
4. 请问ARM支持哪几种异常类型,请列出其中断向量地址。
异常源分类
要进入异常模式,一定要有异常源,ARM规定有7种异常源:
异常源 | 描述 |
---|---|
Reset | 上电时执行 |
Undef | 当流水线中的某个非法指令到达执行状态时执行 |
SWI | 当一个软中断指令被执行完的时候执行 |
Prefetch | 当一个指令被从内存中预取时,由于某种原因而失败,如果它能到达执行状态这个异常才会产生 |
Data | 如果一个预取指令试图存取一个非法的内存单元,这时异常产生 |
IRQ | 通常的中断 |
FIQ | 快速中断 |
异常向量表是一段特定内存地址空间,每种ARM异常对应一个字长空间(4Bytes),正好是一条32位指令长度,当异常发生时,CPU强制将PC的值设置为当前异常对应的固定内存地址。
异常向量表:
![](https://i-blog.csdnimg.cn/blog_migrate/007b20d46077660fa316413ccd2afcff.png)
跳入异常向量表操作是异常发生时,硬件自动完成的,剩下的异常处理任务完全交给了程序员。由上表可知,异常向量是一个固定的内存地址,我们可以通过向该地址处写一条跳转指令,让它跳向我们自己定义的异常处理程序的入口,就可以完成异常处理了。
正是由于异常向量表的存在,才让硬件异常处理和程序员自定义处理程序有机联系起来。异常向量表里0x00000000地址处是reset复位异常,之所以它为0地址,是因为CPU在上电时自动从0地址处加载指令,由此可见将复位异常安装在此地址处也是前后结合起来设计的,不得不感叹CPU设计师的伟大,其后面分别是其余7种异常向量,每种异常向量都占有四个字节,正好是一条指令的大小,最后一个异常是快速中断异常,将其安装在此也有它的意义,在0x0000001C地址处可以直接存放快速中断的处理程序,不用设置跳转指令,这样可以节省一个时钟周期,加快快速中断处理时间。
存储器映射地址0x00000000是为向量表保留的。在有些处理器中,向量表可以选择定位在高地址0xFFFF0000处【可以通过协处理器指令配置】,当今操作系统为了控制内存访问权限,通常会开启虚拟内存,开启了虚拟内存之后,内存的开始空间通常为内核进程空间,和页表空间,异常向量表不能再安装在0地址处了。
比如Cortex-A8系统中支持通过设置CP15的C12寄存器将异常向量表的首地址放置在任意地址。
5. 在Linux中与外设的通信是层次化的,请画出外设寻址的分层模型。
本题有点没整明白在问什么,如果分析有误,欢迎给我留言。
Linux下一切皆文件,应用程序访问外设都需要通过驱动来操作外设。
Linux下设备类型分为:字符设备、块设备、网络设备。
架构图参考下图:
此外题目问画出外设寻址的分层模型,我想可能是想问如何访问SDRAM。
如下图所示,CPU要访问SDRAM或者flash需要先通过AHB总线,然后再通过外存接口控制器对外存寻址,然后进行数据的读写。
![](https://i-blog.csdnimg.cn/blog_migrate/6f50e3ddc687e86837673cb428693789.png)
6. 中断是嵌入式中重要的组成部分,中断服务程序通常需要满足哪些要求?
中断是嵌入式系统中重要的组成部分,很多外设和cpu的交互都是通过中断的方式进行。衡量一个OS的实时性很重要的一个特性就是中断的响应时间,长时间处于中断状态所以中断反应。一旦进入中断状态,cpu是在占用的,所以必须快速的出中断,否则会影响整体性能。
中断服务程序需要满足如下要求:
不能返回值;
不能向ISR传递参数;
ISR应该尽可能的短小精悍;
要尽快出中断;
耗时的中断要放到中断的底半部;
中断不要调用会因其休眠的函数;
通常中断中不做浮点操作;
中断函数中不可调用不可重入的函数,会引起意想不到的问题。
7. 使用共享资源时,为了使之满足互斥条件,通常有哪些方法?
共享资源也叫临界资源,在进程和线程、内核下都有自己的同步互斥机制, 仅考虑互斥的话,分开来看:
进程:信号量(值设置为1) 线程:互斥体、信号量(也叫信号灯) 内核:原子操作,自旋锁,信号量,互斥锁
8. 嵌入式操作系统有哪些同步通信服务?
Linux进程间通信方式主要有
信号
信号量
管道
消息队列
共享内存
套接字(本地的还有域套接字)
9. 请举例说明什么是可重入函数与不可重入函数。
1. 不可重入函数
在实时系统的设计中,经常会出现多个任务调用同一个函数的情况。如果有一个函数不幸被设计成为不可重入这样:那么不同任务调用这个函数时可能修改其他任务调用这个函数的数据,从而导致不可预料的后果。这样的函数是不安全的函数,也叫不可重入函数。
满足下列条件的函数多数是不可重入的
函数体内使用了静态的数据结构;
函数体内调用了malloc()或者free()函数(malloc和free为不可重入函数);
函数体内调用了标准I/O函数。
2. 可重入函数
可重入函数可以被一个以上的任务调用,而不必担心数据被破坏。可重入函数任何时候都可以被中断,一段时间以后又可以运行,而相应的数据不会丢失。可重入函数或者只使用局部变量,即保存在CPU寄存器中或堆栈中;或者使用全局变量,则要对全局变量予以保护
如何写出可重入函数在函数体内不访问那些全局变量,不使用静态局部变量,坚持只使用缺省态(auto)局部变量,写出的函数就将是可重入的。如果必须访问全局变量,记住利用互斥信号量来保护全局变量。或者调用该函数前关中断,调用后再开中断。Linux常见的可重入函数
3. 举例
A) 可重入函数
void strcpy(char * dest, char * src) {
while(*dest++=*src++);
*dest = 0;
}
B) 不可重入函数1
char temp;//全局变量
void swapchar(char * x, char * y) {
temp = *x;
*x=*y;
*y=temp;//访问了全局变量
}
C) 不可重入函数2
void swapchar(char * x, char * y) {
char temp;//局部变量
temp = *x;
*x=*y;
*y=temp;//使用了全局变量
}
4. 不可重入函数的使用注意事项
int Exam = 0;
unsigned int example( int para )
{
unsigned int temp;
Exam = para; // (**)
temp = Square_Exam( );
return temp;
}
Exam 是 int 型全局变量,函数 Squre_Exam() 返回 Exam 平方值,所以函数example()不具有可重入性。
此函数若被多个进程调用的话,其结果可能是未知的,因为当 Exam = para语句刚执行完后,另外一个使用本函数的进程可能正好被激活,那么当新激活的进程执行到此函数时,将使 Exam 赋与另一个不同的 para 值,所以当控制重新回到 “temp = Square_Exam( )” 后,计算出的temp很可能不是预想中的结果。
此函数应如下改进:
int Exam = 0;
unsigned int example( int para )
{
unsigned int temp;
[申请信号量操作] //(1) 加锁
Exam = para;
temp = Square_Exam( );
[释放信号量操作] // 解锁
return temp;
}
申请不到“信号量”,说明另外的进程正处于给 Exam 赋值并计算其平方过程中(即正在使用此信号),本进程必须等待其释放信号后,才可继续执行。
若申请到信号,则可继续执行,但其它进程必须等待本进程释放信号量后,才能再使用本信号。
5. 可重入函数
![](https://i-blog.csdnimg.cn/blog_migrate/07d3bb3bbd7b86d98fbc443c700d32f3.png)
10. int *fun(void)与int (*fun)(void)区别?
int *fun(void) fun是一个函数名,该函数的参数是void型,返回值是 int *
int (*fun)(void) fun是一个函数指针,该指针用于指向一个函数,函数的参数是void型,返回值是 int 即类型如下:
int function(void)
{
return 0;
}
11. 32位系统上有如下C程序:
struct person
{
int a;
unsigned short int m;
char b;
char *q;
char c;
};
char str[]="yikoulinux";
char *p = str;
int n = 10;
则sizeof(str)、sizeof(p)、sizeof(n)、sizeof(int)、sizeof(struct person)的值分别是:
编写测试程序
1 #include <stdio.h>
2
3 int main(int argc, const char *argv[])
4 {
5 struct person
6 {
7 int a;
8 unsigned short int m;
9 char b;
10 char *q;
11 char c;
12 };
13 char str[]="yikoulinux";
14 char *p = str;
15 int n = 10;
16
17 struct person pe;
18
19 printf("sizeof(str):%d sizeof(p):%d sizeof(n):%d sizeof(int):%d sizeof(struct person):%d\n",si
20
21 printf("%p\n %p\n %p\n %p \n %p \n",
22 &pe.a,&pe.m,&pe.b,&pe.q,&pe.c);
23 return 0;
24 }
编译执行:
sizeof(str):11 sizeof(p):4 sizeof(n):4 sizeof(int):4 sizeof(struct person):16
0xbf917d18
0xbf917d1c
0xbf917d1e
0xbf917d20
0xbf917d24
解释:
类型 | 值 | 解释 |
---|---|---|
sizeof(str) | 11 | 这是计算数组str的大小,因为没有设置数组大小,以赋值字符串大小为准,而常量字符串在后一个位置会补一个空字符'\0',所以一共11个字节 |
sizeof(p) | 4 | p是一个指针,32位机的指针都是32位,4个字节 |
sizeof(n) | 4 | n是一个整型变量,所以是4个字节 |
sizeof(int) | 4 | 计算int 类型占用的内存大小,是4个字节 |
sizeof(struct person) | 16 | 默认情况下结构体成员会字节对齐,详细分布见下图 |
![](https://i-blog.csdnimg.cn/blog_migrate/e9595d6e1da0db34d6432551f68806b1.png)
5T技术资源大放送!包括但不限于:C/C++,Arm, Linux,Android,人工智能,单片机,树莓派,等等。在公众号内回复「peter」,即可免费获取!!
记得点击分享、赞和在看,给我充点儿电吧