八股第二期

volatile关键字的作用是什么?在什么情况下需要使用?

作用:防止编译器对变量进行优化,使用该变量需要从内存中获取而不是使用再寄存器中的备份;

使用情况:

指向设备寄存器的指针和映射的硬件寄存器通常加volatile;

一个中断服务程序中的变量

多线程应用中被几个任务共享的临界变量

一个参数可以同时是const和volatile吗?为什么?

这两个关键字修饰变量的作用不同,且不冲突,常量const关键字表示该变量的值不能被程序改变,而volatile是编译器不对其进行优化;并不冲突

一个指针可以是volatile吗?请解释。

当一个指针被修饰为volatile时,表示指针本身的值(即指针指向的内存地址)可能会被外部因素改变。

除了修饰指针本身,volatile还可以修饰指针所指向的数据,这意味着指针指向的数据可能在程序之外被改变。每次访问该数据时,都应从内存中读取最新的值。

临界区和临界资源的定义是什么?

临界区是指在多线程或多进程环境中,多个线程或进程访问共享资源的代码区域。

需要适当使用同步互斥机制来防止数据竞态,保证操作的原子性

临界资源是指在多线程或多进程环境中,所有线程或进程需要访问的共享资源。

临界资源通常包括:

  • 共享数据结构:如全局变量、静态变量、共享内存等。
  • 硬件资源:如设备寄存器、文件句柄、网络连接等。

需要适当使用同步互斥机制来确保线程安全;

什么是原子操作?原子性如何影响临界资源的保护?

原子操作是指一个不可分割的操作,中间没有任何其他操作能够干扰。原子操作的关键特性是:

  • 不可中断:在操作执行过程中,任何其他线程或进程都不能观察到操作的中间完成状态。
  • 不可分割:操作在执行时,系统会保证不被中断或分割。

典型的原子操作:简单变量的读取和写入,特殊的原子指令,原子操作库函数

原子性对临界资源保护的具体影响:

**避免竞态条件:**通过使用原子操作,可以确保对临界资源的访问是安全的,不会因为中断或其他线程的干扰导致数据不一致。

**简化同步机制:**有些操作如果是原子的,就不需要复杂的同步机制(如互斥锁、信号量)来保护。例如,简单的计数器递增操作如果是原子的,就不需要使用互斥锁。

**提高性能:**使用原子操作可以减少上下文切换和锁竞争,提高系统的整体性能和响应速度。

如何通过关中断来保护临界资源?

在进入临界区之前,禁用所有中断。这确保了在临界区内的代码不会被中断打断;执行对共享资源的操作,因为中断被禁用,所以不会有其他中断或任务干扰;在离开临界区之后,重新启用中断,以允许正常的中断处理。

如果是在嵌套中断中,需要保存当前中断状态,结束后再恢复之前的中断状态

什么是空任务控制块链表?它与任务控制块链表有什么区别?

任务控制块(TCB)链表是用于管理系统中所有任务的链表。每个任务都有一个任务控制块(TCB),它包含有关任务的各种信息;

空任务控制块链表是一个特殊的链表,用于管理系统中空闲或待分配的任务控制块。

区别:

  • 任务控制块链表:用于管理和调度系统中的所有任务,包含任务的状态、优先级等信息。
  • 空任务控制块链表:用于管理空闲的(未使用的)TCB,优化TCB的分配和释放。
  • 任务控制块链表:每个 TCB 包含任务的所有信息,如任务状态、栈指针等。
  • 空任务控制块链表:每个空 TCB 仅包含一个链表指针,用于组织空闲 TCB。
  • 任务控制块链表:用于任务调度、切换和管理,涉及任务的创建、销毁、优先级调整等操作。
  • 空任务控制块链表:用于高效管理未使用的 TCB,涉及 TCB 的分配和回收。

FreeRTOS的任务栈是如何设定的?参考依据是什么?

在创建任务时,必须指定任务的栈大小。栈大小通常以字(words)为单位

在调用 xTaskCreatexTaskCreateStatic 函数时,需要传入栈大小参数。

