目录:
2.15 volatile和static关键字的含义和使用场景
3.1 U-Boot、Kernel(内核)和RootFS(根文件系统)启动流程总结
3.9.2. 表示层(Presentation Layer)
3.10.5. 消息传递机制(Message Passing)
3.10.8. 内存映射文件(Memory-Mapped Files)
3.13.3. 条件变量(Condition Variable)
4.5.4. 条件变量(Condition Variable)
4.6 Makefile最终是使用什么把可执行文件编译出来的?
9.10 make的时候执行那一条命令是怎么找的。冒号后面写指令有什么要求和限制?
4.11 把gpio当成按键,按下的时候上报应用层,具体驱动怎么去写?
4.13 按键gpio需要把中断配置成什么样的,怎么触发,怎么捕获状态,怎么实现长按?
4.14 应用层不用read怎么拿到驱动层的数据,通知或触发形式?
一、基础面经
1.1 熟练掌握C/C++
【八股文】嵌入式软件工程师-2025校招必备-详细整理_嵌入式面试八股文-CSDN博客
【面经】C++八股文(地平线C++一面)_c++面经八股文-CSDN博客
1.2 数据结构和算法
数据结构与算法的八股文自述(持续更新)_数据结构八股文-CSDN博客
1.3 Linux操作系统
全网最全Linux八股文讲解(25秋招走起~)_linux内存管理 八股文-CSDN博客
Linux 命令大全(看这一篇就足够)_linux命令-CSDN博客
八股面试大总结②——Linux系统_linux面试八股-CSDN博客
【嵌入式软件工程师面经】Linux文件IO_linux嵌入式开发面经-CSDN博客
1.4 makefile
Makefile基础语法 看这一篇就够了_makefile 文件-CSDN博客
makefile常见知识点及示例_makefile -include-CSDN博客
1.5 shell
linux shell 脚本 入门到实战详解[⭐建议收藏!!⭐]-CSDN博客
1.6 Linux进程通信
【嵌入式软件工程师面经】Linux网络编程Socket_socket面经-CSDN博客
1.7 stm32
全网最全的MCU面试经(基于STM32F103)_单片机面经-CSDN博客
二、8.20一面问到的问题
面经素材来源于牛客网:
2.1 比特率:
常见的比特率:
38400 bps
57600 bps
115200 bps
230400 bps
460800 bps
921600 bps
UART(通用异步收发传输器)协议中的波特率(Baud Rate)是指数据传输的速率,即每秒钟传输的比特数(bit/s)。波特率115200表示每秒钟传输115200个比特。
在UART通信中,波特率是一个非常重要的参数,因为它决定了数据传输的速度。发送端和接收端必须使用相同的波特率,以确保数据能够正确地传输和接收。如果波特率不匹配,接收端可能会错误地解释传输的数据,导致通信失败。
波特率115200是一个相对较高的速率,通常用于需要快速数据传输的应用,例如在嵌入式系统中与外部设备进行通信。需要注意的是,波特率越高,对硬件的要求也越高,因为需要更快的时钟和更精确的定时来确保数据的准确传输。
UART协议的波特率115200表示每秒钟可以传输115200比特(bit)。要将其转换为千字节(KB)每秒,我们需要使用以下转换:
1 字节(Byte)= 8 比特(bit)
1 千字节(KB)= 1024 字节(Byte)首先,将波特率从比特转换为字节每秒:
115200 比特/秒 ÷ 8 = 14400 字节/秒
然后,将字节每秒转换为千字节每秒:
14400 字节/秒 ÷ 1024 ≈ 14.06 KB/秒
所以,波特率为115200时,理论上的最大数据传输速度大约是14.06 KB/秒。然而,实际的数据吞吐量可能会小于这个值,因为UART传输通常还包括起始位、停止位和可能的奇偶校验位。这些额外的位在每个发送的字节中占据空间,但不携带有效的数据负载,因此它们会稍微降低有效的数据传输速率。
2.2 同步串口和异步串口的区别(软件和硬件)
2.2.1 数据传输方式
1. 同步串口
- 数据传输方式:同步串口传输数据时,发送方和接收方使用一条共同的时钟信号进行同步。数据和时钟信号一起传输,确保接收方按照发送方的时钟信号正确接收数据。
- 时钟信号:在同步传输中,时钟信号是必需的,可能是独立的时钟线或嵌入在数据流中的时钟信息。
- 数据流:数据按位发送,接收方根据时钟信号的节拍同步接收每一位数据。
2. 异步串口
- 数据传输方式:异步串口传输数据时,双方无需共享时钟信号。数据通过起始位(start bit)、数据位、奇偶校验位(可选)和停止位(stop bit)进行传输。
- 时钟信号:没有专用的时钟信号。接收方通过起始位的边缘检测来同步其内部时钟,以便正确接收每一位数据。
- 数据流:数据以字符为单位发送,每个字符前后都有额外的控制位(起始位和停止位)。
2.2.2 硬件实现
1. 同步串口
- 时钟线:需要额外的时钟线或时钟嵌入技术用于数据同步。
- 硬件复杂性:由于需要处理时钟信号,硬件实现相对复杂,需要具备同步时钟的硬件支持。
- 传输速度:通常能够支持更高的传输速度,因为数据传输是同步的,没有额外的起始和停止位。
2. 异步串口
- 时钟同步:不需要额外的时钟线,同步通过起始位完成。
- 硬件简单性:实现简单,通常只需要发送和接收数据线(TX和RX),加上地线(GND)。
- 传输速度:相对较慢,因为每个数据包(字符)需要额外的控制位,且同步依赖于起始位的边缘检测。
2.2.3 软件实现
1. 同步串口
- 协议复杂性:协议通常较复杂,需要处理时钟信号的同步和数据的传输。
- 数据处理:软件需要处理时钟信号,以确保数据在正确的时钟节拍上发送和接收。
- 同步机制:可能需要额外的软件逻辑来处理时钟同步和数据接收的时序问题。
2. 异步串口
- 协议简单性:协议较为简单,通过起始位和停止位进行数据包的同步。
- 数据处理:软件处理相对简单,只需检测起始位并在固定的时间间隔内读取数据位。
- 同步机制:依赖于硬件的边缘检测来同步时钟,软件不需要额外处理时钟信号。
2.2.4 应用场景
1. 同步串口
- 应用场景:适用于数据传输速率要求较高的应用,如音视频传输、数据采集等。
- 典型协议:SPI(串行外设接口)、I2S(集成电路声频接口)等。
2. 异步串口
- 应用场景:适用于一般的串行通信应用,如串口调试、低速数据传输等。
- 典型协议:UART(通用异步收发传输器)、RS-232、RS-485等。
2.3 串口传输大量数据如何保持数据完整性
2.3.1 使用校验位(Parity Bit)
- 奇偶校验:在每个数据帧中添加一个奇偶校验位,以检测单个比特错误。
- 奇校验:数据帧中1的个数为奇数。
- 偶校验:数据帧中1的个数为偶数。
- 优点:简单有效,能检测到单个比特错误。
- 缺点:无法纠正错误,且对多个比特错误无能为力。
2.3.2 使用校验和或者 CRC(循环冗余校验)
- 校验和:发送数据块的所有字节之和,接收方重新计算校验和并进行比较。
- CRC:一种更复杂的校验方法,能检测并纠正多种类型的错误。
- 实现:在发送数据块之前计算CRC值,并将其附加到数据块后面,接收方根据同样的算法计算CRC值并进行比较。
- 优点:高效且能够检测和纠正多位错误(尤其是CRC)。
- 缺点:需要额外的计算资源和带宽。
2.3.3 使用握手协议
- 硬件握手:使用额外的控制线路(如RTS/CTS)来管理数据流,避免数据溢出。
- RTS(请求发送):发送方请求发送数据。
- CTS(清除发送):接收方准备好接收数据。
- 软件握手:使用协议(如XON/XOFF)控制数据流。
- XON:接收方发送XON字符,通知发送方继续发送数据。
- XOFF:接收方发送XOFF字符,通知发送方暂停发送数据。
- 优点:有效管理数据流,防止数据溢出和丢失。
- 缺点:增加了通信延迟和复杂性。
2.3.4 分块传输和确认机制
- 分块传输:将大数据分成多个小块进行传输,每个块包含一个序列号。
- 确认机制:接收方在成功接收每个数据块后发送确认消息(ACK),接收失败则发送否认消息(NAK)并请求重传。
- 优点:确保每个数据块都被正确接收,适用于大数据传输。
- 缺点:增加了通信开销和延迟。
2.3.5 使用缓冲区
- 发送和接收缓冲区:在发送和接收数据时使用缓冲区存储数据,避免数据丢失。
- 发送缓冲区:发送方在缓冲区中存储数据,串口控制器从缓冲区中读取数据并发送。
- 接收缓冲区:接收方在缓冲区中存储接收到的数据,处理器从缓冲区中读取数据进行处理。
- 优点:提高数据传输效率,避免数据丢失。
- 缺点:需要额外的内存资源。
2.3.6 使用更高层协议
- 协议栈:如Modbus、HTTP等更高级别的通信协议,内置了错误检测和纠正机制。
- 优点:综合性强,提供了完整的错误处理和数据管理功能。
- 缺点:复杂度和资源消耗较高。
2.4 串口多次传输大量数据如何减少中断产生
2.4.1 使用DMA(直接内存访问)
- DMA简介:DMA控制器可以在不占用CPU的情况下直接在内存和外设之间传输数据。
- 优点:大幅减少CPU中断负担,因为数据传输由DMA控制器处理,CPU仅在传输完成或发生错误时收到中断。
- 操作:
- 配置DMA控制器,设置源地址、目标地址和传输数据长度。
- 启动DMA传输。
- 在传输完成时处理DMA中断。
2.4.2 改变中断触发条件
- FIFO缓冲区:许多串口控制器支持硬件FIFO(先进先出)缓冲区,可以根据FIFO中的数据量触发中断。
- 中断触发阈值:
- 接收中断触发阈值:设置在接收FIFO达到一定阈值(如半满或3/4满)时触发中断。
- 发送中断触发阈值:设置在发送FIFO空余空间达到一定阈值时触发中断。
- 优点:通过适当设置中断触发阈值,可以减少中断频率,提高效率。
2.4.3 增大软件缓冲区
- 环形缓冲区(Circular Buffer):在软件中实现一个较大的环形缓冲区,用于存储接收到的数据。
- 优点:较大的缓冲区可以减少频繁的中断处理,提高数据处理效率。
- 操作:
- 接收中断处理:将接收到的数据放入环形缓冲区。
- 处理函数:分批从缓冲区读取数据进行处理。
2.4.4 批量传输
- 批量发送:每次发送尽可能多的数据,以减少发送中断的次数。
- 批量接收:每次接收尽可能多的数据,以减少接收中断的次数。
- 优点:减少中断次数,提高传输效率。
- 操作:
- 在发送数据时,尽量一次写入多字节数据至发送FIFO。
- 在接收数据时,尽量一次读取多字节数据至接收缓冲区。
2.4.5 中断优先级管理
- 设置中断优先级:将串口中断设置为较低优先级,避免其频繁打断高优先级任务。
- 中断屏蔽:在处理关键任务时暂时屏蔽串口中断,避免高频中断影响关键任务的执行。
- 优点:有效管理中断,保证系统的实时性和高效性。
2.4.6 使用任务调度(RTOS)
- 实时操作系统(RTOS):在RTOS中使用任务调度和中断服务例程(ISR)配合处理串口数据。
- 优点:RTOS可以更好地管理中断和任务,提高系统的整体性能和响应能力。
- 操作:
- 在ISR中只进行快速的中断处理,将复杂的处理任务推送到任务中。
- 使用消息队列或信号量将数据传递到任务进行处理。
2.4.7 硬件流控制
- 硬件流控制(RTS/CTS):通过硬件握手信号控制数据流,避免缓冲区溢出。
- 优点:硬件流控制可以有效管理数据流,减少因溢出导致的中断。
- 操作:
- 在启用RTS/CTS的情况下,发送方在准备发送数据前检查CTS信号。
- 接收方在接收缓冲区接近满时,拉低RTS信号,通知发送方暂停发送数据。
2.4.8 优化中断服务程序(ISR)
- 精简ISR:确保中断服务程序尽可能简短,只处理必须的操作,如读取数据和清除中断标志。
- 延迟处理:将复杂的处理操作推迟到中断之外的任务或线程中进行。
- 优点:减少中断处理时间,降低中断频率对系统性能的影响。
2.5 IIC 的特点
2.5.1. 简单的硬件连接
- 双线制:I2C使用两条总线线进行通信,分别是:
- SDA(串行数据线):传输数据。
- SCL(串行时钟线):提供时钟信号。
- 节省引脚:由于只需要两条线,可以节省芯片的引脚数和PCB布线复杂度。
2.5.2. 多主多从结构
- 多主设备:I2C总线支持多个主设备(Master),即多个设备可以发起通信。
- 多从设备:总线支持多个从设备(Slave),即多个设备可以接收通信。
- 地址唯一性:每个从设备拥有唯一的地址,主设备通过地址来选择与哪个从设备通信。
2.5.3. 数据传输方式
- 半双工通信:数据传输是双向的,但同一时刻只能进行单向传输。
- 位传输:数据按位传输,每个时钟周期传输一个比特。
- 字节传输:通常以字节(8位)为单位传输数据,每个字节传输后从设备需要发送一个ACK(应答信号)。
2.5.4. 时钟信号控制
- 主设备生成时钟:时钟信号由主设备生成和控制,从设备依据时钟信号同步数据传输。
- 时钟伸展:从设备可以通过拉低SCL线的方式延长时钟周期,以便有足够时间处理数据。
2.5.5. 数据完整性
- 应答信号:每个字节传输后,从设备需要发送ACK信号确认接收成功。
- 仲裁机制:多个主设备同时发起通信时,通过仲裁机制确保总线上只有一个主设备在传输数据,避免冲突。
2.5.6. 数据传输速率
- 标准模式(Standard Mode):最高传输速率100 kbps。
- 快速模式(Fast Mode):最高传输速率400 kbps。
- 高速模式(High-Speed Mode):最高传输速率3.4 Mbps。
- 快速模式+(Fast Mode Plus):最高传输速率1 Mbps。
- 超高速模式(Ultra-Fast Mode):最高传输速率5 Mbps(较少使用)。
2.5.7. 硬件实现简单
- 开漏驱动:I2C设备使用开漏或开集电极输出,需要外部上拉电阻将总线线拉高到电源电压。
- 线与操作:多个设备可以共同驱动总线线,通过线与操作实现电平拉低。
2.5.8. 支持多种器件
- 广泛应用:适用于多种类型的芯片,如微控制器、EEPROM、传感器、实时时钟(RTC)、LCD驱动器等。
- 兼容性:不同厂商的I2C设备通常可以兼容使用,促进了I2C在嵌入式系统中的普及。
2.5.9. 软件实现灵活
- 位操作:通过软件控制I/O引脚可以实现I2C通信(软I2C),这种方式灵活但速度较慢。
- 硬件I2C模块:许多微控制器内置I2C硬件模块,提供高效的硬件支持和简化的软件实现。
2.6 I2C是一对多与多对多
2.6.1. 一对多(单主多从)
在单主多从模式下,I2C总线上只有一个主设备(Master),可以与多个从设备(Slave)通信。这是I2C最常见的使用模式。
特点:
- 一个主设备:主设备负责生成时钟信号(SCL)和控制数据流(SDA)。
- 多个从设备:每个从设备有一个唯一的7位或10位地址,用于主设备选择和通信。
- 简单控制:主设备主动发起通信,从设备被动响应,无需复杂的仲裁机制。
应用场景:
- 嵌入式系统:微控制器作为主设备,控制多个外围设备如传感器、存储芯片、显示驱动等。
- 传感器网络:中央处理单元读取多个传感器的数据。
数据传输过程:
- 主设备发送起始信号:拉低SDA线,然后在SCL线保持高电平时拉低SCL线。
- 发送从设备地址和读写位:主设备发送从设备的地址和读/写位。
- 从设备应答:目标从设备收到地址后发送ACK信号。
- 数据传输:主设备与从设备之间进行数据传输,每个字节后跟随ACK信号。
- 停止信号:主设备完成数据传输后发送停止信号,拉高SDA线然后拉高SCL线。
2.6.2. 多对多(多主多从)
在多主多从模式下,I2C总线上可以有多个主设备,每个主设备可以与一个或多个从设备通信。这种模式需要额外的机制来处理主设备之间的冲突。
特点:
- 多个主设备:多个设备可以发起通信并控制总线。
- 多个从设备:总线上可以有多个从设备,每个从设备有唯一地址。
- 仲裁机制:确保在多个主设备同时发起通信时,总线上只有一个主设备可以继续通信,避免冲突。
应用场景:
- 复杂系统:需要多个控制器协同工作,每个控制器都可能需要与多个从设备通信。
- 分布式系统:例如多个微控制器或处理器在同一总线上协同工作。
仲裁过程:
- 同时发起通信:多个主设备可能同时发起通信。
- 仲裁信号:主设备通过监控SDA线上的信号进行仲裁,只有最低地址的主设备继续通信。
- 失去仲裁的主设备:在仲裁过程中失去控制权的主设备将停止发送数据,并等待下一次机会。
仲裁机制:
- 线与操作:I2C总线使用开漏或开集电极驱动,通过SDA线和SCL线的线与操作实现仲裁。
- 数据监控:主设备在发送数据的同时监控SDA线,如果检测到与自己发送的数据不同,则停止通信,表示失去仲裁。
总结
- 一对多(单主多从):简单、高效,适用于大多数嵌入式系统和简单的器件网络。
- 多对多(多主多从):灵活、复杂,需要仲裁机制,适用于需要多个控制器协同工作的场景。
2.7 IIC中的设备号是不是唯一的有没有重复
I2C(Inter-Integrated Circuit)总线上的设备地址理论上是唯一的,但在实际应用中有时可能会遇到设备地址冲突的问题。以下是对I2C设备地址的详细解释:
2.7.1 设备地址的唯一性
1. 地址分配
- 7位地址:标准I2C设备地址是7位的,这意味着最多可以有128个不同的地址(0x00到0x7F)。
- 10位地址:有些I2C设备支持扩展的10位地址模式,这样可以分配更多的设备地址。
2. 唯一性原则
- 唯一性:在同一条I2C总线上,每个设备必须有一个唯一的地址,以确保主设备能够正确地选择和通信。
- 制造商分配:设备地址通常由设备制造商分配,并在数据手册中明确注明。
2.7.2 地址冲突的可能性
1. 地址重复
- 相同设备:如果在同一I2C总线上连接了多个相同型号的设备,这些设备默认地址可能是相同的。
- 固定地址设备:一些设备的地址是固定的,无法通过配置改变,这可能导致地址冲突。
2. 可配置地址
- 可编程地址引脚:许多设备有可编程的地址引脚(如A0、A1、A2),通过配置这些引脚的高低电平可以改变设备的地址。
- 软件配置地址:有些设备允许通过软件命令配置设备地址。
2.7.3 解决地址冲突的方法
1. 使用可配置地址引脚
- 硬件配置:通过改变设备的地址引脚配置,确保每个设备的地址唯一。
- 跳线或拨码开关:通过跳线或拨码开关改变引脚电平,方便灵活地设置地址。
2. 使用多路复用器(I2C Multiplexer)
- 多路复用器:使用I2C多路复用器(如PCA9548A),将I2C总线分为多个子总线,每个子总线可以连接相同地址的设备。
- 选择子总线:主设备通过控制多路复用器选择需要与之通信的子总线。
3. 使用不同的I2C总线
- 多总线架构:如果微控制器或处理器支持多个I2C总线,可以将地址相同的设备连接到不同的I2C总线。
4. 软件地址映射
- 地址映射:在软件中维护设备地址映射表,通过逻辑方式管理和区分同一地址的不同设备。
2.8 如何模块化的去封装IIC
- 抽象层次:将I2C通信的低层实现和应用逻辑分离。
- 硬件抽象层(HAL):提供对不同硬件平台的支持。
- 接口设计:定义易于使用的接口函数,支持初始化、读写和关闭I2C通信。
- 错误处理:考虑通信过程中可能出现的错误并处理。
- 配置灵活性:支持不同I2C总线速率和设备地址的配置。
2.9 在什么平台和媒介上使用的基于TCP/IP的网络通信
通过TCP/IP协议进行数据传输,如社交媒体应用、即时通讯应用等。
通过TCP/IP协议进行支付交易,如支付宝、微信支付等。
通过TCP/IP协议进行控制和数据传输,如智能灯泡、智能插座等。
通过TCP/IP协议进行设备监控和数据采集,如工业传感器、机器人等。
通过TCP/IP协议进行实时视频和音频传输,如Zoom、Teams等。
通过TCP/IP协议进行实时流媒体传输,如Twitch、YouTube Live等。
2.10 Socket的使用流程
-
服务器端
- 创建Socket
- 绑定地址
- 监听连接
- 接受连接
- 接收和发送数据
- 关闭连接
-
客户端
- 创建Socket
- 连接服务器
- 发送和接收数据
- 关闭连接
2.11 对于RTOS的理解
RTOS(实时操作系统)是一种专门为实时应用程序设计的操作系统,它能够在严格的时间限制内,按时完成任务和响应事件。RTOS与通用操作系统(如Windows、Linux)在设计目标和性能要求上有显著的不同。
2.11.1. 实时性
实时操作系统的核心特性是实时性,它需要确保在规定的时间内响应和处理事件。RTOS一般可以分为两类:
- 硬实时系统:必须在严格的时间限制内完成任务,否则系统会发生严重故障。例如,航空控制系统、医疗设备等。
- 软实时系统:尽量在规定的时间内完成任务,但偶尔的延迟是可以接受的。例如,多媒体应用、网络通信等。
2.11.2. 调度算法
RTOS采用专门的任务调度算法,以确保任务能够在规定的时间内执行。常见的调度算法有:
- 优先级调度:每个任务都有一个优先级,优先级高的任务先执行。
- 抢占式调度:高优先级的任务可以抢占正在执行的低优先级任务。
- 时间片轮转(Round Robin):每个任务按顺序轮流执行,每个任务分配到的CPU时间片相等。
2.11.3. 任务管理
RTOS提供了丰富的任务管理功能,包括任务创建、删除、挂起、恢复等。任务(或线程)是RTOS中最基本的执行单元,每个任务可以拥有自己的栈空间和执行上下文。
2.11.4. 中断处理
RTOS对中断的处理非常高效,能够快速响应硬件中断并执行相应的中断服务程序(ISR)。中断处理的延迟和中断关闭的时间通常都是可预测和可控的。
2.11.5. 同步与通信
RTOS提供了多种任务间的同步和通信机制,以确保任务协同工作。例如:
- 信号量(Semaphore):用于任务间的同步和互斥。
- 消息队列(Message Queue):用于任务间的消息传递。
- 事件标志(Event Flag):用于任务间的事件通知。
2.11.6. 内存管理
RTOS的内存管理通常非常高效,支持动态内存分配和静态内存分配。由于实时系统对确定性要求高,很多RTOS会采用内存池(Memory Pool)来进行内存分配,以减少碎片和提高分配效率。
2.12 RTOS如何根据任务去分配栈的大小
使用静态代码分析工具来评估代码的栈深度需求。这些工具可以跟踪函数调用链,计算每个函数需要的栈空间。
在任务栈中填充已知的标记值(如0xAA),运行一段时间后检查栈中未被覆盖的标记值的位置,以确定实际使用的最大栈深度。
2.13 野指针是什么 危害有哪些
野指针(Dangling Pointer)是指指向非法内存地址的指针。具体来说,野指针是指在释放或失效后仍然持有某个地址值的指针。
指针在指向内存块后,该内存块被释放
指针超出了其作用范围
未初始化的指针
危害:
使用野指针访问或修改内存会导致程序崩溃,产生段错误(Segmentation Fault)。
野指针导致的操作是不可预测的,可能会覆盖重要数据、修改程序代码或导致逻辑错误。这种不可预测的行为非常难以调试和排查。
野指针可以被恶意利用,导致程序出现安全漏洞。例如,通过缓冲区溢出,攻击者可以覆盖返回地址,执行任意代码。
野指针可能导致内存泄漏,特别是在指针没有正确释放或重复释放的情况下。
避免:
初始化指针、及时释放内存、释放后置空、防止超出作用域、使用智能指针
2.14 内存泄漏和内存溢出分别是什么
内存泄漏是指程序在动态分配内存后,没有适当地释放已分配的内存,导致这些内存无法被重新分配和使用。随着时间的推移,未释放的内存块会不断积累,最终可能耗尽系统内存资源。
内存溢出是指程序试图使用超过可用内存限制的内存空间。这通常发生在程序分配了超过系统或进程可以提供的内存,或者在使用静态数组时,访问了数组边界外的内存地址。
2.15 volatile和static关键字的含义和使用场景
volatile
关键字用于告诉编译器,被修饰的变量可能会在程序的控制之外被改变,因此编译器不应该对该变量进行优化。具体来说,volatile
确保每次访问该变量时都从内存中读取,而不是使用寄存器中的缓存值。
使用场景:
当变量表示硬件寄存器时,如控制寄存器、状态寄存器等,这些寄存器的值可能会被硬件设备直接修改。
在多线程环境中,一个线程可能会修改另一个线程正在使用的变量。使用volatile
可以确保线程间的可见性。
在中断服务例程中,变量可能会被中断处理程序修改。
static关键字
在不同的上下文中有不同的含义,用于控制变量和函数的作用域和生命周期,适用于局部变量、全局变量、函数和C++类成员。
使用场景:
静态局部变量:用于需要在函数调用之间保持状态的变量。
静态全局变量和函数:用于限制变量和函数的可见性,避免命名冲突。
静态成员变量和函数:用于在C++类中共享数据和函数,而不依赖于类的实例。
2.16 linux与freertos的区别
类型:
- Linux:是一种功能完善的开源操作系统,属于Unix家族。它是一个多用户、多任务的操作系统,通常用于桌面计算机、服务器和嵌入式系统。
- FreeRTOS:是一种实时操作系统(RTOS),专为资源受限的嵌入式设备设计。它是一个轻量级内核,适合用于对响应时间有严格要求的应用场合。
内核结构:
- Linux:采用的是一个整体式内核(Monolithic Kernel),内核中包含了所有的驱动程序、文件系统、网络协议栈等。这使得Linux功能强大,但相对庞大。
- FreeRTOS:采用的是一个微内核结构,提供基本的任务管理、任务间通信、计时等功能。其代码量小,适合资源有限的设备。
实时性:
- Linux:标准Linux内核并不是实时的,但可以通过PREEMPT_RT补丁进行实时性增强,使其适用于一些软实时应用。
- FreeRTOS:专为实时性设计,具有低延迟和高确定性的特点,适用于需要严格时间约束的应用场合。
资源需求:
- Linux:需要较多的资源,包括内存和处理能力,通常运行在较强大的硬件上。
- FreeRTOS:资源需求很低,可以在非常有限的硬件资源上运行,如简单的微控制器。
应用场景:
- Linux:适用于复杂的嵌入式应用,如智能手机、机顶盒等,也用于服务器和桌面系统。
- FreeRTOS:常用于小型嵌入式设备,如传感器、物联网设备、简单控制器等。
三、8.23一面问到的问题
面经素材来源于牛客网:
3.1 U-Boot、Kernel(内核)和RootFS(根文件系统)启动流程总结
1. U-Boot
- 初始化硬件设备。
- 加载并跳转到操作系统内核。
2. Kernel(内核)
- 解压并初始化内核。
- 初始化硬件设备和子系统。
- 挂载根文件系统。
- 启动
init
进程。
3. RootFS(根文件系统)
init
进程启动系统服务和守护进程。- 用户登录并运行应用程序。
嵌入式系统从上电或复位状态到操作系统运行,再到用户可以操作的状态。每个步骤都有其特定的作用和初始化任务,确保系统能够正常启动和运行。
3.2 Uboot的如何保存命令?
在U-Boot中,保存环境变量到非易失性存储器(如Flash存储或EEPROM)的过程通常包括设置环境变量和使用saveenv
命令将其保存。
首先,你需要设置你想要保存的环境变量,使用setenv
命令。
设置好环境变量后,需要使用saveenv
命令将它们保存到非易失性存储器中。这样,当系统重启时,这些环境变量的值仍然存在。
3.3saveenv;boot命令和bootz有什么区别?
1. saveenv
命令:
saveenv
命令用于将当前环境变量保存到非易失性存储器(如Flash存储或EEPROM)中。这样,当系统重启时,可以恢复这些变量。
2. boot命令:
boot
命令通常是一个通用命令,用于启动系统。它会根据当前环境变量中的配置信息启动操作系统。
3. bootm
和 bootz
命令的区别:
bootm
命令用于启动符合U-Boot镜像格式的内核镜像(通常是uImage格式)。
bootz
命令用于启动符合zImage格式的内核镜像。这种格式通常用于现代Linux内核。
3.4 介绍一下SPI的四种模式?
SPI的四种模式通过CPOL和CPHA两个参数的组合来定义。CPOL决定了时钟信号在空闲状态时的电平,而CPHA决定了数据在时钟信号的哪个边沿被采样。四种模式的定义如下:
- 模式0(CPOL=0, CPHA=0)
- 模式1(CPOL=0, CPHA=1)
- 模式2(CPOL=1, CPHA=0)
- 模式3(CPOL=1, CPHA=1)
3.5 堆是向上生长还是向下生长的?
堆(Heap)
- 增长方向:向上增长
- 解释:堆通常用于动态内存分配(例如使用
malloc
、calloc
、realloc
等函数分配的内存)。在许多系统中,堆的起始地址在较低的内存地址处,随着更多内存的分配,堆会向更高的内存地址增长。
栈(Stack)
- 增长方向:向下增长
- 解释:栈用于函数调用、局部变量和返回地址等。栈通常从高地址开始,随着更多的数据被压入栈中(如函数调用嵌套),栈会向更低的内存地址增长。
3.6 GPIO输出有哪几种模式?
推挽、开漏、上下拉
1. 推挽模式(Push-Pull):
- 工作原理:推挽模式使用两个晶体管,一个连接电源(Vcc),另一个连接地(GND)。这两个晶体管交替工作,可以将输出引脚驱动为高电平(Vcc)或低电平(GND)。
- 应用:适用于需要较高驱动能力的场合,例如驱动LED、继电器等。
2. 开漏模式(Open-Drain)(在一些微控制器中也称为开集电极模式Open-Collector,主要用于NPN晶体管):
- 工作原理:开漏模式仅使用一个晶体管连接到地(GND)。当晶体管导通时,输出引脚被拉低到地。当晶体管不导通时,输出引脚处于高阻状态(即悬空),需要通过外部上拉电阻将其拉到高电平。
- 应用:适用于需要多个设备共享一条信号线的场合,如I²C总线、按键输入等。
3. 上拉电阻(Pull-Up Resistor):将GPIO引脚通过一个电阻连接到电源(Vcc)。在开漏模式中,当引脚处于高阻状态时,上拉电阻将其拉高到Vcc。
4. 下拉电阻(Pull-Down Resistor):将GPIO引脚通过一个电阻连接到地(GND)。在某些应用中,当引脚处于高阻状态时,需要通过下拉电阻将其拉低到GND。
3.7 推挽和开漏有什么区别,为什么12C要用开漏?
1. 驱动能力:
- 推挽模式:可以主动驱动引脚到高电平或低电平,通常驱动能力较强。
- 开漏模式:只能主动驱动引脚到低电平,高电平需要通过外部上拉电阻实现。
2. 电平切换:
- 推挽模式:由于两个晶体管的交替工作,电平切换速度较快。
- 开漏模式:由于依赖外部上拉电阻,高电平的恢复时间可能较慢(取决于上拉电阻的值和电容负载)
3.信号线共享:
- 推挽模式:不适合多个设备共享同一信号线,因为可能会发生冲突(即一个设备输出高电平,另一个设备输出低电平时会短路)。
- 开漏模式:适合多个设备共享同一信号线,通过拉低电平进行通信,高电平由上拉电阻保证。
为什么12C要用开漏?
1. 多设备连接:I2C总线是一个多主机、多从机的系统,多个设备可以共享同一条数据线和时钟线。开漏配置允许多个设备在同一条线上进行通信,而不会产生信号冲突。当一个设备需要发送低电平信号时,它会将总线拉低(接地),而其他设备可以选择不做任何操作,保持高阻抗状态(不驱动总线),从而避免信号冲突。
2. 上拉电阻:在开漏配置中,输出端子只有在低电平时才会驱动,总线在没有任何设备驱动时会通过上拉电阻被拉高到高电平。这种结构简化了电路设计,因为只需在总线上添加一个上拉电阻即可实现高电平。
3. 电平兼容性:通过使用开漏输出,I2C总线可以更容易地与不同电压等级的设备进行连接。由于开漏配置只需要将信号拉低,因此可以使用不同电源电压的设备互相通信,只需选择合适的上拉电阻电压即可。
3.8 信号槽函数的原理是什么?
信号槽(Signal-Slot)机制是Qt框架中的核心机制之一,它提供了一种用于对象间通信的方式,尤其适用于GUI编程。这种机制使得对象可以通过信号(signal)和槽(slot)进行松耦合的通信。
Qt中的信号槽机制是通过元对象系统(Meta-Object System)和运行时类型信息(RTTI)实现的。
1. 元对象系统:
- Qt使用元对象编译器(moc, Meta-Object Compiler)生成特殊的元对象代码。这些代码包含类的元数据,包括信号和槽的信息。
- 每个QObject派生类都有一个与之关联的元对象(QMetaObject),存储了信号和槽的信息。
2. 信号的声明和实现:
- 信号在类中使用
signals
关键字声明,但没有具体实现。moc工具会生成这些信号的实现代码。 - 信号实际是由
QObject
类的QMetaObject
系统管理的。
3. 槽的声明和实现:
- 槽函数是普通的成员函数,可以在类中使用
slots
关键字声明,但这是可选的,任何公共成员函数都可以作为槽函数。
4. 连接信号和槽:
- 使用
QObject::connect
函数将信号和槽连接起来。connect
函数将信号的发射者(sender)、信号(signal)、接收者(receiver)和槽(slot)关联起来。 - Qt使用QMetaObject系统和一些内部数据结构记录这些连接关系。
5. 信号的发射和槽的调用:
- 当信号被发射(emit)时,Qt的事件系统会查找到所有连接到这个信号的槽函数,并依次调用它们。
3.9 OSI七层模型?
OSI(Open Systems Interconnection,开放系统互连)七层模型是由国际标准化组织(ISO)制定的网络互连模型,旨在规范计算机网络通信的各个方面,并促进不同网络设备和协议之间的互操作性。OSI模型将网络通信过程分为七个层次,每一层都有特定的功能。这七层自上而下依次为:应用层、表示层、会话层、传输层、网络层、数据链路层和物理层。
3.9.1. 应用层(Application Layer)
功能:
- 提供用户与网络服务之间的接口。
- 处理特定的网络应用程序,如电子邮件、文件传输和远程登录等。
协议:
- HTTP、FTP、SMTP、POP3、IMAP、SNMP等。
3.9.2. 表示层(Presentation Layer)
功能:
- 数据格式化、转换和加密。
- 处理数据的编码和解码,如字符编码、数据压缩和加密解密等。
协议:
- JPEG、MPEG、TLS、SSL等。
3.9.3. 会话层(Session Layer)
功能:
- 管理和控制会话,包括建立、维持和终止通信会话。
- 提供会话恢复和检查点功能。
协议:
- NetBIOS、PPTP、RPC等。
3.9.4. 传输层(Transport Layer)
功能:
- 提供可靠的数据传输服务,包括错误检测和纠正、数据分段和重组等。
- 实现流量控制和拥塞控制。
协议:
- TCP、UDP、SCTP等。
3.9.5. 网络层(Network Layer)
功能:
- 负责数据包的路由和转发。
- 管理逻辑地址(如IP地址),实现路径选择和路由。
协议:
- IP、ICMP、IGMP、IPsec等。
3.9.6. 数据链路层(Data Link Layer)
功能:
- 负责在物理网络上实现可靠的数据传输。
- 提供帧的封装与解封装、地址寻址、错误检测和校正等功能。
协议:
- Ethernet、PPP、HDLC、Frame Relay等。
- MAC地址、ARP、RARP等。
3.9.7. 物理层(Physical Layer)
功能:
- 定义物理连接、传输介质和信号的电气特性。
- 负责比特流的传输,包括电压、电缆、接头、传输速率和物理拓扑等。
协议和标准:
- EIA/TIA-232、EIA/TIA-449、V.35、RJ45、光纤、同轴电缆等。
3.10 进程间通讯方式有哪些?
3.10.1. 管道(Pipes)
匿名管道(Anonymous Pipes)
- 特点:通常用于具有亲缘关系的进程(例如父进程和子进程)之间的单向通信。
- 实现:创建一个管道,父进程可以将数据写入管道,子进程可以从管道读取数据。
- 优点:简单高效,适合短时间的、单向的数据流传输。
- 缺点:只能用于具有亲缘关系的进程,通信方向固定。
命名管道(Named Pipes, FIFO)
- 特点:可以在无亲缘关系的进程之间进行单向或双向通信。
- 实现:通过在文件系统中创建一个特殊文件(命名管道),不同进程可以打开这个文件进行读写操作。
- 优点:灵活性较高,可以跨网络进行通信。
- 缺点:复杂度较高,系统开销大。
3.10.2. 消息队列(Message Queues)
- 特点:一种消息传递机制,允许进程以消息的形式进行通信。
- 实现:进程可以向队列发送消息,也可以从队列接收消息。消息队列由操作系统管理,消息按顺序存储。
- 优点:可以实现同步和异步通信,支持消息优先级。
- 缺点:系统开销大,消息队列长度受限。
3.10.3. 共享内存(Shared Memory)
- 特点:允许多个进程共享同一块内存区域,从而实现高速的数据交换。
- 实现:通过操作系统提供的共享内存机制,进程可以映射同一块物理内存到各自的地址空间中。
- 优点:通信速度非常快,适合大数据量的传输。
- 缺点:需要同步机制(如信号量)来防止竞争条件和数据一致性问题。
3.10.4. 信号量(Semaphores)
- 特点:主要用于进程间的同步,控制对共享资源的访问。
- 实现:信号量是一种计数器,可以被多个进程用来协调对共享资源的访问。
- 优点:提供了强有力的同步机制,解决了进程间竞争条件问题。
- 缺点:编程复杂度高,容易发生死锁。
3.10.5. 消息传递机制(Message Passing)
- 特点:通过发送和接收消息来实现进程间通信。
- 实现:消息传递系统提供了发送消息(send)和接收消息(receive)的API,进程通过这些API进行通信。
- 优点:灵活性高,适用于分布式系统。
- 缺点:系统开销大,编程复杂度高。
3.10.6. 套接字(Sockets)
- 特点:用于不同主机或同一主机上的不同进程之间的通信。
- 实现:基于网络协议(如TCP/IP)的通信机制,可以实现跨网络的进程间通信。
- 优点:支持跨网络通信,灵活性高。
- 缺点:编程复杂度高,通信速度受网络环境影响。
3.10.7. 信号(Signals)
- 特点:一种用于通知进程某个事件已经发生的机制。
- 实现:操作系统通过信号机制向进程发送通知,进程可以捕获并处理这些信号。
- 优点:适用于异步事件通知,编程简单。
- 缺点:信息量有限,只能传递简单的事件通知。
3.10.8. 内存映射文件(Memory-Mapped Files)
- 特点:将文件映射到进程的地址空间,使得文件的内容可以像内存一样被访问。
- 实现:通过系统调用将文件映射到内存,多个进程可以共享这段内存。
- 优点:适合文件的共享和大数据传输,通信效率高。
- 缺点:需要考虑文件的一致性和同步问题。
3.11 介绍一下TCP和UDP?tcp粘包怎么处理?
3.11.1 TCP(传输控制协议)
特点
- 面向连接:在进行数据传输之前,必须先建立连接(三次握手),传输结束后要断开连接(四次挥手)。
- 可靠性:提供可靠的数据传输,通过确认(ACK)、重传和超时机制,确保数据准确无误地到达目的地。
- 有序性:数据按照发送的顺序接收,避免乱序。
- 流量控制:通过滑动窗口机制控制发送方发送数据的速率,防止网络拥塞。
- 拥塞控制:通过拥塞避免算法(如慢启动、拥塞避免、快速重传和快速恢复)来调整发送方的数据传输速率。
应用场景
- 需要高可靠性的数据传输,如文件传输(FTP)、电子邮件(SMTP)、远程登录(SSH、Telnet)和网页浏览(HTTP/HTTPS)等。
3.11.2 UDP(用户数据报协议)
特点
- 无连接:发送数据前无需建立连接,直接发送数据报文。
- 不可靠:不保证数据报文能准确到达目的地,不进行确认、重传和超时处理。
- 无序性:数据报文可能乱序到达,接收方按到达顺序接收。
- 无流量控制和拥塞控制。
- 简洁高效:较低的头部开销(8字节),适合对时延敏感的应用。
应用场景
- 需要快速传输且对可靠性要求不高的应用,如视频流、音频流、在线游戏、DNS查询等。
3.11.3 TCP粘包问题及处理方法
在TCP协议中,粘包是指发送端发送的多个数据包在接收端被合并成一个数据包,或者一个数据包被拆分成多个数据包的现象。粘包问题主要由以下原因引起:
- 发送方原因:发送方为了提高效率,可能会将多个小数据包合并成一个大数据包发送。
- 接收方原因:接收方读取数据时,由于缓冲区大小的限制,可能会一次读取多个数据包或一个数据包的一部分。
处理方法
- 固定长度分割:预先规定每个数据包的固定长度,接收方按固定长度读取数据。
- 特殊分隔符:在数据包之间添加特殊分隔符(如换行符、null字符等),接收方根据分隔符区分数据包。
- 长度字段:在每个数据包的包头加入长度字段,标示数据包的长度,接收方根据长度字段读取完整的数据包。
- 应用层协议:设计应用层协议,规定数据包的格式和解析方式,通过应用层代码处理粘包问题。
3.12 介绍一下TCP字节流和UDP的报文?
3.12.1 TCP字节流
- 字节流:TCP是一种面向字节流的协议,数据被视为连续的字节流而不是离散的消息。发送方发送的字节流可以被接收方按需读取。
- 无边界:TCP不保留发送时的数据边界,应用层需要自己定义消息的边界和解析方式。
- 组装和拆分:由于操作系统的TCP/IP栈会根据网络状况动态调整传输的数据块大小,发送方的数据可能被拆分成多个TCP段发送,接收方接收到的数据可能是多个段的一个或多个部分。
3.12.2 UDP报文
- 报文:UDP是一种面向报文的协议,每次发送和接收的数据都是一个完整的报文。
- 有边界:UDP保留应用层数据报的边界,每个数据报是独立的单位,接收方按报文方式处理。
- 简单快速:UDP报文没有TCP的复杂机制,适合简单快速的通信场景。
3.13 线程如何同步?
线程同步是指在多线程环境中,通过某种机制确保多个线程按照一定的顺序和规则访问共享资源,以避免数据不一致、竞争条件和死锁等问题。
3.13.1. 互斥锁(Mutex)
- 概念:互斥锁是一种最简单的同步机制,用于保护共享资源,确保同一时间只有一个线程可以访问该资源。
- 实现:通过加锁(lock)和解锁(unlock)操作来控制对共享资源的访问。
- 优点:简单易用,适用于大多数同步场景。
- 缺点:可能导致死锁,需要谨慎使用。
3.13.2. 信号量(Semaphore)
- 概念:信号量是一种计数器,用于控制多个线程对共享资源的访问。信号量可以用于限制同时访问某个资源的线程数量。
- 实现:通过P操作(等待)和V操作(释放)来控制信号量的值。
- 优点:可以控制并发访问的数量,适用于资源池等场景。
- 缺点:编程复杂度较高,容易出错。
3.13.3. 条件变量(Condition Variable)
- 概念:条件变量用于线程间的通信,允许线程在某个条件满足时被唤醒。
- 实现:通常与互斥锁一起使用,通过等待(wait)和通知(notify)操作来实现线程间的同步。
- 优点:适用于复杂的线程同步场景,如生产者-消费者问题。
- 缺点:编程复杂度较高,需要正确处理条件和锁的关系。
3.13.4. 读写锁(Read-Write Lock)
- 概念:读写锁允许多个线程同时读取共享资源,但写操作时只允许一个线程访问。
- 实现:通过读锁(read lock)和写锁(write lock)来控制对共享资源的访问。
- 优点:适用于读多写少的场景,可以提高并发性能。
- 缺点:编程复杂度较高,需要正确处理读写锁的关系。
3.14 什么是死锁?如何避免?
死锁(Deadlock)是指在并发系统中,两个或多个进程或线程相互等待对方释放资源,导致所有进程或线程都无法继续执行的情况。死锁会导致程序停止运行,资源无法被释放,从而严重影响系统性能和可靠性。
3.14.1死锁的四个必要条件(Coffman条件)
互斥条件(Mutual Exclusion):资源不能同时被多个进程使用,每个资源要么空闲要么被一个进程独占。
持有并等待条件(Hold and Wait):一个进程已经持有至少一个资源,但又在等待额外的资源,同时这些资源被其他进程占用。
不剥夺条件(No Preemption):资源不能被强制从进程中剥夺,资源只能由持有它的进程主动释放。
循环等待条件(Circular Wait):存在一个进程链,链中的每个进程都在等待下一个进程持有的资源,形成一个循环。
3.14.2 避免死锁
1. 破坏互斥条件
- 共享资源:尽量减少独占资源的使用,将资源设计为可共享的。
2. 破坏持有并等待条件
- 资源预分配:在进程开始时一次性分配所有所需资源,如果无法满足则让进程等待,这样就不会持有部分资源并等待其他资源。
- 资源请求顺序:规定进程在请求资源时必须一次请求所有需要的资源,而不是部分持有后再请求。
3. 破坏不剥夺条件
- 资源剥夺:允许系统在某些条件下剥夺进程持有的资源,如当一个进程请求资源失败时,将其已经持有的资源释放,重新排队等待所有资源。
4. 破坏循环等待条件
- 资源有序分配:为所有资源编号,规定进程必须按顺序请求资源,即若进程持有编号为n的资源,则只能请求编号大于n的资源。这样可以防止形成循环等待。
四、8.22一面问到的问题
面经素材来源于牛客网:
4.1 SPI通信有哪些内核接口
-
将一个SPI驱动注册到内核中。spi_register_driver(struct spi_driver *drv)
-
从内核中注销一个SPI驱动。spi_unregister_driver(struct spi_driver *drv)
-
通过总线号查找对应的SPI主控设备。spi_busnum_to_master(int busnum)
-
分配并初始化一个spi_alloc_master(struct device *host, unsigned size)
spi_master
结构体。 -
注册一个SPI主控设备到内核中。spi_register_master(struct spi_master *master)
-
分配并初始化一个spi_alloc_device(struct spi_master *master)
spi_device
结构体。 -
将一个SPI设备添加到主控设备上。spi_add_device(struct spi_device *spi)
-
同步传输一个SPI消息。spi_sync(struct spi_device *spi, struct spi_message *message)
-
异步传输一个SPI消息。spi_async(struct spi_device *spi, struct spi_message *message)
-
向SPI设备写数据。spi_write(struct spi_device *spi, const void *buf, size_t len)
-
从SPI设备读数据。spi_read(struct spi_device *spi, void *buf, size_t len)
-
向SPI设备发送一个8位命令并读取一个8位响应。spi_w8r8(struct spi_device *spi, u8 cmd)
-
向SPI设备发送一个8位命令并读取一个16位响应。spi_w8r16(struct spi_device *spi, u8 cmd)
4.2 应用层和驱动是怎么交互的?
4.2.1 通过设备文件(Device Files)
设备文件是操作系统提供的一种抽象机制,它允许应用程序像读写普通文件一样对设备进行操作。设备文件通常位于/dev
目录下。交互过程如下:
打开设备文件:应用程序使用系统调用open()
来打开设备文件,获取文件描述符。
读写设备文件:应用程序使用read()
和write()
系统调用从设备文件读取数据或向设备文件写入数据。
控制设备:应用程序使用ioctl()
系统调用向设备驱动发送控制命令。
关闭设备文件:操作完成后,应用程序使用close()
系统调用关闭设备文件。
4.2.2 通过系统调用(System Calls)
驱动程序可以向操作系统注册特定的系统调用,应用程序通过这些系统调用与驱动进行交互。常用的系统调用包括:
open()
:打开设备文件。read()
:读取设备文件。write()
:写入设备文件。ioctl()
:发送设备控制命令。close()
:关闭设备文件。
驱动程序需要实现相应的文件操作函数(如open()
, read()
, write()
, ioctl()
, release()
等),并将这些函数注册到内核中。
4.2.3 通过内存映射(Memory Mapping)
某些设备可以通过内存映射(mmap()
)机制来访问。这种方式允许应用程序直接访问设备的内存空间,提高访问效率。
1. 内存映射设备文件:应用程序使用mmap()
将设备文件映射到进程的地址空间。
2. 访问设备内存:应用程序可以直接操作映射的内存区域。
3. 取消内存映射:操作完成后,应用程序使用munmap()
取消内存映射。
4.3 stm32和imx6u有什么区别
4.3.1. 制造商
- STM32:由意法半导体(STMicroelectronics)生产,是一系列基于ARM Cortex内核的微控制器(MCU)。
- i.MX 6:由恩智浦半导体(NXP Semiconductors)生产,是一系列基于ARM Cortex内核的应用处理器(AP)。
4.3.2 架构和内核
- STM32:基于ARM Cortex-M系列内核(如Cortex-M0, Cortex-M3, Cortex-M4, Cortex-M7等),适用于低功耗、高效率的嵌入式应用。
- i.MX 6:基于ARM Cortex-A系列内核(如Cortex-A9),适用于高性能、多任务处理的复杂应用,如多媒体处理、工业控制、汽车电子等。
4.3.3. 性能
- STM32:通常具有较低的主频(几十MHz到几百MHz),适用于实时控制和低功耗应用。
- i.MX 6:具有较高的主频(几百MHz到GHz级别),适用于需要较高计算能力和图形处理能力的应用。
4.3.4. 内存和存储
- STM32:通常具有较小的片上内存(几十KB到几MB的Flash和RAM),适用于简单的嵌入式系统。
- i.MX 6:具有较大的片上内存和外部存储接口(几MB到几十MB的RAM,以及支持SDRAM、eMMC等外部存储),适用于复杂的应用系统。
4.3.5. 外设和接口
- STM32:提供丰富的外设接口,如GPIO、UART、SPI、I2C、ADC、DAC、定时器等,适用于各种传感器和执行器的控制。
- i.MX 6:提供更高级的外设接口,如USB、Ethernet、HDMI、LCD控制器、GPU等,适用于多媒体和网络通信应用。
4.4 通信实时性是通过什么手段去保证的?
4.4.1. 硬件中断
- 中断优先级:通过设置不同的中断优先级,确保高优先级的中断能够及时得到响应。
- 快速中断处理:设计中断服务程序(ISR)时,尽量减少处理时间,可以在中断中只进行必要的处理,将复杂的处理放到任务或线程中。
4.4.2. 实时操作系统(RTOS)
- 任务调度:使用RTOS(如FreeRTOS、RTEMS、VxWorks等),它们提供优先级调度算法,确保高优先级任务得到及时响应。
- 定时器和延迟管理:RTOS提供精确的定时器和延迟管理,帮助实现周期性任务和时间敏感的操作。
- 互斥和同步机制:使用RTOS的互斥锁、信号量和消息队列等机制,确保任务间的同步和资源的有序访问,避免竞争条件。
4.4.3. 优化通信协议
- 轻量级协议:选择轻量级协议(如CAN、UART等)来减少协议开销,提高通信速度和实时性。
- 协议优化:对现有通信协议进行优化,例如减少数据包头部信息,使用固定长度的数据包等。
4.4.4. 硬件加速
- DMA(Direct Memory Access):使用DMA控制器进行数据传输,减少CPU的负担,提高数据传输效率和实时性。
- 专用通信处理器:使用专门的通信处理器或协处理器,处理复杂的通信任务,减轻主处理器的负担。
4.4.5. 网络优化
- 优先级队列:在网络交换机或路由器中使用优先级队列,确保高优先级的数据包优先传输。
- 带宽管理:配置网络带宽,预留一定的带宽给实时通信应用。
- QoS(Quality of Service):采用QoS技术,保证重要数据包在网络传输中的优先级。
4.4.6. 时间同步
- NTP/PTP:使用网络时间协议(NTP)或精确时间协议(PTP),确保分布式系统中的时钟同步,保证协同操作的实时性。
- 硬件时钟同步:在分布式系统中使用专用的硬件时钟同步机制,确保各节点的时钟保持一致。
4.5 线程的同步机制
线程同步是一种在多线程编程中用于协调线程之间对共享资源的访问和操作的技术。无论是在单核还是多核处理器上,多线程程序都可能面临竞争条件、死锁和资源争用等问题。
4.5.1. 互斥锁(Mutex)
互斥锁用于确保一次只有一个线程能够访问共享资源。
- 特点:简单易用,但可能导致死锁和优先级反转问题。
- 实例:在C语言中使用
pthread_mutex_t
,在C++中使用std::mutex
4.5.2. 自旋锁(Spinlock)
自旋锁在等待锁可用时会不断轮询检查,而不是睡眠。
- 特点:适用于锁的持有时间较短的情况,避免了线程上下文切换的开销,但会消耗CPU资源。
- 实例:在Linux内核中常用的自旋锁,在C++中可以使用
std::atomic_flag
实现简单的自旋锁。
4.5.3. 读写锁(Read-Write Lock)
读写锁允许多个线程同时读取共享资源,但写操作需要独占锁。
- 特点:提高了读操作的并发性,适用于读多写少的情况。
- 实例:在C语言中使用
pthread_rwlock_t
,在C++中使用std::shared_mutex
。
4.5.4. 条件变量(Condition Variable)
条件变量用于在线程之间实现复杂的同步机制,如等待某个条件成立。
- 特点:需要与互斥锁一起使用,适用于需要线程等待某个条件的场景。
- 实例:在C语言中使用
pthread_cond_t
,在C++中使用std::condition_variable
。
4.5.5. 信号量(Semaphore)
信号量用于控制对资源的访问,允许多个线程同时访问有限的资源。
- 特点:可以指定资源的数量,适用于需要控制并发访问数量的场景。
- 实例:在C语言中使用
sem_t
,在C++中可以使用std::counting_semaphore
(C++20)。
4.6 Makefile最终是使用什么把可执行文件编译出来的?
Makefile 是使用 make
工具来自动化构建过程的配置文件。最终将源代码编译成可执行文件,是通过在 Makefile 中定义的编译器(如 gcc
、g++
)和链接命令实现的。make
工具根据 Makefile 中定义的规则和依赖关系,执行相应的编译和链接命令来生成目标文件和可执行文件。
以下是一个简单的 Makefile 示例,展示了如何使用 gcc
编译 C 程序:
# Makefile 示例
# 指定编译器
CC = gcc
# 编译器选项
CFLAGS = -Wall -g
# 目标文件
TARGET = myprogram
# 源文件
SRCS = main.c utils.c
# 生成的对象文件,将 .c 替换为 .o
OBJS = $(SRCS:.c=.o)
# 默认目标
all: $(TARGET)
# 链接目标文件
$(TARGET): $(OBJS)
$(CC) $(CFLAGS) -o $@ $(OBJS)
# 编译 .c 文件为 .o 文件
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
# 清理生成的文件
clean:
rm -f $(TARGET) $(OBJS)
CC
变量指定了使用gcc
作为编译器。CFLAGS
变量指定了编译器选项,如-Wall
用于启用所有警告,-g
用于生成调试信息。TARGET
变量指定了生成的可执行文件名。SRCS
变量列出了所有源文件。OBJS
变量根据源文件列表生成对应的目标文件列表。all
目标是默认目标,它依赖于可执行文件。- 可执行文件目标依赖于所有对象文件,并使用
$(CC)
和$(CFLAGS)
进行链接生成可执行文件。%.o: %.c
规则定义了如何将.c
文件编译为.o
文件,使用$(CC)
和$(CFLAGS)
进行编译。clean
目标用于删除生成的可执行文件和对象文件。
4.7 用什么命令把c文件生成可执行文件
要将 .c
文件编译成可执行文件,可以使用 C 编译器(如 gcc
)来完成。
如果只有一个源文件 main.c
,可以直接使用以下命令:
gcc -o myprogram main.c
gcc
是 GNU 编译器集合中的 C 编译器。-o myprogram
指定输出文件名为myprogram
。main.c
是要编译的源文件。
如果有多个源文件,例如 main.c
和 utils.c
,可以使用以下命令:
gcc -o myprogram main.c utils.c
为了启用警告信息和调试信息,通常会添加一些编译选项,例如:
gcc -Wall -g -o myprogram main.c utils.c
-Wall
启用所有警告信息。-g
生成调试信息,便于调试程序。
4.8 Makefile添加依赖库怎么操作?
在 Makefile 中,链接库时通常使用 -l
选项指定库名,使用 -L
选项指定库路径。-I
选项用于指定头文件路径。
-l<library>
:链接名为library
的库文件。-L<path>
:指定库文件所在的目录。-I<path>
:指定头文件所在的目录。
qmake如何添加库,如何添加要编译的文件?
在使用 qmake
工具生成 Qt 项目的构建文件时,可以通过编辑项目文件(通常是 .pro
文件)来添加库和要编译的源文件。
添加库
要在 qmake
中添加库,可以使用 LIBS
变量。这里有几个例子说明如何添加不同类型的库:
要添加要编译的源文件,可以使用 SOURCES
和 HEADERS
变量。
添加单个源文件和头文件:
SOURCES += main.cpp \
myclass.cpp
HEADERS += myclass.h
添加多个源文件和头文件: 你可以在一个语句中添加多个文件,也可以使用通配符:
SOURCES += main.cpp \
myclass.cpp \
anotherclass.cpp
HEADERS += myclass.h \
anotherclass.h
# 项目名称
TEMPLATE = app
TARGET = MyApp
# 编译选项
CONFIG += c++11
CONFIG += qt
# 添加库和头文件路径
LIBS += -L/path/to/library -lmylib
INCLUDEPATH += /path/to/library/includes
# 添加源文件和头文件
SOURCES += main.cpp \
src/myclass.cpp
HEADERS += src/myclass.h
# 添加Qt模块(例如:核心、GUI)
QT += core gui
这个 .pro
文件描述了一个包含源文件和头文件的简单 Qt 应用程序,并链接到一个外部库 mylib
。通过编辑 .pro
文件,你可以轻松地管理项目的依赖项和源代码文件。
4.9 编译一个hello.c具体怎么写Makefile
首先,确保你的 hello.c
文件
在项目根目录 (/path/to/project/
) 创建一个名为 Makefile
的文件
# Makefile for compiling hello.c
# 指定编译器
CC = gcc
# 编译器选项
CFLAGS = -Wall -g
# 目标可执行文件的名称
TARGET = hello
# 源文件
SRCS = hello.c
# 由源文件生成的对象文件
OBJS = $(SRCS:.c=.o)
# 默认目标
all: $(TARGET)
# 链接对象文件生成可执行文件
$(TARGET): $(OBJS)
$(CC) $(CFLAGS) -o $@ $(OBJS)
# 编译源文件生成对象文件
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
# 清理编译生成的文件
clean:
rm -f $(TARGET) $(OBJS)
# 伪目标,表示这些目标不对应于实际文件
.PHONY: all clean
CC = gcc
: 指定使用 GCC 作为编译器。CFLAGS = -Wall -g
: 编译器选项,-Wall
用于启用所有警告,-g
用于生成调试信息。TARGET = hello
: 生成的可执行文件名。SRCS = hello.c
: 列出所有源文件。OBJS = $(SRCS:.c=.o)
: 将.c
扩展名替换为.o
扩展名,生成目标文件列表。all: $(TARGET)
: 默认目标,依赖于可执行文件。$(TARGET): $(OBJS)
: 链接目标文件生成可执行文件,使用$(CC)
和$(CFLAGS)
。%.o: %.c
: 规则定义如何将.c
文件编译为.o
文件,使用$(CC)
和$(CFLAGS)
。clean
: 清理生成的文件。.PHONY
: 指定这些目标是伪目标,不对应于实际文件。
9.10 make的时候执行那一条命令是怎么找的。冒号后面写指令有什么要求和限制?
make
工具通过解析 Makefile
来决定执行哪条命令。
Makefile
中的每个规则由目标、依赖关系和命令组成。
4.10.1 目标和依赖关系
Makefile
中的每个规则由目标、依赖关系和命令组成。基本格式如下:
target: dependencies
command
target
是生成的文件或目标(可以是中间文件或最终的可执行文件)。dependencies
是生成目标所依赖的文件或其他目标。command
是生成目标所需执行的命令。
4.10.2 默认目标
make
默认会执行 Makefile
中第一个出现的目标。如果你运行 make
而没有指定目标,它将试图构建第一个目标。
4.10.3 目标和依赖关系查找过程
当你运行 make
时,make
会:
- 查找文件
Makefile
(或者makefile
)。 - 寻找第一个目标,尝试构建它。
- 检查目标的依赖文件:
- 如果依赖文件是其他目标,递归地构建这些依赖。
- 如果依赖文件是现有的文件,检查它们的时间戳。
- 如果目标的依赖文件比目标更新,那么目标需要重建。
- 按顺序执行与目标相关联的命令。
4.10.4 规则中的命令要求
- 每个命令前必须有一个硬制表符(Tab)字符,而不是空格。
- 通常每个命令都在独立的 shell 中执行。
- 可以使用
@
前缀来抑制命令的回显。 - 可以使用
-
前缀来忽略命令执行中的错误。
4.10.5 示例命令解释
$(CC) $(CFLAGS) -o $@ $(OBJS)
:$@
是目标的名称(在这例中是hello
)。$(OBJS)
是目标的依赖项(在这例中是hello.o
)。
$(CC) $(CFLAGS) -c $< -o $@
:$<
是第一个依赖项(在这例中是hello.c
)。$@
是目标的名称(在这例中是hello.o
)。
4.10.6 示例:执行 make
时的具体步骤
- 运行
make
。 - 查找
Makefile
,找到第一个目标all
。 all
依赖于hello
,需要构建hello
。hello
依赖于hello.o
,需要构建hello.o
。- 使用
$(CC) $(CFLAGS) -c hello.c -o hello.o
命令编译hello.c
生成hello.o
。 - 使用
$(CC) $(CFLAGS) -o hello hello.o
命令链接生成hello
可执行文件。
4.10.7 特殊变量
在命令中使用一些特殊变量,可以简化规则:
$@
: 目标的完整名称。$<
: 第一个依赖文件的名称。$^
: 所有依赖文件的列表。$?
: 所有比目标更新的依赖文件的列表。
4.11 把gpio当成按键,按下的时候上报应用层,具体驱动怎么去写?
在Linux内核中,将GPIO(通用输入输出端口)用作按键并上报应用层,通常涉及编写一个内核驱动程序。这个驱动程序需要配置GPIO为输入模式,检测按键状态变化,并通过内核提供的输入子系统将按键事件上报给用户空间。
- 配置GPIO为输入模式:使用
gpio_request
和gpio_direction_input
函数。 - 设置中断(可选):如果按键状态变化需要通过中断来检测,使用
request_irq
函数。 - 初始化输入设备:使用
input_dev
结构体和相关函数。 - 上报按键事件:使用
input_report_key
和input_sync
函数。 - 注册输入设备:使用
input_register_device
函数。 - 清理和释放资源:在模块卸载时,释放GPIO和中断,并注销输入设备。
4.12 怎么把gpio注册成中断?
- 请求GPIO并配置为输入模式。
- 将GPIO映射到中断号。
- 请求中断。
- 定义中断处理函数。
4.13 按键gpio需要把中断配置成什么样的,怎么触发,怎么捕获状态,怎么实现长按?
- 请求并配置GPIO为输入模式。
- 将GPIO映射到中断号。
- 请求中断,并设置触发方式为双边沿触发(上升沿和下降沿)。
- 在中断处理函数中读取GPIO状态。
- 在按键按下时记录时间。
- 在按键释放时计算时间差,并判断是否为长按。
4.14 应用层不用read怎么拿到驱动层的数据,通知或触发形式?
4.14.1 使用poll
/select
机制
poll
和select
机制允许应用程序等待文件描述符变为可读、可写或有异常发生。当驱动层有数据或者有事件发生时,可以通过这些系统调用来通知应用层。
-
驱动层:
使用wake_up_interruptible
函数唤醒等待队列。在poll
函数中实现文件的等待队列。 -
应用层:
使用poll
或select
系统调用来等待驱动层的数据或事件。
4.14.2 使用ioctl
接口
ioctl
接口可以用于应用层和驱动层之间的控制和数据交换。驱动层可以在ioctl
实现中处理应用层的请求,并返回数据。
-
驱动层:
实现ioctl
函数,用于处理应用层的特定请求。在ioctl
函数中可以返回驱动层的数据。 -
应用层:
使用ioctl
系统调用发送请求并获取数据。
4.14.3 使用sysfs
接口
sysfs
提供了一种将内核对象的属性导出到用户空间的机制。通过在sysfs
中创建文件,可以方便地实现驱动层和应用层之间的数据交换。
-
驱动层:
在sysfs
中创建属性文件。在属性文件的读写函数中处理数据交换。 -
应用层:
通过读写/sys
目录下的属性文件进行数据交换。
4.14.4 使用relay
子系统
relay
子系统适用于高性能、大量数据传输的场景。驱动层可以使用relay
来将数据传输到应用层。
-
驱动层:
使用relay
接口创建通道,并写入数据。 -
应用层:
通过relay
接口读取数据。
4.15 谈谈linux用户态和内核态
用户态是普通应用程序(如文本编辑器、浏览器等)运行的空间。用户态程序运行时受限于操作系统的各种安全和资源管理机制,无法直接访问硬件或操作系统的核心数据结构。这种设计提供了安全性和稳定性,因为用户态程序的错误不会直接影响到整个系统。
内核态是操作系统内核运行的空间。内核拥有系统的最高权限,能够直接访问硬件资源和管理系统的所有数据结构。内核态运行的代码包括操作系统的核心部分、设备驱动程序、系统服务和内核模块等。
用户态和内核态的转换
系统调用:用户态程序通过调用系统提供的系统调用接口来请求内核服务。例如,文件操作、进程管理、网络通信等操作都需要通过系统调用来实现。典型系统调用:open()
, read()
, write()
, fork()
, exec()
等。
中断:硬件设备(如时钟、硬盘、网络接口等)通过中断信号通知内核处理事件。中断处理程序运行在内核态,处理完中断后返回用户态。例如,键盘输入、中断时钟信号等都会引起中断。
异常:异常是指程序运行过程中发生的错误或特殊条件(如除零错误、非法内存访问等)。异常处理程序在内核态运行,处理该异常并决定如何恢复或终止程序。