禾赛科技 嵌入式软件工程师二面

前言

禾赛科技二面更侧重Linux驱动开发、系统级编程和激光雷达专业知识,面试官通常是技术leader或架构师,会深入考察项目经验和问题解决能力。二面难度明显高于一面,时长约40分钟,通过二面基本就能拿到offer。

禾赛科技嵌入式二面12题详解

1. 详细介绍一下你做过的某个项目,重点说明你的贡献

参考答案:

我做过一个基于Linux的工业相机数据采集系统,负责整个底层驱动和数据处理模块的开发。

项目背景:工业相机通过MIPI CSI接口连接到ARM处理器,需要实时采集1080P@60fps的图像数据,进行预处理后通过网络传输到上位机。

我的贡献

  1. 驱动开发:基于V4L2框架开发了MIPI CSI驱动,实现了相机的初始化配置、数据采集、格式转换等功能。通过设备树配置硬件资源,使用DMA传输图像数据到内存,避免CPU拷贝开销。
  2. 性能优化:初期帧率只能达到30fps,通过分析发现瓶颈在内存拷贝。我改用零拷贝技术,用户空间直接mmap驱动分配的DMA缓冲区,将帧率提升到60fps,CPU占用率从80%降到20%。
  3. 多线程架构:设计了生产者-消费者模型,采集线程负责从驱动读取图像,处理线程负责图像预处理(去噪、增强),发送线程负责网络传输。通过无锁队列实现线程间通信,保证实时性。
  4. 稳定性保障:添加了看门狗机制,监控各线程状态;实现了异常恢复机制,相机掉线后自动重连;增加了日志系统,方便问题定位。

技术难点:最大的挑战是解决图像撕裂问题。由于采集和处理异步进行,处理线程可能读到正在写入的缓冲区。我通过实现三缓冲机制解决:一个缓冲区用于采集,一个用于处理,一个作为备份,通过原子操作切换缓冲区指针,保证了数据一致性。

项目成果:系统稳定运行在客户产线上,连续工作超过3个月无故障,满足了工业级可靠性要求。

2. 如何避免竞态条件?

参考答案:

竞态条件是指多个执行流(进程/线程/中断)并发访问共享资源,由于执行顺序不确定导致结果不可预测。

产生原因:读-改-写操作不是原子的,比如count++实际是三步:读取count到寄存器、寄存器加1、写回count。如果两个线程同时执行,可能都读到相同的值,最终只加了1次。

避免方法

  1. 互斥锁:最常用的方法,通过mutex保护临界区,同一时刻只有一个执行流能访问共享资源。Linux内核中用mutex或spinlock,用户空间用pthread_mutex。
  2. 原子操作:对于简单的操作(加减、位操作、比较交换),使用原子操作API,硬件保证不可分割。Linux内核提供atomic_t类型和相关操作函数。
  3. 关中断/关抢占:在内核中,如果临界区很短(几条指令),可以关闭中断或禁止抢占。但这会影响系统实时性,要慎用。
  4. 读写锁:如果读操作远多于写操作,用读写锁可以提高并发度。多个读者可以同时访问,写者独占访问。
  5. 无锁编程:使用CAS(Compare-And-Swap)等原子操作实现无锁数据结构,如无锁队列、无锁栈。性能高但实现复杂。

激光雷达应用:点云数据缓冲区被采集线程写入、处理线程读取,必须用锁保护。我通常用双缓冲+原子指针切换的方式,既保证了数据一致性,又避免了锁竞争。

3. 什么是优先级反转?如何解决?

参考答案:

定义:优先级反转是指高优先级任务被低优先级任务阻塞,导致高优先级任务无法及时执行的现象。

经典场景:低优先级任务L持有锁,高优先级任务H需要这个锁被阻塞,此时中优先级任务M抢占了L,导致L无法释放锁,H被M间接阻塞。这违反了优先级调度原则。

危害:系统实时性下降,高优先级任务响应延迟增加,严重时可能导致系统失效。火星探路者号就曾因优先级反转导致系统重启。

