欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 文旅 > 八卦 > 大疆嵌入式笔试试题整理最全 嵌入式 linux C语言 RTOS 操作系统 FreeRTOS面试题目STM32单片机面试题目

大疆嵌入式笔试试题整理最全 嵌入式 linux C语言 RTOS 操作系统 FreeRTOS面试题目STM32单片机面试题目

2024/10/24 4:12:53 来源:https://blog.csdn.net/qq_46187594/article/details/142064418  浏览:    关键词:大疆嵌入式笔试试题整理最全 嵌入式 linux C语言 RTOS 操作系统 FreeRTOS面试题目STM32单片机面试题目

大疆嵌入式笔试试题整理最全

加粗内容:需要补习的

主要整理大疆的嵌入式笔试,题目来自:https://blog.csdn.net/m0_46132568/article/details/126223090

大疆嵌入式笔试(试题1)

  1. 缓存和寄存器那个比较快?
    CPU-寄存器-缓存-内存

  2. 波特率是什么单位。比特率是什么单位

    1波特即每秒传输1 个码元符号(通过不同的调制方式,可以在一个码元上负载多个bit位信息)。1比特每秒是指每秒传输1 比特(bit)

    实际是哦那个中,常用串口通信协议举例,条件是:1个起始位,8个数据位,0个校验位,1个停止位,也就是说,8,N,1;8个数据位,一个停止位,无校验位。

    这个条件分析一下就是,如果我要传输一个字节(8 位)的数据,则我应该在总线上产生 10 个电平变换,也就是串行总线上,需要 10 位,才能发送 1 个字节数据。

    1秒可以发送9600位,那么用9600/10,就是1秒可以发送 960 个字节数据,则每发送一个字节需要的时间就是:1/960 ~= 0.00104166…s ~= 1.0416667 ms。

    此时就可以得出一个结论,在 9600 波特率下,大概 1ms 发送 1 个字节的数据。

  3. 对ARM处理器异常的理解

    外部中断,内部异常,系统调用都属于

  4. Cotex_M基础知识的掌握

    LDR R0,=0x12345678是直接将值赋给R0吗
    就是把0x12345678这个地址中的值存放到r0中

  5. IO密集型类知识

  6. 支持优先级继承调度器会动态改变任务优先级

  7. git命令相关知识

  8. )哪些不是实时操作系统

    要注意的是带有RT的基本上都是实时性操作系统
    WIN,Linux都是分时操作系统

  9. ARM_V8支持64位?

    大致来说,ARMv8架构跟AMD和Intel的做法一样,采取64位兼容32位的方式。应用程序可以以32位模式执行,也可以以64位模式执行

  10. )NEON,PMIPB等操作

  11. SPI有几种工作模式
    SPI总线有四种工作方式(SP0, SP1, SP2, SP3),其中使用的最为广泛的是SPI0和SPI3方式。

  12. 栈空间大小会不会影响编译出来的bin固件大小
    这个。。暂时还没听说过

  13. 00编译优化是不是默认不优化

  14. inline内联基础知识及其作用

    在 c/c++ 中,为了解决一些频繁调用的小函数大量消耗栈空间(栈内存)的问题,特别的引入了 inline 修饰符,表示为内联函数。栈空间就是指放置程序的局部数据(也就是函数内数据)的内存空间。

  15. 给图片大小和码率还是啥,求带宽多少Gbps

  16. VTOR作用,普通中断是不是只把R0-R3数据压栈,PRIMASK作用

VTOR中断向量表偏移寄存器

  1. cortex-M3 中断调用过程
    入栈

