豆芽八股专栏之嵌入式

Posted (ノへ ̄、)。

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了豆芽八股专栏之嵌入式相关的知识,希望对你有一定的参考价值。

嵌入式

1、什么是嵌入式

2、精简指令集和复杂指令集

clsc  复杂指令集

risc  精简指令集,处理器使用的是一些比较简单常用的指令来执行各种操作

3、cpu内部架构和工作原理

控制单元 、运算单元、存储单元  利用cpu总线连接起来

控制单元:程序计数器(pc指针,包含当前执行的指令的地址)+ 指令寄存器(存放当前执行指令)

4、cpu内核态和用户态

存在一个程序状态寄存器来控制这两个模式

用户态下有很多限制,很多命令无法实现,内核态有最高的权限

5、cpu流水线

指令取址、指令译码、指令执行、访存、写回

6、嵌入式流水线有什么不同

使用超流水线,将原来的五个步骤分成对个,多个一起执行加快了运行速度

7、什么是超流水线

8、什么是乱序执行

cpu允许将多条指令不按程序步骤发给相应电路去处理的技术,然后将运算结果按照原来的程序的指令顺序排列后返回程序

9、cpu两种架构体系

冯 :程序和数据存储空间共享

哈:程序和数据存储空间分离

10 说说rom 和ram的区别

ram随机存储器,与cpu直接数据交换,可读可写(运行程序)

10 、说说了解哪些存储器

flash 闪存 不加电也能保存数据,

NOR flash   程序可直接运行在这

NAND flash   高存储

13、什么是DMA

外部设备不通过cpu直接与系统交换数据的接口技术

14 中断的使用场景和注意事项

(二)

4、A,RM处理器模式有哪几种

1、用户模式

2、快速中断

3、外部中断

4、管理模式

5、数据访问中止模式

6、系统模式

7、未定义指令中止模式

5、ARM处理器几种模式切换的过程

软中断 、复位       管理模式

外部中断        快速中断或者外部中断模式

cpu异常      数据访问中止模式 ,无效指令   未定义指令中止模式

6、嵌入式中断的过程

保存现场

模式切换

获得中断源

中断处理

中断返回

7、说下DMA

一种为i/o使用一种特殊的直接存储器访问芯片,它可以直接控制外围设备的数据流,而无需持续的cpu干扰,

8、中断和异常的区别

9、大端模式和小端模式

大端 高数据放在低地址,低数据放在高地址

小端  低位有效数据放在高地址,高位有效数据存在低地址

10、

12、什么是mmu,工作原理是什么

mmu是用来管理虚拟内存的,物理内存的控制线路,同时负责虚拟地址映射为物理地址

扩大地址空念、内存保护与共享】避免内存碎片、

21、c语言结构体怎么定义节省内存

1、在保证值域的情况下,用小字节变量代替大字节变量

2、将各个成员按其所占字节树从小到大声明,以后减少中间的填补空间

3、取消字节对齐

三、 

3、bootloader的启动分哪两个阶段

stage1:

硬件初始化

为stage 2准备RAM空间

将stage2代码复制到RAM空间

设置好堆栈

跳转到stage2的c入口点

stage2

初始化硬件

检测系统内存映像

将内核映像和根文件系统映像从flash上读到RAM空间中

为内核设置启动参数

调用内核

7、说下设备的种类、各自有什么特点

字符设备:以字为单位进行数据处理,一般不使用缓存技术,只能按照顺序读写

块设备数据   可以按照可寻址的块为单位进行处理,大多数快设备允许随机访问

网络接口用于网络通信

1、i2c

总线有两根双向数据线,一根是数据线 SDA,一根是时钟先 SCL

空闲: SDL  SCL都是高电平

起始信号   SCL 高      SDL由高变低的跳变

停止信号 SCL 高   SDL由低变高的跳变

主设备在SCL线上产生一个时钟脉冲,将SDL线上传输一个数据位,当一个字节数据传输完之后,从设备将拉低SDA线,回传给主设备一个应答位,这就是一个字节的真正传输

系统中的外围设备都有一个七位的从器件地址,通过这个地址来选择从设备

2、spi

一个主多格从

四根线

1、主设备数据输入,从设备输出

2、主设备输出,从设备输入

3、时钟线

4、时钟使能线,当要和某个从设备通信就将之使能

3、uatr

数据包的内容由起始位、主体数据、校验位、停止位组成,通讯双方的数据包格式以及波特率要约定一样

起始位:先发一个逻辑0

数据位:

嵌入式八股文汇总

1、C/C++

1.1 关键字

(参考”嵌入式及Linux那些事“以及众多帖子汇总而成)

volatile

​ 当声明指向设备寄存器的指针时一定要用volatile,它会告诉编译器不要对存储在这个地
址的数据进行假设。

​ 中断服务程序中修改的供其他程序检测的变量。 中断中直接从变量地址中读取数据,而不是从寄存器中读取。

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

static

​ 1、函数体内的变量,这个变量只被声明一次。

​ 2、在模块内的变量,表示只能被模块内函数使用,不能被模块外函数访问,表示本地全局变量

​ 3、模块内的函数,限制在模块内使用,同上。

extern

1、引用同一文件变量

使用在声明之前时,可以使用关键字extern,让声明在程序任意位置。

2、引用另一个文件中的变量

extern可以引用其他文件中的全局变量,而且extern只需要指明数据类型和

extern int num=4; 这样不行。

3、引用另一个文件中的函数

可以不用包含头文件引用函数。

new/delete malloc/free

​ 1、new/delete是操作符,malloc/free是库函数

​ 2、new/delete可以调用构造函数/析构函数,m/f 只是分配内存。

struct 和 union区别

