大家好,春招说来就来啦!最近我可太有感触了,好多小伙伴都在疯狂准备面试,一心想拿下大厂 offer。前阵子,有个读者找到我,说想试试模拟面试,我就想着帮他一把。结果好家伙,这面试过程可太让我惊喜了!不管我抛出啥问题,他都能稳稳接住。我把模拟面试过程中的一些问题整理了出来,希望对大家有帮助。
模拟面试问题汇总
基础问题
C/C++
关键字static的作用是什么?
在函数体,只会被初始化一次,一个被声明为静态的变量在这一函数被调用过程中维持其值不变。
在模块内(但在函数体外),一个被声明为静态的变量可以被模块内所用函数访问,但不能被模块外其它函数访问。它是一个本地的全局变量(只能被当前文件使用)。
在模块内,一个被声明为静态的函数只可被这一模块内的其它函数调用。那就是,这个函数被限制在声明它的模块的本地范围内使用(只能被当前文件使用)。
C语言中 struct与 union的区别是什么?
-
- 结构体与联合体虽然都是由多个不同的数据类型成员组成的,但不同之处在于联合体中所有成员共用一块地址空间,即联合体只存放了一个被选中的成员,而结构体中所有成员占用空间是累加的,其所有成员都存在,不同成员会存放在不同的地址。在计算一个结构型变量的总长度时,其内存空间大小等于所有成员长度之和(需要考虑字节对齐),而在联合体中,所有成员不能同时占用内存空间,它们不能同时存在,所以一个联合型变量的长度等于其最长的成员的长度。
-
- 对于联合体的不同成员赋值,将会对它的其他成员重写,原来成员的值就不存在了,而对结构体的不同成员赋值是互不影响的。
堆与栈有什么区别?
申请方式栈的空间由操作系统自动分配/释放,堆上的空间手动分配/释放。
申请大小的限制栈空间有限。在Windows下,栈是向低地址扩展的数据结 构,是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,在WINDOWS下,栈的大小是2M(也有的说是1M,总之是 一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示overflow。因此,能从栈获得的空间较小堆是很大的自由存储区。堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。
申请效率栈由系统自动分配,速度较快。但程序员是无法控制的。堆是由new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便.
什么是外部碎片和内部碎片?内存碎片如何解决
函数指针和指针函数有什么区别?
函数指针如果在程序中定义了一个函数,那么在编译时系统就会为这个函数代码分配一段存储空间,这段存储空间的首地址称为这个函数的地址。而且函数名表示的就是这个地址。既然是地址我们就可以定义一个指针变量来存放,这个指针变量就叫作函数指针变量,简称函数指针。
指针函数首先它是一个函数,只不过这个函数的返回值是一个地址值。函数返回值必须用同类型的指针变量来接受,也就是说,指针函数一定有“函数返回值”,而且,在主调函数中,函数返回值必须赋给同类型的指针变量。
数组下标可以为负数吗?
可以,因为下标只是给出了一个与当前地址的偏移量而已,只要根据这个偏移量能定位得到目标地址即可。下面给出一个下标为负数的示例:
操作系统
请简述操作系统的主要功能有哪些
答案:操作系统主要有进程管理(负责进程的创建、调度、终止等,确保多个进程能高效有序地共享 CPU 资源)
存储管理(对内存进行分配、回收与保护,让不同进程合理使用内存空间)
设备管理(统一管理各类外部设备,如打印机、磁盘等,实现设备的驱动加载、分配与回收,为用户程序提供统一的设备操作接口)
文件管理(负责文件的存储、检索、共享与保护,提供文件系统给用户方便地操作文件)和
作业管理(控制用户作业的提交、执行与结束流程,协调系统资源来完成用户任务)解释:了解操作系统全貌,看面试者是否清楚其核心职能,这是深入学习操作系统各模块的基础。
操作系统中的进程和线程的区别
进程是资源分配的基本单位,拥有独立的地址空间,包含代码段、数据段、堆栈段等,不同进程间数据相对隔离;
线程是 CPU 调度的基本单位,隶属于进程,一个进程内可以有多个线程,它们共享进程的地址空间和资源,如打开的文件、全局变量等,但拥有各自独立的栈空间用于保存局部变量和函数调用信息。
线程间切换开销相对进程切换小,因为无需切换地址空间等大量资源信息。解释:这是操作系统并发编程的关键概念,能反映面试者对并发执行单元的理解深度。
FreeRTOS 相关部分
FreeRTOS 任务主要有哪几种状态
FreeRTOS 任务主要有就绪(Ready)、运行(Running)、阻塞(Blocked)、挂起(Suspended)状态。
就绪态任务等待 CPU 资源,一旦获得就进入运行态;
运行态任务时间片用完或被更高优先级任务抢占,会回到就绪态;
运行态任务如果等待某个事件(如信号量未获取到、队列无数据等),就会进入阻塞态,等事件满足条件后回到就绪态;
挂起态任务是被人为暂停,需要通过 API 调用才能恢复到就绪态,与阻塞态不同在于挂起态不依赖于某个事件的发生,纯粹是外部控制。
解释:深入考查对 FreeRTOS 任务调度核心机制的掌握,任务状态流转是理解任务运行逻辑的关键。
FreeRTOS 中如何使用队列进行任务间通信
首先要创建队列,使用 xQueueCreate() 函数,设置好队列长度和单个消息大小。比如在一个数据采集任务和数据处理任务间通信,采集任务采集到数据后,
使用 xQueueSend() 函数将数据指针(或数据本身,取决于数据大小和设计)放入队列
数据处理任务则在循环中使用 xQueueReceive() 函数尝试从队列获取数据,当队列中有数据时,处理任务就能取到并进行后续处理。
FreeRTOS 中信号量和互斥量的区别
信号量主要用于任务间同步或资源计数,比如协调多个任务对某个共享资源的访问顺序,初始值可以设定为大于 0,表示资源初始可用数量,任务获取信号量后信号量值减 1,释放加 1;
互斥量本质也是一种特殊的信号量,初始值为 1,用于互斥访问共享资源,同一时间只允许一个任务获取互斥量进入临界区操作共享资源,避免数据冲突,获取不到互斥量的任务会进入阻塞态等待,直到持有互斥量的任务释放。
解释:这两者容易混淆,区分它们能看出面试者对 FreeRTOS 同步与互斥机制的精准理解。
中断与内存管理部分
FreeRTOS 中,基于中断的任务切换机制是怎样的?
当中断发生时,硬件首先保存当前任务的上下文(寄存器值等),然后进入中断服务程序(ISR),
在 ISR 中如果满足任务切换条件(如更高优先级任务就绪且中断允许任务切换),会调用 FreeRTOS 的中断任务切换函数,
将当前任务状态更新,选择最高优先级就绪任务,恢复新任务的上下文,实现从旧任务到新任务的切换,最后从中断返回后新任务开始运行。
在一个具有多个中断源的FreeRTOS系统中,不同中断源需要与不同任务进行通信。请设计一种方案,实现各中断源与对应任务之间高效、可靠的通信,并说明如何避免通信过程中的数据丢失和冲突。假设系统资源有限,如何在满足通信需求的同时优化系统资源的使用?
答案:可以使用消息队列实现中断源与任务的通信。为每个中断源创建独立的消息队列,中断发生时,将相关数据发送到对应的队列。任务从队列中读取数据进行处理。
为避免数据丢失和冲突,发送数据时设置合适的超时时间,确保数据成功入队。在中断处理中,使用中断安全的队列操作函数。对于系统资源有限的情况,合理设置队列大小,避免过大的内存占用。同时,可复用部分数据结构,减少内存开销。例如,使用共享缓冲区,通过标志位区分不同中断源的数据。
FreeRTOS 空闲内存是用什么数据结构管理的,简单讲讲是如何管理的。
链表实现内存分配基于空闲内存块链表,初始化时将系统空闲内存按一定大小划分成多个内存块,每个内存块包含控制信息(如大小、是否已分配标志等),并通过链表指针连接起来,分配内存时遍历链表找合适大小的空闲块,标记为已分配并返回给申请者,回收时将内存块重新链入空闲链表并更新状态。
FreeRTOS 的链表实现内存分配中,如何处理内存碎片化问题?有哪些优化策略?
内存碎片化是指随着内存的反复分配与回收,内存空间被分割成许多小块,即使总空闲内存足够,但难以满足较大内存需求的情况。FreeRTOS 处理碎片化的一种策略是采用内存合并算法,在回收内存块时,检查相邻的空闲内存块,若满足合并条件(如相邻块空闲且属于同一类型内存区域),则将它们合并成一个更大的空闲块,减少碎片数量;
优化策略还包括定期进行内存整理,不过这通常会带来一定的性能开销,所以要权衡使用,例如在系统空闲时段执行整理操作,或者根据内存使用阈值触发整理,另外合理设置初始内存块大小和分配策略,如采用大内存块优先分配,对于频繁分配小内存块的场景,单独开辟小内存区域进行管理,避免小碎片过多影响大内存需求的分配。
在 FreeRTOS 任务调度中,如果多个任务处于同一优先级,系统是如何处理的?
当多个任务处于同一优先级时,FreeRTOS 采用时间片轮转调度算法在这些任务间分配 CPU 时间。每个任务运行一个预先设定好的时间片(由系统节拍周期和配置参数决定),时间片用完后,任务会被强制挂起,切换到同优先级的下一个就绪任务运行,如此循环往复,保证同优先级任务都能获得公平的 CPU 执行机会。
若任务在时间片内主动放弃 CPU(如进入阻塞态等待资源),则立即切换到同优先级的下一个就绪任务。
对于 FreeRTOS 中的队列,当队列已满,继续调用 xQueueSend() 函数会发生什么情况?如何根据不同需求处理这种情况?
当队列已满,继续调用 xQueueSend() ,默认情况下任务会进入阻塞态,等待队列有空闲空间,直到超时(可设置超时时间)。
若不想让任务阻塞,可以使用 xQueueSendToBackFromISR() 或 xQueueSendToFrontFromISR() 等从中断安全版本的函数(用于中断服务程序中),它们不会阻塞任务,而是直接返回错误码,由调用者根据返回值决定后续操作,比如在中断中简单记录错误信息,后续再处理;
或者在任务中结合错误处理逻辑,决定是重试发送、调整数据处理策略还是采取其他补救措施。
USART 相关
USART(通用同步异步收发器)的工作模式,异步和同步模式有何区别?
答案:USART 异步模式下,数据传输不需要时钟同步信号,收发双方依靠起始位、停止位和波特率来同步数据,每个字符独立传输,传输效率相对较低,但接线简单,常用于低速、远距离通信场景。同步模式则需要额外的时钟线来同步数据传输,数据按位连续传输,传输速率高,适合高速、近距离且对时钟同步要求严格的应用,如与某些高速外设芯片通信。
IIC 相关
描述 IIC(集成电路总线)协议的通信流程,起始信号和停止信号是如何定义的?
答案:IIC 通信以起始信号开始,当时钟线SCL 为高电平时,数据线 SDA 由高电平向低电平跳变表示起始信号;停止信号则相反,在SCL 为高电平时,SDA 由低电平向高电平跳变。通信时,主机发送起始信号后,接着发送设备地址(包含读写位),从机响应后,主机开始传输或接收数据,数据传输以字节为单位,8 位数据后有一个应答位,由从机反馈,整个过程靠 SCL 时钟线同步。
IIC 总线上如何实现多设备连接?设备地址冲突怎么解决?
- 答案:IIC 允许通过不同的设备地址在同一总线上连接多个设备。设备地址通常由固定部分(厂商定义)和可编程部分(用户设置)组成。若出现设备地址冲突,首先要排查硬件连接是否正确,确认是否有重复地址设备误接入;在软件层面,若使用可编程地址的设备,修改冲突设备的地址,确保每个设备地址唯一,保证通信顺畅。
SPI 相关
SPI(串行外设接口)有几种工作模式,它们是如何区分的?
答案:SPI 有四种工作模式(0、1、2、3),主要依据时钟极性(CPOL)和时钟相位(CPHA)区分。CPOL 决定时钟空闲时电平,0 为低电平,1 为高电平;CPHA 决定数据采样时刻,0 表示在时钟前沿采样,1 表示在时钟后沿采样。如模式 0:CPOL = 0,CPHA = 0,时钟空闲低电平,数据在上升沿采样;模式 3:CPOL = 1,CPHA = 1,时钟空闲高电平,数据在下降沿采样。不同模式适配不同外设需求。
对比 SPI 与 IIC 协议,它们在传输速率、硬件连接、应用场景上有何不同?
答案:传输速率上,SPI 通常比 IIC 快,SPI 全双工通信,时钟频率较高,能实现高速数据传输;IIC 半双工,受应答机制等限制,速率相对较低。硬件连接方面,SPI 至少需要 4 根线(MISO、MOSI、SCK、SS),不同设备可能还需额外控制线;IIC 只需 2 根线(SDA、SCL),连接简单。应用场景,SPI 适合高速数据传输如显示屏、Flash 存储驱动;IIC 多用于连接低速、对功耗要求低且内部有 IIC 接口的芯片,如传感器、EEPROM 等。
裸机与基于 RTOS 的开发调试综合
在裸机开发中,如何处理多个任务之间的资源共享问题?以 USART 数据发送为例,若有两个任务都要向 USART 发送数据,你会怎么做?
- 答案:裸机开发中,处理资源共享可采用关中断、查询标志位等方法。以 USART 发送为例,可设一个全局标志位,任务先查询标志位,若 USART 空闲(标志位表示可发送),则置位标志位,独占 USART 发送数据,发送完成后置零标志位,期间若另一任务查询,发现标志位已置,需等待。这种方式简单直接,但在复杂系统中容易出现优先级倒置等问题,相比 RTOS 缺乏灵活性。解释:考查裸机环境下解决并发问题的基本思路,凸显与 RTOS 环境下任务管理的差异。
在基于 RTOS(如 FreeRTOS)的开发中,使用消息队列实现任务间通信有什么优势?如果要将 USART 接收的数据传递给另一个处理任务,如何用消息队列搭建通信链路?
- 答案:使用消息队列优势在于解耦任务间关系,提高系统灵活性、可维护性,避免任务间强耦合造成的开发困难。比如将 USART 接收任务和处理任务解耦,接收任务只管接收数据放入队列,处理任务从队列取数据处理,两个任务可独立开发、调试。搭建通信链路时,先创建消息队列(设定队列长度、消息大小),USART 接收任务用 “xQueueSend ()” 函数将接收的数据放入队列,处理任务用 “xQueueReceive ()” 函数从队列获取数据进行后续处理。解释:深入考查对 RTOS 任务通信核心机制的运用,以实际场景展示优势与操作方法。
当基于 RTOS 的系统出现死机现象,你会如何排查故障?请列出至少三种排查方向。
- 答案:一是查看任务堆栈是否溢出,RTOS 任务堆栈空间有限,若任务内局部变量过多、函数调用链过长或任务死锁等,可能导致堆栈溢出,可通过调试工具查看堆栈使用情况;二是检查任务间同步互斥机制,如信号量、互斥量是否正确使用,若出现死锁,会导致系统停滞,分析任务阻塞、等待资源的情况排查;三是排查中断处理,是否有中断服务程序未正确返回、中断优先级设置不当干扰任务调度等,借助硬件调试器查看中断触发与执行情况。
项目问题
为什么要多版本固件存储?基于什么场景考虑的?
Xmodem 协议相关
在基于 Xmodem 协议实现系统本地在应用升级(IAP)过程中,Xmodem 是如何进行数据包传输的校验的?如果校验出错,系统采取了哪些措施来保证升级的可靠性?
-
- 答案:Xmodem 通常采用 CRC(循环冗余校验)或 Checksum(校验和)来校验数据包。以 CRC 为例,发送方根据数据包内容计算出 CRC 值并附加在数据包末尾发送,接收方收到数据包后,按照相同算法重新计算 CRC 值,与接收到的 CRC 值比对。若校验出错,系统一般会采取
重发
- 机制,向发送方请求重发出错的数据包,并重试一定次数,若多次重发仍失败,则停止升级并报错,提示用户检查连接或升级文件完整性,以此确保升级过程数据准确可靠。
当本地 IAP 升级触发时,如何确保系统在升级过程中的状态切换是安全的?比如从正常运行状态进入升级准备态,再到升级执行态,期间涉及的任务暂停、资源释放等操作是如何协调的?
- 答案:首先,在触发升级前,系统会发送通知给正在运行的任务,让其有序停止,保存关键数据,如正在处理的瞳孔检测数据缓存等,避免数据丢失。然后,关闭不必要的外设及中断,防止在升级过程中受到干扰。对于资源释放,会释放一些动态分配的内存,如为临时图像处理开辟的缓冲区,确保升级所需内存空间充足。进入升级准备态时,系统会初始化 Xmodem 协议相关参数、打开通信端口等;升级执行态,严格按照 Xmodem 流程接收数据包、校验、写入 Flash 存储区,各状态转换通过设置标志位和状态机机制协调,保障升级流程顺畅。
MQTT 协议相关
在基于 MQTT 协议实现云端空中升级(OTA)时,如何保证设备与云端连接的稳定性?若遇到网络中断情况,系统有什么策略来恢复连接并继续未完成的升级?
- 答案:为保证连接稳定性,设备端与云端建立 MQTT 连接时,会设置保活心跳包机制,设备按一定时间间隔(如 30 秒)向云端发送心跳包,云端收到后回复确认,若设备一段时间未收到确认,就判定连接可能中断,主动重连。遇到网络中断,系统首先尝试重连网络,重连成功后,从上次断点处继续升级。这需要在升级前记录已接收数据包的序号、升级进度等信息,重连后向云端请求补发缺失数据包,利用 MQTT 的 QoS(服务质量)机制确保数据可靠传输,保障升级完整性。
MQTT 协议有不同的 QoS 级别(0、1、2),在本项目的 OTA 升级中,选用了哪个 QoS 级别?为什么?结合瞳孔检测系统的实时性与可靠性需求进行阐述。
答案:本项目选用 QoS 1。原因是 QoS 0 只管发送,不保证接收,对于固件升级这种对数据完整性要求高的场景风险太大;QoS 2 虽保证最强可靠性,但带来较高的开销与延迟,因需多次握手确认,在网络条件一般时会影响升级效率。而 QoS 1 提供 “至少一次” 的送达保证,即消息若丢失会重发,既满足固件升级对数据不丢失的要求,又在一定程度上兼顾了实时性,确保升级数据包能较快传输,符合瞳孔检测系统需要及时更新固件又不能长时间中断服务的特性。
通用升级问题
在进行本地 IAP 和云端 OTA 升级时,如何对升级包进行版本管理?如何确保只有合法的、经过验证的升级包才能用于系统升级?
答案:对于版本管理,系统为每个固件版本设定唯一版本号,遵循一定规则,如主版本号。次版本号。修订号,升级时对比本地版本与待升级版本号,只有待升级版本号高于本地版本号才允许升级。对于升级包验证,采用数字签名技术,在制作升级包时,使用私钥对包内数据生成签名,设备端用公钥验证签名,若签名不匹配,说明升级包可能被篡改,拒绝升级,以此保证只有合法、安全的升级包能用于系统,防止恶意软件入侵。
无论是本地 IAP 还是云端 OTA 升级,一旦升级失败,如何进行降级处理或故障恢复,以保证瞳孔检测系统尽快恢复到可用状态?
- 答案:升级失败后,系统首先会尝试回滚操作,若本地有备份的上一版本固件,从备份区恢复固件到运行区,重启系统恢复正常功能;若没有备份,对于本地 IAP 失败,会提示用户通过特定工具重新刷入原始固件;对于云端 OTA 失败,会在网络恢复稳定后,重新下载上一可靠版本固件进行升级,期间系统尽量维持部分基础功能运行,如仅提供简单人机交互告知用户升级状态,避免完全 “死机”,保障瞳孔检测系统的可用性。
FreeRTOS 任务间通信机制相关
在多线程框架下,如何确保任务的优先级分配合理?以宠物喂养机为例,哪些任务应该具有较高优先级,哪些相对较低?如果优先级设置不当,可能会出现什么问题?
答案:对于宠物喂养机,像紧急制动任务(防止电机异常运转造成危险)、实时监测宠物接近喂食口防止夹伤的任务等关乎设备安全与宠物健康的任务应设置较高优先级,确保能及时响应。而一些如定期记录设备运行日志、非紧急状态下向云端上传历史数据的任务优先级相对较低。
若优先级设置不当,例如将日志记录任务优先级设得过高,当系统资源紧张时,可能导致关键的监测与控制任务得不到及时执行,出现食物投放延迟、无法及时响应宠物靠近等问题,影响设备正常运行甚至引发安全隐患。
flash分区
在对 Flash 进行分区实现 BootLoader 基础上的 OTA 升级时,Flash 各分区的功能是如何划分的?为什么要这样划分?
- 答案:一般分为 BootLoader 区、应用程序区、OTA 升级暂存区。BootLoader 区存放启动引导代码,负责系统初始化、判断是否有可用的 OTA 升级包,若有则将升级包从暂存区搬运到应用程序区并启动更新。应用程序区运行宠物喂养机日常功能代码。OTA 升级暂存区用于在接收云端 OTA 升级包时临时存储,其大小要根据升级包最大可能尺寸合理设置。这样划分保证了系统启动的独立性与升级过程的安全性,即使升级失败,BootLoader 仍能维持基本启动功能,尝试恢复或等待再次升级,避免设备 “变砖”,保障设备持续可用性。解释:Flash 分区是 OTA 升级底层架构关键,合理布局反映面试者对嵌入式系统存储管理与升级流程理解,关乎项目可维护性。
总结
- 面试的时候,自我介绍逻辑清晰,把个人的亮点都讲了出来:保送研究生+探索+坚持+热爱运动。面试过程问题回答的也很详细,但是在某些问题上有些过于“着急”。面试回答问题时无重点,面试官可能没有耐心听你讲完。遇到某些不会的问题,最好不要直接说不会,而是尝试表达自己对于这个问题思考的过程。
没过多久,这位读者就来报喜了,成功拿下了两个大厂实习offer。
关于模拟面试
看完这次模拟面试,相信大家心里都有数了,嵌入式软件工程师面试到底是咋回事。
如果你也想在春招里杀出重围,稳稳拿捏面试节奏,对那些高频问题了如指掌,别犹豫,赶紧来找我!我肯定根据你的情况,给你定制一套专属模拟面试,让你提前适应面试场景,把自己的短板都补上。面试完成后会深入分析面试存在的问题及解决办法,手把手教你如何回答问题。
春招不等人,机会都是留给有准备的人,咱们一起加油,向着理想的岗位冲!
想要参加模拟面试(社招校招均可)的同学,可以扫码加我微信,备注 模拟面试,一定要记得备注!