导图社区 嵌入式知识点
这是一篇关于嵌入式知识点的思维导图,主要内容有涉及硬件、代码型、概念型、内存管理、线程管理等。
编辑于2022-09-25 21:26:39 浙江省嵌入式的知识
涉及硬件
总线
常规释义
全双工
双方可同时发送数据,不会干扰,肯定是两条data线
半双工
双方同时只能一个发数据,一般一条data线
LSB/MSB
一个字节中:L:低位,M:高位
IIC

NXP(原PHILIPS)公司设计
特性
半双工,两根线SDA、SCL,默认高电平
【上拉电阻】4.7K-10K
故总线空闲时,两线处于高电平
标准100kb/s,快速400kb/s
真正【多主机】总线
每个设备都有可配置的【唯一地址】
总线详解
SDA:数据
主机【起始/停止信号】:SCL高电平时,SDA发生跳变。

从机【应答信号】:ACK-1bit
主机拉高SCL并立即读取SDA为低则为有效应答。

主机发送完8bit,等待从机给应答信号。来确认从机接收到数据
非应答时
不想继续读了,主机可以发NACK
SCL:时钟
边沿触发:SCL跳变时读取SDA状态。所以SDA宽度比SCL长。对SDA取2次样

数据格式
8bit设备地址:7位从机地址+读/写地址
0:主向从写数据
1:主向从读数据
DATA:高位先发
【实现】
发送数据函数
//IIC发送一个字节 //返回从机有无应答 //1,有应答 //0,无应答 void IIC_Send_Byte(u8 txd) { u8 t; SDA_OUT(); IIC_SCL=0; //拉低时钟开始数据传输 for(t=0;t<8;t++) { IIC_SDA=(txd&0x80)>>7; //高位先发 txd<<=1; delay_us(2); IIC_SCL=1; delay_us(2); IIC_SCL=0; delay_us(2); } }
读取数据函数
//读1个字节,ack=1时,发送ACK,ack=0,发送nACK u8 IIC_Read_Byte(unsigned char ack) { unsigned char i,receive=0; SDA_IN(); //SDA设置为输入 for(i=0;i<8;i++ ) { IIC_SCL=0; delay_us(2); IIC_SCL=1; receive<<=1; if(READ_SDA) receive++; delay_us(1); } if (!ack) IIC_NAck(); //发送nACK else IIC_Ack(); //发送ACK return receive; }
写数据
代码
//address 要写入的地址 //date 要写入的数据 void write_add(uchar address,uchar date) { IIC_Start(); IIC_Send_Byte(0xA0); delay(2); IIC_Send_Byte(address); delay(2); IIC_Send_Byte(date); delay(2); IIC_Stop(); }
时序图

读数据
代码
//IIC读取数据 // address 要读取数据的地址 uchar read_add(uchar address) //指定地址读一个字节数据 { uchar add; IIC_Start(); IIC_Send_Byte(0xA0); delay(2); IIC_Send_Byte(address); delay(2); IIC_Start(); IIC_Send_Byte(0xA1); delay(2); dd=IIC_Read_Byte(); IIC_Stop(); return add; }
时序图

高能详解

GPIO设置
要求两线都能输入输出
常态都推挽输出,SDA读取时带上拉的输入
总结
冲突解决
冲突检测
仲裁
应用
EEPROM
MPU6050陀螺仪
LCD12864点阵屏
坑
“死锁”
现象与原因
如果设备需要反复重启,很有可能在从机设备返回数据的时候,SDA被锁住。【从机响应阶段】或【从机发送数据阶段】 具体原因是从机设备在回数据,还没有发送完成,【主机时钟消失】,从机等待时钟信号, MCU重启,如果从机设备的电源没有复位,从机继续等待 MCU 时钟信号,数据一直被钳住,总线无法完成数据交互。
定位分析

解决方式
1.提供9个时钟信号,解除死锁
2.给从设备硬件复位
硬件IIC
挂载多个器件可能会有各种诡异问题,所以一般采用模拟IIC
其他
要求各设备连接到总线的输出端时必须是漏极开路(OD)输出或集电极开路(OC)输出。
漏极开路(Open Drain)即高阻状态
电路分析时高阻态可做开路理解
搭配上拉电阻,就成高电平
SPI
特性
高速全双工、4根线
MISO/SDI
Master Input Slave Output,主设备输入,从设备输出
MOSI/SDO
Master Output Slave Input,主设备输出,从设备输入
SCLK
Serial Clock,时钟信号,主设备提供
CS
Chip Select,片选使能,主设备控制
也可3根线:单线传输
主从模式:1主多从
速率:受限于系统时钟1/2
总线详解
CS/NSS:片选信号
【空闲】高电平,【起始信号】拉低,【停止信号】拉高
所以从机片选信号是加“非”
极性(CPOL寄存器)

空闲的时钟信号是电平:0低/1高
数据传输从跳变开始:0上升沿/1下降沿
相位(CPHA寄存器)

一个时钟周期有2个跳变沿。而相位,直接决定SPI总线从第几个跳变沿开始采样数据。
0:第一个跳变开始采样/1:第二个跳变开始采样
4种模式
CPOL=0;CPHA=0

CPOL=0;CPHA=1

CPOL=1;CPHA=0

CPOL=1;CPHA=1