​ 1、联合体公用一块地址空间,联合体变量长度等于最长的成员的长度

​ 2、对不同成员赋值,会将其他成员重写。

const

​ 1、定义变量为常量

​ 2、修饰参数为常量

​ 3、修饰返回值为常量

总结:只读

sizeof和strlen

​ 1、sizeof是运算符,strlen是函数

​ 2、sizeof可以用类型、函数作为参数,strlen只能计算char*,还必须以/0结尾

​ 3、sizeof编译的时候计算,strlen是运行期计算,表示字符串长度,不是内存大小。

typedef和 define

1、都是替对象去一个别名,增强程序的可读性

2、define为预处理指令,不做正确性检查,只有带入之后才能发现

3、typedef用来定义类型别名,不止包含内部类型还包含自定义类型(与机器无关),方便记忆

4、define不仅可以给类型取别名,还能定义常量、变量、编译开关。

5、define没有作用域限制,typedef有。

# define还是 const ,谁定义常量最好

1、define只是文本替换,声明周期止于编译期,不分配内存空间,存在于代码段。const常量存在于数据段,堆栈中分配了空间。

2、const有数据类型,编译器可以对const进行安全检查。

3、const有保护常量不被修改的作用,提高程序的健壮性。

总结:一般倾向于用const定义常量

1.2 内存

C语言内存分配方式

1、静态储存区分配

2、栈上分配

3、堆上分配

C++内存管理是怎样的

分为代码段、数据段、BSS段、堆区、栈区、文件映射区

代码段:分为只读区和文本区,只读取储存字符串常量,文本区储存机器代码。

数据段:储存以及初始化的全局变量和静态变量

BSS段:储存未初始化的全局变量和静态变量,以及初始化为0的全局和静态。

堆区:手动分配的内存

栈:局部变量参数返回值等

映射区:储存动态链接库,mmap函数的文件映射

堆和栈的区别

1、申请方式。 栈为操作系统自动分配/释放,堆为手动

2、申请大小,栈空间有限,向低地址拓展的连续区域,堆是向高地址拓展的不连续区域,链表储存的空闲地址。

3、申请效率,栈是系统自动分配,速度快,不可控。堆是由new分配,速度比较慢,容易产生内存碎片。

栈的作用

1、储存临时变量

2、多线程编程的基础。每个线程至少有一个栈用来存储临时变量和返回的值。

内存泄漏

申请了没有释放,由程序申请的一块内存,没有任何指针指向它,这个内存就泄露了。

避免内存泄漏方法

1、分配的内存以链表管理,使用完毕后从链表删除,程序结束的时候检查链表

2、良好的编程习惯,在设计内存的程序段,检验出内存泄漏,使用了内存分配的函数,使用完毕后将使用的相应函数释放掉

3、smart pointer

指针

数组指针和指针数组

int (*p)[20]; 数组指针,本质是一个指针,指向一个数组

int *p[20]; 指针数组,本质是一个数组,里面装的是指针。

函数指针和指针函数

1、函数指针 int(*p)(int,int);本身是一个指针,指向一个函数的地址

2、指针函数 int *p(int,int); 指针函数表示一个函数,返回数是指针。

数组名和指针区别

1、指针保存的是地址,数组保存的是数据,单数组名是第一个元素的地址

2、指针间接访问,数组直接下标或者偏移量

3、sizeof 有区别,指针为指针大小,数组为全体数据大小

指针常量,常量指针、指向常量的指针

1、int *const p 指向地址不变,地址值可变

2 int const *p 指向地址可变,地址值不能边

3、const int * const p 都不能变

指针与引用区别

1、都是地址,指针是地址,应用是别名

2、引用本质是指针常量,对象不变,对象的值可变

3、++不同,指针是地址自增,引用是对象自增

4、指针需要解引用

5、指针可为空,引用不行

6、sizeof不同 一个是指针大小一个是对象大小

野指针

1、指向不可用内存的指针,指针被创建时如果没有初始化就是野指针

2、指针被free、delete时没有指向NULL就是野指针

3、指针超出了变量的地址范围

智能指针

C++智能指针是指一个类,用来存储指针

1.3 预处理

预处理器标识#error的目的是什么?

1、遇到#error就会生成一个编译错误提示信息,并停止编译

define声明一年多少秒

#define SECOND_OF_PER_YEAR (3652460*60)UL

#include"" 和 include<>区别

<>号先搜索标准库搜索系统文件比较快,“”号先搜索工作路径搜索自定义文件比较快

头文件作用

1、通过文件调用库功能,源码保护

2、头文件加强类型安全检查,编译器报错

头文件定义静态变量

1、资源浪费,每个头文件都会单独存在一个静态变量

不使用流程控制语句,打印1~1000数字

#include<stdio.h>
#define A(x) x;x;x;x;x;x;x;x;x;x;
int main()

	int n=1;
	A(A(A(printf("%d",n++))));
	return 0;



1.4 变量

全局变量和静态变量

1、全局变量作用域为程序块,局部变量为当前函数

2、全局变量储存在静态区,后者为栈

3、全局变量生命周期为主函数,局部变量生命周期在局部函数中,甚至循环体内

1.5 函数

写个函数在main函数执行前执行

1、attribute可以设置函数属性

#include <stdio.h>
void before() __attribute__((constructor));
void after() __attribute__((destructor));
void before() 
  printf("this is function %s\\n",__func__);
  return;

void after()
  printf("this is function %s\\n",__func__);
  return;

int main()
  printf("this is function %s\\n",__func__);
  return 0;

// 输出结果
// this is function before
// this is function main
// this is function after

为什么析构函数必须是虚函数