中断发生后,中断服务函数运行前,会先把xPSR, PC, LR, R12以及R3‐R0总共8个寄存器由硬件自动压入适当的堆栈中(中断前使用的MSP就压入MSP,中断前使用的是PSP就压入PSP

  1. 内联函数的作用
    避免函数调用的开销
  2. ARM Cotex -M 都是哈佛体系?-A冯诺依曼体系?
    有一些ARM(Cortex-M系列)是哈佛结构,而另一些ARM(Cortex-A)是冯诺依曼结构(或者更准确说是混合结构)
  3. I2S总线相关知识

2S特点
①支持全双工和半双工通信。(单工数据传输只支持数据在一个方向上传输;半双工数据传输允许数据在两个方向上传输,但是在某一时刻,只允许数据在一个方向上传输,它实际上是一种切换方向的单工通信;全双工数据通信允许数据同时在两个方向上传输,因此,全双工通信是两个单工通信方式的结合,它要求发送设备和接收设备都有独立的接收和发送能力。
②支持主/从模式。(主模式:就是主CPU作为主机,向从机(挂载器件)发送接收数据。从模式:就是主CPU作为从机,接收和发送主机(挂载器件)数据。而主从机的分别其实是一个触发的作用,主机主动触发,从机只能被动响应触发。)

I2S总线拥有三条数据信号线:
1、SCK: (continuous serial clock) 串行时钟
对应数字音频的每一位数据,SCK都有1个脉冲。SCK的频率=2×采样频率×采样位数。
2、WS: (word select) 字段(声道)选择
用于切换左右声道的数据。WS的频率=采样频率。
命令选择线表明了正在被传输的声道。
WS为“1”表示正在传输的是左声道的数据。
WS为“0”表示正在传输的是右声道的数据。
WS可以在串行时钟的上升沿或者下降沿发生改变,并且WS信号不需要一定是对称的。在从属装置端,WS在时钟信号的上升沿发生改变。WS总是在最高位传输前的一个时钟周期发生改变,这样可以使从属装置得到与被传输的串行数据同步的时间,并且使接收端存储当前的命令以及为下次的命令清除空间。
3、SD: (serial data) 串行数据
用二进制补码表示的音频数据。 I2S格式的信号无论有多少位有效数据,数据的最高位总是被最先传输(在WS变化(也就是一帧开始)后的第2个SCK脉冲处),因此最高位拥有固定的位置,而最低位的位置则是依赖于数据的有效位数。也就使得接收端与发送端的有效位数可以不同。如果接收端能处理的有效位数少于发送端,可以放弃数据帧中多余的低位数据;如果接收端能处理的有效位数多于发送端,可以自行补足剩余的位(常补足为零)。这种同步机制使得数字音频设备的互连更加方便,而且不会造成数据错位。为了保证数字音频信号的正确传输,发送端和接收端应该采用相同的数据格式和长度。当然,对I2S格式来说数据长度可以不同。

  1. I2C主机发送__作为初始信号

    起始信号:SCL为高电平期间,SDA线由高电平向低电平的变化

  2. 栈空间大小多大,往一个数组写4K btye数据栈会不会溢出?如果会还有哪些情况会溢
    出?如果不会溢出会发生什么问题?
    自行测试linux的栈的默认空间在7~8Mbyte之间接近8M,通过ulimit -s可以知道理论值是8M
    但是栈的空间是不固定的,我们可以用ulimit -s 10240 更改其栈空间,单位KByte
    当然不同系统的默认栈值可能不一样,比如win系统的栈不到1M

    对于堆内存来说,可以申请的内存非常大,几乎可以申请超万个G我也不知道为什么。
    当一个进程退出后,不管是正常退出或者是异常退出,操作系统都会释放这个进程的资源。包括这个进程分配的内存。

    1. 写一个宏定义,给一个结构体成员地址,返回结构体的首地址

(1)给一个正整数n,求从1-n这些数字总共出现’1’的次数(编程题目)
(2)求圆周率的N位精度,这个N可能非常非常大(编程题目)

试题2

参考链接

https://blog.csdn.net/u013193026/article/details/39367825

https://blog.csdn.net/qq_42957923/article/details/88052888

Linux系统中内核线程和普通线程的区别

普通线程和内核线程

https://www.cnblogs.com/alantu2018/p/8526916.html

内核线程和用户线程

https://blog.csdn.net/dongyanxia1000/article/details/68947620)

试题3

题目:https://blog.csdn.net/weixin_43166958/article/details/98477895

选择题
  1. 求a的值

    经过表达式a = 5 ? 0 : 1的运算,变量a的最终值是     0

boolean虚拟机取值时候是掩码去掉前七位之后取末尾判断,0是false,1是true,而5对应的是00001001,所以这块表示的是1,也就是true,所以对应的是三目运算里面的结果是 0

  1. 数组int a[3][4]中的a[2][1]用其他形式表示:

    *(*(a+2)+1)*(a[2] + 1)int *p = &a[0][0] ;//p[9], 表示 a[2][1];
    
  2. 在soc中常常用做对外设寄存器配置总线的是

    i2c,spI

  3. 哈佛结构和冯诺依曼结构的区别:

    要理解哈弗结构和冯诺依曼结构的区别,首先要知道我们在编写程序的时候其实可以对程序的代码划分为两个部分,一部分是程序编写完成后就不再需要对其进行修改了的(也就是逻辑代码部分)另一部分就是在程序编写完毕后其内容会随着程序的运行而不断变化的部分(也就是定义变量)。而哈佛结构和冯诺依曼结构就是对于这个两部分代码的存储方式的区别。
    哈佛结构(Harvard architecture)是一种将程序指令储存和数据储存分开的存储器结构。

    中央处理器首先到程序指令储存器中读取程序指令内容,解码后得到数据地址,再到相应的数据储存器中读取数据,并进行下一步的操作(通常是执行)。程序指令储存和数据储存分开,数据和指令的储存可以同时进行,可以使指令和数据有不同的数据宽度。

哈佛结构的微处理器通常具有较高的执行效率。其程序指令和数据指令分开组织和储存的,执行时可以预先读取下一条指令。
大多数ARM、DSP是哈佛结构。

在这里插入图片描述

冯.诺伊曼结构(von Neumann architecture)是一种将程序指令存储器和数据存储器合并在一起的存储器结构。

大多数CPU和GPU是冯诺依曼结构的。

诺依曼结构则是将逻辑代码段和变量统一都存储在内存当中,他们之间一般是按照代码的执行顺序依次存储。这样就会导致一个问题,如果当程序出现BUG的时候,由于程序没有对逻辑代码段的读写限定,因此,他将拥有和普通变量一样的读写操作权限。于是就会很容易的死机,一旦他的逻辑执行出现一点该变就会出现非常严重的错误。但是,冯诺依曼结构的好处是可以充分利用有限的内存空间,并且会使CPU对程序的执行十分的方便,不用来回跑。

  1. 联合体和结构体

结构体(struct)各成员各自拥有自己的内存,各自使用互不干涉,同时存在的,遵循内存对齐原则。一个struct变量的总长度等于所有成员的长度之和。

而联合体(union)各成员共用一块内存空间,并且同时只有一个成员可以得到这块内存的使用权(对该内存的读写),各变量共用一个内存首地址。因而,联合体比结构体更节约内存。一个union变量的总长度至少能容纳最大的成员变量,而且要满足是所有成员变量类型大小的整数倍。

#include<stdio.h>
//结构体
struct u  //u表示结构体类型名
{char a;     //a表示结构体成员名int b;short c;
}U1;      
//U1表示结构体变量名  
//访问该结构体内部成员时可以采用U1.a=1;其中"点"表示结构体成员运算符//联合体
union u1  //u1表示联合体类型名
{char a;    //a表示联合体成员名int b;short c;
}U2;
//U2表示联合体变量名  
//访问该联合体内部成员时可以采用U2.a=1;其中"点"表示联合体成员运算符//主函数
int main(){printf("%d\n",sizeof(U1));printf("%d\n",sizeof(U2));return 0;
}/*程序运行结果是:
12
4*/
填空题4道

大小端问题

A=0x12345678存入地址1000H~10003H中,

小端模式:1000H=78 1001H=56 1002H=34 1003H=12

大端模式:1000H=12 1001H=34 1002H=56 1003H=78

宏定义计算问题

#define  PRODUCT (x) (x*x)int main()
{int a,b=3;a=PRODUCT(b+2);
}

求a值:

b+2*b+2=3+2*3+2=11

b+2b+2=3+23+2=11

void foo(void)
{unsigned int a = 6;int b = -20;int c;(a+b > 6) ? (c=1) : (c=0);
}

这个问题测试C语言中的整数自动转换原则,这无符号整型问题的答案是c=1。原因是当表达式中存在有符号类型和无符号类型时所有的操作数都自动转换为无符号类型。因此-20变成了一个非常大的正整数,所以该表达式计算出的结果c=1。

简答题3道
  1. 简述任务/线程之间的同步方式

    https://www.cnblogs.com/love-lzb/p/6859072.html?utm_source=itdadao&utm_medium=referral

    互斥锁:锁机制是同一时刻只允许一个线程执行一个关键部分的代码。

    条件变量:条件变量是利用线程间共享全局变量进行同步的一种机制。条件变量上的基本操作有:触发条件(当条件变为 true 时),等待条件,挂起线程直到其他线程触发条件。

    信号量:为控制一个具有有限数量用户资源而设计。

    事 件:用来通知线程有一些事件已发生,从而启动后继任务的开始。

  2. 简述可执行程序的内存布局

  3. 设计机制保证锁可以按照先到先得的方式被任务获取,并且占用内存空间最小(题目没有说全,记不清,考察操作系统和队列的应用)

编程题2道

1、比较输入字符串s1和s2前n个字符,忽略大小写,如果字符串s1和s2相同则返回0,不同则返回第一个不同字符的差值。

tolower是一种函数,功能是把字母字符转换成小写,非字母字符不做出处理。

#include <stdio.h>  
#include <string.h>  
#include <ctype.h>  int strcmpx(const char *s1, const char *s2, unsigned int n)  
{  int c1, c2;  do {  c1 = tolower(*s1++);  c2 = tolower(*s2++);  } while((--n > 0) && c1 == c2 && c1 != 0);  return c1 - c2;  
}  
int main(void)  
{  int n = 4;  char str3[] = "ABCf";  char str4[] = "abcd";  printf("strcmpx(str3, str4, n) = %d", strcmpx(str3, str4, n));  return 0;  
}  

2、N X N数组,输出行中最小,列中最大的数的位置,比如:

1 2 3
4 5 6
7 8 9

输出:row=2,col=0

分析:

在矩阵中,一个数在所在行中是最大值,在所在列中是最小值,则被称为鞍点。

鞍点C++实现

https://blog.csdn.net/Sherry_Yue/article/details/83833516)

//C语言输出矩阵马鞍点#include<stdio.h>  void Input_Matrix(int m,int n,int a[100][100])              //输入矩阵元素
{for (int i = 0; i < n; i++){for (int j = 0; j < m; j++){scanf("%d", &a[i][j]);}}
}void Output_Matrix(int m, int n, int a[100][100])    //输出矩阵元素
{for (int i = 0; i < m; i++){for (int j = 0; j < n; j++){printf("%-5d", a[i][j]);}printf("\n");}
}void Matrix_Mn(int m, int n, int a[100][100])     //输出矩阵马鞍点
{int flag = 0;int min, max, k, l;for (int i = 0; i < n; i++){for (int j = 0; j < m; j++){min = a[i][j];for (k = 0; k < m; k++){if (min < a[i][k])break;}if (k == m){max = a[i][j];for (l = 0; l < n; l++){if (max > a[l][j])break;}if (l == n){printf("%-5d%-5d\n", i, j);printf("矩阵元素为:a[%d][%d]=%d\n",i,j, a[i][j]);flag = 1;}}}}if (flag == 0){printf("该矩阵没有马鞍点!\n");}
}
int main(void)
{int m, n;int a[100][100];for (;;){printf("请输入矩阵的行数和列数:\n");scanf("%d %d", &n, &m);printf("请输入矩阵中的元素:\n");Input_Matrix(m, n, a);printf("矩阵输出为:\n");Output_Matrix(m, n, a);printf("马鞍点输出(该点所在的行数和列数):\n");Matrix_Mn(m, n, a);}return 0;
}

题目要求是一个数在所在行中是最小值,在所在列中是最大值,不确定还能不能称为鞍点,但是算法思路相似的。

思路:

  • 先找第i行上最小的元素t,以及所在列minj
  • 判断t是否为第minj列的最大值,如果不是则在minj列中继续寻找最大值并输出,如果是则输出
#include <stdio.h>
#define N 3
int a[N][N]={1,2,3,4,5,6,7,8,9};
int main()
{int i,j,t,minj;for(i=0;i<N;i++){t=a[i][0];minj=0;for(j=1;j<N;j++)//行上最小{if(a[i][j]<t){t=a[i][j];minj=j;//找到了行上最小的数所在的列}}int k;for(k=0;k<N;k++)if(a[k][minj]>t)//判断是否列上最大break;if(k<N) continue;//接着查找下一行printf("所求点是:a[%d][%d]:%d\n",i,minj,t);}return 0;
}

相关基础知识:

break:

(1).结束当前整个循环,执行当前循环下边的语句。忽略循环体中任何其它语句和循环条件测试。
(2).只能跳出一层循环,如果你的循环是嵌套循环,那么你需要按照你嵌套的层次,逐步使用break来跳出。

continue:

(1).终止本次循环的执行,即跳过当前这次循环中continue语句后尚未执行的语句,接着进行下一次循环条件的判断。
(2).结束当前循环,进行下一次的循环判断。
(3).终止当前的循环过程,但他并不跳出循环,而是继续往下判断循环条件执行语句.他只能结束循环中的一次过程,但不能终止循环继续进行。

题目四

https://blog.csdn.net/qq_38410730/article/details/80951443

填空选择题

1、ARM指令和Thumb指令。

https://www.cnblogs.com/yygsj/p/5428500.html

解答:在ARM的体系结构中,可以工作在三种不同的状态,一是ARM状态,二是Thumb状态及Thumb-2状态,三是调试状态。而ARM状态和Thumb状态可以直接通过某些指令直接切换,都是在运行程序,只不过指令长度不一样而已。

ARM状态:arm处理器工作于32位指令的状态,所有指令均为32位;
Thumb状态:arm执行16位指令的状态,即16位状态;
thumb-2状态:这个状态是ARM7版本的ARM处理器所具有的新的状态,新的thumb-2内核技术兼有16位及32位指令,实现了更高的性能,更有效的功耗及更少地占用内存。总的来说,感觉这个状态除了兼有arm和thumb的优点外,还在这两种状态上有所提升,优化;
调试状态:处理器停机时进入调试状态。

关于这个知识点还有几个注意点:

ARM处理器复位后开始执行代码时总是只处于ARM状态;
Cortex-M3只有Thumb-2状态和调试状态;
由于Thumb-2具有16位/32位指令功能,因此有了thumb-2就无需Thumb了。
另外,具有Thumb-2技术的ARM处理器也无需再ARM状态和Thumb-2状态间进行切换了,因为thumb-2具有32位指令功能。

2、哪种总线方式是全双工类型、哪种总线方式传输的距离最短?(选择题)

解答:几种总线接口的通信方式的总结如下图所示:

在这里插入图片描述

3、TCP与UDP的区别。(选择题)

解答:TCP和UDP的区别总结如下图所示:

https://blog.csdn.net/u011957758/article/details/72353485

在这里插入图片描述

4、Linux的用户态与内核态的转换方法。(选择题)

解答:Linux下内核空间与用户空间进行通信的方式主要有syscall(system call)、procfs、ioctl和netlink等。

syscall:一般情况下,用户进程是不能访问内核的。它既不能访问内核所在的内存空间,也不能调用内核中的函数。Linux内核中设置了一组用于实现各种系统功能的子程序,用户可以通过调用他们访问linux内核的数据和函数,这些系统调用接口(SCI)称为系统调用;
procfs:是一种特殊的伪文件系统 ,是Linux内核信息的抽象文件接口,大量内核中的信息以及可调参数都被作为常规文件映射到一个目录树中,这样我们就可以简单直接的通过echo或cat这样的文件操作命令对系统信息进行查取;
netlink:用户态应用使用标准的 socket API 就可以使用 netlink 提供的强大功能;
ioctl:函数是文件结构中的一个属性分量,就是说如果你的驱动程序提供了对ioctl的支持,用户就可以在用户程序中使用ioctl函数控制设备的I/O通道。
5、linux目录结构,选项是/usr、/tmp、/etc目录的作用。(选择题)

/usr:不是user的缩写,其实usr是Unix Software Resource的缩写, 也就是Unix操作系统软件资源所放置的目录,而不是用户的数据啦。这点要注意。 FHS建议所有软件开发者,应该将他们的数据合理的分别放置到这个目录下的次目录,而不要自行建立该软件自己独立的目录;

  • **/tmp:这是让一般使用者或者是正在执行的程序暂时放置档案的地方。**这个目录是任何人都能够存取的,所以你需要定期的清理一下。当然,重要资料不可放置在此目录啊。 因为FHS甚至建议在开机时,应该要将/tmp下的资料都删除;
  • /etc:系统主要的设定档几乎都放置在这个目录内,例如人员的帐号密码档、各种服务的启始档等等。 一般来说,这个目录下的各档案属性是可以让一般使用者查阅的,但是只有root有权力修改。 FHS建议不要放置可执行档(binary)在这个目录中。 比较重要的档案有:/etc/inittab, /etc/init.d/, /etc/modprobe.conf, /etc/X11/, /etc/fstab, /etc/sysconfig/等等。

6、下面这段程序的运行结果?(选择题)

int main(){const int x=5;const int *ptr;ptr=&x;*ptr=10;printf("%d\n",x);return 0;
}

解答:编译出错。

这道题主要是讲解const与指针的问题:

const int a;
int const a;
const int *a;
int * const a;
const int * const a;
int const * const a;

前两个的作用是一样,a是一个常整型数;
第三个意味着a是一个指向常整型数的指针(也就是,整型数是不可修改的,但指针可以);
第四个意思a是一个指向整型 数的常指针(也就是说,指针指向的整型数是可以修改的,但指针是不可修改的);
最后两个意味着a是一个指向常整型数的常指针(也就是说,指针指向的整型数 是不可修改的,同时指针也是不可修改的)。
也就是说:本题x是一个常量,不能改变;ptr是一个指向常整型数的指针。而当*ptr=10;的时候,直接违反了这一点。同时要记得一点,const是通过编译器在编译的时候执行检查来确保实现的。

7、在32位系统中,有如下结构体,那么sizeof(fun)的数值是()

#pragma pack(1)struct fun{int i;double d;char c;
};

解答:13。

可能是一般的内存对齐做习惯了,如果本题采用内存对齐的话,结果就是24(int 4 double char 7)。但是#pragma pack(1)让编译器将结构体数据强制按1来对齐。

每个特定平台上的编译器都有自己的默认“对齐系数”(32位机一般为4,64位机一般为8)。我们可以通过预编译命令#pragma pack(k),k=1,2,4,8,16来改变这个系数,其中k就是需要指定的“对齐系数”。

只需牢记:

8、Linux中的文件/目录权限设置命令是什么?(选择题)

解答:chmod

9、下面四个选项是四个整数在内存中的存储情况,请选择其中最大的一个。(选择题)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

解答:大端小端问题:

所谓的大端模式(BE big-endian),是指数据的低位保存在内存的高地址中,而数据的高位,保存在内存的低地址中(低对高,高对高);
所谓的小端模式(LE little-endian),是指数据的低位保存在内存的低地址中,而数据的高位保存在内存的高地址中(低对低,高对高)。

那么A:12345678、B:56781234、C:12785634、D:56341278。

10、C语言的各种变量的存取区域,给你一段小程序,让你分析各个变量的存储区域(填空题)

解答:具体的题目内容忘了,但是大体上给出各个变量可能的存储区域:

堆:堆允许程序在运行时动态地申请某个大小的内存。一般由程序员分配释放;
栈:由编译器自动分配释放,存放函数的参数值,局部变量等值;
静态存储区:一定会存在且不会消失,这样的数据包括常量、常变量(const 变量)、静态变量、全局变量等;
常量存储区:常量占用内存,只读状态,决不可修改,常量字符串就是放在这里的。

11、下面这段程序的运行结果?(填空题)

int main() {int a[10] = { 0,1,2,3,4,5,6,7,8,9 };memcpy(a + 3, a, 5);for (int i = 0; i<10; i++){printf("%d ", a[i]);}return 0;
}

解答:0 1 2 0 1 5 6 7 8 9

首先看一下内存复制函数memcpy()函数的定义:

void * memcpy ( void * destination, const void * source, size_t num );

将source指向的地址处的 num 个字节 拷贝到 destination 指向的地址处。注意,是字节。

因为memcpy的最后一个参数是需要拷贝的字节的数目!一个int类型占据4个字节!这样的话,本题5字节,实际上只能移动2个数字(往大的去)。如果要想达到将a地址开始的5个元素拷贝到a+3地址处,需要这么写:

memcpy(a + 3, a, 5*sizeof(int));

https://blog.csdn.net/baidu_35679960/article/details/80953973

12、C语言编译过程中,volatile关键字和extern关键字分别在哪个阶段起作用?(填空题)

解答:volatile应该是在编译阶段,extern在链接阶段。

volatile关键字的作用是防止变量被编译器优化,而优化是处于编译阶段,所以volatile关键字是在编译阶段起作用。

参考文章:C语言文件的编译与执行的四个阶段并分别描述和volatile为什么要修饰中断里的变量。

https://blog.csdn.net/yimingsilence/article/details/52800987

https://blog.csdn.net/fengyunjh6/article/details/9055359

13、linux系统打开设备文件,进程可能处于三种基本状态,如果多次打开设备文件,驱动程序应该实现什么?(填空题)

简答题

1、简述实时操作系统和非实时操作系统特点和区别。

解答:实时操作系统是保证在一定时间限制内完成特定功能的操作系统。实时操作系统有硬实时和软实时之分,硬实时要求在规定的时间内必须完成操作,这是在操作系统设计时保证的;软实时则只要按照任务的优先级,尽可能快地完成操作即可。

实时性最主要的含义是:任务的最迟完成时间是可确认预知的。

在这里插入图片描述

2、简述static关键字对于工程模块化的作用。

解答:在C语言中,static有下3个作用:

函数体内的static变量的作用范围为该函数体,不同于auto变量,该变量的内存只被分配一次,以为其值在下次调用时仍维持上次的值(该变量存放在静态变量区);
在模块内static全局变量可以被模块内所有函数访问,但不能被模块外其他函数访问。(注意,只有在定义了变量后才能使用。如果变量定义在使用之后,要用extern 声明。所以,一般全部变量都会在文件的最开始处定义。);
在模块内的static函数只可被这一模块内的其他函数调用,这个函数的使用范围被限制在声明它的模块内。
在嵌入式系统中,要时刻懂得移植的重要性,程序可能是很多程序员共同协作同时完成,在定义变量及函数的过程,可能会重名,这给系统的集成带来麻烦,因此保证不冲突的办法是显示的表示此变量或者函数是本地的,static即可。在Linux的模块编程中,这一条很明显,所有的函数和全局变量都要用static关键字声明,将其作用域限制在本模块内部,与其他模块共享的函数或者变量要EXPORT到内核中。

3、无锁可以提高整个程序的性能,但是CPU需要对此提供支持,请以x86/ARM为例简述。

解答:无锁编程具体使用和考虑到的技术方法包括:原子操作(atomic operations), 内存栅栏(memory barriers), 内存顺序冲突(memory order), 指令序列一致性(sequential consistency)和顺ABA现象等等。

在这其中最基础最重要的是操作的原子性或说原子操作。原子操作可以理解为在执行完毕之前不会被任何其它任务或事件中断的一系列操作。原子操作是非阻塞编程最核心基本的部分,没有原子操作的话,操作会因为中断异常等各种原因引起数据状态的不一致从而影响到程序的正确。

对于原子操作的实现机制,在硬件层面上CPU处理器会默认保证基本的内存操作的原子性,CPU保证从系统内存当中读取或者写入一个字节的行为肯定是原子的,当一个处理器读取一个字节时,其他CPU处理器不能访问这个字节的内存地址。但是对于复杂的内存操作CPU处理器不能自动保证其原子性,比如跨总线宽度或者跨多个缓存行(Cache Line),跨页表的访问等。这个时候就需要用到CPU指令集中设计的原子操作指令,现在大部分CPU指令集都会支持一系列的原子操作。

而在无锁编程中经常用到的原子操作是Read-Modify-Write (RMW)这种类型的,这其中最常用的原子操作又是 COMPARE AND SWAP(CAS),几乎所有的CPU指令集都支持CAS的原子操作,比如X86平台下中的是 CMPXCHG(Compare Are Exchange)。

继续说一下CAS,CAS操作行为是比较某个内存地址处的内容是否和期望值一致,如果一致则将该地址处的数值替换为一个新值。CAS操作具体的实现原理主要是两种方式:总线锁定和缓存锁定。所谓总线锁定,就是CPU执行某条指令的时候先锁住数据总线的, 使用同一条数据总线的CPU就无法访问内存了,在指令执行完成后再释放锁住的数据总线。锁住数据总线的方式系统开销很大,限制了访问内存的效率,所以又有了基于CPU缓存一致性来保持操作原子性作的方法作为补充,简单来说就是用CPU的缓存一致性的机制来防止内存区域的数据被两个以上的处理器修改。

最后这里随便说一下CAS操作的ABA的问题,所谓的ABA的问题简要的说就是,线程a先读取了要对比的值v后,被线程b抢占了,线程b对v进行了修改后又改会v原来的值,线程1继续运行执行CAS操作的时候,无法判断出v的值被改过又改回来。

解决ABA的问题的一种方法是,一次用CAS检查双倍长度的值,前半部是指针,后半部分是一个计数器;或者对CAS的数值加上版本号。

https://blog.csdn.net/liusuper2088/article/details/41047501

编程题

1、已知循环缓冲区是一个可以无限循环读写的缓冲区,当缓冲区满了还继续写的话就会覆盖我们还没读取到的数据。下面定义了一个循环缓冲区并初始化,请编写它的Write函数:

typedef struct RingBuf {char *Buf;unsigned int Size;unsigned int RdId;unsigned int WrId;
}RingBuf;void Init(RingBuf *ringBuf, char *buf, unsigned int size) {memset(ringBuf, 0, sizeof(RingBuf));ringBuf->Buf = buf;ringBuf->Size = size;ringBuf->RdId = 0;ringBuf->WrId = 0;
}

解答:实际上我觉得提供的初始化代码部分,对WrId的初始化有点问题,Write()函数的完整代码如下:

typedef struct RingBuf {char *Buf;unsigned int Size;unsigned int RdId;unsigned int WrId;
}RingBuf;void Init(RingBuf *ringBuf, char *buf, unsigned int size) {memset(ringBuf, 0, sizeof(RingBuf));ringBuf->Buf = buf;ringBuf->Size = size;ringBuf->RdId = 0;ringBuf->WrId = strlen(buf);
}void Write(RingBuf *ringBuf, char *buf, unsigned int len) {unsigned int pos = ringBuf->WrId;while (pos + len > ringBuf->Size) {memcpy(ringBuf->Buf + pos, buf, ringBuf->Size - pos);buf += ringBuf->Size - pos;len -= ringBuf->Size - pos;pos = 0;}memcpy(ringBuf->Buf + pos, buf, len);ringBuf->WrId = pos + len;
}void Print(RingBuf *ringBuf) {for (int i = 0; i < ringBuf->Size; i++) {cout << ringBuf->Buf[i];}cout << endl;
}int main()
{RingBuf *rb = (RingBuf *)malloc(sizeof(RingBuf));char init_str[] = "ABC";int size = 6;Init(rb, init_str, size);char p[] = "1234567";Write(rb, p, 7);Print(rb);return 0;
}

2、已知两个已经按从小到大排列的数组,将它们中的所有数字组合成一个新的数组,要求新数组也是按照从小到大的顺序。请按照上述描述完成函数:

int merge(int *array1, int len1, int *array2, int len2, int *array3);

解答:这道题本质上就是一道合并排序,网上一大堆的程序案例,就不多介绍了。下面这段程序是我自己写的,并不是从网上贴的,如果有一些BUG,还请指出。

int merge(int *array1, int len1, int *array2, int len2, int *array3) {int retn = len1 + len2;if ((*array1 < *array2 || len2 == 0) && len1 > 0) {*array3 = *array1;merge(++array1, --len1, array2, len2, ++array3);}if ((*array1 >= *array2 || len1 == 0)  && len2 > 0) {*array3 = *array2;merge(array1, len1, ++array2, --len2, ++array3);}return retn;
}

题目五

后面的文章
下一篇文章介绍

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com