虹软科技 嵌入式开发软件 一面
1. 自我介绍
您好,我是XXX,目前就读于XX大学电子信息工程/自动化专业。我的技术方向是嵌入式系统开发,对单片机、RTOS、驱动开发有深入的学习和实践。
技术能力方面:
- 熟练掌握C/C++编程
- 熟悉ARM Cortex-M系列单片机的开发
- 熟悉FreeRTOS、RT-Thread等实时操作系统的使用和移植
- 了解常见的通信协议如UART、SPI、I2C、CAN等
- 了解网络协议如TCP/IP、MQTT
项目经验方面:
我做过智能小车、物联网数据采集系统等项目。
在智能小车项目中,我负责底层驱动开发和传感器数据采集,实现了电机控制、超声波避障、循迹等功能。
在物联网项目中,我负责设备端的开发,使用MQTT协议实现数据上报和远程控制。
比赛经验方面:
我参加过全国大学生电子设计竞赛、智能车竞赛等,获得过省级奖项。通过比赛锻炼了快速开发和调试的能力,也积累了团队协作的经验。
我对嵌入式技术充满热情,喜欢研究底层原理,善于解决实际问题。希望能加入贵公司,在实际项目中不断提升自己。
2. FreeRTOS的任务调度机制,抢占式和时间片轮转的区别
FreeRTOS调度机制
FreeRTOS使用基于优先级的抢占式调度。每个任务有一个优先级,数字越大优先级越高。调度器总是运行就绪态中优先级最高的任务。
当高优先级任务就绪时,会立即抢占低优先级任务的CPU。低优先级任务被挂起,高优先级任务开始运行。这就是抢占式调度。
对于相同优先级的任务,FreeRTOS使用时间片轮转。每个任务运行一个时间片(tick),时间片到了就切换到下一个同优先级任务。这样保证了同优先级任务的公平性。
任务状态
FreeRTOS的任务有四种状态:
- 运行态:任务正在执行
- 就绪态:任务可以运行但还没轮到
- 阻塞态:任务在等待事件,比如等待信号量、延时
- 挂起态:任务被主动挂起,不参与调度
任务从运行态可以进入阻塞态,比如调用延时函数。从阻塞态可以进入就绪态,比如延时到期。从就绪态可以进入运行态,被调度器选中。
调度时机
调度发生在几个时机:
- 系统滴答中断,每个tick都会检查是否需要切换任务
- 任务主动放弃CPU,比如调用延时、等待信号量
- 中断服务程序结束时,可能有高优先级任务就绪
抢占式 vs 时间片轮转
抢占式调度:
- 保证了实时性,高优先级任务可以立即响应
- 但可能导致低优先级任务饥饿,长时间得不到执行
时间片轮转:
- 保证了公平性,同优先级任务都能得到执行机会
- 但实时性相对差一些,任务可能要等待一个时间片
FreeRTOS结合了两者的优点,不同优先级用抢占式,相同优先级用时间片轮转。这样既保证了实时性,又保证了公平性。
实际应用
在设计任务时,要合理分配优先级:
- 紧急的、实时性要求高的任务设置高优先级
- 不紧急的、后台任务设置低优先级
要避免优先级反转问题。低优先级任务持有资源,高优先级任务等待资源,中优先级任务抢占CPU,导致高优先级任务无法执行。可以使用优先级继承或优先级天花板解决。
要注意任务的栈大小设置。栈太小会导致栈溢出,栈太大会浪费内存。可以使用栈溢出检测功能,及时发现问题。
3. MQTT协议的原理,和TCP有什么区别?
MQTT协议
MQTT是Message Queuing Telemetry Transport的缩写,是一种轻量级的消息传输协议。它基于发布-订阅模式,适合物联网场景。
MQTT的核心概念是主题Topic。发布者向某个主题发布消息,订阅者订阅某个主题接收消息。中间有一个Broker代理服务器,负责消息的转发。
MQTT支持三种QoS质量等级:
- QoS 0:最多一次,消息可能丢失
- QoS 1:至少一次,消息可能重复
- QoS 2:恰好一次,消息不丢失不重复
MQTT vs TCP
TCP:
- 传输层协议,提供可靠的字节流传输
- 点对点通信,需要知道对方的IP和端口
- 需要自己处理消息的封装、解析、重传等
- 适合两个设备直接通信
MQTT:
- 应用层协议,基于TCP实现,提供消息传输功能
- 发布-订阅模式,不需要知道对方地址,通过主题通信
- 已经实现了消息封装、解析等功能,使用更简单
- 适合多个设备通过服务器通信,特别是一对多、多对多的场景
为什么用MQTT
- 轻量级:协议开销小。MQTT的消息头只有2字节,适合带宽受限的物联网场景
- 支持弱网络环境:有心跳机制,可以检测连接状态。有遗嘱消息机制,设备异常断开时可以通知其他设备
- 支持QoS:可以根据需求选择消息质量。重要消息用QoS 2保证可靠,不重要的消息用QoS 0节省资源
- 支持主题订阅:实现灵活的消息路由。可以用通配符订阅多个主题,比如
sensor/+/temperature订阅所有传感器的温度数据
MQTT点对点通信
MQTT本身是发布-订阅模式,不是点对点。但可以通过设计主题实现点对点。
比如设备A要给设备B发消息,可以发布到主题device/B/message。设备B订阅这个主题,就能收到消息。
或者使用私有主题,每个设备有唯一的主题。设备A发布到设备B的主题,只有设备B能收到。
但这种方式需要知道对方的设备ID,而且Broker会转发所有消息,效率不高。如果真的需要点对点,直接用TCP更合适。
实际应用
在物联网项目中,我使用MQTT实现设备和云平台的通信:
- 设备定期发布传感器数据到主题
sensor/data,云平台订阅这个主题接收数据 - 云平台发布控制命令到主题
device/控制,设备订阅这个主题接收命令 - 使用QoS 1保证消息可靠,避免数据丢失
- 使用遗嘱消息机制,设备断开时自动发布离线消息,云平台可以及时发现设备离线
4. Linux驱动框架的理解,字符设备驱动的实现
Linux驱动框架
Linux把设备分为三类:
- 字符设备:按字节流访问,比如串口、LED
- 块设备:按块访问,比如硬盘、SD卡
- 网络设备:用于网络通信,比如网卡
Linux的驱动框架实现了应用层和驱动层的隔离。应用程序通过设备文件访问设备,不需要知道驱动的实现细节。驱动程序实现设备的操作函数,注册到内核。
这种分层设计的好处是应用程序可以用统一的接口访问不同的设备。驱动程序可以独立开发和更新,不影响应用程序。
字符设备驱动
字符设备驱动的核心是file_operations结构体,定义了设备的操作函数:
open:打开设备release:关闭设备read:读取数据write:写入数据ioctl:控制设备
驱动程序实现这些函数,然后注册到内核。应用程序通过open、read、write等系统调用访问设备,内核会调用驱动的对应函数。
注册字符设备需要设备号,包括主设备号和次设备号。主设备号标识驱动程序,次设备号标识具体设备。可以静态分配设备号,也可以动态分配。
设备文件
设备文件是应用程序访问设备的接口,位于/dev目录下。设备文件有设备号,内核根据设备号找到对应的驱动。
可以用mknod命令手动创建设备文件,也可以用udev自动创建。现代Linux系统都使用udev,驱动加载时自动创建设备文件。
驱动加载
驱动可以编译到内核中,也可以编译为模块动态加载。模块方式更灵活,可以在运行时加载和卸载。
驱动模块有init函数和exit函数:
init函数在模块加载时执行,注册设备exit函数在模块卸载时执行,注销设备
使用insmod命令加载模块,rmmod命令卸载模块,lsmod命令查看已加载的模块。
FreeRTOS vs Linux
FreeRTOS没有驱动框架,驱动代码和应用代码混在一起。应用程序直接调用驱动函数,耦合度高。
如果要在FreeRTOS上实现类似Linux的驱动框架,可以定义统一的设备接口。每个驱动实现这些接口,注册到设备管理器。应用程序通过设备管理器访问设备,不直接调用驱动函数。
这样可以实现应用和驱动的解耦,提高代
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
这是一个全面的嵌入式面试专栏。主要内容将包括:操作系统(进程管理、内存管理、文件系统等)、嵌入式系统(启动流程、驱动开发、中断管理等)、网络通信(TCP/IP协议栈、Socket编程等)、开发工具(交叉编译、调试工具等)以及实际项目经验分享。专栏将采用理论结合实践的方式,每个知识点都会附带相关的面试真题和答案解析。
查看1道真题和解析