1、基类指针指向子类时,释放基类指针也能释放掉子类的空间,防止内存泄漏。

2、最好是作为父类的类的析构函数作为虚函数

为什么C++默认的析构函数不是虚函数?

1、虚函数有额外的虚函数表和虚指针表,占用额外的内存,对于那些不会被继承的类当然也不需要虚函数作为析构函数。

静态函数和虚函数的区别?

1、静态函数编译时确定运行时机

2、虚函数运行时动态绑定,并且使用虚函数表,内存开销增加

重载与覆盖

1、覆盖是子类和父类的关系,垂直关系,重载是一个类之间的关系,水平关系

2、覆盖一对一,重载多个方法

3、覆盖由对象类型决定,重载根据调用的参数表决定

虚函数表实现多态方法

原理
虚函数表示一个类的地址表,子类创建时,按照函数声明吮吸会将函数的地址存在虚函数表中。子类重写父类虚函数的时候,父类虚函数表中的位置会被子类虚函数地址覆盖。

C语言函数调用方法

1、使用栈来支持函数调用操作,栈被用来传递参数,返回值,局部变量等。

2、函数调用主要操作栈帧结构

select函数

int select(int maxfdp,fd_set *readfds,fd_set *writefds,fd_set *errorfds,struct timeval *timeout);

fork wait exec函数

1、附近产生的子进程使用fork拷贝出一个父进程的副本

数组的下标可以为负数吗?

可以,数组下标指地址偏移量,根据偏移量能定位得到目标地址。

inline函数和宏定义的区别

1、内联函数在编译时展开,而宏在预编译时展开。

2、在编译的时候,内联函数直接被嵌入到目标代码中去,而宏只是一个简单的文本替换。

3、内联函数可以进行诸如类型安全检查、语句是否正确等编译功能,宏不具有这样的功能。

4、宏不是函数,而inline是函数。

5、宏在定义时要小心处理宏参数,一般用括号括起来,否则容易出现二义性。而内联函数不会出现二义性。

6、inline可以不展开,宏一定要展开。因为inline指示对编译器来说,只是一个建议,编译器可以选择忽略该建议,不对该函数进行展开。

7、宏定义在形式上类似于一个函数,但在使用它时,仅仅只是做预处理器符号表中的简单替换,因此它不能进行参数有效性的检测,也就不能享受C++编译器严格类型检查的好处,另外它的返回值也不能被强制转换为可转换的合适的类型,这样,它的使用就存在着一系列的隐患和局限性。

宏和函数的优缺点

1、函数调用时,先求出实参表达式的值,然后带入形参。而使用带参数的函数只是进行简单的字符替换

2、函数调用实在程序运行时处理的,分配的临时的内存单元;而宏展开则是在编译时进行的,在展开时不分配i内存单元,不进行值的传递,也没有"返回值的概念"

3、函数实参形参都要定义类型,二者要求一致 ,宏不存在类型问题,宏没有类型,宏的参数只是一个符号代表,展开时代入指定的字符就行,宏定义时字串可以是任意内心的数据

4、函数只可以得到一个返回值,宏可以设法得到多个

5、使用宏次数多时,展开后源程序长,每次展开都使程序增长,而函数调用不使源程序变长。

6、宏的替换不占用时间,只占用编译时间,函数调用占用运行时间。

简单回答:宏由编译计算,增加编译时间,函数运行的时候计算,增加运行时间;函数的返回值入口参数有数据类型,宏只是简单的符号加减。

ASSERT()作用

ASSERT()是一个调试程序时经常使用的宏,在程序运行时它计算括号内的表达式,如果表达式为FALSE (0), 程序将报告错误,并终止执行。如果表达式不为0,则继续执行后面的语句。

strcpy()和memcpy()的区别

1、复制的内容不同。strcpy只能复制字符串,而memcpy可以复制任意内容,例如字符数组、整型、结构体、类等。
2、复制的方法不同。strcpy不需要指定长度,它遇到被复制字符的串结束符"\\0"才结束,所以容易溢出。memcpy则是根据其第3个参数决定复制的长度。
3、用途不同。通常在复制字符串时用strcpy,而需要复制其他类型数据时则一般用memcpy

1.6 位操作

求解整型类二进制表示1的个数

int func(int x)

	int countx = 0;
    while(x)
    
        countx++;
        x = x&(x-1);
    
	return countx;

求解整型类二进制表示0的个数

int CountZeroBit(int num)

    int count = 0;
        while (num + 1)
        
            count++;
            num |= (num + 1); //算法转换
        
    return count;

给定一个整型变量a,写两段代码,第一个设置a的bit 3,第二个清除a 的bit 3。在
以上两个操作中,要保持其它位不变。

void clearbit3(int a)

	a&=~(1<<3);

void setbit3(int a)

	a|=1<<3;


1.7 容器与算法

map与set区别和底层实现

1、底层实现都是红黑树

2、map是键值对,关键字起到索引作用,值表示与索引相关联的数据,set是关键字的集合并且每个元素只包含一个关键字。

3、set迭代器是const不能修改元素值,map允许修改value不能修改key

4、map支持下标操作,set不支持,map可以用key作为下标,set用find

STL的allocator有什么作用?

1、内存配置有alloc::allocate()负责,内存释放由alloc::deallocate()负责;对象构造由::construct()负责,对象析构由::destroy()负责。

2、提升内存管理效率, STL采用了两级配置器,当分配的空间大小超过128B时,会使用第一级空间配置器;当分配的空间大小小于128B时,将使用第二级空间配置器。第一级空间配置器直接使用malloc()、realloc()、free()函数进行内存空间的分配和释放,而第二级空间配置器采用了内存池技术,通过空闲链表来管理内存。