以Cortex-M4内核为例,在未使用FPU(浮点运算单元),将16个通用寄存器全部入栈,每个寄存器占用4个字节,一共是64字节,然后根据中断嵌套的次数乘以对应的值;

如果是使用FPU,需要额外将34个浮点寄存器入栈,应该是200字节,然后再根据中断嵌套层数来计算。

FreeRTOS移植到哪些平台,移植过程占用哪些硬件资源?

可以移植到多种平台,单片机、嵌入式处理器、微处理器等等

移植过程:

  • 选择对应目标处理器架构的FreeRTOS版本。
  • 安装相应的工具链。
  • 对FreeRTOS进行配置。
  • 实现FreeRTOS底层函数。
  • 搭建FreeRTOS应用程序,实现任务调度。

移植过程:

内存管理:需要为FreeRTOS分配一定的内存空间。

任务管理:需要配置任务的堆栈大小、优先级等。

时钟和定时器:需要配置FreeRTOS使用哪个时钟源和定时器。

信号量和队列:需要配置信号量和队列的大小和类型。

调度器配置:需要选择FreeRTOS的调度器类型和优化设置。

关键字static的作用是什么?

static是被声明为静态类型的变量,存储在静态区中,其生命周期为整个程序;如果是静态局部变量,其作用域为一对{}内;如果是静态全局变量,其作用域为当前文件静态变量;static修饰的全局变量,只能在本文件被调用;修饰的函数也只能在本文件调用

为什么 static变量只初始化一次?

对于所有的对象(不仅仅是静态对象),初始化都只有一次,而由于静态变量具有“记忆”功能,初始化后,一直都没有被销毁,都会保存在内存区域中,所以不会再次初始化存放在静态区的变量。

const和宏定义的区别?

执行的过程不一样:

宏-预编译 const编译

宏不会检查代码错误,只是替换,但是const会编译报错。

宏的好处:定义代码或字符串、方法、参数 const不能。 坏处:使用大量宏,容易造成编译时间久,每次都需要重新替换

const的用法:

  1. int *const p ; p为只读,*p为变量。
  2. const int *p ; *p为只读,p为变量。
  3. int const * const p ;*p、p都为只读。
  4. const int * const p ;*p、p都为只读。

引用和指针有什么区别?

指针和引用都是地址的概念,指针指向一块内存,它的内容是所指内存的地址;引用是某块内存的别名。

程序为指针变量分配内存区域,而不为引用分配内存区域

指针使用时要在前加 * ,引用可以直接使用

引用在定义时就被初始化,之后无法改变;指针可以发生改变。 即引用的对象不能改变,指针的对象可以改变

理论上指针的级数没有限制,但引用只有一级

++引用与++指针的效果不一样。

例如就++操作而言,对引用的操作直接反应到所指向的对象,而不是改变指向;而对指针的操作,会使指针指向下一个对象,而不是改变所指对象的内容

h头文件中的ifndef/define/endif 的作用

#define是C语言中定义的语法,它是预处理指令,在预处理时进行简单而机械的字符串替换

** #ifndef _ #define _是条件编译**:

一般放在头文件里面,作用就是以防你在.c文件里面不小心重复包含头文件的时候不会报错

底层:通过判断这个字符串是否被定义过,从而决定是否跳过某些语句来达到,条件编译,防止报错的效果的

全局变量和局部变量的区别?

全局变量定义在函数外,局部变量定义在函数内,两这作用域不同,生命周期也不同;

全局变量存储在全局区,局部变量存储在栈区;

C语言内存分区:

地址由低到高分别是栈区、堆区、全局区、常量区和代码区:

代码区:存放程序的代码,即CPU执行的机器指令,并且是只读的;常量区:存放常量;

静态区(全局区):静态变量和全局变量的存储区域是一起的,一旦静态区的内存被分配, 静态区的内存直到程序全部结束之后才会被释放;

堆区:由程序员调用malloc()函数来主动申请的,需使用free()函数来释放内存,若申请了堆区内存,之后忘记释放内存,很容易造成内存泄漏

栈区:存放函数内的局部变量,形参和函数返回值。栈区之中的数据的作用范围过了之后,系统就会回收自动管理栈区的内存(分配内存 , 回收内存),不需要开发人员来手动管理