总结
应用
EEPROM,FLASH,实时时钟,AD转换器
接线:主设备SDO接从设备SDI
优缺点
缺点
没有指定的流控制
没有应答机制确认是否接到数据
优点
RS485
一个定义平衡数字多点系统中的驱动器和接收器的电气特性的标准
特性
半双工
不支持星型或环形网络
会有反射信号干扰
双绞线
通过两根通信线之间的电压差的方式来传递信号
抑制共模干扰能力
理论距离:1200m
可通过中继器放大,提升距离
波特率:9600
同一总线最多32个结点
总线详解
线路图

差分信号
负逻辑
0:+2V~+6V
1:-2V~-6V
接地
收发器共模电压:-7V~+12V
干扰
差模干扰
在两根信号线之间传输,属于对称性干扰
方法:
电路中增加一个偏值电阻,并采用双绞线;
共模干扰
信号线与地之间传输,属于非对称性干扰
方法:
采用屏蔽双绞线并有效接地
强电场处采用镀锌管屏蔽
布线时远离高压线
线性稳压电源(纹波干扰小于50mV)
其他:
普通网线没有屏蔽层,不能防止共模干扰
不能用线径太细的网线,会导致传输距离降低和可挂接的设备减少,至少0.4mm平方或用标准的网线。
LIN总线
Local Interconnect Network
基于UART/SCI低成本串行通讯协议 (通用异步收发器/串行接口)
定位车身网络模块节点间的低端通信,低成本
主要用于智能传感器和执行器的串行通信
与上层CAN网络相连,形成CAN-LIN网关节点。
电平一般12V、 max速率:20kbps、最多1主+15从节点
具有可预测的EMC(电磁兼容性)性能,因为限制EMC才限制速率
功能
信号的配置、处理、识别和诊断功能
总线详解

波形图

