最近在准备秋招,在此每天总结一些面试常问的问题,希望可以和大家一起进步。(持续更新中……)
目录
27.栈帧指针FP、堆栈指针SP、程序计数器PC和链接寄存器LR
1. I2C为什么加上拉电阻,为什么使用开漏输出
I2C总线支持总线上有多个主设备和多个从设备,用推挽输出的话可能出现设备间短路的情况,所以使用开漏输出。因为开漏输出无法输出高电平,所以使用上拉电阻实现输出高电平的能力。开漏输出还能实现线与的作用,多个主机抢占总线进行仲裁时会用到线与的特性。
2. 硬中断和软中断的区别
1) 硬件中断是由硬件外设引发的, 软中断是执行中断指令产生的;
2) 硬件中断的中断号是由中断控制器提供的, 软中断的中断号由指令直接指出, 无需使用中断控制器;
3) 硬件中断可设置CPU的屏蔽位进行屏蔽, 软中断不可屏蔽
4) 硬件中断处理程序要确保它能快速地完成任务, 这样程序执行时才不会等待较长时间, 称为上半部;
5) 软中断处理硬中断未完成的工作, 是一种推后执行的机制, 属于下半部。
3. 什么是交叉编译,为什么要使用交叉编译
交叉编译是指在一中计算机平台中编译出可以在另一种平台上运行的程序,比如在X86架构的计算机上编译出32位ARM架构的程序,我们称对应的编译器为交叉编译器。交叉编译在嵌入式系统开发中应用非常广泛,因为嵌入式设备通常资源有限,处理器性能不足以运行我们要使用的编译器,甚至有的嵌入式设备连操作系统都没有,自然就无法运行编译器了,所以我们通常选择交叉编译的方式生成目标平台的程序。
4. 嵌入式基于ROM运行和基于RAM运行有什么区别
1)运行速度:基于ROM运行速度相对较慢,因为基于ROM运行会涉及到把变量和程序从flash拷贝到RAM的过程;
2) RAM资源:基于ROM运行的可用RAM资源比基于RAM运行时的资源要多,因为基于RAM运行时所有的程序和数据都保存在了RAM中;
3)程序重定位:基于RAM运行时一般会将代码从外部存储介质加载到RAM中,加载过程涉及到重定位的操作;
4) 数据安全性:基于ROM运行更安全,因为ROM是只读存储器,程序无法被恶意修改;
5. I2C能接多少个设备,由什么决定的
理论上可以挂载127个设备,由I2C地址决定。因为设备地址为7位(也有10位的),也就是2^7 = 128个地址,但是广播地址0X00不能用,剩下127个地址,对应127个设备。I2C协议没有限制总线上的最大设备数,但是规定了总线电容不得超过400pF,所以实际上I2C总线上的设备数推荐不超过8个。
注:规定总线电容是因为I2C外部由两个上拉电阻,电阻和总线电容会产生RC延时效应,总线电容越大,那信号的边沿就会越平缓,传输速度较快时可能会导致信号质量出现问题。
6. I2C读取寄存器失败应该如何排查问题
1)检查数据线和时钟线是否通过上拉电阻上拉到电源,数据线和时钟线是否连接正确
2) 检查设备地址是否正确,总线上的设备地址是否冲突
3) 检查设备供电是否正常,设备是否关闭写保护
4) 检查通信速率是否过高,是否超过了设备支持的通信速率
5) 可通过示波器检查通信时序是否正确,是否符合I2C协议的规范
7. SPI读取寄存器失败应该如何排查问题
1) 检查信号线是否连接正确,片选线是否使能、是否接线过长,是否接触不严
2) 通信速率是否过高,是否超过了设备支持的通信速率
3) 数据线外接可能导致数据异常,比如用杜邦线来连接主机和从设备,可能会受到外界的干扰,导致数据错乱
4) 检查CPOL和CPHA是否配置正确,如果主机和从机的CPOL和CPHA配置相反,仍然可以正常收发数据,但可能导致数据移位
5) 如果数据错位,可以尝试更改SCK信号线的上下拉
6)如果出现数据错乱或者通信错误,可使用示波器查看通信波形,进行排查
8. RS485数据异常应该如何排查问题
1) 检查波特率是否配置正确,是否过高
2) 检查时钟是否正确,比如电路板上的晶振是否与推荐电路的晶振频率一样
3) 检查RS485收发器芯片终端引脚A、B之间的电阻值是否为120Ω,有些USB转RS485模块上已经在引脚间加了120Ω电阻了,如果再加一个电阻会导致阻抗失配,从而导致数据异常
4) 检查附近是否有强磁干扰,若有则需要加隔离保护或远离干扰源
5) 检查RS485的双绞线屏蔽层是否接地
9. 什么是回调函数,为什么要使用回调函数
回调函数就是通过函数指针调用的函数,回调函数不是由函数的实现方直接调用,而是在特定的事件或某条件发生时由另一方调用的,用于对该事件或条件进行响应。使用回调函数可以把调用者和被调用者分开,调用者不必关心谁是被调用者,只需要知道存在一个具有特定原型和限制条件的被调用函数,使得程序设计更加灵活。
10. SPI可以去掉哪根线
SPI由四根线:SCLK、MISO 、MOSI、CS。当只需要主机向从机发送指令,而不需要从机回复数据时,MISO信号线就可以去掉;当主机只想读取从机的数据,而不需要向从机发送指令时,MOSI就可以去掉;如果只有一个从机,那么CS片选线也可以不要,直接将其固定为有效电平,一直处于使能的状态。需要注意的是,时钟线不能去掉,发送数据和接收到数据时都需要使用时钟线进行数据采样。
11. STM32的启动流程
STM32有三种复位方式:上电复位、硬件复位和软复位,复位后ARM内核会设置堆栈大小、初始化中断向量表。从0X0000 0000处取出堆栈指针MSP的初始值,从0X0000 0004中取出程序计数器指针PC的初始值,PC指针指向的就是中断向量表中的复位中断函数,之后内核就会从PC指向的地址读取指令执行了。事实上,STM32可以通过设置BOOT0和BOOT1引脚来选择启动模式,如下:
系统时钟的第四个上升沿锁存BOOT引脚的值,通过选择不同的启动模式,可将对应的地址映射到0X0000 0000和0X0000 0004这两个地址上。在复位中断服务程序中会跳转到SystemInit函数中进行配置系统时钟,然后调用__main函数初始化用户堆栈,最后调用main函数。
(启动流程主要由启动文件实现的,在ARM汇编语言基础-CSDN博客的第11小节对STM32的启动文件进行了详细分析。)
12. STM32程序跑飞或者卡死怎么调试
STM32程序跑飞可能有以下几种原因:
1) 中断处理不当:打开某中断后没及时响应和清理中断标志,导致频繁进入中断,造成程序死机的假象。
2) 堆栈溢出:若程序代码量较大,程序中存在大量的函数调用和局部变量,可能导致堆栈溢出,使程序跑飞。
3) 内存越界:比如访问数组越界可能会意外修改系统寄存器中的数据,导致程序失控。
4) 看门狗:如果开启看门狗但没在适当的时间喂狗,会导致程序频繁重启。
5) 晶振失效、野指针、外部电磁干扰等。
找到程序跑飞的位置:
情况一:程序逻辑执行不正常,但程序没有报错,中断可以正常运行。
解决方法:使用调试器在线调试,找到程序卡死的位置;在程序中添加打印或点灯判断程序卡死位置。检查是不是因为while()等循环体的判断条件错误,导致死循环了。
情况二:程序逻辑执行不正常,部分中断不能正常运行,但程序没有报错。
解决方法:大概率就是中断服务函数中没有及时清除中断标志,导致频繁进入中断,造成假死的现象。可以将不能运行的中断的优先级调整为最高优先级,看是否能正常运行,如果能运行了就说明程序卡死在中断程序中了,检查相应的中断函数就行了。
情况三:程序报错了,直接卡死。
解决方法:此时应该是发生了操作错误,比如数组越界,除数为0等,此时程序会自动跳转到HardFault_Handle函数中,解决方法可以查看这篇文章:http://t.csdnimg.cn/sbFxZ。
(除此之外,还可能遇到其他导致程序卡死的情况,之后遇到了会在此进行补充。)
13. 介绍一下I2C的传输时序
这个问题在面试时经常会问到,务必掌握,这里总结一下。I2C有两根数据线:SCL时钟线和SDA数据线,SDA和SCL在空闲时都处于高电平状态,图示如下:
(1)起始信号:SCL为高电平时,拉低SDA表示起始信号,如①所示;
(2)停止信号:SCL为高电平时,拉高SCL表示停止信号,如②所示;
(3)应答信号:发送器每发送一个字节数据,会释放SDA数据线,在第9个时钟接收器会回复一个ACK应答信号(低电平表示应答),如③所示;
(4)数据有效性:SCL高电平期间SDA数据线必须保持稳定,只有在SCL低电平期间,SDA才可以变化,并且在SCL上升沿之前就应该准备好;
(5)重复起始信号:当主机想开始一个新的传输,但不想释放总线的使用权,那就可以发出一个新的起始信号,也就是重复起始信号。使用重复起始信号可以避免两次数据传输间的重新确定通讯设备的过程,从而节省时间和开销。重复起始信号和起始信号一样,SDA在SCL低电平时拉高,然后SCL拉高,这样两根信号线就处于空闲状态了,然后主机重新将SDA拉低,则表示发送一个重新起始信号。
(6)I2C读写时序描述:
写时序:(需要注意的是,I2C从机设备地址通常是7位,有的从机地址为10位,10位的不常用 ,这里就不介绍了)
① 主机先向从机发送一个起始信号,表示开始传输数据;
② 接着主机会发送一字节从机地址,前7位为从机地址,第8位是读写位,0表示写数据,1表示读数据;
③ 总线上的从机接收到这8bit数据后,会和自己的设备地址进行比较,若匹配成功,从机则会向主机发送一个ACK应答信号;
④ 主机接收到应答信号后才会继续发送数据,发送数据的顺序为高位在前,低位在后;
⑤ 主机向从机写数据时通常是写两字节数据,先发送一字节数据指定要写入的寄存器地址,等待从机回复一个ACK应答;
⑥ 接着主机再发送一字节数据指定要写入的数据,等待从机回复ACK应答;
⑦ 最后从机发送一个停止信号表示通讯结束。
上面是写一个数据的情况,如果要写入多个数据也比较简单,也是每次发送一字节数据,等待从机回复一个应答信号,当数据发送完成后发送一个停止信号就行了,示意图如下:
读时序:(前三步和写时序相同,这里也只介绍7位从机地址的情况)
① 主机先向从机发送一个起始信号,表示开始传输数据;
② 接着主机会发送一字节从机地址,前7位为从机地址,第8位为读写位,这里读写位为0,因为要先发送从机地址(0表示写数据,1表示读数据);
③ 总线上的从机接收到这8bit数据后,会和自己的设备地址进行比较,若匹配成功,从机则会向主机发送一个ACK应答信号;
④ 主机发送一字节数据指定要读取的寄存器地址,等待从机回复一个ACK应答信号;
④ 接着主机重新发送一个起始信号和从机地址,此时地址的读写位为1,表示要开始读数据了;
⑤ 从机向主机回复一个ACK应答后,就开始向主机寄存器中的数据,每读取一字节数据,主机都要给从机发送一个ACK应答;
⑥ 数据读取完成后,主机向从机回复一个NACK非应答,表示通讯结束;
⑦ 最后从机发送一个停止信号表示通讯结束。
14. 什么是I2C死锁,怎么解决
最近面试被问到了这个问题,之前竟然没听说过,这里总结一下。
(1)I2C死锁时的现象
SCL一直为高,SDA一直为低。(注意,只有硬件I2C会出现死锁,软件模拟I2C不会出现死锁的现象)
(2)I2C死锁的原因
一般有两种情况会导致死锁,一种是从设备向主机回复ACK应答时,主机意外复位,而从机不会自动复位;另一种是从设备向主机发送数据位是0时,主机意外复位,而从机不会自动复位。这两种情况其实本质一样:就是当从设备将SDA数据线拉低时,主机意外复位,但从机不会自动复位,就会导致I2C死锁。因为主机意外复位后SCL仍为高电平,而从设备不会自动复位,I2C规定只有在SCL低电平时,SDA才允许变化,所以从设备就会一直输出低电平;而主机检测到SDA为低电平后,会认为当前总线已经被占用。此时从设备在等待主机将SCL拉低,而主机在等待从设备释放SDA数据线,两者互相等待,也就是造成了I2C死锁。
(3)常见的解决I2C死锁的方法
① 选择带复位功能的从设备;
② 在I2C总线上添加一个总线恢复设备,当检测到SDA数据线被拉低超过指定的时间时,就在总线上产生9个SCL时钟,使从设备完成数据发送,进而释放SDA,从死锁中恢复出来(这个总线恢复设备一般需要具备编程功能,一般可以使用单片机来实现,也可以使用具有I2C死锁恢复的缓冲器);
③ 主机检测到SDA被拉低超过指定时间后,主动复位从设备,前提是总设备具有复位引脚,并且主机可以控制从设备的复位引脚;
④ 主机检测到SDA被拉低超过指定时间后,推送9个SCL时钟,使从设备完成数据发送,进而释放SDA,从死锁中恢复出来。
15. 简单对比下SPI和I2C
① I2C是串行同步半双工的通信协议,SPI总线是串行同步全双工的通信协议;
② I2C有两根数据线,SPI有四根数据线,相应的,I2C的读写时序就要比SPI要复杂一些;
③ I2C通过从机地址来选择通讯设备,SPI通过片选线来选择从机设备;
④ I2C比SPI传输速率慢,I2C在标准模式下传输速率可达100kbit/s, 在快速模式下可达400kbit/s, 在高速模式下可达3.4Mbit/s,SPI的传输速率并没有一个官方的标准,通常能达到甚至超过10 Mbit/s,已知有的器件的SPI已达到50Mbit/s;
⑤ I2C支持多主多从,而SPI总线上只能有一个主设备;
综上,SPI总线适用于对速度要求较高且连接设备数量较少的应用场景,而I2C总线适用于连接设备数量较多且速度要求相对较低的应用场景。
16. 关于STM32的ADC外设的一些问题
在项目中通常会使用ADC来采集传感器数据,这里简单回顾下ADC的相关内容。STM32的ADC是12位逐次逼近型的模数转换器,用来将引脚上连续变化的模拟电压转换为内存中存储的数字变量。有18个输入通道,分别为16个外部通道和2个内部通道(内部温度和内部参考电压)。先看下STM32的ADC外设框图。
ADC有两种触发方式:软件触发和硬件触发,软件触发就是在程序中调用一条代码触发ADC转换,硬件触发就是④中的这些触发源。图中②为输入通道,分别对应不同的GPIO引脚,用来输入从外部(如传感器)采集到的模拟量;ADC可以将多个通道以任意顺序进行转换,转换时可以选择两种转换类型,也就是③中的规则通道和注入通道,介绍如下:(平常用的最多的是规则通道)
规则通道:最多16个输入通道进行转换,但是对应的规则数据寄存器只有一个,也就是说每转换完成一个输入通道后,就要及时将数据从规则数据寄存器中读走,否则就会被下一个通道转换结果覆盖,所以通常配合DMA一起使用。
注入通道:最多4个输入通道进行转换,对应的注入数据寄存器有四个,所以就不用关心数据被覆盖的问题了。
区别:除了转换通道的数量不同外,注入通道的优先级比规则通道的优先级高,当规则通道转换过程中,如果启动注入通道,则注入通道会打断规则通道的执行,注入通道转换完成后才会继续执行规则通道,示意图如下:
实际项目中,通常使用规则通道进行ADC转换,因为规则通道只有一个数据寄存器,所以通常和DMA进行配合使用。注意,只有ADC1和ADC3有DMA功能,ADC2没有DMA功能,但可以在双ADC模式与ADC1共同使用DMA传输。
DMA:DMA是直接存储器读取,用来实现外设和存储器、存储器和存储器之间的高速数据搬运,不需要CPU的干预,节省了CPU资源。STM32F1系列的DMA有12个独立可配置的通道(DMA1有7个通道,DMA2有5个通道),每个通道支持软件触发和特定的硬件触发。以STM32的DMA1为例,若想使用DMA搬运ADC1的结果,就需要开启ADC1的DMA输出,如下所示。
ADC有两种转换模式:单次转换模式和连续转换模式,单次模式下只进行一次转换,连续转换模式下,本此转换完成后会马上启动下一次转换。当采集多路传感数据时,可采用ADC扫描模式+DMA的方式,如下所示。
开启ADC转换后,这7个通道依次进行AD转换,然后将转换结果存储到ADC_DR寄存器中,同时触发DMA请求,DMA就会马上将数据搬运到目标地址中,我们通常会定义一个数组来保存数据,并且要设置DMA的目的地址自增,以免数据被覆盖。
(这里只介绍了ADC和DMA的大致内容,用来进行简单回顾,具体的代码编写就不展示了,看江科大吧)
最后介绍几个常见的面试问题:
1) 你使用的ADC分辨率是多少,如何提高ADC的精度?
我使用的是STM32F103,F1系列只支持12位分辨率的ADC,F4系列可配置 12 位、10 位、8 位或 6 位分辨率。可以通过滤波电路减少噪声和干扰,软件上使用滤波算法进行滤波,常见的滤波算法有中值滤波、加权平均滤波法、最小二乘法等。若长时间使用ADC,需要进行定期校准和校正。
2) ADC的通道数是多少?
STM32F103有18个ADC通道,分别为16个外部通道和2个内部通道(内部温度和内部参考电压)。
3) ADC的采样频率是多少?
STM32F103的ADC最大的转换速率为 1Mhz,也就是转换时间为 1us(在 ADCCLK=14M,采样周期为 1.5 个 ADC 时钟下得到),不要让 ADC 的时钟超过 14M,否则将导致结果准确度下降。
(ADC的问题先总结这些吧,面试问到其他问题的话会在此补充)
17. STM32程序的组成、存储和运行
使用Keil编译完程序后,会打印出如下信息,程序占用的空间,介绍如下:
Code:程序的代码域,也就是编译生成的机器指令,存放在flash中;
RO-data:只读数据域,也就是程序中的常量,程序不能修改其内容,这些区域被存储在flash中,如const定义的变量就是典型的RO-data;
RW-data:可读可写数据域,指初始化为非0值的全局变量,程序可以修改其内容,程序运行时RW-data会常驻在RAM中;
ZI-data:0初始化数据,也就是未初始化或初始化为0值的全局变量,程序刚运行时会将这些数据全部初始化为0,之后就和RW-data一样常驻在在RAM中;除此之外,程序中的堆栈空间也是属于ZI-data区域的,这些空间都会被初始值化为0值。
程序运行时占用flash空间的大小:RO Size = Code + RO Data
程序运行时占用RAM空间的大小:RW Size = RW Data + ZI Data
烧写程序时占用flash空间的大小:ROM Size = Code + RO Data + RW Data
可见,在程序文件中并没有为ZI Data分配空间,这是因为ZI Data都是0,只要在程序运行前将其所在的区域都初始化为0就可以了,从而节省了flash的存储空间。下面介绍STM32程序运行时的存储状态,如下图所示:
由于PC机和ARM的CPU架构不同,所以程序运行时的存储位置也是不同的,x86构架的PC机的CPU是基于冯诺依曼体系的,程序运行时会将程序从硬盘提取到RAM中运行,CPU从RAM中读取程序和数据。由于单片机的RAM资源有限,所以其CPU架构基本都是哈佛体系的,也就是程序和数据分来存储,程序运行时CPU直接从flash中读取程序,从RAM中读取数据。程序开始运行时,内核会直接从flash中读取代码,并且将RW Data从flash搬运到SRAM中,并且添加ZI Data,ZI Data段内存都被初始化为0,之后才正式开始执行主程序。
18. 介绍一下OSI七层模型
OSI 七层模型是国际标准化组织(ISO)制定的一个用于计算机或通信系统间网络互联的标准体系,一般称为 OSI 参考模型或七层模型。OSI将计算机网络通信协议划分为物理层、数据链路层、网络层、传输层、会话层、表示层、应用层这七个不同层次,每一层负责不同的功能,这种模型有助于在不同的系统之间进行通信时,更好地理解和管理网络通信的过程。介绍如下:
应用层:应用层是OSI模型中最高、最靠近用户的一层,为用于提供应用接口,也为用户直接提供各种网络服务。常见的网 络协议有HTTP、FTP、TFTP、SMTP、SNMP、DNS、TELNET、HTTPS、POP3、DHCP。
表示层:表示层用于向应用层提供数据的编码和转换功能,以确保当前系统的应用层发送的数据能被另一个系统的应用层识 别。该层可以提供一种标准表达形式,将计算机内部的多种数据格式转换成通信中采用的标准表示形式。此外, 数据压缩/解压缩和加密/解密(提供网络的安全性)也是表示层可提供的功能之一。
会话层:会话层用于建立、管理和终止表示层实体间的通信会话,该层的通信由不同设备中的应用程序间的服务请求和响应 组成。将不同实体的表示层之间的连接称为会话,会话是属于软件层面的,允许不同机器上的用户之间建立会话 关系。会话层的作用就是组织、协调会话中两个进程间的通信,并对数据交换进行管理。
传输层:传输层建立了主机端到端的连接,定义了传输数据的协议端口号,以及端到端的流控和差错校验。该层的作用就是 为上层协议提供端到端的可靠和透明的数据传输服务,包括差错校验处理和流控等问题。我们通常说的,TCP、 UDP 协议就工作在这一层,端口号即是这里的“端”。
网络层:网络层用于逻辑地址寻址,实现不同网络之间的路径选择。本层通过 IP 寻址来建立两个节点之间的连接,为源端 发送的数据包选择合适的路由和交换节点,以确保数据能够正确无误地按照地址传送给目的端的运输层。网络层 也就是通常说的IP 层。该层包含的协议有:IP(Ipv4、Ipv6)、ICMP、IGMP 等。
数据链路层:数据链路层接收来自物理层的原始比特流,将其封装成数据帧后传送到上一层,并会检测和纠正传输中出现的 错误。同时,也负责将来自上层的数据帧拆装为比特流,转发到物理层,还负责处理接收端发回的确认帧的信息,以便提供可靠的数据传输。
物理层:物理层的主要功能是利用传输介质为数据链路层提供物理连接,实现比特流的透明传输,尽可能屏蔽具体传输介质 和物理设备的差异,使数据链路层不必考虑网络传输介质的类型。实际上,网络传输信号的传输是通过物理层实 现的,物理层规定了物理设备标准、电平、传输速率等,通过物理介质传输比特流。常用设备有(各种物理设备) 集线器、中继器、调制解调器、网线、双绞线、同轴电缆等,这些都是物理层的传输介质。
网络层 |
说明 |
应用层 |
为用户提供各种网络服务接口。 |
表示层 |
进行数据处理,如编码/解码、加密/解密、压缩/解压缩等。 |
会话层 |
建立、管理、终止应用程序间的会话。 |
传输层 |
为两台主机进程之间的通信提供通用的数据传输服务,包括差错校验、流控等。 |
网络层 |
逻辑地址寻址,实现数据在不同网络间的路径选择。 |
数据链路层 |
帧编码/帧解码,进行硬件地址寻址,差错校验等功能。 |
物理层 |
建立、维护和断开物理连接,透明地传输比特流。 |
上面的内容比较多,下面举个例子理解一下:
应用层可理解为人机交互界面,比如在微信发送一句话:“你好”,表示层会将这两个字翻译成机器码,同时对数据进行加密压缩等操作;会话层检测到有数据要传输时,会找到数据接收方并与其建立会话关系;传输层可理解为同一个软件的两个端口,我打开的微信软件是一个端口,对方也必须使用微信才能接收到我发送的数据;传输层准备好后就可以进行数据传输了,但全国的微信用户非常多,要想将数据准确的发送给对方,就可通过网络层提供的IP地址实现寻址,并选择最优路径,实现准确的数据传输;网络层接收到数据后需要继续往下传输到数据链路层,数据链路层将数据帧解码成比特流,并会进行差错校验等操作,最后传输到物理层,网络传输信号其实是通过物理层实现的,数据到达物理层后会变成信号进行传输。当数据达到对方主机后,会执行一个逆向过程:物理层接收到信号后,依次向上传输,最后到达应用层,微信页面显示接收到的数据:“你好”。
其他帖子(http://t.csdnimg.cn/kbf5t)找到记忆口诀,如下所示:
19. 介绍一下TCP/IP 四层、五层模型
TCP/IP模型是OSI模型的简化版本,TCP/IP五层模型中,将OSI最上面的三层(应用层、表示层和会话层)合并成了一个应用层;TCP/IP四层模型是在五层模型的基础上将数据链路层和物理层合并为网路接口层。在实际应用中使用的是四层模型,TCP/IP五层模型是专门为了介绍网络原理而设计的。
20. 介绍一下数据的封装与拆封
我们在进行网络通信时,需要数据包在不同网络设备之间传输。这个过程就需要对数据包进行封装与解封装,如下所示:(左封装、右拆封)
用户发送数据时,数据从应用层依次向下传递,在传递给传输层之前会先使用相关协议对数据进行封装,如MQTT、HTTP 等协议,其实就是在数据前加一个头部;同样,传输层也会使用相关协议在数据前加上一个头部,如TCP、UDP协议等;网络层接收到数据后会在数据前加上IP头部,然后将数据传递给数据链路层;数据链路层会对数据进行最后一次封装,以使用以太网接口为例,会对数据加上以太网头部,最后将数据交给网卡;网卡硬件设备将数据转换成电平信号发送出去。数据的接收过程与发送过程正好相反,依次解析数据前的头部,最后将数据传递给应用层,对应的就是数据的拆封过程。
21. 介绍一下TCP和UDP的区别
1)连接性:TCP是面向连接的协议,传输数据前需要进行三次握手,输出传输完成后需要进行四次挥手,并且TCP是全双工的,即数据可以在两个方向上同时传输。而UDP是一种无连接、不可靠的协议,传输数据时不需要建立连接,发送方可以直接发送数据,接收方可以随时接收数据。
2)可靠性:TCP是一种可靠的协议,不怕数据丢包、乱序。TCP协议通过建立连接、序号机制、数据校验、超时重传、流量控制、拥塞控制等机制保证数据的可靠传输。而UDP没有这些机制,它只是简单地实现从一端主机到另一端主机的数据传输,并不确保数据正确、完整、有序地到达目的地。
3)通信模式: TCP通常用于一对一通信,也就是只有一个发送方和一个接收方。而UDP可实现一对多、多对多通信,因此可用于实现广播和组播功能。
4)传输速率:由于TCP传输数据时需要建立连接,并且需要一些机制保证数据传输的可靠性,所以相比之下TCP的传输效率较低。
5)报文大小:因为TCP提供可靠传输,所以数据报文的头部中会包含一些序列号、控制好等一些控制信息。而UDP的报文头部中只包含源端口、目标端口等一些必要的控制信息。所以相比之下TCP的报文会大一些。
22. 介绍一下什么是三次握手的四次挥手
为了保证客户端和服务器端的可靠连接,TCP建立连接时必须要进行三次握手,目的是为了确认双方的接收能力和发送能力是否正常。最开始的时候客户端和服务器都是处于CLOSED关闭状态,由客户端发起建立连接的请求,服务器会时刻监听、等待客户端的连接,示意图如下所示。
1) 第一次握手:客户端将报文标志位SYN置1,随机产生一个序号值seq=J保存在序号字段里,指明客户端准备连接服务器的端口,数据发送给服务器后,客户端进入 SYN_SENT 状态,等待服务器端确认。
2) 第二次握手:服务器接收到报文后,若检测到标志位 SYN=1 ,则知道客户端在请求建立连接,服务器会将TCP报文标志位SYN和ACK都置1,确认号为ack=J+1,产生一个随机序号值 seq=K,将该数据包发送给客户端已确认连接请求,此时服务器进入 SYN_RCVD 状态。
3) 第三次握手:客户端接收到确认报文后,会检查确认号ack是否为J+1、ACK是否为1,若检查通过则将标志位ACK置1,ack=K+1,再将该数据包发送给服务器端,服务器会检查ack是否为K+1、K是否为1,若正确则成功建立连接,客户端和服务器端会进入 ESTABLISHED 状态。
三次握手完成后,客户端与服务器端之间就可以开始传输数据了,双方会得到彼此的窗口大小、序列号等信息,传输TCP报文时,每个TCP报文首部的SYN标志位都会被置0,因为SYN标志位只用于发起连接。四次挥手是指关闭TCP连接的过程,当客户端和服务端需要关闭 TCP 连接时,双方总共需要发送4个数据包已确认断开连接。在 socket 编程中,这一过程由客户端或服务端任一方执行 close 来触发,示意图如下所示。
1) 第一次挥手:断开TCP连接时,双方都要单独进行关闭。当数据发送完成后,客户端会将报文标志位FIN置1,序号seq=M,M为前面已经传送过来的数据的最后一个字节的序号,将报文发送给服务器后,客户端会进入 FIN_WAIT_1 (终止等待1)状态。
2) 第二次挥手:服务器接收到客户端发送的FIN标志位置1的报文后,会向客户端发送一个ACK=1的报文,确认号ack= seq+1,表示同意客户端的关闭请求。报文发送给客户端后,服务器进入CLOSE-WAIT (关闭等待)状态。客户端接收到服务器的确认请求后,会进入FIN-WAIT-2(终止等待2)状态。
3) 第三次挥手:服务器将最后的数据发送完毕后,会向客户端发送一个FIN报文段请求关闭TCP连接,之后进入LAST-ACK(最后确认)状态,等待客户端的确认。
4) 第四次挥手:客户端接收到服务器发送的FIN报文后,会向服务器发送一个ACK报文,之后进入 TIME_WAIT(时间等待) 状态。服务器接收到ACK报文后就关闭连接,但此时TCP连接还未终止,客户端等待2MSL(最长报文寿命)后依然没有收到回复,则证明 Server 端已正常关闭,最后客户端才会关闭连接。
23.什么是冯诺依曼结构和哈佛结构,区别是什么
冯诺依曼结构:也叫普林斯顿结构,特点是程序中的指令和数据混合存储,存储在同一块外部存储器的不同物理地址上,共享同一总线,程序运行时再把指令和数据加载到内存中。优点是:结构简单、易于设计和实现且成本较低;缺点是:指令和数据共享总线,会导致吞吐量受限,影响性能。应用场景:常用于对成本敏感、对处理速度要求不高的场景,如X86、ARM7、MIPS等处理器使用的是该结构。
哈佛结构:特点是程序中的指令和数据是分开独立存储的,每个存储器都独立编址、独立访问,各自有自己的总线。优点是:指令和数据可以在一个时钟周期内进行并行访问,避免了总线冲突,提高效率。指令存储器通常为ROM只读存储器,可防止程序被意外修改,提高了安全性。缺点是:结构复杂,设计和实现的难度较大,成本较高。应用场景:常用于对处理速度和实时性较高的场景,如数字信号处理器(DSP)、单片机(如8051系列单片机)等处理器使用的是该结构。
混合结构:随着处理器技术的更新迭代,CPU的工作频率越来越高,可达到GHz级别,而内存RAM的访问频率一般为MHz级别(目前的DDR4、SDRAM的访问频率也达到了GHz级别)。这会拖慢CPU的效率,所以现代的ARM SOC芯片通常采用混合结构:即CPU内部采用哈佛结构,外部采用冯诺依曼结构。具体来说,SOC内部引入了Cache机制,通过指令Cache和数据Cache来缓存指令和数据,CPU通过不同的总线访问两种Cache,可实现对指令和数据进行并行操作。SOC外部采用冯诺依曼结构,指令和数据混合存储在同一存储区,共享外部存储器。(Cache其实就是静态随机访问存储器SRAM,其访问速度介于CPU和DRAM之间,是CPU与内存间的高速缓冲存储器)
24.Cache是什么,有什么用
作用:随着技术的发展,CPU的工作频率越来越高,可达到GHz级别,而内存的访问频率一般为MHz级别(目前DDR4 SDRAM也可达到GHz级别)。Cache其实就是静态随机访问存储器SRAM,其运行速度介于CPU和内存DRAM之间,是在CPU与内存间插入的一组高速缓冲存储器,用来解决两者速度不匹配带来的瓶颈问题。
原理:CPU从内存读取数据时,会一次性读取一片数据缓存到Cache中,下次CPU读取数据时,会先到Cache中检查要读取的数据是否存在,若存在,则称为缓存命中(Cache Hit),CPU就直接从Cache中读取数据,若数据不存在,则称为缓存未命中(Cache Miss),CPU就重新到内存中读取数据,并重新缓存从该地址开始的一片数据到Cache中。CPU向内存写数据与读数据类似,会先写到Cache中,然后CPU会根据一些特殊标志位选择合适的时间将数据写入到内存中。
多级Cache:CPU从Cache读取数据时,缓存命中的确可以提高效率,但若缓存未命中,则CPU就需要到内存中读取数据,并重新刷新数据到Cache中,此过程比较耗时。有如下两种改进方法:(1)增大Cache的容量来提高缓存命中的概率,但会增加成本,占用很大的芯片面积,导致芯片发热量增加,所以CPU附近的一级Cache通常只有几十K字节.(2)在一级Cache和CPU间添加二级Cache,二级Cache的访问速度较低,对应的成本也会降低。
目前CPU一般为多核结构,CPU内部会集成多个Core,每个Core有自己独立的L1 Cache,X86架构的CPU中每个Core还会有自己独立的L2 Cache,L3 Cache被所有Core共享。
补充:Cache通常用在高性能的处理器中,而C51系列单片机、Coretex-M0/M1/M2/M3/M4系列的ARM处理器中不包含Cache,因为这些处理器是低功耗、低成本处理器,而Cache会增加成本和功耗。且这些处理器工作频率不高,也没必要使用Cache。此外,Cache无法保证实时性,当缓存未命中时,CPU从RAM中读取数据的时间是不确定的,所以不适用于实时性场景。
25.ARM处理器的工作模式
为保证设备能够长期稳定运行,ARM架构处理器提供7种工作模式,程序正常运行时工作在用户模式,当程序出错或产生中断时,ARM处理器就会切换到对应的特权工作模式,如下表所示。
处理器处于用户模式时,没有权限对内存和底层硬件进行操作,程序若想对底层硬件进行读写操作,则需要通过系统调用或软件中断进入特权模式,运行操作系统内核或硬件驱动代码来对底层的硬件设备进行读写操作。
26.ARM处理器内部寄存器介绍
ARM处理器内部除了包含算术运算单元、逻辑运算单元、浮点运算单元和控制单元,还包含一系列寄存器,包括通用寄存器、状态寄存器和控制寄存器,ARM处理器在不同工作模式下,其寄存器会有一些差异,具体介绍如下。
如上所示,除了FIQ模式,在其他模式下都可使用R0~R12寄存器,这些寄存器是共用的,我们将其称为通用寄存器(为保证快速相应中断,处理器在FIQ模式下有自己独立的R8~R12寄存器)。其他的寄存器都是在各自模式下独立使用的,我们将其称为专用寄存器。下面对这些寄存器的功能进行具体介绍。(ARM程序通常默认使用满递减堆栈)
R0~R3:用来传递函数参数,当参数个数大于4个时,使用堆栈来传递剩余的参数,返回值通过R0~R1返回。
R4~R11:用来保存程序运算的中间结果或函数的局部变量。
R12:函数调用过程中的临时寄存器,通常用来保存函数的栈帧基址,记作FP。
R13:堆栈指针寄存器SP,用来维护和管理函数调用过程中的栈帧变化,总是指向当前正在运行的函数的栈帧。
R14:链接寄存器LR,用来保存函数调用时,上一级函数调用者的返回地址。
R15:程序计数器PC,总是指向正在运行的指令,CPU默认从该寄存器保存的地址处读取指令,每读取一次指令,PC寄存器的地址自动增加。ARM三级流水线中,一条指令的执行流程包括:取指、译码和执行,因此PC指针的值为当前正在运行的指令地址加8。
CPSR:处理器状态寄存器,用来表征当前处理器的运行状态,图示如下:
SPSR:当处理器切换模式或发生异常时,还寄存器用来保存当前模式下的处理器现场,也就是CPSR寄存器中的值,当模式切换回来时,就从SPSR寄存器中恢复原先的处理器状态。
(STM32处理器为Cortex-M 架构处理器,使用xPSR来替代CPSR,ARM汇编语言基础-CSDN博客中对xPSR寄存器进行了介绍)
27.栈帧指针FP、堆栈指针SP、程序计数器PC和链接寄存器LR
指针 | 说明 |
---|---|
FP | 栈帧指针,FP指针始终指向当前函数调用的栈帧的底部,也就是当前函数局部变量和参数的起始位置。FP的作用是作为函数调用时栈的标记,使得编译器可以有效地管理局部变量和函数参数。尤其是在多次函数调用时,FP帮助函数在栈中定位自己的数据。它的值在函数进入时被设置,并在函数返回时恢复,以便在函数返回后能够正确地恢复调用者的栈环境。 |
SP | 堆栈指针,SP指针指向当前栈的顶部,且会随着数据的入栈和出栈进行动态的变化。在ARM架构中,通常使用R13寄存器作为SP指针。通常由R13寄存器作为SP指针。 |
PC | 程序计数器,指向正在执行的指令的地址,且自动更新。其主要作用就是在程序执行时跟踪指令的地址,每次执行完一条指令后,PC会自动指向下一条指令,当遇到跳转指令时,PC指针会被显式的更改,从而跳转到指定的地址。通常由R15寄存器作为PC指针。 |
LR | 链接寄存器,LR指针用于保存下一条要执行的指令的地址。当进行函数调用时(如BL、BLX指令),处理器会自动将下一条指令的地址保存到LR寄存器中,当函数执行完成后,将LR寄存器中的值加载到PC指针中,即可实现函数返回。当发行异常或中断时,LR会被设置为特定的值,以便在处理完异常或中断后返回到正确的位置。在ARM汇编语言中,LR还可用于实现子程序之间的链接。通常由R14寄存器作为LR指针。 |
注:在ARM架构中,通常使用R11作为栈帧指针,但在Thumb模式下可能会使用R7作为栈帧指针。具体使用的是哪个寄存器取决于目标平台(如ARMv7、ARMv8)、指令集(ARM指令集或Thumb指令集)和编译器设置等方面。此外,在ARM架构中,FP指针不是必须的,当程序比较简单时,可不使用FP指针,直接使用SP指针对栈进行访问,以减少寄存器的使用,提高代码指向效率。在一些特定的硬件平台或实时系统中,也可能为了节省寄存器资源或特高代码运行效率,不使用栈帧指针FP,而通过精心设计的代码来直接操作堆栈,以满足系统的特殊需求。
28.PC指针真的指向当前正在执行指令的地址么?
ARM架构的处理器采用的是三级流水线技术,一条指令的执行周期包括:取指、译码和执行三步。比如处理器在执行第N条指令时,第N+1条指令正在译码,第N+2条指令正在取指。为适应流水线的工作方式,PC指针实际上指向的是第N+2条指令的地址。每条指令的长度为4字节,所以PC=当前指令地址+8,图示如下。
当程序进行跳转时,会将下条要执行的语句的地址保存在LR寄存器中,当子程序执行完成后返回时,就可以将LR中的地址加载到PC寄存器中,实现子程序返回。在程序跳转前,保存到LR寄存器中的地址实际是PC-4,也就是第N+1条指令的地址。
注:ARM架构中有两种指令集:ARM指令集和Thumb指令集,ARM指令的长度为4字节,而Thumb指令的长度为2字节,所以在Thumb状态下,PC=当前指令地址+4,对应的,在程序跳转时,LR=PC-2。
29.讲讲中断和异常的区别
在嵌入式系统中,中断和异常是两种处理特殊事件的机制,具体介绍如下。
中断:中断是由外部硬件设备触发,用来通知处理器及时响应外部事件,确保系统对外部环境的实时性,比如定时器到时、外设请求数据传输等。当中断发生时,处理器会停止当前的任务,跳转执行对应的中断处理函数,中断处理完成后处理器再回到之前的任务继续执行。
异常:异常通常由处理器在执行指令时检测到错误或特殊情况而引发的,主要用于处理程序运行时出现的错误或特殊情况,以确保系统的稳定性和安全性,比如非法指令、除零错误、内存访问违规等。当发生异常时,处理器会调用操作系统的异常处理程序来处理异常,处理方式一般包括终止程序、忽略异常或修复错误后继续执行等。