STL迭代器如何删除元素?

对于序列容器vector,deque来说,使用erase(itertor)后,后边的每个元素的迭代器都会失效,但是后边
每个元素都会往前移动一个位置,但是erase会返回下一个有效的迭代器;
对于关联容器map set来说,使用了erase(iterator)后,当前元素的迭代器失效,但是其结构是红黑树,
删除当前元素的,不会影响到下一个元素的迭代器,所以在调用erase之前,记录下一个元素的迭代器即
可。
对于list来说,它使用了不连续分配的内存,并且它的erase方法也会返回下一个有效的iterator,因此上
面两种正确的方法都可以使用

STL中map与unordered_map有什么区别?

1、map底层红黑树实现,unordered_map采用hash表实现’

2、map中序遍历有序,un——map无序

vector和list的区别是什么

1、vector为数组实现,list为双向链表

2、vector支持随机访问,list不行

3、vector顺序储存,list随机

4、vector一次性分配内存,不够才二倍扩容,list一个个分配

5、vector随机访问性能好,插入删除比较慢,list反之

迭代器与指针

1、迭代器又名游标模式,提供一种顺序访问一个聚合对象中各个元素,但又不暴露该对象的内部表示。

2、迭代器是类模板,表现得象指针,重载了指针一些操作,封装了指针,指针的++只是递增地址,但是不能对list生效,迭代器可以。

3、迭代器有着更良好的用法begin,end等不用担心越界

STL里resize和reserve的区别是什么?

1、resize改变当前容器内含有元素的数量,会新增元素0,reserve只是增加空间,不新增元素。

1.8 类和数据抽象

c++类成员访问权限

1、C++通过 public、protected、private 三个关键字来控制成员变量和成员函数的访问权限

2、类内随便访问,类外通过对象访问,且只能访问public成员

引用与指针的区别

1、引用必须被初始化,指针不必。

2、引用初始化以后不能被改变,指针可以改变所指的对象。

3、不存在指向空值的引用,但是存在指向空值的指针。

struct与class区别

1、c++中两者都可以定义类,但是struct没有权限,默认public

面对对象和泛型编程

1、面对对象是一种程序设计思想,把对象作为程序的基本单元,一个对象包括了数据和操作数据的函数

2、泛型编程让类型参数化,使程序可以从逻辑功能上抽象。吧处理的对象当成参数来传递

右值引用,和左值的区别。

  1. 左值可以寻址,而右值不可以。
  2. 左值可以被赋值,右值不可以被赋值,可以用来给左值赋值。
  3. 左值可变,右值不可变(仅对基础类型适用,用户自定义类型右值引用可以通过成员函数改变)。C++的类和C里面的struct有什么区别?

析构函数可以为 virtual 型,构造函数则不能,为什么?

1、虚函数主要是用作多态,如果构造函数也用了,那么派生类必须在初始化列表给基类参数初始化

2、构造函数运行的时候对象的动态类型还不完整,没法确定没所以不能动态绑定。

C++的类和C里面的struct有什么区别?

c++中的类具有成员保护功能,并且具有继承,多态这类特点,而c里的struct没有
c里面的struct没有成员函数,不能继承,派生等等.

C++中空类默认产生哪些类成员函数?

1、构造函数

2、拷贝构造

3、析构函数

4、赋值运算符重载函数

5、取值运算符重载函数

6、const取址运算符重载函数

静态成员函数与非静态成员函数的区别

前者没有 this 指针,后者有 this 指针。
静态成员函数只要用来访问静态数据成员,而不访问非静态成员

1.9 面对对象

面向对象和面向过程有什么区别?

面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了;面向对象是把构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为。

1、面对对象以对象为中心,面向过程以过程为中心

2、面对对象把代码封装成一个整体,其他对象不能直接修改其数据。面向过程直接使用程序来处理数据,各模块存在控制与被控制的关系。

3、面对对象是将问题分为不同的对象,给予对象赋予属性和行为。面对过程则是将事件分为不同的步骤,按照步骤完成编程。

面对对象的基本特点

1、封装: 把过程和数据封装起来,只有定义的接口才能调用

2、继承:子类继承父类的功能

3、多态:不同的对象对从父类继承的同一动作做出不同的反应,

4、抽象:不打算了解问题全部,只关注当前目标。过程抽象和数据抽象。过程抽象是指任何操作都被当成实体看待,不在乎它是不是由其他子函数完成。

什么是深拷贝?什么是浅拷贝?

1、深拷贝复制一份

2、浅拷贝哟与可能共享成员变量

友元

1、友元函数:普通函数对一个访问某个类中的私有或者保护成员

2、友元类:类A中的成员函数访问类B中的私有或保护成员。

初始化列表和构造函数初始化的区别?

Example::Example() : ival(0), dval(0.0)  //初始化列表的构造函数
Example::Example()   //构造函数
   
ival = 0;   
dval = 0.0;

结果是一样的,使用初始化列表的构造函数表示 初始化类的成员,使用初始化列表的构造函数是对类成员的赋值,而不是初始化。所以一下情况需要对成员初始化所以必须用初始化列表的方法。

1、成员类型为没有默认构造函数的类

2、const成员或引用类型的成员

类的成员变量的初始化顺序是什么?

​ 1、成员变量在使用初始化列表初始化时,与构造函数中初始化成员列表的顺序无关,只与定义成员变量的顺序有关。

​ 2、不使用初始化列表的话就与构造函数有关。

Public继承、protected继承、private继承的区别?

1、public继承就是公有继承完还是公有,保护还是保护,私有还是私有

2、protected继承就是公有变保护,保护还是保护,私有还是私有

3、private继承就是所有变成私有

