面灵AI→

汇川技术嵌入式一面:项目追问、线程同步与 select/epoll

轮次
一面
时间
2026-10
来源
牛客网

《面试题目》

  1. 自我介绍
  2. 说一个最值得分享的项目(执法记录仪)
  3. 项目大概是个什么?
  4. 你在里面负责什么工作?
  5. 整个项目的软件框架是怎样的?
  6. 为什么使用 shared_ptr,而不是自己实现一个?
  7. 消费者和生产者线程如何进行同步?
  8. 如果遇到生产者快、消费者慢的场景,怎么处理?
  9. 如果遇到程序崩溃,怎么调试?
  10. 海康实习的 AI 自主调试工具是怎么做的?
  11. 如何让 AI 去完成这块项目?
  12. 如何避免 AI 写出来的代码有问题?
  13. 锐明实习过程中用到的 select 多路复用,讲讲大致理解
  14. select 和 epoll 的区别?

《参考解析》

项目叙述要有层次:讲执法记录仪这类嵌入式项目,面试官想听的是「你做了什么判断」,而不是技术栈罗列。一个稳妥的顺序是:业务背景与约束(录像时长、双通道、本地存储、断电保护等)→ 软件分层(应用层、业务层、驱动层、OS 抽象层各管什么)→ 数据流(采集到编码、落盘、上传整条链路怎么走)→ 你负责的模块与关键取舍 → 可量化的结果(帧率、内存水位、连续运行时长)。被追问「软件框架」时落到进程/线程划分、模块间接口和线程通信方式上,最好能画出一个框图和一条数据流。

shared_ptr 的选型理由:用标准库智能指针而不是自研引用计数,主要买三件东西:经过大规模验证的线程安全引用计数、异常安全(异常路径上也能正确释放)、以及与 weak_ptr、自定义 deleter、make_shared 等设施的配合。自己实现要把原子性、循环引用、异常路径释放、控制块与对象的内存布局全处理对,测试成本远大于收益。但也要主动说清代价:控制块有额外内存与原子操作开销,shared_ptr 循环引用必须靠 weak_ptr 打破,所有权不明确时容易掩盖设计问题。资源紧张的嵌入式工程里更常见的是 unique_ptr 表达独占所有权、跨线程传引用计数可考虑侵入式计数(计数内嵌在对象里),能讲清这个取舍比背结论更值钱。

生产者消费者同步与背压:标准做法是互斥量加条件变量配一个有界队列,通常用 not_empty 与 not_full 两个条件变量,队列满就是天然背压。生产者快、消费者慢时,先分清业务能不能丢:录像/监控这类场景常选择丢帧保实时,策略可以是最新帧覆盖最旧帧,或按帧重要性分级丢弃;不能丢的走阻塞等待,但要防止生产者长时间阻塞导致采集端丢数据。工程上还要做的优化:批量出队(一次唤醒取走多条)降低锁竞争、SPSC 场景改用无锁环形缓冲(原子读写指针对加内存屏障)、用内存池和零拷贝减少分配与拷贝、按负载动态降采样。面试官真正想听的是「丢还是等,由业务决定,并且我做了权衡」。

崩溃调试的完整闭环:只答「看日志和 gdb」会被追问「然后呢」,要把流程讲成闭环。第一步是现场保护与信息收集:确认 core dump 是否开启(ulimit -c、coredumpctl)、拿到日志与 dmesg,把 core 和二进制、符号文件配对保存。第二步是复现与缩小范围:能否稳定复现、最小复现条件、二分提交或按地址特征定位。第三步是分析:gdb 里 bt full、thread apply all bt、切换 frame 看局部变量、info registers,配合 ASan/UBSan/Valgrind 与 TSan(怀疑竞态时),用 addr2line 解析崩溃地址。第四步是归因分类:空指针与野指针、越界写、栈溢出、并发竞态、资源耗尽(fd/内存泄漏)。第五步是修复与防复发:加断言和埋点、跑长稳与压力测试、把用例沉淀成回归,并复查相似代码路径。

AI 生成代码的风险管控:可控的用法是把 AI 限定在「接口清晰、有测试兜底」的范围内:写工具函数、状态机骨架、协议解析、单元测试,不让它直接改中断处理、时序敏感的驱动和内存紧张的路径。质量闸门是编译告警全清零、静态分析(cppcheck、clang-tidy)、单测与 ASan/UBSan 跑通、上板实测;人工 review 重点盯资源释放路径、边界与错误分支、并发假设、以及依赖的库在目标平台的可用性。流程上用小步提交和可回滚的分支控制爆炸半径。AI 自主调试工具的思路则是把日志、core、构建产物和最近改动喂给模型做归因假设,再由人验证结论,别让它直接改代码后就合入。

select 与 epoll 的区别:两者的本质都是让一个线程同时管多个 fd,避免一连接一线程。select 用位图保存 fd 集合,受 FD_SETSIZE 限制(常见 1024),每次调用都要把集合从用户态拷进内核,返回后还得自己遍历一遍找就绪 fd,复杂度 O(n),而且入参集合会被内核改写,每次都要重新准备。epoll 用内核事件表,epoll_ctl 注册一次、epoll_wait 直接返回就绪列表,只与活跃 fd 成正比,也没有 fd 数量限制(受 ulimit 约束)。使用 epoll 要知道 LT 与 ET 的区别:LT 是默认模式、没处理完会反复通知,ET 只在状态变化时通知一次,必须配非阻塞 fd 并循环读到 EAGAIN,否则会丢事件。另外 select 跨平台可移植、Linux 上 epoll 是专有,还有 poll 作为无 fd 上限的折中。