为什么要进行内存对齐?

在C++中规定了空结构体和空类的内存所占小为1字节,因为c++中规定,任何不同的对象不能拥有相同的内存地址。

而在C语言中,空的结构体在内存中所占大小为0。(gcc中测试为0,其他编译器不一定)

1.平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。

2.性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。

ELF文件的作用?

更新完程序后,根据DBC生成的A2L文件的信号地址会发生改动,这需要从ELF文件里获取新版软件的地址来更新A2l中信号的地址。

A2L 文件包括:设备参数信息、接口数据信息和Mcu参数信息。

优先级反转?

优先级反转,是指在使用信号量时,当高优先级任务正等待信号量,一个介于两个任务优先之间的中等优先级任务开始执行,它导致一个高优先级任务在等待一个低优先级任务,而低优先级任务却无法执行的情况。

解决方法:优先级继承:让低优先级线程在获得同步资源的时候,临时提升其优先级,释放同步资源后再恢复其原来的优先级。

DMA介绍

DMA直接存储器存取/访问

可以提供外设和存储器之间,或存储器和存储器之间直接的高速数据传输,原理上DMA执行存储器到存储器的数据转运,就是把一个地址的数据取出,送到另一个地址去,而DMA的存在使得这个传输过程无需CPU干预,节省了CPU资源。

编写一个SPI设备驱动程序有哪些步骤

编写设备树

在makefile中添加对应的驱动文件配置和驱动文件,构造DTS节点

编写、并向内核注册spi_driver

虚函数和纯虚函数的区别

虚函数 (Virtual Function): 虚函数在基类中声明,并可以在派生类中被重写(或称为覆盖)。虚函数在基类中具有默认的实现,但该实现可以在派生类中被覆盖。

纯虚函数 (Pure Virtual Function): 纯虚函数在基类中声明,但没有在基类中实现。它必须在派生类中被重写和实现。

C struct、C++ struct的区别

C语言中:

Struct是用户自定义数据类型

C++语言中:

Struct是抽象数据类型,支持成员函数的定义。

其次是C++的struct可以定义访问权限。

C++ struct和class的区别

struct中的成员默认是public的,class中的默认是private的。

然后其实在c++中如果没有多态和虚函数继承,struct和class的存取效率完全相同,如果不需要兼容c,c++代码中尽量使用class。

TCP的三次握手和四次挥手

第一次握手:客户端先向服务器发送连接请求报文

第二次握手:服务器收到请求报文后,同意连接会发出确认报文

第三次握手:客户端收到确认报文后,需要像服务给出确认,此时TCO连接成功

第一次挥手:客户端发出连接释放报文,并且停止发送数据

第二次挥手:服务器端接收到连接释放报文后,发出确认报文

第三次挥手:客户端接收到服务器端的确认请求后,等待服务器发送连接释放报文,服务器将最后的数据发送完毕后,就向客户端发送连接释放报文。

第四次挥手:客户端收到服务器的连接释放报文后,发出确认,当客户端撤销对应的TCB后,TCP连接彻底断开;

RS232和RS485

232:

对输出:输出“1”时的电平应在-5~-15 V之间,输出“0”时的电平应在+5~+15 V之间对输入:输入电平在-3~-15 V之间被认为“1”,在+3~+15 V之间被认为“0”当线路上不传送数据(空闲)时,发送器输出为“1”

485:

对输出:逻辑"1"以两线间的电压差为+(2 至6)V 表示;逻辑"0"以两线间的电压差为-(2 至6)V 表示。对输入:A比B高200mV以上即认为是逻辑"1",A 比B 低200mV 以上即认为是逻辑"0"。485相对于232而言:最高传输速率高(但传输速率越高传输距离越短);采用差分法来传输信号,对共模干扰具有更强的抗干扰力;RS485允许连接128个收发器,具有多机通讯能力。

485相对于232而言:最高传输速率高(但传输速率越高传输距离越短);采用差分法来传输信号,对共模干扰具有更强的抗干扰力;RS485允许连接128个收发器,具有多机通讯能力。

FreeRTOS的中断管理:

中断的作用:

