嵌入式面经问题总结
Posted Jocelin47
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了嵌入式面经问题总结相关的知识,希望对你有一定的参考价值。
arm工作模式
在特权模式下程序可以访问所有的系统资源。非特权模式和特权模式之间的区别在于有些操作只能在特权模式下才被允许,例如直接改变模式和中断使能等。而且为了保证数据安全,一般MMU会对地址空间进行划分,只有特权模式才能访问所有的地址空间。而用户模式如果需要访问硬件,必须切换到特权模式下,才允许访问硬件。
用户模式user是用户程序的工作模式,它运行在操作系统的用户态,它没有权限去操作其它硬件资源,只能执行处理自己的数据,也不能切换到其它模式下,要想访问硬件资源或切换到其它模式只能通过软中断或产生异常。
管理模式Supervisor是CPU上电后默认模式,因此在该模式下主要用来做系统的初始化,软中断处理也在该模式下。当用户模式下的用户程序请求使用硬件资源时,通过软件中断进入该模式。相比与IRQ和FIQ通过硬件触发,Supervisor优先级最低,而且是通过软件触发。
ARM系统 包括两类中断:一类是IRQ中断,另一类是FIQ中断。IRQ是普通中断,FIQ是快速中断,在进行大批量的复制、数据传输等工作时,常使用FIQ中断。FIQ的优先级高于IRQ。
IRQ和FIQ中断都属于ARM异常模式
软中断是什么?(ARM7中式软终端SWI,C-M3是SVC它们是一样的)
软中断在裸机上似乎不是那么重要,但是在操作系统上是必须的。对Linux来说,软中断是用户空间进入内核空间的唯一接口,我们常用的printf,fprintf()等函数的源码中就包含了系统调用(system call),而系统调用中就触发了软中断,然后将应用程序中的一些信息打印到屏幕,或写到硬盘。调用软中断相对应用程序的运行来说是比较费时间的,所以在编写Linux应用程序时要尽可能少的减少系统调用。
PendSV和上面软中断的SVC的区别是什么?
应用程序执行SVC 时都是希望所需的请求立即得到响应。另一方面,PendSV 则不同,它是可以像普通的中断一样被悬起的(不像SVC 那样会上
访)。OS 可以利用它“缓期执行”一个异常——直到其它重要的任务完成后才执行动作。
悬起PendSV 的方法是:手工往NVIC 的PendSV 悬起寄存器中写1。悬起后,如果优先级不高,则将缓期等待执行。
PendSV的典型使用场合是在上下文切换中;
一个系统中有两个就绪的任务,上下文切换被触发的场景可以是
1、执行一个系统调用
2、系统滴答定时器(SYSTICK)中断
OS部分
中断部分
中断触发到返回的具体行为(CM3权威指南里有)
概念引导:R14链接寄存器一般保存中断运行前程序运行PC的地址,在中断恢复后将R14中保存的地址恢复到R15 PC寄存器中。
具体行为:
1、保存当前的PC值到R14;保存PC值后将当前程序运行状态保存到SPSR(程序状态备份寄存器)中
2、切换到相应的中断类型,IRQ或者FIQ模式
3、根据异常向量表跳转到相应的中断服务子程序的地址,PC进入中断服务子程序中开始处理中断
4、中断完成后,需要恢复现场,将SPSR保存的程序运行状态恢复到CPSR中,R14中保存的被中断程序的地址恢复到PC中
权威指南中的分析:
一、中断的响应:
三步操作:
1、入栈: 把8个寄存器的值压入栈
2、取向量:从向量表中找出对应的服务程序入口地址
3、堆栈指针MSP/PSP,更新堆栈指针SP,更新连接寄存器LR,更新程序计数器PC
入栈
响应异常的第一个行动,就是自动保存现场的必要部分。
依次把xPSR, PC, LR, R12以及R3‐R0由硬件自动压入适当的堆栈中:如果当响应中断时,当前的代码正在使用PSP,则压入PSP,即使用线程堆栈;否则压入MSP,使用主堆栈。一旦进入了服务例程,就将一直使用主堆栈。
取向量
更新寄存器
二、异常返回
比如第一个指令:通过BX LR告诉CPU我现在需要异常返回了。因为现在LR种保存的是EXC_RETURN,EXC_RETURN具体位段如下:可以知道是继续中断嵌套执行中断还是返回用户程序。
中断嵌套如何实现(NVIC相关)
中断机制的具体实现(还在答中断具体行为,他问的应该是两级向量表如何查找)
任务部分
任务在内存中的组织方式(TCB-用户栈-用户代码)
上下文切换时任务在内存中是如何变动,任务调度点
优先级反转如何解决,任务抢占如何发生,通信机制
流水线冲突与解决以及cache-miss
程序生成到运行的过程
第一个阶段是预处理:预处理阶段主要的工作是
- 将所有的”#define”删除,并且展开所有宏定义。
- 处理所有条件预编译指令,比如”#if”
- 处理”#include”预编译指令,将包含的文件插入到该预编译指令的位置
- 删除所有注释”//”和”/**/”
- 添加行号和文件
- 保留所有的#pragme编译器指令,因为编译器需要使用它们
经过预编译后的.i文件不包含任何宏定义,因为所有的宏已经被展开。
第二个阶段是进行编译:
- 编译阶段会将展开的程序进行词法及语法分析,并且编译器还会将代码做一定的优化处理,这是.i文件变成.s文件。
第三个阶段是汇编:(汇编部分结合了第三章的内容)
- 汇编会将编译器检查后的代码由高级语言翻译成机器语言,使得执行的程序并不依赖于某一个特定的CPU。这时由.s文件生成.o目标文件,同时编译后生成的不同的信息按照段的形式进行存储,比如
程序源代码编译后的机器指令放在代码段(.text段)里、全局变量和局部静态变量数据存放在.data段、为初始化的全局变量和局部静态变量数据存在.bss段,同时还有符号表以及段表其他信息。
第四个阶段是链接:(链接部分结合了第四章的内容)
链接一般分为两步链接:
- 第一步 空间与地址分配
这一步,连接器将能够获得所有输入目标文件的段长度,并且将它们合并,计算出输出文件中各个段合并后的长度与位置,并建立映射关系。 - 第二部 符号解析与重定位
获取上一步的信息,读取输入文件中段的数据,重定位信息,进行符号解析与重定位,调整代码中的地址。因为第一步我们已经确定每个段以及相应的符号的位置,所以我们通过这一步就可以进行重定位确定需要跳转的地址(P103-106介绍了对于外部引用的变量未链接前只是存放一个临时地址,经过分配空间后有了地址,重定位后就可以找到真正的地方了)。这一步是连接过程的核心,特别是重定位过程。
静动态链接的区别
进程间通信方式
- 无名管道:只能用于具有亲缘关系的进程之间的通信。
函数:int pipe(int pipefd[2]); 相当于在内核开辟了一块缓冲区。 - fifo有名管道:任意两个进程间通信。
函数:int mkfifo(const char *pathname, mode_t mode);
1、内核会为fifo文件开辟一块缓冲区,操作fifo文件,可以操作缓冲区,实现进程间通信——实际上就是文件读写。
2、open函数的注意事项:打开fifo文件的时候,read端会阻塞等待write端open,write端同理,也会阻塞等待另外一端打开,除非对端的OPEN也打开。 - 信号量:实现进程间的同步。
- 信号:用于进程间的异步通信。
通过发送信号事件不同轮询或者中断的去查看事件,只需要事件好了以后通过发送信号通知我,实现的是异步通信。 - 消息队列:数据传输。
- 共享内存:数据传输,开辟一块内存区域,大家都能访问,进程退出后,这块内存会保存下来,后来者还可以继续使用。
- mmap:数据传输,跟共享内存不同的是,这里是在内存开辟一片缓冲区,把文件映射到内存上,你直接去操作内存(操作内存就是快!)就可以了。
- Sokect:套接字缓冲区,不同主机之间不同进程间的通信。
io多路复用
为什么要用io多路复用:
首先我有一个网络有很多客户端连接请求,如果我用多线程去处理的话,会带来上下文切换,如果客户端特别多的话,会带来上下文切换带来的代价特别高。
因此从多线程->单线程。
一、select
select缺点:
1、限制1024位的位图;
2、每次遍历完一次后,要通过FDSET重新置位所有的fds,再重新判断,所以fd_set是不可重用的;
3、每次都要将fd_set从用户态拷贝到内核态,仍有一个开销;
4、从内核态返回后,我并不知道哪些置位了,我又得从0到fd_max O(n)的去遍历一边判断有哪些置位了。
二、poll
poll: poll在select的基础上将位图改为了pollfd
struct pollfd {
int fd; /* 文件描述符 */
short events; /* 等待的事件 */
short revents; /* 实际发生的事件 */
};
通过建立一个struct pollfd类型的数组。
跟select相比解决了select的缺点1、2,3和4没有解决:
1、select限制为1024,而poll可以自己规定大小,没有事件数量限制;
2、不需要重新置位fd_set,只需要把revents判断后置0即可;
三、epoll
手撕memcpy
I2C SPI 基础知识速率 工作模式
以上是关于嵌入式面经问题总结的主要内容,如果未能解决你的问题,请参考以下文章