主从机
主机
任务
1. 调度帧次序,进行主从机交互
2. 监测数据、处理错误
3. 作为标准时钟参考
4. 接收从机的唤醒命令
从机
先接收到帧头后,才能选择处理,不能直接发送
发送应答
接收应答
不处理
报文域
帧头(主机)+(应答)+应答(从机)
帧头header
1. 同步间隔段(≥13位显性)
理解:总线空闲时,突然来了个长时间显性电平,标志着帧的开始
间隔符:至少1位隐性
2. 同步段(0x55)
动态调整“波特率”
10位:起始位(0)+0xaa+停止位(1)
波形:5个01
3. PID段(受保护ID)
起始位(0)+帧ID(6b)+校验位(2b:P0P1)+结束位(1)
帧ID
64个:0x00-0x3f
标识了帧类别和目的地
信号帧
60个:0x00-0x3B
无条件帧
单一发布结点
从机无条件应答(汇报自身情况)
事件触发帧
可能存在多个发布节点!冲突解决
冲突解决进度表
广播询问各个是否发生变化
偶发帧
诊断帧
主机请求帧:0x3C
从机应答帧:0x3D(不是帧的应答)
保留帧
0x3E、0X3F
校验位
P0:0.1.2.4异或
P1:1.3.4.5异或再取反
(应答间隔)
应答response
1. 数据段(1-8B)
信号帧+信号signal
诊断帧+诊断消息
2. 校验和段
【数据格式】低字节先发,没有数据长度码,发布节点发的是应答数据不是帧头
“线与”逻辑
记忆:线与的意思,0与任何=0,所以0很明显。
“0”为显性电平、“1”为隐性电平
【0与X=0】一个节点显性电平,则总线就显性电平
【1或1=1】所有节点隐性电平或不动作时,则总线就隐性电平
CAN
UART
通用异步收发传输器,一种串行异步收发协议
总线详解
3根线
空闲:高电平
TX:本机发送数据线
接对方RX
RX:本机接收数据线
接对方TX
GND
数据流
1. 起始位1位
2. 数据5-9位
低字节先发
3. 奇偶校验位0-1位
无校验
奇校验
0:奇数个”1“
偶校验
0:偶数个”1“
4. 停止位1/1.5/2位
外部晶振
Stm32没有内部晶振,只有内部HSI的RC振荡器,精度差,达不到72MHz
代码型
堆栈概念
嵌入式堆栈一般指栈,RAM中的一种数据结构。
堆Heap
自由度高
程序员动态分配内存大小。二级缓存。
自己申请内存malloc,系统采用链表方式存储。 就像在家里自己做饭,需要处理菜、炒菜、加料、吃饭、洗碗打扫。
malloc介绍
原理:维护一个内存空闲链表,获取锁防止多线程
(<128K时)采用brk方式
只分配虚拟地址,不映射到物理内存,所以没有初始化
只是推动_edata指针而已
高地址不释放,低地址无法真正释放
当第一次读写时:若对应物理地址不在物理内存中(有重用时就不用分配新的), 则引起内核缺页中断,此时分配物理内存
(>128K时)采用mmap方式
在堆与栈中间找个地方分配一块虚拟地址
可以单独释放,跟brk不同
产生大量内核缺页中断
扩展
内核缺页中断
是内核行为,内核态CPU消耗大。mmap(1M)=1M/4K次中断
虚拟映射物理
1.CPU将虚拟地址给MMU(内存管理单元)
2.MMU做物理地址转换映射
如果MMU出错:exception。 CPU会执行对应调用函数,叫做do_translation_fault
new
栈Stack
先进后出FILO,就是弹夹打枪一样。
系统自动分配。 指数据临时存储的地方,所以才有进栈(PUSH)、出栈(POP)的说法。
典型的应用:函数调用与返回;函数调用的时候存储断点,做递归时要用到栈。
调用函数入栈顺序:1函数下一条语句的地址。2.传参(右先)。3.局部变量。
一级缓存(比寄存器慢点)中断使用所以很快,栈数据可以共享。
栈的大小固定
实现方式
静态数组:编译即确定:
static int stack[100];
动态分配的数组
static int *stack; stack=(int*) malloc(1000 * sizeof(int)); free(stack);
动态分配的链式结构
优点:是无长度上限,需要的时候再申请分配内存空间, 缺点:链接字段需要消耗一定的内存,访问效率不如数组
代码
typedef struct STCAK_NODE{ int value; struct STCAK_NODE *next;//操作的时候都是针对头部,尾部没法找,所以是先进先出! }StackNode;//typedef时:StackNode是STCAK_NODE别名 static StackNode *mystack; //push操作 StackNode *new_node; new_node = (StackNode *)malloc(sizeof(StackNode)); new_node->value = valueTmp; new_node->next = mystack;//新元素放到链表【头部】,因为尾巴不好找嘛!!!!!!!!!!! mystack = new_node; //pop操作 StackNode *first_node; first_node = mystack; mystack= first_node->next;//!!!!!!!!!! free(first_node);
IO多路复用
描述:通过一种机制,监视多个描述符,一旦描述符就绪,能通知程序完成操作
复杂度:
select->O(n);poll->O(n);epoll->O(1)
数据交互:内核与用户空间,挺消耗资源
三种机制
select
所有操作系统基本都有
能知道有IO事件,不知道是谁,需要全部轮询
最大1024(32位)2048(64位)
函数申明
int select( int maxfdp, //集合内描述符最大值 fd_set *readfds, //可NULL,监测集合内可读的句柄 fd_set *writefds, //可NULL,监测集合内可写的句柄 fd_set *exceptfds, //可NULL,监测集合内文件错误异常 struct timeval *timeout //超时时间 );//返回0:超时无事件。返回<0:文件异常。返回>0:集合内有文件可读写文件总和 【fd_set】结构体:文件描述符(句柄)集合。 FD_ZERO(fd_set *) 清空集合 FD_SET(int ,fd_set*) 新增集合 FD_CLR(int,fd_set*) 删除集合 FD_ISSET(int ,fd_set* ) 检查集合中某fd是否可读写 【timeval】结构体:时间结构体,秒+毫秒
poll
与select差不多,基于链表存储,没有最大限制
整个fd数组复制于用户态和内核态之间,比较浪费
【水平触发】:
报告fd后若无被处理,则下次poll时会再次报告fd,拷贝数据到内核空间
epoll
2种模式
LT默认EPOLLLT
只要这个fd还有数据可以读,每次 epoll_wait都会返回它的事件
ET高速EPOLLET(边沿触发)
只有新数据流进入才提示,所以read最好一次性读光。
好处:系统不会充斥大量你不关心的就绪文件描述符
linux特有
事件驱动event poll
回调机制
通过epoll_ctl注册fd,一旦该fd就绪,内核就会采用类似callback的回调机制来激活该fd,epoll_wait便可以收到通知
epoll会把哪个流发生了怎样的I/O事件通知我们。
内存拷贝
用mmap()文件映射内存提高速度,减少复制开销。其他两个都是需要内核态拷贝动作。
本质:都是同步IO,需要自己阻塞来读写。而异步IO无需自己负责读写,会把数据从内核拷贝到用户空间
常用数据结构
队列
线性结构,先进先出FIFO。队头(front)可删除、队尾(rear)可插入。
定义:【数组】+2个变量(头和尾)
代码
#define MAX 128 typedef struct{ char data[MAX];//按照(队头)[0][1][2](队尾)顺序插入数据,队尾下标不断更新 int front,rear;//front为队首指针 rear为队尾指针 }MyQueue; //初始化 int InitQueue(MyQueue *Q){ (*Q).front=0; (*Q).rear=0; memset(Q->data,0,sizeof(Q->data)); return 0; } //队列是否满了,牺牲一个空间.因为rear是指向当前可插入的空间 int QueueFull(MyQueue *Q){ if(((*Q).rear+1)%MAX == (*Q).front){ return-1; } return 0; } int QueueEmpyt(MyQueue *Q){ if((*Q).rear == (*Q).front){ return-1; } return 0; } //队尾rear入队 int EnQueue(MyQueue *Q,char e){ if(QueueFull(Q)){ return-1; } (*Q).data[(*Q).rear]=e; (*Q).rear=((*Q).rear+1)%MAX; return 0; } //队头front出队 int DeQueue(MyQueue *Q,char *e){ if(QueueEmpyt(Q)){ return -1; } *e=(*Q).data[(*Q).front]; (*Q).front=((*Q).front+1)%MAX; return 0; } void Printf(MyQueue *Q){ printf("队列数据打印(%d-%d):",Q->front,Q->rear); cout << Q->data << endl; } /** * @brief: 向队列内插入数组 * @param {MyQueue} *Q 被插入的队列 * @param {char} buff 带插入的数组 * @param {int} len 插入长度 * @return 成功插入长度 */ int InQueueArray(MyQueue *Q, char *buff, uint32_t len){ int i,result; for(i=0;i<len;i++){ result = EnQueue(Q, *(buff + i)); if(result!=0){ printf("插入第%d(%c)数据时失败\n",i,*(buff + i)); break; } } return i; }
测试代码
/***************测试队列***************/ int i=0,result=0; char inData = 'a'; char outData = 0; MyQueue recvData; InitQueue(&recvData); for(i=0;i<16;i++){ result = EnQueue(&recvData,inData); if(result!=0){ printf("input error:%c\n",inData); break; } printf("input:%c\n",inData); inData++; } Printf(&recvData); for(i=0;i<6;i++){ result = DeQueue(&recvData,&outData); if(result!=0){ break; } printf("out:%c\n",outData); } Printf(&recvData); for(i=0;i<4;i++){ result = EnQueue(&recvData,inData); if(result!=0){ printf("input error:%c\n",inData); break; } printf("input:%c\n",inData); inData++; } Printf(&recvData); for(i=0;i<6;i++){ result = DeQueue(&recvData,&outData); if(result!=0){ break; } printf("out:%c\n",outData); } Printf(&recvData); for(i=0;i<4;i++){ result = EnQueue(&recvData,inData); if(result!=0){ printf("input error:%c\n",inData); break; } printf("input:%c\n",inData); inData++; } Printf(&recvData); input:a input:b input:c input:d input:e input:f input:g input:h input:i input error:j 队列数据打印(0-9):abcdefghi out:a out:b out:c out:d out:e out:f 队列数据打印(6-9):abcdefghi input:j input:k input:l input:m 队列数据打印(6-3):klmdefghij out:g out:h out:i out:j out:k out:l 队列数据打印(2-3):klmdefghij input:n input:o input:p input:q 队列数据打印(2-7):klmnopqhij
记忆:就是数组,按照(队头)[0][1][2](队尾)顺序插入数据,队尾插入数据,队头删除数据
链表
线性结构,也是一个天然的递归结构。
头结点:
myLink:的next可为NULL表示空表,也可不存在。
首元结点
myLink->next:是头结点后面第一个结点,链表中第一个元素所在的结点。
头指针
永远指向第一个结点。(可为头结点,也可为首元结点)
单链表中可以没有头结点,但是不能没有头指针!
typedef struct Link{ char cData; //代表数据域 struct Link * next; //代表指针域,指向直接后继元素 }link;
代码
/////////////////////////////////////////////////////////////////////////////// #include <stdio.h> #include <stdlib.h> typedef struct Link{ int elem; struct Link *next; }link; link * initLink(); //链表插入的函数,p是链表,elem是插入的结点的数据域,add是插入的位置 link * insertElem(link * p,int elem,int add); //删除结点的函数,p代表操作链表,add代表删除节点的位置 link * delElem(link * p,int add); //查找结点的函数,elem为目标结点的数据域的值 int selectElem(link * p,int elem); //更新结点的函数,newElem为新的数据域的值 link *amendElem(link * p,int add,int newElem); void display(link *p); int main() { //初始化链表(1,2,3,4) printf("初始化链表为:\n"); link *p=initLink(); display(p); printf("在第4的位置插入元素5:\n"); p=insertElem(p, 5, 4); display(p); printf("删除元素3:\n"); p=delElem(p, 3); display(p); printf("查找元素2的位置为:\n"); int address=selectElem(p, 2); if (address==-1) { printf("没有该元素"); }else{ printf("元素2的位置为:%d\n",address); } printf("更改第3的位置的数据为7:\n"); p=amendElem(p, 3, 7); display(p); return 0; } link * initLink(){ link * p=(link*)malloc(sizeof(link));//创建一个头结点 link * temp=p;//声明一个指针指向头结点,用于遍历链表 //生成链表 for (int i=1; i<5; i++) { link *a=(link*)malloc(sizeof(link)); a->elem=i; a->next=NULL; temp->next=a; temp=temp->next; } return p; } link * insertElem(link * p,int elem,int add){ link * temp=p;//创建临时结点temp //首先找到要插入位置的上一个结点 for (int i=1; i<add; i++) { if (temp==NULL) { printf("插入位置无效\n"); return p; } temp=temp->next; } //创建插入结点c link * c=(link*)malloc(sizeof(link)); c->elem=elem; //向链表中插入结点 c->next=temp->next; temp->next=c; return p; } link * delElem(link * p,int add){ link * temp=p; //遍历到被删除结点的上一个结点 for (int i=1; i<add; i++) { temp=temp->next; } link * del=temp->next;//单独设置一个指针指向被删除结点,以防丢失 temp->next=temp->next->next;//删除某个结点的方法就是更改前一个结点的指针域 free(del);//手动释放该结点,防止内存泄漏 return p; } int selectElem(link * p,int elem){ link * t=p; int i=1; while (t->next) { t=t->next; if (t->elem==elem) { return i; } i++; } return -1; } link *amendElem(link * p,int add,int newElem){ link * temp=p; temp=temp->next;//tamp指向首元结点 //temp指向被删除结点 for (int i=1; i<add; i++) { temp=temp->next; } temp->elem=newElem; return p; } void display(link *p){ link* temp=p;//将temp指针重新指向头结点 //只要temp指针指向的结点的next不是Null,就执行输出语句。 while (temp->next) { temp=temp->next; printf("%d",temp->elem); } printf("\n"); }
树
应用场景
1. xml,html等,那么编写这些东西的解析器的时候,不可避免用到树
2. 路由协议就是使用了树的算法
3. mysql数据库索引
4. 文件系统的目录结构
5. 所以很多经典的AI算法其实都是树搜索,此外机器学习中的decision tree也是树结构
子主题
概念型
锁
作用
多线程、多进程等共同处理一个东西的时候,容易出错。
自旋锁spinlock
野蛮死等
while (抢锁(lock) == 没抢到) {}
互斥锁:mutex
QT:QMutex
.h中QMutex mutex ;
函数中:QMutexLocker locker(&mutex);//加入互斥锁
函数跳出后自动解锁
函数
pthread_mutex_init
pthread_mutex_lock
阻塞等待
pthread_mutex_trylock
不阻塞
pthread_mutex_unlock
同步锁:synchronized
死锁
产生
我拿着你要的资源,你拿着我要的资源。都在阻塞
定位与恢复
文件锁
多线程读写文件
1.类似linux日志文件服务,让logger写文件,实现比较复杂,但是最安全
2.加锁
文件锁:flock()
对整个文件加锁
数据段锁fcntl()
针对文件某部分加锁
读写锁
也叫共享-独占锁,会阻塞的
适用于读频率远远大于写频率
没有写锁,多线程都能加读锁
只有都不加锁时,才能写锁
进程同步
基本概念
同一操作系统上,多个进程之间需要协作与防止竞争冲突。即同步与互斥(统一称为制约关系)。需要进程间交换信息,可以为通讯。
临界资源
描述
如打印机,许多变量、数据等都可以被若干进程共享。对临界资源的访问,必须互斥地进行。访问临界资源的那段代码称为临界区。可以把临界资源的访问过程分成四个部分
访问过程四部分
1.进入区
为了进入临界区使用临界资源,在进入区要检查可否进入临界区,如果可以进入临界区,则应设置正在访问临界区的标志,以阻止其他进程同时进入临界区。
2.临界区
进程中访问临界资源的那段代码,又称临界段。可以进行处理了
3.退出区
将正在访问临界区的标志清除。
4.剩余区
代码中的其余部分。
进程通讯PIC (Inter Process Communication)
实现方式
共享内存
Socket
消息传递
消息队列
安卓handler、QT信号槽
管道PIP
同步
互斥量
信号量
文件和写记录锁
读写锁
条件变量
进程同步
描述
源于进程合作,同步亦称直接制约关系,着重进程之间的执行顺序。
例如,输入进程A通过单缓冲向进程B提供数据。当该缓冲区空时,进程B不能获得所需数据而阻塞,一旦进程A将数据送入缓冲区,进程B被唤醒。反之,当缓冲区满时,进程A被阻塞,仅当进程B取走缓冲数据时,才唤醒进程A。
实现方式
Mutex(互斥) 可以跨进程使用
Semphore(信号量) 可以跨进程使用
进程互斥
描述
主要源于资源共享,互斥亦称间接制约关系
当一个进程进入临界区使用临界资源时,另一个进程必须等待, 当占用临界资源的进程退出临界区后,另一进程才允许去访问此临界资源。
四大准则
描述:为禁止两个进程同时进入临界区,同步机制应遵循以下准则:
1.空闲让进。
临界区空闲时,可以允许一个请求进入临界区的进程立即进入临界区。
2.忙则等待。
当已有进程进入临界区时,其他试图进入临界区的进程必须等待。
3.有限等待。
对请求访问的进程,应保证能在有限时间内进入临界区。
4.让权等待。
当进程不能进入临界区时,应立即释放处理器,防止进程忙等待。
实现方式
软件实现
单标志法
双标志先检查法
双标志后检查法
Peterson算法
信号量PV
硬件实现
中断屏蔽法
硬件指令TestAndSet();//不允许被中断
网络
http/https
http(80)
通常承载于TCP
永远是客户端发起的,服务端回应。所以易联只能用Mqtt不用http,没法主动推送
单次链接,无状态协议:这次与上次没有关系的。
方式
post
用于服务端创建/更新资源
数据在主题里
POST /test/demo_form.php HTTP/1.1 Host: w3school.com.cn name1=value1&name2=value2
不会被缓存,不会有历史记录,不能收藏书签,数据长度没有要求
get
数据在链接上
/test/demo_form.php?name1=value1&name2=value2
可以被缓存,浏览记录,书签,有长度限制(url太长)
put
head
delete
patch
options
https(443)
承载于TLS/SSL
TCP/IP 泛指协议簇
协议种类
因为TCP与IP最具代表性,故用它来泛指
应用协议
Http(80)、HTTPS(443)、FTP(21/20)、TELNET(23)、SMTP(25)、SNMP(161)
传输协议
TCP、UDP
详细介绍
TCP
面向【连接的、可靠的】流协议。
流就是指不间断的数据结构,当应用程序采用 TCP 发送消息时,虽然可以保证发送的顺序,但还是犹如没有任何间隔的数据流发送给接收端。
提供可靠性传输,实行“顺序控制”或“重发控制”机制。
具备“流量控制”、“拥塞控制”、提高网络利用率等众多功能。
主要场景:【可靠传输】
全双工
【三次握手】
UDP
不提供复杂的控制机制,利用 IP 提供面向【无连接】的通信服务
【无可靠性】的数据报协议。细微的处理它会交给上层的应用去完成
立即发送,丢包不重发,包可能出现乱时序。应用根据需要进行重发处理
主要场景:对【高速传输】和【实时性】有较高要求: 包总量较少(DNS、SNMP等);视频、音频等多媒体(即时通信);广播通信
网际协议
IP、ICMP、ARP
路由控制协议
RIP、OSPF、BGP
包术语
包:全能术语
消息:应用协议的包
段:(传输层)
数据包:数据链路层以上的包
帧:{数据链路层}
比特:(物理层)
各种"地址" 识别通信
【数据样式】:【IP首部】(源IP地址 +目标IP地址+协议号TCP)+【TCP首部】(源端口号+目标端口号) + 【数据】
MAC 地址:数据链路
同一链路中不同的计算机
IP 地址
TCP/IP 网络中互连的主机和路由器
端口号:程序地址
主机上的不同应用程序
标准既定
固定知名端口号:0-1023
任何用途:1024-49151
系统(时序动态分配法):49152-65535
IP首部TCP协议号
来区分(TCP 和 UDP)
延伸
OSI模型
模型
·7应用层(HTTP、FTP、DNS、EMAIL)
·6表示层(VTP)
·5会话层
·4传输层 transport(TCP、UDP)
TCP
·3网络层 Network(IP、ICMP)
路由器
·2数据链路层Data Link(ARP、PPP、 以太网、Wi-fi、MAC、FDDI等)
交换机、网桥
·1物理层(ISO.02110、IEEE802)
中继器、集线器、光纤
定义每一层的“作用”
“作用”==“协议”
“协议”是约定,具体内容是”规范“
区别
OSI
注重:通信协议必要的功能是什么
优点
缺点
TCP/IP
注重:在计算机上实现协议应该开发哪种程序
优点
缺点
【如图】从发送到接收详细流程

解析(不用太看)

OSI模型图

数据封装图

TCP【三次握手】
【有连接】的通信。在数据通信开始之前先做好两端之间的【准备工作】
三次握手步骤
【三次握手】是指建立 TCP 连接时,C端S端共发送【三个包】确认连接的建立。 socket编程中,由客户端connect来触发。
【如图】

详细描述

四次挥手
终止TCP连接,socket中由任一端close触发
发送FIN(finish)来终止本方向的连接,收到FIN只是意味不再收数据,但仍能发数据
【如图】一方主动,另一方被动关闭

详细描述

【如图】同时发起主动关闭

可以把ack和FIN合并发,形成三次挥手,表示我收到你断开了,并且我没有数据要发了
可靠性
(引入不同序列号)包发送后收到对方的ACK应答能说明对方确实收到了,没收到ACK则丢的可能性很大。
底层自动重发机制,发的序列号是一样的
超时重发
等待确认应答超时了,将进行重发。
超时时间:每次发包都会计算往返时间和偏差,再加点预留值作为新的超时时间
重发超过一定次数,会认为对方异常,主动断开连接
发送长度
三次握手时,确定双方(较小)的发送包的消息长度MSS(放在TCP首部),当传输大量数据时,以MSS大小进行分割
窗口控制
设计原因
如果发一包,等应答,再发下一包,这样效率太低,所以引入窗口概念。发送窗口与接收窗口。 连续发送N个包(段)后,收到对方回应的希望下个包是多少,再开始发送下一批包。如果连续收到希望下个包是一样时,表示确实丢了,需要重发。它能有效提高吞吐量,控制流量
窗口大小
第一次窗口大小是根据链路带宽的大小来决定的。看对方回应,再决定后续窗口大小,它不是一直不变的,会根据链路情况与通信情况改变
窗口滑动协议

一种TCP流量控制的方法
拥塞控制
还没理解
IP协议
还没写
Socket
【如图】中间软件抽象层

int socket(int domain, int type, int protocol);
domain:协议域
AF_INET、AF_INET6、AF_LOCAL(Unix域socket)、AF_ROUTE等等
决定了socket的地址类型
type:协议类型
SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET等等
protocol:指定协议
IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等
return:句柄
TCP中的socket
【如图】具体实现,类似TCP

三次握手

四次挥手

bind()服务端
服务器端:启动会绑定一个IP与端口(网址),让大家连接它。
客户端不需要bind:connect时随机生成
内存管理
内存泄露 memory leak
描述与原因
已申请的内存未被释放,导致无法继续申请内存
不会直接使程序挂掉,但是会最终导致内存溢出,使程序挂掉
分类
常发性
代码常被执行,每次都会导致泄露
偶发性
在特定情况或环境下会发生。
一次性
代码只被执行一次,或仅一块内存泄露
如构造函数分配内存,析构函数没有释放内存
隐式
不能及时释放内存,直到结束时才释放。例如服务器程序运行个把月
常见例子
handler
错误写法
public class MainActivity extends AppCompatActivity { private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { //... } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); loadData(); } private void loadData(){ //...request Message message = Message.obtain(); mHandler.sendMessage(message); } }
原因
此方式mHandler是Handler的非静态匿名内部类的实例。所以它持有外部类Activity的引用,消息队列是在Looper线程中不断轮询处理消息,当这个Activity退出时,消息队列中还有未处理的消息或者正在处理消息,而消息队列中的Message持有mHandler实例的引用,mHandler又持有Activity的引用,所以导致该Activity的内存资源无法及时回收,引发内存泄漏
正确写法
public class MainActivity extends AppCompatActivity { private MyHandler mHandler = new MyHandler(this); private TextView mTextView ; private static class MyHandler extends Handler { private WeakReference<Context> reference; public MyHandler(Context context) { reference = new WeakReference<>(context); } @Override public void handleMessage(Message msg) { MainActivity activity = (MainActivity) reference.get(); if(activity != null){ activity.mTextView.setText(""); } } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mTextView = (TextView)findViewById(R.id.textview); loadData(); } private void loadData() { //...request Message message = Message.obtain(); mHandler.sendMessage(message); } @Override protected void onDestroy() { super.onDestroy(); mHandler.removeCallbacksAndMessages(null); } }
分析定位
Linux下
总内存查看
cat /proc/meminfo
MemTotal: 5933132 kB MemFree: 4485932 kB 可用的物理内存=MemFree+Buffers+Cached MemAvailable: 4822944 kB Buffers: 122148 kB Cached: 630048 kB SwapCached: 0 kB Active: 806136 kB Inactive: 461288 kB Active(anon): 516344 kB Inactive(anon): 230112 kB Active(file): 289792 kB Inactive(file): 231176 kB Unevictable: 32 kB Mlocked: 32 kB SwapTotal: 7999484 kB SwapFree: 7999484 kB Dirty: 204 kB Writeback: 0 kB AnonPages: 515264 kB …
进程内存查看
cat /proc/{pid}/maps
检测方法
C/C++常用工具
【Valgrind】针对C/C++
简介
Valgrind是一套Linux下,开放源代码(GPL V2)的仿真调试工具的集合。Valgrind由内核(core)以及基于内核的其他调试工具组成。内核类似于一个框架(framework),它模拟CPU环境,并提供服务给其他工具;而其他工具则类似于插件 (plug-in),利用内核提供的服务完成各种特定的内存调试任务。Valgrind的体系结构如下图所示: 
内核是虚拟的CPU环境
能定位到源代码行,编译-g参数,优化选择O0,虽然会效率低
调试内存问题,不需要重新编译源程序。它的输入就是二进制的可执行程序
编译:g++ -g -o test test.cpp 使用:valgrind --tool=memcheck ./test
工具们
Memcheck
最广泛、重量级内存检测器,能发现如:使用未初始化的内存,使用已经释放了的内存,内存访问越界等问题
两个全局表

Valid-Address 表
进程整个地址空间中的每个字节B,还有与之对应的1bit。 负责记录该地址是否能够被读写。
Valid-Value 表
进程的整个地址空间的每个字节B都有与之对应的8bits;CPU每个寄存器也有与之对应的bit向量。bits记录该字节或寄存器是否有有效的、已初始的值
检测原理
【Address表】当要读写内存中某个字节时,首先检查这个字节对应的 Address bit。如果该位置是无效位置,memcheck 则报告读写错误。
【Value表】当内存中的某个字节被加载到真实的CPU 中时,该字节对应的 V bits 也被加载到内核中。一旦寄存器中的值,被用来产生内存地址,或者该值能够影响程序输出,则 memcheck 会检查对应的V bits,如果该值尚未初始化,则会报告使用未初始化内存错误。
未释放内存,内存越界、内存覆盖
//未释放内存 { int *p = malloc(sizeof(int));//结束后没free } //内存越界 int a[4]; a[5]=1; //内存覆盖 strncpy(x+20,x,20); strncpy(x+20,x,21);
Callgrind
检查函数调用过程中出现的问题
Cachegrind
检查程序中缓存使用出现的问题
Helgrind
检查程序中出现的多线程竞争问题
Massif
检查程序中堆栈使用中出现的问题
Extension
可利用core提供的功能,自己编写特定的内存调试工具
【tcmalloc】:
【ccmalloc】: Linux和Solaris下对C和C++程序的简单的使用内存泄漏和malloc调试库
【LeakTracer】: Linux、Solaris和HP-UX下跟踪和分析C++程序中的内存泄漏
【Electric Fence】: Linux分发版中由Bruce Perens编写的malloc()调试库
【Leaky】: Linux下检测内存泄漏的程序
【Dmalloc】: Debug Malloc Library
【MEMWATCH】: 由Johan Lindh编写,是一个开放源代码C语言内存错误检测工具,主要是通过gcc的precessor来进行
【KCachegrind】: A visualization tool for the profiling data generated by Cachegrind and Calltree
Linux内核检测
kmemleak
当内存对象没有被释放是,将其记录在"/sys/kernel/debug/kmemleak"中。
重载函数
重写new与delete
通过链表容器保存new与delete的信息,通过打印信息,来判断此时有哪些未释放
代码
记录信息容器
#ifndef MEMINFOCONTAINER_H_ #define MEMINFOCONTAINER_H_ #define MAX_MSG_SIZE 255 struct mem_info { void *ptr; char msg[MAX_MSG_SIZE]; struct mem_info *next; }; class MemInfoContainer { public: MemInfoContainer(); virtual ~MemInfoContainer(); void Add(void *ptr, char *msg); void Remove(void *ptr); struct mem_info *GetMemInfo(); private: struct mem_info m_header; struct mem_info *m_p_cusor; }; #endif /* MEMINFOCONTAINER_H_ */
重载new与delete
#include <stddef.h> #ifdef _DEBUG void *operator new(size_t nsize, const char *filename, int linenum); void *operator new[](size_t nsize, const char *filename, int linenum); #define DEBUG_NEW new(__FILE__, __LINE__) #define DEBUG_DELETE delete #endif void print_mem_info(); #endif /* MEM_RECORD_H_ */ #ifndef MEM_RECORD_H_ #define MEM_RECORD_H_ void *operator new(size_t nsize, const char *filename, int linenum) { void *ptr = malloc(nsize); char msg[MAX_MSG_SIZE]; sprintf(msg, "new [%s] [%d]", filename, linenum); s_mem_info.Add(ptr, msg); return ptr; } void *operator new[](size_t nsize, const char *filename, int linenum) { void *ptr = malloc(nsize); char msg[MAX_MSG_SIZE]; sprintf(msg, "new[] [%s] [%d]", filename, linenum); s_mem_info.Add(ptr, msg); return ptr; } void operator delete(void *ptr) throw () //没有*operator { free(ptr); s_mem_info.Remove(ptr); } void operator delete[](void *ptr) throw () { free(ptr); s_mem_info.Remove(ptr); } #define new DEBUG_NEW #define delete DEBUG_DELETE
输出方法
void print_mem_info() { struct mem_info *p_next = s_mem_info.GetMemInfo()->next; printf("Begin MemInfo :\n--------------------------------\n"); while (p_next != NULL) { printf("MemInfo address[%<u>ld</u>] %s\n", (long)p_next->ptr, p_next->msg); p_next = p_next->next; } printf("End MemInfo :\n--------------------------------\n"); }
main测试
#include <stdio.h> #include <mem_trace/mem-debug.h> int main(int argc, char *argv[]) { int *pInt = new int; int *pIntArray = new int[10]; printf("The <u>memery</u> info now\n"); print_mem_info(); delete pInt; delete[] pIntArray; printf("\n"); printf("The <u>memery</u> info before exit\n"); print_mem_info(); } //输出结果 The memery info now Begin MemInfo : -------------------------------- MemInfo address[27636048] new[] [memdebug_demo.cpp] [15] MemInfo address[27635728] new [memdebug_demo.cpp] [14] End MemInfo : -------------------------------- The memery info before exit Begin MemInfo : -------------------------------- End MemInfo : --------------------------------
github代码
解决方案
修改JVM启动参数(-Xms,-Xmx),直接增加虚拟机内存。
检查错误日志。
使用内存查看工具查看内存使用情况(如jconsole)
代码排查
(1) 检查在数据库中取的数据量是否超过内存
(2) 检查是否有过大的集合或对象
(3) 检查是死循环或递归是否会导致溢出
(4) 检查是否有大量对象的创建导致内存问题
(5) 检查是否有大量的连接对象或监听器等未关闭
避免
尽量少使用枚举
尽量使用静态内部类而不是内部类
尽量使用轻量级的数据结构
养成关闭连接和注销监听器的习惯
谨慎使用static关键字
谨慎使用单例模式
内存溢出 out of memory
描述与原因
操作空间超出指定内存空间
如申请char但是存了int
线程管理
线程泄露
算法
商密
DES
国密
SM4
哈希
MD5
卡尔曼滤波
排序
树
红黑树
MQTT
消息队列遥测传输协议
特性
发布
提供一对多的消息发布,解除应用程序耦合。
消息发布服务质量(QoS)
至多一次(最简单)
会发生消息丢失或重复、
常用于传感器数据传输
至少一次(最常用)
确保消息到达,但消息重复可能会发生
只有一次(最复杂)
确保消息到达一次
常用于严格的计费系统、即时通讯类的APP的推送
订阅Subscription
构建于TCP/IP协议上的长连接
设计原则
(1) 精简,不添加可有可无的功能;
(2) 发布/订阅(Pub/Sub)模式,方便消息在传感器之间传递;
(3) 允许用户动态创建主题,零运维成本;
(4) 把传输量降到最低以提高传输效率;
(5) 把低带宽、高延迟、不稳定的网络等因素考虑在内;
(6) 支持连续的会话控制;
(7) 理解客户端计算能力可能很低;
(8) 提供服务质量管理;
(9) 假设数据不可知,不强求传输数据的类型与格式,保持灵活性。
协议详解
身份
发布者Publish
消息代理Broker(服务器)
(1) 接受来自客户的网络连接
(2) 接受客户发布的应用信息
(3) 处理来自客户端的订阅和退订请求
(4) 向订阅的客户转发应用程序消息
订阅者Subscribe
分类
订阅Subscription
为了获取某些主题的消息(有些平台可能允许订阅当前不存在的主题)
包含主题筛选器+服务质量QoS
会话Session
客户端与服务器建立连接后就是一个会话
一个会话可以有多个订阅,每个订阅都有不同主题筛选器
主题Topic
应用程序消息的标签
被订阅者订阅后,就会收到该主题消息内容
主题筛选器Topic Filter
订阅表达式中使用
负载Payload
消息订阅者收到的内容
方法API
Connect
Disconnect
Subscribe
订阅
UnSubscribe
取消Topics订阅
Publish
发布,客户端发送消息
数据结构
1. 固定头Fixed header
存在于所有MQTT数据包中,表示数据包类型和分组类标识
结构
子主题
2. 可变头Variable header
存在于部分MQTT数据包中,数据包类型决定可变头是否存在及其具体内容。
3. 消息体payload
存在于部分MQTT数据包中,表示客户端收到的具体内容。