FreeRTOS 提供了一些用于在中断处理中使用的函数,以确保在中断上下文中正确使用实时操作系统。

ARM Cortex-M的中断处理机制

1.中断向量表(Interrupt Vector Table,IVT)

中断向量表是一个包含中断处理程序地址的表格,每个中断对应表中的一个入口

中断号通过硬件自动映射到中断向量表的相应位置

2.中断处理过程

当一个中断发生时,处理器会根据中断号查找 IVT 中相应位置的中断处理程序的地址;

处理器保存当前上下文,包括寄存器值和状态寄存器等,将控制权转移到中断服务例程;

中断服务例程执行完毕后,处理器会恢复之前保存的上下文,并返回到中断发生前的状态;

3.NVIC

  • Cortex-M 处理器通过 NVIC 管理中断优先级。
  • 可以设置每个中断的优先级,并通过 NVIC 控制中断的使能和屏蔽。

Bootloader

分为boot和loader

boot:

为了跳到C语言中;为了C语言运行程序会进行一系列的初始化;

loader:

开始执行应用逻辑,加载系统内核;

BootLoader就是单片机启动时候运行的一段小程序,这段程序负责单片机固件的更新,可以实现单片机选择性的自己下程序。可以更新,也可以不更新,更新的话,BootLoader更新完程序后,跳转到新程序运行;不更新的话,BootLoader直接跳转到原来的程序去运行

BootLoader更新完程序后并不擦除自己,下次启动后依然先运行BootLoader程序,又可以选择性的更新或者不更新程序,所以BootLoader就是用来管理单片机程序的更新

在实际的单片机工程项目中,如果加入了BootLoader功能,就可以给单片机日后升级程序留出一个接口,方便日后单片机程序更新,需要两个工程一个是app工程一个是bootloader工程

BootLoader工程生成的.hex通常下载到ROM或Flash中的首地址,这样可以保证上电后先运行BootLoader程序

#牛客创作赏金赛##牛客解忧铺##牛客在线求职答疑中心##晒一晒我的offer##我的实习求职记录#
秋招嵌入式八股 文章被收录于专栏

心得和八股文(含面试真题) 全部免费

全部评论

相关推荐

