1. C/C++内存有哪几种类型?
C中,内存分为5个区:
堆(malloc)、栈(如局部变量、函数参数)、程序代码区(存放二进制代码)、全局/静态存储区(全局变量、static变量)和常量存储区(常量)。此外,C++中有自由存储区(new)一说。
2.C语言之标准C语言32个关键字:
1、基本数据类型:
signed unsignedchar int float double short long void
2、构造数据类型:
struct union enum
3、数据存储类别:
auto static extern register
4、数据优化:
const volatile
5、9条基本语句:
if else switch case break default while do for return continue goto
6、其它:
typedef sizeof
预编译处理符 “#”
#include
#define
#ifdef
#ifndef
#if
#else
#else if
#endif
等等。
3. C和C++的区别?
1). C++是C的超集;
2). C是一个结构化语言,它的重点在于算法和数据结构。C程序的设计首要考虑的是如何通过一个过程,对输入(或环境条件)进行运算处理得到输出(或实现过程(事务)控制),而对于C++,首要考虑的是如何构造一个对象模型,让这个模型能够契合与之对应的问题域,这样就可以通过获取对象的状态信息得到输出或实现过程(事务)控制。
4.新增关键字和运算符:
C++在 C 语言基础上新增了一些保留字: class、 friend、 virtual、 inline、 private、public、 protected、 this、 string;也新增了一些运算符: new、 delete、 operator::。
5:命名空间:
使用标准命名空间:
using namespace std; //引入标准命名空间 使用标准命名空间,标准库中类以及函数等全部放在 std 的命名空间中
自定义命名空间:
namespace 命名空间名字 {
变量、函数等
}
使用作用域限定符 :
:: - 作用域限定符,相当于"的"
如:std::cout << “大家好才是真的 好”;
无名名字空间 :
如果一个标示符没有被置于任何名字空间中,则默认为无名/匿名名字空间中,可以 使用如下形式去访问标示符:
::无名名字空间成员名
6:结构体、联合、枚举的不同:
结构体的不同:
C++中:
struct Student{…};
[struct] Student s;
C++可以省略关键字,并且可以在内部定义函数;
联合体的不同:
(1)定义联合变量的时候可以省略 union 关键字
(2)支持匿名联合
枚举的不同之处 :
(1)枚举类型在定义变量时也可以省略 enum 关键字
(2)C 语言的枚举 本质上就是整型,可以使用整数进行赋值 C++中的枚举 是一种独立的数据类型,不能使用整数进行赋值
(int 类型的值域 比 枚举类型的大)
7:布尔类型 以及 引用:
布尔类型:
C语言中bool类型为int型;
C++中bool类型为一个单字节整数:0 1 true false
引用类型
引用并不是一种独立的数据类型,类似于 C 中的指针,其实是变量的别名
作为参数进行传递的时候,使用引用并不会开辟一个内存空间;
而使用指针进行传参的时候会开辟一个内存空间;引用比使用指针更加安全;
(1)引用必须初始化,指针可以不初始化
(2)引用不可以为空,指针可以为空
(3)引用不可以更换目标,指针可以
(4)可以定义指向指针的指针,但是不可以定义指向引用的指针
引用作为函数的返回值
永远不要返回对局部变量(包括函数的形参)的引用,可以返回 静态局部变量/全局 变量/动态内存/实参的引用/成员变量 的引用,这些返回值都是安全的
8:类型转换与动态分配内存:
.类型转换 隐式类型转换:
一般从小 -> 大 显式类型转换(强制类型转换):
一般从大到小的转换 如:
char c ; int i = (int)c; //C 语言风格
int i = int©; //C++风格
四种类型转换符(转换算子)
(1)静态类型转换:
目标类型 变量名 = static_cast<目标类型>(源类型变量);
如果在目标类型和源类型之间只要有一个方向上可以做隐式类型转换,那么在两个 方向上都可以做静态类型转换;反之,如果在两个方向上都不能做隐式类型转换,那么在 两个方向上都不能做静态类型转换
(2)常量类型转换:
目标类型 变量名 = const_cast<目标类型>(源类型变量);
功能:主要用于去除指针/引用上的常属性
(3)重解释类型转换:
目标类型 变量名 = reinterpret_cast<目标类型>(源类型变量名);
功能:主要用于任意两种指针类型之间的转换,以及指针和整型之间的转换
(4)动态类型转换:
目标类型 变量名 = dynamic_cast<目标类型>(源类型变量名); 功能:主要用于具有多态特性的父子类 指针/引用 之间的转换(以后讲到)
9:函数的重载、缺省参数、哑元以及内联:
(1)重载的概念:
在同一个作用域中,函数名相同,函数的参数列表不同构成重载关系,在不同的作用域 中遵循标示符隐藏原则
(2)函数重载的方式
a.函数名相同,参数类型不同
b.函数名相同,参数个数不同
c.函数名相同,参数顺序不同
d.函数名相同,const 修饰的常函数和普通成员函数构成重载;
哑元: 只有数据类型没有名称的参数叫哑元
如:
void fn(int){}
fn(); //error
fn(66); // ok
用途:(1)为了兼容以前的代码
(2)用于运算符重载,主要用于区分前后缀自增减运算符
内联函数:
使用 inline 关键字修饰的函数叫做内联函数,
面试官经常考察其和C语言当中的宏进行比较;
类似于 C 语言中的宏函数,可以进行指令的替换,相对于宏函数有一定的优势,可以检 查数据类型,可以计算表达式的值等等;
注意:
inline 关键字修饰函数,仅仅表示这是一种建议而不是要求,所有使用 inline 关键字 修饰的函数不一定会做内联的处理;反之,没有使用 inline 关键字修饰的函数也可能根据 编译器的优化策略进行内联处理
(1)多次调用的小而简单的函数适合内联
(2)调用次数极少并且大而复杂的函数不适合内联
(3)递归函数无法内联
10:什么是面向对象?面向对象的意义是什么?
面向对象是一种对现实世界理解和抽象的方法、思想,通过将需求要素转化为对象进行问题处理的一种思想。其核心思想是数据抽象、继承和动态绑定(多态)。
面向对象的意义在于:将日常生活中习惯的思维方式引入程序设计中;将需求中的概念直观的映射到解决方案中;以模块为中心构建可复用的软件系统;提高软件产品的可维护性和可扩展性。
11:什么时候生成默认构造函数(无参构造函数)?什么时候生成默认拷贝构造函数?什么是深拷贝?什么是浅拷贝?默认拷贝构造函数是哪种拷贝?什么时候用深拷贝?
1). 没有任何构造函数时,编译器会自动生成默认构造函数,也就是无参构造函数;当类没有拷贝构造函数时(用同类型对象进行初始化的时候),会生成默认拷贝构造函数。
2).浅拷贝是指对象进行赋值时,对对象的数据成员进行简单的赋值,默认拷贝构造函数就是一种浅拷贝,大多情况下,浅拷贝已经可以很好的工作了,但当对象中一旦出现了动态成员,那么浅拷贝就会出错
深拷贝:相当于浅拷贝,不仅对对象的数据成员进行赋值,同时也为动态成员开辟新的空间;
3). 当系统中有成员指代了系统中的资源时,需要深拷贝。比如指向了动态内存空间,打开了外存中的文件或者使用了系统中的网络接口等。如果不进行深拷贝,比如动态内存空间,可能会出现多次被释放的问题。是否需要定义拷贝构造函数的原则是:类是否有成员调用了系统资源,此时定义拷贝构造函数,一定是定义深拷贝,否则没有意义。
12:初始化列表的重要性:
成员变量的初始化顺序和初始化列表中的先后顺序无关,而是取决于成员变量的定 义顺序;
1)类中包含常量和引用型的成员变量,那么必须通过构造函数和初始化列表的形式 进行初始化
2)在子类中显式地构造其基类部分(以后讲到)
13:析构函数:
对于全局变量/局部变量/块变量,当生命周期结束时自动调用析构函数;而对 于堆区的对象来说,当 delete 时会自动调用析构函数
14:构造函数和析构函数的执行顺序:
构造函数:
1). 首先调用父类的构造函数;
2). 调用成员变量的构造函数;
3). 调用类自身的构造函数。
析构函数:
对于栈对象或者全局对象,调用顺序与构造函数的调用顺序刚好相反,也即后构造的先析构。对于堆对象,析构顺序与delete的顺序相关。
15:友元类和友元函数:
概念:可以通过 friend 关键字,把一个全局函数、另一个类的成员函数或者另一个类 整体,声明为某个类的友元
格式:
friend 返回值类型 函数名(参数);
friend 类名;
友元声明可以出现在授权类的公有、私有或者保护等任何区域,且不受访问 控制限定符的约束
16:this指针:
什么是this指针?作用是什么?
答案其实在于类的成员函数本身就有一个隐含的参数,该参数就是所谓的this指针。在不同的对象调用的时候,编译器会自动将该对象的地址赋予“this”
this指针是C++类成员函数的一个隐含参数,当对象调用该成员函数的时候,编译器会将该对象的地址作为参数传入,通过this指针来对不同类进行操作,进而实现各个功能。
this指针存在哪里?
this指针是类成员函数的一个隐含参数,不需要人为操作,因此它与其它参数不同,不存于栈中,而是在寄存器里。
this指针可以为空吗?
this指针可以为空,但有条件,当我们要进行的操作不需要this指针去指向某个对象(例如仅仅是打印一个东西)的时候就可以令它为空
17:常函数和常对象:
常函数 :
在类成员函数的形参表之后,函数体之前加上 const 关键字,该成员函数的 this 指针即具有常属性,这样的成员函数被称为常函数
格式:
class 类名 {
返回类型 函数名 (形参表) const {
函数体;
}
};
在常函数内部无法修改成员变量的值,除非该成员 变量被 mutable 关键字修饰
常对象:
被 const 关键字修饰的对象、对象指针或对象引用,统称为常对象
例如:
const Student s(“张飞”);
const Student *ps = &s;
const Student *rs = s;
注意:
1)通过常对象只能调用常函数,通过非常对象既可以调用常函数,也可以调 用非常函数
2)原型相同的成员函数,常版本和非常版本构成重载
a.常对象只能选择常版本
b.非常对象优先选择非常版本,如果没有非常版本,也能选择常版本
18:静态成员:
用 static 修饰的成员为静态成员。
class A {
static int num;
};
1)静态成员属于类而不属于对象
静态成员依然受类作用域和访问控制限定符的约束
2) 静态成员变量为该类的所有对象实例所共享
3) 访问静态成员,既可以通过类也可以通过对象
4) 静态成员函数只能访问静态成员,而非静态成员函数既可以访问静态成员,也可以 访问非静态成员
注意:事实上,类的静态成员变量和静态成员函数,更象是普通的全局变量和全局函数, 只是多了一层类作用域和访问控制属性的限制,相当于具有成员访问性的全局变量和全局函数
19:解释下封装、继承和多态?
1). 封装:
封装是实现面向对象程序设计的第一步,封装就是将数据或函数等集合在一个个的单元中(我们称之为类)。
封装的意义在于保护或者防止代码(数据)被我们无意中破坏。
2). 继承:
继承主要实现重用代码,节省开发时间。
子类可以继承父类的一些东西。
a. 公有继承(public):
公有继承的特点是基类的公有成员和保护成员作为派生类的成员时,它们都保持原有的状态,而基类的私有成员仍然是私有的,不能被这个派生类的子类所访问。
b. 私有继承(private):
私有继承的特点是基类的公有成员和保护成员都作为派生类的私有成员,并且不能被这个派生类的子类所访问。
c. 保护继承(protected):
保护继承的特点是基类的所有公有成员和保护成员都成为派生类的保护成员,并且只能被它的派生类成员函数或友元访问,基类的私有成员仍然是私有的。
3). 多态:
了解多态之前首先了解一下虚函数
虚函数:
使用 virtual 关键字修饰的函数称为虚函数;
形如:
class 类名
{
virtual 返回类型 函数名 (形参表) { … }
};
虚函数覆盖:
如果子类的成员函数和基类的虚函数具有相同的函数原型,那么该成员函数就也是 虚函数,无论其是否带有 virtual 关键字,且对基类的虚函数构成覆盖;
有效的虚函数覆盖需要满足如下条件:
a.该函数必须是成员函数,既不能是全局函数也不能是静态成员函数
b.该函数必须在基类中用 virtual 关键字声明为虚函数拥有完全相同的签名,即函数名、形参表和常属性严格一致
多态:
1)如果子类提供了对基类虚函数的有效覆盖,那么通过一个指向子类对象的基类 指针,或者引用子类对象的基类引用,调用该虚函数,实际被调用的将是子类中的覆盖 版本,而非基类中的原始版本,这种现象称为多态
2)多态的重要意义在于:一般情况下,调用哪个类的成员函数是由调用者指针或 引用本身的类型决定的,而当多态发生时,调用哪个类的成员函数则完全由调动者指针 或引用的实际目标对象的类型决定
20:在什么情况下,析构函数需要是虚函数?
若存在类继承关系并且析构函数中需要析构某些资源时,析构函数需要是虚函数,否则当使用父类指针指向子类对象,在delete时只会调用父类的析构函数,而不能调用子类的析构函数,造成内存泄露等问题
请你回答一下为什么析构函数必须是虚函数?为什么C++默认的析构函数不是虚函数?
将可能会被继承的父类的析构函数设置为虚函数,可以保证当我们new一个子类,然后使用基类指针指向该子类对象,释放基类指针时可以释放掉子类的空间,防止内存泄漏。
C++默认的析构函数不是虚函数是因为虚函数需要额外的虚函数表和虚表指针,占用额外的内存。而对于不会被继承的类来说,其析构函数如果是虚函数,就会浪费内存。因此C++默认的析构函数不是虚函数,而是只有当需要当作父类时,设置为虚函数。
内联函数、构造函数、静态成员函数可以是虚函数吗?
都不可以。内联函数需要在编译阶段展开,而虚函数是运行时动态绑定的,编译时无法展开; 构造函数在进行调用时还不存在父类和子类的概念,父类只会调用父类的构造函数,子类调用子类 的,因此不存在动态绑定的概念;静态成员函数是以类为单位的函数,与具体对象无关,虚函数是 与对象动态绑定的,因此是两个不冲突的概念;
构造函数中可以调用虚函数吗?
可以,但是没有动态绑定的效果,父类构造函数中调用的仍然是父类版本的函数,子类中调用的仍然是子类版本的函数
21.内存分配的方式有几种?
1.从静态存储区上分配内存:
内存在程序编译的时候就已经分配好了,这块内存在程序整个运行期间都存在。例如:全局变量
2.在栈上进行创建,在执行函数时,函数内部的存储单元都可以在栈上进行创建,函数执行结束后,这些存储单元将会被释放。
3.从堆上进行分配(动态内存分配):
程序在运行时使用malloc和new申请任意内存大小,程序员自己负责何时free或delete释放内存,动态内存的使用由我们确定,使用非常灵活,但存在问题较多。
22.函数的调用过程:
如下结构的代码:
int main(void)
{
...
d = fun(a, b, c);
cout<<d<<endl;
...
return 0;
}
调用fun()的过程大致如下:
main()
1).参数拷贝(压栈),注意顺序是从右到左,即c-b-a;
2).保存d = fun(a, b, c)的下一条指令,即cout<
fun()=====
4).移动ebp、esp形成新的栈帧结构;
5).压栈(push)形成临时变量并执行相关操作;
6).return一个值;
7).出栈(pop);
8).恢复main函数的栈帧结构;
9).返回main函数;
main…
23.什么是内存泄漏?面对内存泄漏和指针越界,你有哪些方法?你通常采用哪些方法来避免和减少这类错误?
用动态存储分配函数动态开辟的空间,在使用完毕后未释放,结果导致一直占据该内存单元即为内存泄露。
malloc的时候要确定在哪里free;
指针使用完毕后将指针指向为NULL;
24.C++中有了malloc / free , 为什么还需要 new / delete?
1). malloc与free是C++/C语言的标准库函数,new/delete是C++的运算符。它们都可用于申请动态内存和释放内存。
2). 对于非内部数据类型的对象而言,光用maloc/free无法满足动态对象的要求。对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。
由于malloc/free是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于malloc/free。因此C++语言需要一个能完成动态内存分配和初始化工作的运算符new,以一个能完成清理与释放内存工作的运算符delete。注意new/delete不是库函数。
25.用预处理指令#define 声明一个常数,用以表明1年中有多少秒(忽略闰年问题)
#define SECONDS_PER_YEAR (60 * 60 * 24 * 365ul)
26.写一个标准宏MIN,输入两个参数,返回较小的一个:
#define MIN(a,b)((a)>=(b)?(b):(a))
27.已知数组table,用一个宏求出数组元素个数:
#define NTBL(table) (sizeof(table)/sizeof(table[0]))
1.请简单介绍一下Linux的启动进程:
嵌入式Linux系统从软件角度出发,可以分为四个部分:
引导加载程序(bootloader);
Linux内核;
文件系统;
应用程序;
其中bootloader是系统启动或复位以后执行的第一段代码;它主要用来初始化处理器及外设,然后调用Linux内核;
Linux内核完成系统的初始化之后需要挂载某个文件系统作为根文件系统(Root Filesystem);
根文件系统是Linux系统核心组成部分,它可以作为Linux系统中文件和数据的存储区域,通常它还包括系统配置文件和运行应用程序所需要的库;
应用程序可以说是嵌入式系统的灵魂,它所实现的功能通常就是设计该嵌入式系统所要达到的目标,如果没有应用程序的支持,任何硬件上设计精良的嵌入式系统都没有了实用意义。
2.请说说BootLoader的作用:
(1)初始化ARM:
因为 Linux 内核一般都会在 RAM 中运行,所以在调用 Linux 内核之前 bootloader 必须设置和初始化 RAM,为调用 Linux内核做好准备。初始化 RAM 的任务包括设置CPU 的控制寄存器参数,以便能正常使用 RAM 以及检测RAM 大小等
(2)初始化串口:
串口在 Linux 的启动过程中有着非常重要的作用,它是 Linux内核和用户交互的方式之一。Linux 在启动过程中可以将信息通过串口输出,这样便可清楚的了解 Linux 的启动过程。虽然它并不是 Bootloader 必须要完成的工作,但是通过串口输出信息是调试Bootloader 和Linux 内核的强有力的工具,所以一般的 Bootloader 都会在执行过程中初始化一个串口做为调试端口
(3)检测处理器类型:
Bootloader在调用 Linux内核前必须检测系统的处理器类型,并将其保存到某个常量中提供给 Linux 内核。Linux 内核在启动过程中会根据该处理器类型调用相应的初始化程序
(4)设置 Linux启动参数:
Bootloader在执行过程中必须设置和初始化 Linux 的内核启动参数。目前传递启动参数主要采用两种方式:即通过 struct param_struct 和struct tag(标记列表,tagged list)两种结构传递。struct param_struct 是一种比较老的参数传递方式,在 2.4 版本以前的内核中使用较多。从 2.4 版本以后 Linux 内核基本上采用标记列表的方式。但为了保持和以前版本的兼容性,它仍支持 struct param_struct 参数传递方式,只不过在内核启动过程中它将被转换成标记列表方式。标记列表方式是种比较新的参数传递方式,它必须以 ATAG_CORE 开始,并以ATAG_NONE 结尾。中间可以根据需要加入其他列表。Linux内核在启动过程中会根据该启动参数进行相应的初始化工作
(5)调用 Linux内核映像:
Bootloader完成的最后一项工作便是调用 Linux内核。如果 Linux 内核存放在 Flash 中,并且可直接在上面运行(这里的 Flash 指 Nor Flash),那么可直接跳转到内核中去执行。但由于在 Flash 中执行代码会有种种限制,而且速度也远不及 RAM 快,所以一般的嵌入式系统都是将 Linux内核拷贝到 RAM 中,然后跳转到 RAM 中去执行
不论哪种情况,在跳到 Linux 内核执行之前 CPU的寄存器必须满足以下条件:r0=0,r1=处理器类型,r2=标记列表在 RAM中的地址
3.说说异步IO和同步IO的区别:
同步:就是我调用一个功能的时候,在该功能结束前,我就一直在这里等待结果;
异步:就是我调用一个功能,不需要知道该功能的结果,该功能有结果后通知我(回调通知)
阻塞:就是调用我(函数),我(函数)没有接收完数据或者没有得到结果之前,我不会返回。
非阻塞:就是调用我(函数),我(函数)立即返回,通过select通知调用者
同步IO和异步IO的区别就在于:数据拷贝的时候是否阻塞;
阻塞IO和非阻塞IO的区别就在于: 应用程序的调用是否立即返回;
4.请列举常用的串行通信方式(两种以上),并简述串行通信与并行通信的不同之处以及优缺点:
5.进程间通信和同步的手段有哪些?举例说明:
管道pipe:管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。
命名管道FIFO:有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。
信号 ( sinal ) : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
消息队列:消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
共享内存:共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步和通信。
信号量:信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
套接字Socket:套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同机器间的进程通信。
进程间的同步: 让进程间相互知道对方进程的情况。作用是让不同进程在一个特定状态之前,等待其他进程的执行。
一、互斥锁(mutex)
二、条件变量(cond)
三、信号量(sem)
同步与互斥
同步与异步
阻塞与非阻塞
并行与并发
并行与串行
6.线程和进程的区别:
进程是CPU分配资源的最小单位;线程是操作系统调度执行的最小单位;线程是轻量级的进程;
进程间的数据难以共享,除了只读代码段外,父子进程之间并未共享内存,因此,必须采用一些进程的通信方式,在进程间进行信息交换;
调用fork()来创建进程的代价相当高,即使利用写时复制技术,仍然需要复制一些进程列表和文件描述符表之类的多种进程属性,这意味着fork()调用在时间上的开销,仍然不菲。
线程能够方便快速的共享资源,只需要将数据复制到共享(全局或堆)变量即可
创建线程比创建进程快10倍,甚至更多,线程间是共享虚拟地址空间的,无需采用写时复制来复制内存,也无需复制列表。
7.线程之间共享和非共享资源:
共享资源:进程ID,进程组ID,文件描述符表
非共享资源:线程ID,线程特有数据,栈,本地变量和函数的调用链接信息;
8.线程同步:
即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作,其他线程才能对该内存地址进行操作,而其他线程则处于等待状态。
可以通过互斥锁进行加锁和解锁操作,让线程对共享资源进行原子操作;
9.死锁及其产生场景:
死锁:两个或两个以上线程在执行过程中,因抢夺资源而发生互相等待的现状,形成死锁;
形成场景:
忘记释放锁,
重复加锁
多线程多锁,抢占锁资源;
如何避免:如果所有并发事务按同一顺序访问对象,则发生死锁的可能性会降低;避免事务中的用户交互;保持事务简短并在一个批处理中。
10.读写锁的特点:
如果有其它线程读数据,则允许其它线程执行读操作,但不允许写操作。
如果有其它线程写数据,则其它线程都不允许读、写操作。
写是独占的,写的优先级高。
11.Linux系统编程select,poll,epoll有什么区别?
epoll是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。
因为它会复用文件描述符集合来传递结果而不用每次等待事件之前都必须重新准备要被侦听的文件描述符集合;
另一点原因就是获取事件的时候,它无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合。
多路IO转接技术:
select 多路IO复用接口,它只会告诉你有几个线程到达,但是是哪几个线程到达了,你需要挨个去遍历一遍;
epoll多路IO复用接口很勤快,她不仅会告诉你有几个线程到了,还会告诉你分别是哪几个线程到了。
select缺点:
1.每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大
2.同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大
3.select支持的文件描述符数量太小了,默认是1024
4.fds集合不能重用,每次都需要重置
epoll多路IO复用接口:
内部是通过红黑树和双链表来实现的;
epoll多路IO复用的工作模式分为:
LT模式(水平触发)
ET模式(边沿触发)
12.TCP,UDP的区别?
UDP:用户数据报传输协议;基于包进行传输,是面向无连接的不可靠服务;在传输数据之前不需要建立连接,远地主机的运输层收到UDP报文后,不需要给出任何数据;传输数据慢,能广播;
TCP:传输数据报协议,基于流,是一种面向连接的可靠服务,在传输之前需要事先建立连接,数据传输完之后断开连接,数据传输慢,能广播;
13:TCP/IP网络传输模型及三次握手四次挥手:
第一次握手:客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确认;
第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。
编程实现人脸识别需要基于人工智能平台,基于HTTP协议通讯,顺便简单了解HTTP协议相关的周边,也是在为面试做准备。
HTTP协议是建立在TCP/IP协议之上的应用层协议,默认端口为80或者8080
特点:无状态,无连接
HTTP协议请求和响应的工作流程:
1.客户端连接web服务器,与HTTP协议的端口80建立一个TCP套接字连接
2.发生HTTP请求:
通过TCP套接字,客户端想web服务器发送一个文本的请求报文:请求报文由:请求行,请求头部,空格,请求数据4部分组成
3.服务器接收请求并返回HTTP响应:
web服务器解析请求,定位请求资源,服务器将资源复本写入TCP套接字由客户端读取,一个响应由相应行,响应头部,空行,响应数据组成;
4:释放TCP连接:
若connection模式为close,则服务器主动关闭TCP连接,客户端被动关闭TCP连接;
若connection模式为keepalive,则该连接会保持一段时间,在这段时间内继续接收请求;
5:客户端浏览器解析HTML内容:
客户端浏览器首先解析状态行,查看表明请求是否成功的状态代码;然后解析响应头,之后客户端浏览器读取数据;
HTTP协议有8种请求方法,着重介绍以下两种:
get请求;post请求
两个请求方法的区别?
本质区别:
get请求产生一个TCP数据包:浏览器会把http heafer和data一并发送出去;服务器响应200 ok
post请求产生两个TCP包,先发送http header 服务器响应100.再发data,服务器响应两个数据包
url可见性:
get url参数可见
post url参数不可见
数据传输:
get:通过拼接url进行传递参数
post:通过body体传输参数
缓存性:
get请求是可以缓存的;
post请求是不可以缓存的;
后退页面的反应:
get请求:后退页面不产生影响
post请求:后退页面,需要重新递交申请
传输数据大小:
get传输数据一般不超过2-4k
post传输数据大小通过配置文件而定。可以无限大;
安全性:
两者之间没有多大区别;
因为url可以通过抓包很容易获取
HTTPS协议:
HTTP协议是明文传输的,很容易被截取,泄露个人数据;HTTPS协议是在HTTP协议和TCP协议之间多添加了一层,进行了身份的验证和数据加密;
明文通过加密算法变成密文
密钥将密文转化成明文
加密的方法有对称加密和非对称加密;
HTTPS协议相对于HTTP协议的优缺点:
优点是:正确率更高,安全率更强;
HTTPS协议是由SSL协议和HTTP协议构建的可进行加密传输,身份认证的网络协议;要比HTTP协议更加安全,防止传输过程中数据被窃取,改变,保证了数据的完整性;
缺点是:效率低,成本高
HTTPS协议使页面的加载时长延长近50%,耗电增加10%~20%
HTTPS协议还会影响缓存,怎加数据开销和功耗;
HTTPS协议的安全是有范围的,中间人攻击,伪造证书;
15:MQTT协议:
是一种物联网传输协议
在使用之前需要安装所需要的环境:
先说一下整个协议的构造,整体上协议可拆分为:
固定头部+可变头部+消息体
简历解析:
1.介绍一下你的智能家居的项目有哪些功能?你都是如何实现的?
智能家居项目采用的是cotex-A53系列的树莓派;
我实现的功能是通过手机APP,语音生物识别等控制家电;
项目整体上涉及对门锁,灯光,电扇,空调,窗帘,插座等设备进行控制。
开发支持回家模式和睡觉模式等应用场景;
项目架构采用简单的工厂模式来进行设计;
将TCP服务器,语音识别,人脸识别设计成链表的每一个节点形成控制工厂;
灯光,门锁,窗帘,空调等也设计成链表的每一个节点,形成设备端工厂。
基于这种框架,我们在添加新功能的时候,只需要添加一个链表节点文件就可以了,稳定性和拓展性都做得不错
电视,空调的控制采用的是红外编解码单元,支持遥控器进行操作;
窗帘和灯光采用433兆射频单元来实现远程的控制;
支持人脸识别,刚开始小组采用的是opencv来做,但由于识别效率一般,后来选择华为人工智能平台的人脸识别;
从而让我熟悉Linux C 的HTTPS编程,让自己对第三方库文件的开发有了更好的研发经验
语音处理用的是LD3320模块的二次开发,在keil环境下阅读厂家提供的全部代码,然后找到并识别这条相关代码对串口的数据进行修改,并整合到树莓派串口通信中去
不管是设备端还是控制端,在实际调用过程中有涉及到了临界资源的竞争,我们采用多线程的线程锁来解决这个问题;
通过这个项目对简单的工厂模式,Linux操作系统的文件,进程,线程,网络编程以及Linux下的字符驱动设备开发都有了很大的收获
其中,在这个项目下,着重介绍一下Linux下的字符驱动设备开发:
1.驱动的认知框架;
2.基于驱动框架编写驱动代码;
3.驱动代码的编译:
1.把驱动代码拿到源码树目录的字符设备驱动的文件下来;
2.修改makefile文件,加入对引脚驱动代码的编译
3.回到源码树目录下进行模块代码的编译
4.把生成的.ko文件拿到树莓派当中去
5.编译测试代码,把生成的可执行程序拿到树莓派当中去
6.加载我们刚刚编译好的驱动,运行我们的测试代码;
此时,上层我们看不到信息,信息在内核态进行打印;
至此,驱动代码编译测试成功;
智能楼宇项目整合:
简介:
本产品以STM32F407单片机为中控板的核心,以STM32F103单片机为节点板的核心,可以用来检测光照、温湿度、烟雾浓度以及有害气体浓度。中控板与节点板之间通过modbus协议通信,节点板上装有各种传感器用来收集节点板处的各项数据,中控板可以将收集到的数据通过WIFI模块传送至阿里云的物联网平台,可通过网页或者手机APP实时查看数据。
节点板数据采集:
温湿度传感器采用的是DHT11,为4针单排直插,误差在±2°C
光照强度的测量是通过光敏电阻测量后交由ADC处理数据,光敏电阻器一般用于光的测量、光的控制和光电转换。光敏电阻的光敏电阻器硫化镉光敏电阻器,它是由半导体材料制成的,阻值随入射光线的强弱变化而变化,本产品中测取得值在0-4095之间。
有害气体浓度通过MQ2传感器进行收集
烟雾浓度通过MQ135传感器进行收集
功能处理与异常报警:
当光照强度过高时会自动开启节点板的LED灯,当有害气体或者烟雾浓度过高时,会进行蜂鸣器报警,这些也都会在该节点板的OLED屏上进行显示灯和蜂鸣器是否开启。
数据显示模块
节点板上使用12864的OLED屏显示数据,中控板使用480320的LCD屏显示数据。
OLED屏上会显示温湿度的值,由于屏幕较小,在上面加入一个显示灯开关的字和显示beep开关的字用来表示光敏光照强度和烟雾以及有害气体的值是否超过一定值。
而中控板的LCD屏较大且色彩丰富,因此使用GUIbuilder进行界面设计,该屏幕可以用进度条显示从节点板上传过来的数据,也通过设置上方的波形图显示数据,5个不同类型的数据用不同颜色的线条区分,线条的颜色由于下方进度条前各个数据的名字颜色一致。
产品数据的存储
中控板上配备有一个SD卡,目标数据产生时就会存储到SD卡上,可随时进行查看,当阿里云连接之后也可以通过阿里云的网页表格等查看近一个月内的数据。
接口
节点板上存放汉字库的FLASH通过SPI接口与单片机连接
节点板与OLED通过SPI接口连接
中控板与LCD屏通过并行8080接口连接
中控板与节点板使用SPI接口通过modbus协议通信
中控板通过串口接WIFE模块同阿里云通信
SPI:
SPI接口经常称为4线串行接口,以主从方式工作,数据传输过程由主机初始化。
其使用的4条信号线分别为:
SCLK:串行时钟,用来同步数据传输,由主机输出
MOSI:主机输出,从机输入
MISO:主机输入,从机输出
SS:片选线,低电平有效,由主机输出
modbus协议:
modbus协议是工厂上常用的一种通信协议,一种通信约定
简单的一条MODBUS-RTU报文由:
从机地址 功能号,数据地址,数据 CRC校验组成
通过modbus协议主机可以对从机进行读写数据操作
处理过程通过MODBUS任务处理函数进行解决
FRee-RTOS的移植
其实说不上移植笔记,FreeRTOS已经移植至众多平台(MCU),包括MSP430,STM32等,这份笔记完全建立在官方代码的基础之上,简单的说就是修改一些设置从而完成一个呼吸灯实验。
.FreeRTOS需要哪些文件:
1)与FreeRTOS内核有关的文件数量仅为3个,分别是list.c queue.c tasks.c
该文件位于FreeRTOS\Source
2)与内存分配有关的文件共有4个,分别是heap_1.c,heap_2.c,heap_3.c,heap_4.c。4个文件只需选择其中的1个,STM32选择heap_2.c。
该文件位于FreeRTOS\Source\portable\MemMang
3)与移植相关的代码包括port.c,portasm.s,portmacro.h。这些代码不但和编译器有关还和平台(MCU)有关。FreeRTOS先以编译器为大类,然后再以平台(MCU)为小类。在这里选择IAR编译器,平台为ARM_CM3。
该文件位于FreeRTOS\Source\portable\IAR\ARM_CM3
4)除了上述内容之外,还包括FreeRTOS内核相关的头文件。
该文件FreeRTOS\Source\include
3.必要的工程设置
开始之前需要引入V3.5库相关头文件,启动代码和CMSIS库。
在IAR中设置相关头文件的路径(应根据实际情况修改)
由于portasm.s需要FreeRTOSConfig.h中的相关宏定义,所以要根据FreeRTOSConfig.h的位置来设置汇编代码的头文件路径,本例中FreeRTOSConfig.h位于User文件夹,所以设置如下图所示。
4.修改启动代码
由于SVC_Handle,PendSV_Handle和SysTick_Handle在portasm.s中被重定义,所以需要在启动代码中修改这些中断向量的名称,并声明这些中断向量为外部函数。这也是初次使用FreeRTOS容易范的错误。
5.代码实现
HTTP服务器协议:
服务器编程基本框架和两种高效事件处理模式;
服务器程序通常需要处理三类事件:I/O事件,信号及定时事件
由两种高效的事件处理模式:reactor 和 proactor
本次采用的是同步I/O方式模拟proactor模式:
原理是:
主线程执行数据读写操作,读写完成后,主线程向工作线程通知这一完成事件,那么从工作线程角度看,特们就直接获得了数据读写的结果,接下来需要做的便是对读写的结果进行逻辑的处理;
使用同步I/O模型,(以epoll_wait为例)模拟出proactor模式下的工作流程如下:
1.主线程向epoll内核事件表上注册socket上的读就绪事件
2.主线程调用epoll_wait等待socket上有数据可读
3.当socket上有数据可读的时候,epoll_wait通知主线程,主线程从socket上循环读取数据,直到没有更多的数据可读,然后将读取到的数据封装成一个请求对象并插入请求队列
4.睡眠在请求队列上的某个工作线程被唤醒,获得请求对象,并处理客户请求,然后往epoll内核事件表中注册socket写就绪事件
5.主线程调用epoll_wait等待socket上可写,当socket上可写时,epoll_wait通知主线程,主线程往socket上写入服务器处理客户请求的结果;
线程同步机制类封装及线程池的实现;
线程池的一般模型:
主线程 --新任务–选择算法–通知机制—子线程
解析HTTP请求报文;
解析请求完成并生成相应信息;
定时检测非活跃连接,服务器压力测试