1.10 虚函数

虚函数注意内容

1、只需要在声明的函数体中使用关键字virtual将函数声明为虚函数,定义中不需要

2、基类某一成员为虚函数之后,派生类中的同名函数自动成为虚函数

3、非类的成员函数不能定义为虚函数,全局函数以及类的成员函数和构造函数也不能定义为虚函数,可以将析构函数定义为虚函数

什么函数不能声明为虚函数

主要有:普通函数(非成员函数);静态成员函数;类联成员函数;构造函数:友元函数。

1.11数据结构

链表和数组的区别

  1. 数组在内存中栈上按顺序存储的,而链表是在堆上随机存储的。

  2. 要访问数组中的元素可以按下标索引来访问,速度比较快,如果对他进行插入操作的话,就得移动很多元素,所以对数组进行插入操作效率很低. 由于连表是随机存储的,链表在插入,删除操作上有很高的效率(相对数组)

  3. 如果要访问链表中的某个元素的话,那就得从链表的头逐个遍历,直到找到所需要的元素为止,所以链表的随机访问的效率就比数组要低

2、ARM体系与架构

2.1 硬件基础

NAND FLASH 和NOR FLASH异同?

类别 读 写 擦除 可靠性 容量 用途 价格

NOR 快 慢 非常慢 比较高 小 保存代码 高

NAND 快 快 快 低 大 保存数据 低

CPU,MPU,MCU,SOC,SOPC联系与差别?

1、CPU:是一台计算机的运算核心和控制核心

2、MPU: 微处理器稍强的CPU

3、MCU:将计算机的CPU、RAM、ROM、定时计数器和多种I/O接口集成在一片芯片上。

4、SOC: 系统级芯片不单单是放简单的代码,可以放系统级的代码,也就是说可以运行操作系统

CPU中cache的作用?cache的基本组织结构?

(1)高速缓冲存储器Cache是位于CPU与内存之间的临时存储器,它的容量比内存小但交换速度快。

在Cache中的数据是内存中的一小部分,但这一小部分是短时间内CPU即将访问的,当CPU调用大量数据时,就可避开内存直接从Cache中调用,从而加快读取速度。由此可见,在CPU中加入Cache是一种高效的解决方案,这样整个内存储器(Cache+内存)就变成了既有Cache的高速度,又有内存的大容量的存储系统了。
(2)全相连映射,直接映射,组相连映射

交叉编译

​ 在一种计算机环境中运行的编译程序,能编译出在另外一种环境下运行的代码,我们就称这种编译器支持交叉编译。这个编译过程就叫交叉编译。

C/C++的编译包括几个部分

1、预编译:预处理器对c程序进行一些预处理工作,例如对宏定义的变量进行替换;

​ 1)将所有的#define删除,并展开所有的宏定义;

​ 2)处理所有的预编译指令,例如:#if,#elif,#else,#endif;

​ 3)处理#include预编译指令,将被包含的文件插入到预编译指令的位置;

​ 4)添加行号信息文件名信息,便于调试;

​ 5)删除所有的注释:// /**/;

​ 6)保留所有的#pragma编译指令,因为在编写程序的时候,我们经常要用到#pragma指令来设定编译器的状态或者是指示编译器完成一些特定的动作;

​ 最后生成.i文件;

​ 总的来说,包括(1)去注释 (2)宏替换 (3)头文件展开 (4)条件编译

2、编译:编译器将c语言程序翻译成汇编语言程序;

​ 1)扫描,语法分析,语义分析,源代码优化,目标代码生成,目标代码优化;

​ 2)生成汇编代码;

​ 3)汇总符号;

​ 4)生成.s文件;

3、汇编:汇编语言通过汇编器编译成可重定位目标程序.o,与之相反称为反汇编;

​ 1)根据汇编指令和特定平台,把汇编指令翻译成二进制形式;

​ 2)合并各个section,合并符号表;

​ 3)生成.o文件;

4、链接:将目标文件和所需的库函数用链接器进行链接,常见的链接器有Unix;

​ 1)合并各个.obj文件的section,合并符号表,进行符号解析;

​ 2)符号地址重定位;

​ 3)生成可执行文件;

描述一下嵌入式基于ROM的运行方式和基于RAM的运行方式有什么区别?

基于RAM

1、将硬盘或者其介质的代码加载到ram中。

2、速度快但是可用RAM少,因为自身的空间要存一部分代码

基于ROM:

1、将部分代码搬到RAM中去,所以可用RAM资源比基于RAM的多。

2.2 中断与异常

中断与异常区别

1、中断是指外部硬件产生的一个电信号从CPU的中断引脚进入,打断CPU的运行,异常是指软件运行过程中发生了一些必须作出处理的事件,CPU自动产生一个陷入来打断CPU的运行。

2、异常处理的时候要考虑与处理器的时钟同步,异常被称为同步中断

中断能不能睡眠 为什么?

1、一般说中断上下文中不能睡眠,这个中断是指硬件事件发生,触发CPU停止当前活动转而去处理硬件请求.

2、根据硬件请求响应处理逻辑的实时紧要与否,将整个中断处理过程分为上半部和下半部.上半部也就是所谓的硬中断处理逻辑,其要求cpu在收到硬件请求后必须马上处理的事情,比如网卡收到数据包了,把数据包从网卡缓存拷贝到主存(可以由DMA完成,但寄存器的修改以及资源设定还是要由cpu去做)的逻辑就需要cpu立即去做,不做的话,网络新来的数据包就可能丢失.所以这些紧要操作逻辑为硬中断处理.
3、下半部有很多种机制,其中就包括软中断,还有tasklet,workqueue等,软中断只是其中的一种,由于历史的原因,有时候是混淆称呼下半部和软中断的.
4、而可以看到软中断逻辑不属于任何进程,所以才不能睡眠,因为一旦睡眠,cpu切换出去,就切不回来了。