这里我总结一下我的嵌入式的八股文积累,体量比较大,基本都是我秋招面试来遇到的面试真题,什么题都有主要是偏C/C++嵌入式这一块,希望能帮到各位小伙伴和后来的人,希望大家送送花点点赞这个系列大概分为好几期吧,尽快肝出来,大家只要面试前抽个一个小时每次背背,基本上八股部分不会有太大问题我都放在我主页置顶希望小伙伴们捧捧场千万别只点收藏哦urat:两根线 无时钟 异步 全双工i2c:两根线 时钟和数据 同步 半双工 可以多主机多从机传输速率100K,400K和3.4M三种速率(bps) SCL和SDA:SCL高电平期间SDA有高变低为起始信号;SCL高电平期间SDA由低变高为终止信号;SCL高电平期间SDC低位0,高为1;iic采用7bit寻址字节,1-7表示从机地址,0位表示传输方向;表示读写每一个字节必须保证是8bit长度。数据传送时,先传送最高位(MSB),每一个被传送的字节后面都必须跟随1bit的应答位,每一帧数据有九位通行流程:1. 启动信号:主设备发送起始信号,通过拉低SDA线,同时SCL线保持高电平。2. 地址发送:主设备发送目标从设备的地址及读/写位。地址后,目标从设备应应答。3. 数据传输:主设备或从设备根据读/写位进行数据传输。每传输一字节后,接收方应发应答位。4. 停止信号:完成数据传输后,主设备发送停止信号,释放SDA线,SCL线保持高电平。- ACK:表示“我已收到,继续发送”。- NACK:表示“我未收到或我已完成,不要再发送了”。spi:四线制或者三线制 时钟 、ss、miso、mosi同步 全双工 有发必有会回 spi传输速率 :可以达到50Mbps一主多从计算数组长度:通过sizeof(),数组长度除以数组第一个元素的长度c通过strlen()函数, c++通过.size()数组和链表的区别:数组和链表是两种不同的**数据存储**方式数组是一组具有相同数据类型的变量集合链表是一个中物理存储单元上不连续的存储结构,各个元素的逻辑顺序通过链表中的指针链接实现数组:(1)数组在内存中连续; (2)使用数组之前,必须事先固定数组长度,不支持动态改变数组大小;(3) 数组元素增加时,有可能会数组越界;(4) 数组元素减少时,会造成内存浪费;(5)数组增删时需要移动其它元素。数组从栈上分配内存,使用方便,但是自由度小。数组在内存中顺序存储,可通过下标访问,访问效率高。数组的大小是固定的,所以存在访问越界的风险。数组的存储空间是栈上分配的,存储密度大,当要求存储的大小变化不大时,且可以事先确定大小,宜采用数组存储数据。数组的存储空间是栈上分配的,存储密度大,当要求存储的大小变化不大时,且可以事先确定大小,宜采用数组存储数据。链表:(1)链表采用动态内存分配的方式,在内存中不连续 (2)支持动态增加或者删除元素 (3)需要时可以使用malloc或者new来申请内存,不用时使用free或者delete来释放内存。链表从堆上分配内存,自由度大,但是要注意内存泄漏。链表从堆上分配内存,自由度大,但是要注意内存泄漏。链表访问效率低,如果想要访问某个元素,需要从头遍历。只要可以申请得到链表空间,链表就无越界风险。链表的存储空间是堆上动态申请的,当要求存储的长度变化较大时,且事先无法估量数据规模,宜采用链表存储。链表插入、删除效率高,当线性表要求频繁插入和删除时,宜采用链表结构。vector和list的区别+应用:Vector:连续存储的容器,动态数组,在堆上分配空间底层实现:数组性能:适用场景:经常随机访问,且不经常对非尾节点进行插入删除,因为尾节点插入涉及空间问题List:动态链表,在堆上分配空间,每插入一个元数都会分配空间,每删除一个元素都会释放空间。底层:双向链表性能:访问:随机访问性能很差,只能快速访问头尾节点插入:很快删除:很快vector拥有一段连续的内存空间,因此支持随机访问,如果需要高效的随即访问,而不在乎插入和删除的效率,使用vector。list拥有一段不连续的内存空间,如果需要高效的插入和删除,而不关心随机访问,则应使用list。进程和线程的区别:进程:一个在内存中运行的应用程序。每个进程都有自己独立的一块内存空间,一个进程可以有多个线程进程有自己的独立地址空间,每启动一个进程,系统就会为它分配地址空间,建立数据表来维护代码段、堆栈段和数据段也因为进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响线程:进程中的一个执行任务(控制单元),负责当前进程中程序的执行。一个进程至少有一个线程,一个进程可以运行多个线程,多个线程可共享数据。线程是共享进程中的数据的,使用相同的地址空间,因此CPU切换一个线程的花费远比进程要小很多线程之间的通信更方便,同一进程下的线程共享全局变量、静态变量等数据,而进程之间的通信需要以(IPC)进行。线程只是一个进程中的不同执行路径。一个线程死掉就等于整个进程死掉。在通信机制上:进程的通信机制**相对很复杂,譬如管道,信号,消息队列,共享内存,套接字等通信机制,而线程由于共享数据段所以通信机制很方便线程通信主要可以分为三种方式,分别为共享内存、消息传递和管道流1.管道pipe:管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。(同步互斥机制)2.命名管道FIFO:有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。管道的实质是一个内核缓冲区,进程以先进先出的方式从缓冲区存取数据:管道一端的进程顺序地将进程数据写入缓冲区,另一端的进程则顺序地读取数据,该缓冲区可以看做一个循环队列3.消息队列MessageQueue:消息队列就是一个消息的链表,是一系列保存在内核中消息的列表。用户进程可以向消息队列添加消息,也可以向消息队列读取消息。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。4.共享内存SharedMemory:共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,共享内存允许两个或多个进程共享一个给定的存储区。它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步和通信。5.信号量Semaphore:信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。6.套接字Socket:套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同及其间的进程通信。基于网路型,基于文件型7.信号 ( sinal ) : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
点赞 评论 收藏
分享
6 4 评论
分享
牛客网
牛客企业服务