解决方法

  1. 优先级继承:当高优先级任务H被低优先级任务L阻塞时,临时提升L的优先级到H的级别,让L尽快完成并释放锁,然后恢复L的原优先级。这是最常用的方法,Linux的rt_mutex和FreeRTOS的mutex都支持。
  2. 优先级天花板:给每个锁设置一个优先级上限(持有该锁的所有任务的最高优先级),任务获取锁时立即提升到这个优先级。可以避免死锁,但可能导致不必要的优先级提升。
  3. 避免共享资源:重新设计系统架构,减少任务间的资源共享,通过消息传递代替共享内存。
  4. 禁止抢占:任务持有锁期间禁止抢占,但这会降低系统响应性,只适合临界区很短的情况。

激光雷达应用:数据采集任务优先级高,数据处理任务优先级低,如果它们共享缓冲区锁,可能发生优先级反转。使用支持优先级继承的互斥锁可以解决这个问题。

4. Linux设备驱动有哪些分类?字符设备和块设备有什么区别?

参考答案:

设备驱动分类

  1. 字符设备:按字节流方式访问,不支持随机访问,如串口、键盘、鼠标、传感器等。
  2. 块设备:按固定大小的块访问,支持随机访问,有缓存机制,如硬盘、SD卡、U盘等。
  3. 网络设备:用于网络通信,不对应/dev下的设备文件,通过socket接口访问,如以太网卡、WiFi模块等。

字符设备和块设备的区别

访问方式

字节流,顺序访问

固定大小块,随机访问

缓存

无缓存,直接读写

有缓存(page cache)

接口

read/write/ioctl

read/write/ioctl + 块层接口

性能

简单,开销小

复杂,有优化(合并、调度)

应用

串口、传感器、GPIO

磁盘、Flash、SD卡

激光雷达应用:激光雷达驱动通常实现为字符设备,用户空间通过read读取点云数据。如果数据量特别大,也可以考虑用mmap映射共享内存,避免拷贝开销。

5. 什么是设备树?设备树的作用是什么?

参考答案:

定义:设备树(Device Tree)是一种描述硬件配置的数据结构,用树形结构表示硬件设备的拓扑关系和属性。以.dts源文件编写,编译成.dtb二进制文件,在系统启动时传递给内核。

作用

  1. 硬件描述与代码分离:以前硬件信息(寄存器地址、中断号、GPIO等)硬编码在驱动中,不同硬件需要修改代码重新编译。设备树将硬件信息提取到配置文件中,同一份驱动代码可以适配不同硬件,只需修改设备树。
  2. 支持热插拔和动态配置:内核启动时解析设备树,动态创建设备节点,驱动通过设备树获取硬件资源。
  3. 标准化硬件描述:提供统一的硬件描述语言,方便移植和维护。

设备树内容:包括CPU信息、内存布局、总线拓扑、外设配置(寄存器地址、中断、时钟、GPIO、DMA等)、自定义属性等。

驱动如何使用:驱动通过compatible属性匹配设备树节点,使用of_系列API读取设备树属性,如of_get_property获取属性值,of_iomap映射寄存器,of_irq_get获取中断号等。

激光雷达应用:激光雷达驱动需要配置MIPI接口、时钟、GPIO(使能、复位)、中断等资源,这些都在设备树中描述。不同型号的激光雷达只需修改设备树,驱动代码无需改动。

6. Linux内核模块如何加载和卸载?

参考答案:

加载模块:使用insmod或modprobe命令。insmod需要指定完整路径和依赖模块,modprobe会自动处理依赖关系,从/lib/modules目录加载。

剩余60%内容,订阅专栏后可继续查看/也可单篇购买

嵌入式面试八股文全集 文章被收录于专栏

这是一个全面的嵌入式面试专栏。主要内容将包括:操作系统(进程管理、内存管理、文件系统等)、嵌入式系统(启动流程、驱动开发、中断管理等)、网络通信(TCP/IP协议栈、Socket编程等)、开发工具(交叉编译、调试工具等)以及实际项目经验分享。专栏将采用理论结合实践的方式,每个知识点都会附带相关的面试真题和答案解析。

全部评论

相关推荐

2025-12-18 19:36
已编辑
门头沟学院 Java
程序员牛肉:可以的,简历没毛病了。 虽然还是偏向同质化,不过学历不错。后续我觉得重心放到刷实习+摆脱同质化问题上
实习简历求拷打
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务