简单说就是:唤醒函数针对进程而言的,下半部的中断不属于进程,所以无法被唤醒

中断的响应执行流程是什么?

cpu接受中断->保存中断上下文跳转到中断处理历程->执行中断上半部->执行中断下半 部->恢复中断上下文。

写一个中断服务需要注意哪些?如果中断产生之后要做比较多的事情你是怎么做的?

1、快进快出,在中断服务函数里尽量快速采集信息。

2、中断中不能有阻塞操作

3、中断服务函数注意返回值,使用操作系统定义的宏,而不是自己定义的。

4、做的事情较多,将这些任务放在后半段tasklet处理。

中断和轮询哪个效率高?怎样决定是采用中断方式还是采用轮询方式去实现驱动?

1、中断是CPU处于被动状态下来接受设备的信号,而轮询是CPU主动去查询该设备是否有请求。
2、请求设备是一个频繁请求cpu的设备,或者有大量数据请求的网络设备,那么轮询的效率是比中断高。

3、如一般设备,并且该设备请求cpu的频率比较低,则用中断效率要高一些。主要是看请求频率。

2.3 通讯协议

异步传输与同步传输?

异步传输:是一种典型的基于字节的输入输出,数据按每次一个字节进行传输,其传输速度低。
同步传输:需要外界的时钟信号进行通信,是把数据字节组合起来一起发送,这种组合称之为帧,其传输速度比异步传输快。

RS232和RS485区别?

  1. 传输方式不同。 RS232采取不平衡传输方式,即所谓单端通讯。 而RS485则采用平衡传输,即差分传输方式。
  2. 传输距离不同。RS232适合本地设备之间的通信,传输距离一般不超过20m。而RS485的传输距离
    为几十米到上千米。
  3. 设备数量。RS232 只允许一对一通信,而RS485 接口在总线上是允许连接多达128个收发器。
  4. 连接方式。RS232,规定用电平表示数据,因此线路就是单线路的,用两根线才能达到全双工的目的;而RS485, 使用差分电平表示数据,因此,必须用两根线才能达到传输数据的基本要求,要实现全双工,必需用4根线

SPI协议

SPI:高速全双工串行总线。

接口:输出线、输入线、时钟线、片选信号线

1、片选信号线由高到低是SPI的起始信号 ,从机检测到自己的NSS线起始信号之后就知道自己被选中了,然后由低到高是停止信号。

2、SPI 使用 MOSI 及 MISO 信号线来传输数据,使用 SCK 信号线进行数据同步。 在时钟线上升沿触发输出,在下降沿被采样。

IIC协议

1、IIC协议是由数据线SDA和时钟SCL构成的串行总线,可发送和接收数据,是一个多主机的半双工通信方式

2、空闲状态

SDA与SCL都处于高电平,就是空闲状态。

2、起始信号

时钟线为高,数据线由高到低就是启动信号,只能由主机发起空闲状态下才能启动该信号

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HEH6Q7Kx-1669710623167)(C:\\Users\\61769\\AppData\\Roaming\\Typora\\typora-user-images\\image-20220601093452355.png)]

3、停止信号

时钟为高,数据线由低到高就是停止信号

4、传输数据格式

SCL为高就会获取SDA数据值,SDA在这期间必须稳定

SCL为低便是SDA电平变化状态,在此期间SDA可以自由变化

可以主动拉低SCL让IIC进入等待状态知道处理结束再释放SCL数据传输会继续

5、ACK应答信号

发送方在第9个时钟脉冲奇迹爱你释放SDA数据,当接收方接收成功时,会输出一个应答信号,低电平有效

6、写操作

start信号–设备地址–方向(读、写)。回应(确定这个设备是否存在)–发送数据–回应–发送完之后主芯片发送一个停止信号。

白色主到从、灰色从到主。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GWFkECVR-1669710623168)(C:\\Users\\61769\\AppData\\Roaming\\Typora\\typora-user-images\\image-20220601095654891.png)]

7、读操作

除了数据需要主到从,其余差不多。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IMX1TAyk-1669710623169)(C:\\Users\\61769\\AppData\\Roaming\\Typora\\typora-user-images\\image-20220601100048451.png)]

嵌人式编程中,什么是大端?什么是小端?

大端模式:低位字节存在高地址上,高位字节存在低地址上。
小端模式:高位字节存在高地址上,低位字节存在低地址上。

//第一种
union w
    int a;
    char b;
c;
c.a = 1;
if(c.b==1) printf("小端");
else printf("大端");
//第二种
int a = 0x12345678;
char *p = (char *)&a;
if(*p==0x78)printf("小端");
else printf("大端");



3、Linux驱动

3.1 指令

Linux指令

查看当前进程 ps;

执行退出 exit;

查看当前路径 pwd;

查看目录 ls -a显示所有文件及目录,-l详细列出

创建目录 mkdir;

创建文件 vi 、 touch;

查看文件内容 vi,cat ;

屏幕输出 echo;

常用的GCC命令

gcc -E test.c -o test.i #把预处理的结果导出到test.i文件

gcc -S test.i -o test.s #编译器将test.i翻译成汇编语言,并将结果存储在test.s文件中。

gcc -c test.s -o test.o #将汇编代码编译为目标文件(.o)但不链接

gcc test.o -o test #将生成的目标文件test.o生成最终的可执行文件test

gcc test.c -o test #将源文件test.c编译链接为可执行文件test

gcc test1.c test2.c -o test 多文件编译

常用的GDB调试指令

