星星充电 嵌入式软件开发 二面 面经
1. 介绍一下你做过的最复杂的项目,重点说架构设计和你遇到的最难的技术问题。
答:
- 二面开场,考察系统思维和技术深度,不是简历复读
- 回答结构:项目背景(一句话)→ 架构设计思路(为什么这么分层)→ 最难的技术问题(具体说,不能说"遇到了很多困难")→ 结果
- 充电桩方向的加分点:有通信协议经验(OCPP/MODBUS)、有多任务设计经验、有实际落地经验
- 面试官会顺着你说的细节追问,只说你真正主导过的部分
2. 充电桩系统中,主控MCU需要同时处理充电控制、计费、通信、安全监控多个任务,你会怎么设计任务优先级和调度策略?
答:
- 任务分级原则:按实时性和安全性划分
- 最高优先级(硬实时):安全监控任务,过压/过流/过温检测,响应时间要求<1ms,直接在中断或最高优先级任务里处理,不能被任何任务阻塞
- 高优先级:充电控制任务,PWM输出、CP信号检测、充电状态机,响应时间<10ms
- 中优先级:通信任务,OCPP消息收发、MODBUS数据读取,响应时间<100ms
- 低优先级:计费任务、日志记录、状态上报,对实时性要求低
- 关键设计:安全监控任务不能依赖通信任务的数据,必须有本地的硬件保护(硬件过流保护电路),软件是第二道防线
- 任务间通信:用消息队列传递数据,不用全局变量直接共享,避免竞态
3. 你在项目中用过哪些进程间通信或任务间通信方式,各自适合什么场景,有没有遇到过通信导致的bug?
答:
- 任务间通信方式对比: 消息队列:有消息边界,异步,适合任务间传递命令和数据,FreeRTOS的xQueueSend/xQueueReceive信号量:用于同步和资源计数,二值信号量做事件通知,计数信号量管理资源池互斥量:保护共享资源,有优先级继承,防优先级反转事件组:一个任务等待多个事件同时发生或任意一个发生,比多个信号量更简洁共享内存+互斥锁:大数据量传递,避免拷贝,但需要仔细管理锁
- 常见通信bug: 消息队列满了丢消息,发送方没有检查返回值,导致数据静默丢失信号量使用不当导致死锁,两个任务互相等待对方的信号量共享数据没有加锁,多任务并发访问导致数据撕裂
4. 请解释TCP的三次握手和四次挥手,TIME_WAIT状态为什么需要等待2MSL,在充电桩的长连接场景中如何处理连接断开重连?
答:
- 三次握手:客户端发SYN → 服务端回SYN+ACK → 客户端发ACK,三次是为了确认双向通路都通畅,两次握手无法确认服务端到客户端的通路
- 四次挥手:主动方发FIN → 被动方回ACK(此时被动方可能还有数据要发)→ 被动方发FIN → 主动方回ACK,进入TIME_WAIT
- TIME_WAIT等待2MSL的原因:确保最后一个ACK能到达对端(对端没收到会重发FIN);让网络中残留的旧报文自然消亡,防止新连接收到属于旧连接的延迟报文
- 充电桩长连接断开重连策略: 检测断连:TCP keepalive(设置SO_KEEPALIVE,配置探测间隔和次数)+ 应用层心跳(OCPP的Heartbeat消息)双重保活重连策略:指数退避,1s、2s、4s...上限60s,加随机抖动防止大量设备同时重连重连后恢复:重新发送BootNotification注册,恢复订阅的topic,检查是否有未完成的事务需要续传
5. 请解释Linux中的epoll机制,为什么比select/poll性能好,在充电桩的网络通信模块中你会怎么用?
答:
- select/poll的问题:每次调用都要把整个fd集合从用户态拷贝到内核,内核O(n)遍历所有fd,fd多时性能差,select还有1024的fd数量限制
- epoll的优势:用事件驱动,epoll_ctl注册fd时内核建立回调,fd就绪时直接加入就绪链表,epoll_wait只返回就绪的fd,O(就绪数量),与总fd数量无关
- ET(边缘触发)vs LT(水平触发):LT只要有数据就持续通知,编程简单;ET只在状态变化时通知一次,必须一次性读完数据(循环read直到EAGAIN),系统调用次数少,延迟低
- 充电桩通信模块设计: 一个主线程用epoll监听所有连接(OCPP WebSocket连接、MODBUS TCP连接、本地管理接口)ET模式+非阻塞socket,收到数据后投递到工作线程队列处理主线程只做事件分发,不做业务处理,保证低延迟响应
6. 请解释什么是内存泄漏,在长期运行的嵌入式Linux系统中如何检测和预防内存泄漏?
答:
- 内存泄漏:申请的堆内存没有被释放,且没有任何指针指向它,进程内存持续增长,最终OOM崩溃
- 不容易发现的泄漏场景: 异常路径上的泄漏,函数中途return或异常,后面的free没有执行容器里存裸指针,容器clear时只释放指针本身,不释放指针指向的对象第三方库资源忘记释放(数据库连接、文件句柄)循环引用(C++的shared_ptr互相持有)
- 检测工具: Valgrind memcheck:最全面,但运行速度慢(10-20倍),适合开发阶段AddressSanitizer(ASan):编译时加-fsanitize=address,运行时开销约2倍,错误信息详细/proc/PID/status中的VmRSS:监控进程内存是否持续增长,简单有效
- 预防:用RAII封装所有资源,C++用智能指针,C语言用统一的资源管理模式(分配和释放在同一个模块)
7. 请解释什么是看门狗任务,在多任务系统中如何设计一个能检测所有关键任务是否正常运行的看门狗机制?
答:
- 简单喂狗的问题:只有一个喂狗任务定期喂狗,只能检测喂狗任务本身是否存活,其他任务卡死了喂狗任务还在跑,看门狗不会触发
- 完善的多任务看门狗设计: 每个关键任务维护
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
嵌入式面试八股文全集 文章被收录于专栏
这是一个全面的嵌入式面试专栏。主要内容将包括:操作系统(进程管理、内存管理、文件系统等)、嵌入式系统(启动流程、驱动开发、中断管理等)、网络通信(TCP/IP协议栈、Socket编程等)、开发工具(交叉编译、调试工具等)以及实际项目经验分享。专栏将采用理论结合实践的方式,每个知识点都会附带相关的面试真题和答案解析。

查看17道真题和解析