gcc -g test.c -o test #编译时生成debug有关的程序信 就是说正常编译不能使用GDB

list 查看源码

next #单步调试(逐过程,函数直接执行),简写n
step #单步调试(逐语句:跳入自定义函数内部执行),简写s

run #运行程序

break + num #设置第num行 为断点

continue #继续运行到下一个断点。

display 追踪具体变量值

delete breakpoints num #删除第num个断点

常用的驱动开发指令

insmod\\modprobe 加载驱动

rmmod #卸载驱动

lsmod #查看已有的字符设备信息

cat /proc/interrupt #查看已有的中断号

Makefile

经典malefile main包含了input、calcu的头文件

objects = main.o input.o calcu.o
main: $(objects)
	gcc -o main $(objects)
%.o : %.c
	gcc -c $<
clean:
	rm *.o
	rm main 

shell相关操作

要求

A、在Linux操作系统启动的时候,自动加载/mnt/test/test程序。
B、当test异常退出之后,自动重新启动。
C、当test程序重启次数超过100次,自动复位操作系统。
假设你所拥有的资源:
A、目标机器是一台具有标准shell的嵌入式计算机,CPU为ARM7 56MB,内存16MB,软件环境基于Linux2.6.11和BusyBox1.2构建。
B、当前已有11个用户进程在运行,占用了大部分的CPU时间和内存,你可使用的内存只有2MB左右,CPU时间由系统分派。

#!/bin/sh

#load *.so that may need
if [ -r /sbin/ldconfig ]; then
ldconfig
fi

#add the libs PATH that may need
export LD_LIBRARY_PATH="/lib"

#count is the counter of test started times
count=0

#main loop
while [ 1 ] ;do
#add execute property for /mnt/test/test
chmod +x /mnt/test/test
#start test
/mnt/test/test
#the running times counter
let count=count+1
echo "test running times is $count"
#Is test running too many times?
if [ "$count" -gt 100 ]; then
echo "Will reboot because of test running too many times"
reboot
fi
#wait for test stoping...
sleep 3
done

3.2 uboot

bootloader

1、Linux启动需要一个bootloader程序,初始化时钟、中断或者其他外设,然后将Linux内核从flash拷贝到SDRAM中,最后启动Linux内核。

2、Bootloader就是一小段程序,它在系统上电时开始执行,初始化硬件设各、准备好软件环境,最后调用操作系统内核。

uboot启动流程

u-boot系统启动流程 ,大多数bootloader都分为stage1和stage2两部分, u-boot也不例外。

依赖于CPU体系结构的代码(如设备初始化代码等)通常都放在stage1且可以用汇编语言来实现,而stage2则通常用C语言来实现,这样可以实现复杂的功能,而且有更好的可读性和移植性。

1、Stage1 start.S代码结构 u-boot的stage1代码通常放在start.S文件中,他用汇编语言写成,其主要代码部分如下

(1) 定义入口:

​ 该工作通过修改连接器脚本来完成。

(2)设置异常向量(Exception Vector)。

(3)设置CPU的速度、时钟频率及终端控制寄存器。

(4)初始化内存控制器。

(5)将ROM中的程序复制到RAM中。

(6)初始化堆栈。

(7)转到RAM中执行,该工作可使用指令ldr pc来完成

2、Stage2

C语言代码部分 lib_arm/board.c中的start arm boot是C语言开始的函数也是整个启动代码中C语言的主函数,同时还是整个u-boot(armboot)的主函数,该函数只要完成如下操作:

(1)调用一系列的初始化函数。

(2)初始化Flash设备。

(3)初始化系统内存分配函数。

(4)如果目标系统拥有NAND设备,则初始化NAND设备。

(5)如果目标系统有显示设备,则初始化该类设备。

(6)初始化相关网络设备,填写IP、MAC地址等。

(7)进去命令循环(即整个boot的工作循环),接受用户从串口输入的命令,然后进行相应的工作

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MW4Cn5Yr-1669710623169)(C:\\Users\\61769\\AppData\\Roaming\\Typora\\typora-user-images\\image-20220216125138086.png)]

uboot启动过程中做了那些事?

1、初始化时钟,关闭看门狗,关中断,启动ICACHE,关闭DCACHE和TLB,关闭MMU,初始化SDRAM,初始化NAND FLASH,重定位。

2、初始化一个串口,检测系统内存映射,将内核映象和根文件系统映象从 Flash上读到SDRAM空间中,为内核设置启动参数,调用内核。

uboot和内核如何完成参数传递?

1、完成相关设置:CPU寄存器设置 R0=0 R1=机器类型ID R2=启动参数标记列表在RAM中起始基地址,设置禁止中断,SVC模式(超级用户模式,有利于硬件初始化)MMU关闭

2、uboot把机器ID通过R1传递给内核,R2存放块内存的基地址,这块内存主要存放uboot给Linux内核的其他参数,参数很多所有需要按规定存放,标记是一种数据结构。

3、标记的数据结构为tag,它由一个tag_header结构和一个联合(union)组成

为什么uboot要关掉caches?

caches是cpu内部的一个2级缓存,它的作用是将常用的数据和指令放在cpu内部。caches是通过CP15
管理的,刚上电的时候,cpu还不能管理caches。上电的时候指令cache可关闭,也可不关闭,但数据
cache一定要关闭,否则可能导致刚开始的代码里面,去取数据的时候,从cache里面取,而这时候RAM中数据还没有caches过来,导致数据预取异常 。

3.3 文件系统

什么是根文件系统?

1、内核启动时所挂载(mount)的第一个文件系统,内核代码的映像文件保存在根文件系统中。

2、挂载之后会把一些初始化脚本和服务加载到内存中去运行。

根文件系统为啥这么重要?

1、根文件系统包含系统启动时所必须的目录和关键性的文件,以及使其他文件系统得以挂载(mount)所必要的文件。比如shell命令程序必须运行在根文件系统上,譬如ls、cd等命令。

2、一套linux体系,只有内核本身是不能工作的,必须要rootfs(上的etc目录下的配置文件、/bin
/sbin等目录下的shell命令,还有/lib目录下的库文件等)相配合才能工作。

3.4 中断

硬中断 / 软中断是什么?有什么区别?

1、硬中断是由硬件产生的,软中断是执行中断指令产生的。

2、硬中断可以直接中断CPU,软中断并不会直接中断CPU。也只有当前正在运行的代码(或进程)才会产生软中断。

3、硬中断可屏蔽、软中断不可屏蔽

4、硬中断又称上半部,要快速完成任务

中断为什么要区分上半部和下半部?

1、调用过程:外部中断产生->发送中断信号到中断控制器->通知处理器产生中断的中断号

2、为了能被新的中断打断。将中断处理一分为二,上半部登记新的中断,处理快速简单的任务,复杂耗时的任务给下半段处理,所以下半段可以被打断。

3、中断下半部一般使用tasklet或工作队列实现

linux中断的响应执行流程?

cpu接受中断->保存中断上下文跳转到中断处理历程->执行中断上半部->执行中断下半部->恢复中断上下文。

3.5 Linux驱动模型

字符设备驱动模型

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-a6VRcWwQ-1669710623169)(C:\\Users\\61769\\AppData\\Roaming\\Typora\\typora-user-images\\image-20220601201707320.png)]

请简述主设备号和次设备号的用途

主设备号:主设备号标识设备对应的特定的驱动程序。虽然现代的linux内核允许多个驱动程序共享主设备号,但我们看待的大多数设备仍然按照“一个主设备对应一个驱动程序”的原则组织。

**次设备号:**次设备号由内核使用,用于确定由主设备号对应驱动程序中的各个设备。依赖于驱动程序的编写方式,我们可以通过次设备号获得一个指向内核设备的直接指针,也可将此设备号当作设备本地数组的索引。(多个设备共用一套程序的话,主设备号代表这个驱动程序,每个设备一个次设备号)

创建设备文件

1、手动创建

mknod /dev/led c 250 0 ,其中dev/led 为设备节点 ,c 代表字符设备, 250代表主设备号, 0代表次设备号。

设备驱动程序中如何注册一个字符设备?

1、将cdev结构嵌入到自己的设备特定结构中

void cdev_init(struct cdev *cdev, struct file_operations *fops)

2、早期注册函数

int register_chrdev(unsigned int major, const char *namem , struct file operations *fopen);

Linux设备中字符设备和块设备有什么主要区别?

**字符设备:**提供连续的数据流,应用程序可以顺序读取,通常不支持随机存取。

**块设备:**应用程序可以随机访问设备数据,程序可自行确定读取数据的位置。

驱动中操作物理绝对地址为什么要先ioremap?

1、ioremap是将io地址空间映射到虚拟地址空间上去,便于操作。

2、因为保护模式下的cpu只认虚拟地址,不认物理地址,所以你要操作外设上的寄存器必须先映射到虚拟内存空间,拿着虚拟地址去跟cpu对接,从而操作寄存器。

Linux移植ARM的基本步骤和完成的任务

1)首先是准备工作,包括下载源码、建立交叉编译环境等;
2)然后是配置和编译内核,必要时还要对源码做一定的修改;
3)第三步就是需要制作文件系统(如RAM disk)来挂接根文件系统;
4)最后是下载、调试内核并在fs中添加自己的应用程序。

ARM-linux启动分几部分,简述流程:

ARM-linux启动分为四个部分:引导加载程序(bootloader),Linux内核,文件系统,应用程序。

bootloader是系统启动和复位后执行的第一段代码,它主要用来初始化处理器及外设,然后调用Linux内核。Linux内核在完成系统的初始化之后需要挂载某个文件系统作为根文件系统(root filesystem)。根文件系统是Linux系统的核心组成部分,它可以作为Linux系统中文件和数据的存储区域,通常它还包括配置文件运行应用程序所需要的库。应用程序实现该嵌入式产品所要实现的目标。

4、操作系统

什么是进程?什么是线程?

进程是资源分配的基本单位,它是程序执行时的一个实例,在程序运行时创建。
线程是程序执行的最小单位,是进程的一个执行流,一个线程由多个线程组成的。

进程和线程有什么区别?

1、进程是资源分配的基本单位,线程是程序运行的基本单位

2、进程有自己的资源空间,线程是共享进程中的数据,所以进程切换开销更大一点

3、线程通讯要简单一些,因为共享全局变量等

4、线程执行开销小,进程执行开销大。

5、多线程中一个

以上是关于豆芽八股专栏之嵌入式的主要内容,如果未能解决你的问题,请参考以下文章

Linux——Linux驱动之iMX6ULL硬件平台下使用MfgTool工具进行系统烧写的原理及步骤总结(ubootkerneldtbrootfs)

Linux——Linux驱动之iMX6ULL硬件平台下使用MfgTool工具进行系统烧写的原理及步骤总结(ubootkerneldtbrootfs)

Linux——Linux驱动之完整的驱动操作硬件基础实践,Linux下如何像裸机那样通过GPIO控制蜂鸣器

Linux——Linux驱动之完整的驱动操作硬件基础实践,Linux下如何像裸机那样通过GPIO控制蜂鸣器

C++基础——C++面向对象之数据封装数据抽象与接口基础总结

嵌入式八股文汇总