Airbox账号密码:linaro
风扇太吵的话:
sudo busybox devmem 0x50029000 32 0x500 sudo busybox devmem 0x50029004 32 0xfa0
1.TPU-MLIR 快速入门手册: https://doc.sophgo.com/sdk-docs/v23.09.01-lts/docs_latest_release/docs/tpu-mlir/quick_start/html/01_introduction.html
2.示例模型仓库地址: https://github.com/sophon-ai-algo/examples
3.TPU-MLIR 官方仓库地址 https://github.com/sophgo/tpu-mlir
4.SOPHON-SAIL 开发手册: https://doc.sophgo.com/sdk-docs/v23.05.01/docs_latest_release/docs/sophon-sail/docs/zh/html/
5.TPU-MLIR 环境搭建与使用指南: https://www.sophgo.com/curriculum/description.html?category_id=43
6.LLM 的概念与实战(其次学习,让然后看看算能学院): https://www.sophgo.com/curriculum/description.html?category_id=47
7.Airbox Demo 参考(首先参考复现): https://zhengorange.github.io/airbox_wiki/
7.1刷机部署等: https://gitee.com/zilla0717/AirboxWiki/blob/master/README.md
8.算能开发者论坛: https://forum.sophgo.com
9.少林派开发板实战课程: https://www.sophgo.com/curriculum/description.html?category_id=6
10.sophoneSDK3开发指南 https://sophgo-doc.gitbook.io/sophonsdk3
请根据我对图像的文字描述,回答问题。 问题:桌子上有什么? 文字描述:'a screwdriver and tape measure on a table' 'a blue rectangular device with a red button' 'a screwdriver with a black handle' 'a close up of a machine' 'a yellow and black tape measure',
SOPHON BM1684X是SOPHON专门针对深度学习领域推出的第四代张量处理器,具有32TOPS计算能力,支持32路高清硬件解码,12路高清硬件编码,适用于深度学习、计算机视觉、高性能计算等环境。
transformer模型分为encoder和decoder,都由注意力机制和FNN前馈神经网络(残差链接和归一化等等)组成。
常见LLM:
LLM的训练过程可以理解为对有效信息的无损压缩过程,压缩率约高,模型的智能水平越高,泛化能力约强,对任务的理解越好。
LLM模型的发展历程:
根据大模型结构分类:只有encoder的模型,如BERT;有encoder和decoder的模型,如T5;只有decoder的模型,如GPT系列;实践证明只有decoder的模型效果更好。
模型压缩:
LLM的训练和推理加速:
TPU-MLIR是一种专用于处理器的TPU编译器。该编译器项目提供了一个完整的工具链,可以将来自不同深度学习框架(PyTorch, ONNX, TFLite和Caffe)的各种预训练神经网络模型转换为高效的模型文件(bmodel/cvimodel),以便在SOPHON TPU上运行。通过量化到不同精度的bmodel/cvimodel,优化了模型在sophon计算TPU上的加速和性能。这使得可以将与对象检测、语义分割和对象跟踪相关的各种模型部署到底层硬件上以实现加速。
传统的编译器是将高级编程语言编译为低级编程语言,比如机器码,组成与平台兼容的库或可执行文件;
Windows中有Microsoft Visual C++ (MSVC) 和 MinGW编译器;
Linux和MacOS中Clang,GNU Compiler Collection (GCC) 编译器;
传统编译器编译流程:
AI编译器是将深度神经网络编译为二进制模型,编译流程与传统编译器相似,区别在于将源代码转化为AI模型,词法单元转换为组成AI模型的算子,主要对AI模型进行优化(模型压缩、量化、算子融合等,减少计算量与存储需求,提高推理性能);
SophonSDK是算能科技基于其自主研发的AI芯片所定制的深度学习SDK,涵盖了神经网络推理阶段所需的模型优化、高效运行支持等能力,为深度学习应用开发和部署提供易用、高效的全栈式解决方案。
包含以下工具包:
TPU-MLIR:Multi-Level Intermediate Representation,基于LLVM(Low-Level Virtual Machine)开发的编译器基础框架;统一IR(Intermediate Representation)格式,并通过多层IR提高通用与可复用性;扩展性与可组合性强,便于实现优化与代码生成;自带Tensor类型,目前主要用于深度学习领域;
模型转换需要在指定的docker执行,主要分两步,一是通过 model transform.py 将原始模型 转换成mlir文件,二是通过 model_deploy.py 将mlir文件转换成bmodel.如果要转INT8模型,则需要调用 run_calibration.py生成校准表,然后传给 model_deploy.py。如果INT8模型不满足精度需要,可以调用run_qtable.py 生成量化表,用来决定哪些层采用浮点计算,然后传给 model_deploy.py 生成混精度模型。
作为框架和硬件之间的桥梁,深度学习编译器可以实现一次性代码开发和重用各种计算能力处理器的目标。算能也开源了自己开发的TPU编译工具——TPU-MLIR (Multi-Level Intermediate Representation)。TPU-MLIR是一个面向深度学习处理器的开源TPU编译器。该项目提供了完整的工具链,将各种框架下预训练的神经网络转换为可在TPU中高效运行的二进制文件bmodel,以实现更高效的推理。本课程以实际实践为驱动,引导您直观地理解、实践、掌握智能深度学习处理器的TPU编译框架。
AI模型代码编译到特定硬件可以使用的二进制的模型bmodel,需要先编译为中间代码IR(在该步骤中完成优化),其复杂度介于高级编程语言与低级机器码之间。
需要多层IR进行转换(Dialect的概念),(实现TPU-MLIR的核心概念)
Dialect组成:
MLIR文本代码组成:
MLIR文本代码解释:
MLIR中Op的定义方式:
训练后量化:训练完成后量化,无需或仅需要少量的数据,易于实现;
量化感知训练:在训练过程中模拟量化重新训练模型,需要带标签的数据,量化后更接近F32模型的精度;在训练过程中插入伪量化算子,将weight与activation量化成低位精度再反量化回FP32引入量化误差。
MLIR实战
Pattern Rewriting Framework: 有向无环图DAG to 有向无环图DAG转换,
分为: Pattern Definition Pattern Rewriter Pattern Application
Dialect1转换为Dialect2通过Dialect Conversion组件进行转换
TPU原理: 一个完整的TPU包含多个Lane; 每个Lane包含Local memory(存储要运算的数据) 和 Execution Units(TPU上最小的计算单元);
指令:GDMA(数据搬运)、BDC(运算)、其他HAU.
智能多媒体关键技术:编解码技术、图像处理技术、多媒体通信技术; 智能多媒体关键指标:解码路数、帧率、分辨率、图像处理接口丰富度、延迟、协议支持;
空间分辨率:
量化:亮度的分辨率,衡量图像亮度的量化精度;
位深:表示图像中每个像素用多少个二进制位表示,灰度图通常是8位,彩色图像通常是24位;
帧率:表示视频中每秒包含的图像数;
码率:是数据传输时单位时间传送的数据比特数,单位是千比特每秒,码率kbps = 文件大小(KB) * 8/时间(s),越高每秒显示的帧数越多;
PSNR:峰值信噪比,常用于两幅图像相似度的测量,基于统计误差衡量,越大含有信息越多,统计意义上两图之间的差异越小,相似度越高;
色彩空间模型(可以相互转换):
3. YUV模型:亮度Y、色度UV(CbCr)组成,表示对蓝色和对红色的便宜程度;
图像存储格式:
图像增强:
直方图:横坐标表示灰度级,纵坐标表示该灰度级 出现的频数(越均匀图像最清晰);
边缘检测,边缘就是像素变化比较明显的区域(一阶导数极致的取区域,通常用一阶差分表示,对于二维图像,通常用梯度来检测),具有丰富的语义信息;
根据不同的卷积核对原图像做卷积可以实现很多图像的基本处理;
对于原信号有噪声的话,可以先滤波,如高斯降噪(可以用高斯滤波卷积核);
在精度要求不高时,Sobel是最常用的边缘检测算子(对高斯核求导再与原图像卷积),缺点是边缘出现了好几个像素,不是只有一个像素值;
Canny边缘检测流程:
为什么需要编码:一个高清视频存在空间冗余(帧内)、时间冗余(帧间)、心理视觉冗余(人眼对色度不敏感对亮度敏感)、编码冗余(可以用熵编码);
涉及的三个技术:预测编码、变换编码、熵编码;
JPEG编码主要步骤:
H.264编码标准:
H.265编码标准:
与多媒体相关的通信技术,包括TCP/IP、UDP、RTP/RTCP、RTSP、RTMP以及多媒体通信协议的应用开发技术。
数字视频接口类型:
无线传输:
TCP与UDP: TCP三次握手、四次挥手还有拥塞控制(慢启动快恢复);
RTP(Real-time Transport Protocol)与RTCP(Real-time Transport Control Protocol):通常基于UDP协议又做了一层控制,工作于应用层和传输层之间,适用于封装要实时传输数据的应用,如视频音频模拟数据等,RTCP提供拥塞控制流量控制等(在接收端会反馈报文的损失给发送端进而调整发送速度);
RTSP(Real Time Streaming Protocol)协议:控制分组基于TCP传输,数据分组基于UDP传输,是双向的,可以发可以拉;
RTMP(Real Time Messaging Protocol):设计用来实时数据通信,基于TCP,多用于直播领域,默认使用端口1935,一般传输的是flv、f4v格式流,延迟在1-3s;
GB28181:国家推进的标准协议,由公安部科技信息化局提出,联网系统在进行视音频传输及控制时应建立两个传输通道:会话通道和媒体流通道。会话通道用于在设备之间建立会话并传输系统控制命令,会话协议采用SIP协议(RFC3261);媒体流通道用于传输视音频数据,经过压缩编码的视音频流采用流媒体协议RTP/RTCP传输。会话协议实现的功能主要包括:注册、心跳保活、目录查询、实时视频点播、录像查询、录像回放/下载、报警事件上报、网络校时、事件订阅等;
嵌入式AI多媒体开发架构
嵌入式AI通过底层多媒体处理接口向算法侧提供API接口,如OpenCV、FFMpeg、BMCV接口,这些接口用于对视频图像编解码、图像的基本处理如色彩空间变换、尺度变换、仿射变换等。
BMCV:自有图像处理加速接口,提供硬件加速的图像处理功能;
两种工作模式:
SOC模式:AI芯片中的处理器作为主控CPU,可独立运行应用程序,通过cache同步系统内存和设备内存(物理上指向同一块内存);
PCIE模式:以PCIE板卡形态插到服务器主机应用程序在服务器CPU上运行,系统内存是服务器操作系统的虚拟内存空间,设备内存是PCIE板卡上的物理内存空间,在物理上是两块内存,通过PCIE同步;
int i; // 整数类型变量,通常为32位(4字节) short s; // 短整数类型变量,通常为16位(2字节) long l; // 长整数类型变量,通常为32位或64位(4或8字节) short int i1; // 等同于 short s; ,为16位(2字节) long int i2; // 等同于 long l; ,为32位或64位(4或8字节) unsigned int i3; // 无符号整数类型变量,通常为32位(4字节) signed int i4; // 有符号整数类型变量,通常为32位(4字节) unsigned u; // 无符号整数类型变量,大小取决于系统(一般为4字节) signed s1; // 有符号整数类型变量,大小取决于系统(一般为4字节) unsigned short us; // 无符号短整数类型变量,通常为16位(2字节) 总结: int、unsigned int、signed int 通常为4字节; short、unsigned short 通常为2字节; long、long int 一般为4或8字节,取决于编译环境; 字节大小可能因编译环境、操作系统和编译器的不同而有所变化。
c++#include<stdio.h>
// 定义链表
struct ListNode{
int name;
char b;
char c[10];
struct ListNode* next;
};
int main(){
int a;
char b;
char str[10] = "zzwxxy";
char str2[10];
printf("please input string\n");
gets(str2);
printf("input string is:\n%s", str2);
}
/*
* 定义字符串方式
char string[] = "zhang";
char string[] = {'z','h','a','n','g'};
char str[] = {“zhang”};
*/
/*
scanf和gets的区别:
使用方法:scanf("%s", str2); gets(str2);
scanf :当遇到回车,空格和tab键会自动在字符串后面添加’\0’,但是回车,空格和tab键仍会留在输入的缓冲区中。
gets:可接受回车键之前输入的所有字符,并用’\0’替代 ‘\n’.回车键不会留在输入缓冲区中
printf()和puts()的区别:
printf("input string is:\n%s", str2); 和 puts(str2);
*/
c++#include <stdio.h>
#include <stdlib.h>
struct Node
{
int data;
struct Node* next;
};
struct Node* head;
// 在链表头节点插入一个值为x的节点
void Insert(int x){
struct Node* temp = (struct Node*)malloc(sizeof(struct Node));
temp->data = x;
temp->next = head;
// if(head != NULL)temp->next = head;
head = temp;
}
// 迭代法实现反转列表
struct Node* Reverse1(struct Node* head){
struct Node *Current,*Prev,*next;
Current = head;
Prev = NULL;
while(Current != NULL){
next = Current->next;
Current->next = Prev;
Prev = Current;
Current = next;
}
head = Prev;
return head;
}
// 递归法实现反转列表
void Reverse2(struct Node* p){
if(p->next ==NULL){
head = p;
return;
}
Reverse2(p->next);
struct Node* q = p->next;
q->next = p;
p->next = NULL;
}
// 链表内指定区间反转 调试好了
struct Node* reverseBetween(struct Node* head, int m, int n ) {
// write code here
if(head == NULL)return head;
if(head->next == NULL)return head;
if(m == n)return head;
struct Node *Current,*Prev,*next,*start,*start_last;
int i;
Current = head;
Prev = NULL;
next = NULL;
// 先找到开始位置
for (i=1; i<m; i++) {
next = Current->next;
// Current->next = Prev;
Prev = Current;
Current = next;
}
// 标记
start_last = Prev;
start = Current;
// 反转
for (i=0; i<(n-m+1); i++) {
next = Current->next;
Current->next = Prev;
Prev = Current;
Current = next;
}
// 头尾节点重指向
if(start != head){
start->next = next;
start_last->next = Prev;//start!=head的情况下,需要保留start上一个指针
}
else {
start->next = next;
head = Prev;//start==head的情况下,直接将head指向待反转的最后一个
}
return head;
}
//打印链表的所有值
void Print(){
struct Node* temp = head;
printf("List is:");
while(temp){
printf("%d",temp->data);
temp = temp->next;
}
printf("\n");
}
void Print2(struct Node*p){
if(p ==NULL){
printf("\n");
return;
}
// 正序打印
// printf("%d",p->data);
// Print2(p->next);
// 反转打印
printf("%d",p->data);
Print2(p->next);
}
int main(){
head = NULL;
int n,x,i;
printf("Please input the number of node:\n");
scanf("%d",&n);
for(i = 0;i<n;i++){
printf("Please input the value of Node:\n");
scanf("%d",&x);
Insert(x);
Print();
}
head = Reverse1(head);
printf("Reverse linked list is:\n");
Print();
Reverse2(head);
printf("Reverse linked list is:\n");
Print();
// head = reverseBetween(head,2,4);
// Print();
// printf("Print2 linked list is:\n");
// Print2(head);
// char name[100];
// printf("What is your name?\n");
// scanf("%s",name);
// printf("Hello,%s,nice to meet you!\n",name);
}
c++#include <stdio.h>
// 1.插入排序,时间复杂度O(n2),空间复杂度O(1)
void InsertSort(int a[], int n)
{
for (int i = 1; i < n; i++)
{
// 若第i个元素大于i-1元素,直接插入。小于的话,移动有序表后插入
if (a[i] < a[i - 1])
{
int j = i - 1;
// 复制为哨兵,即存储待排序元素
int x = a[i];
// 先后移一个元素
a[i] = a[i - 1];
// 查找在有序表的插入位置
while (x < a[j])
{
a[j + 1] = a[j];
// 元素后移
j--;
}
// 插入到正确位置
a[j + 1] = x;
}
}
}
// 2.希尔排序,时间复杂度O(N^1.5),空间复杂度O(1)
void ShellInsertSort(int a[], int n, int dk)
{
for (int i = dk; i < n; ++i)
{
if (a[i] < a[i - dk])
{ // 若第i个元素大于i-1元素,直接插入。小于的话,移动有序表后插入
int j = i - dk;
int x = a[i]; // 复制为哨兵,即存储待排序元素
a[i] = a[i - dk]; // 首先后移一个元素
while (j >= 0 && x < a[j])
{ // 查找在有序表的插入位置
a[j + dk] = a[j];
j -= dk; // 元素后移
}
a[j + dk] = x; // 插入到正确位置
}
}
}
void ShellSort(int a[], int n)
{
int dk = n / 2;
while (dk >= 1)
{
ShellInsertSort(a, n, dk);
dk = dk / 2;
}
}
// 选择排序,时间复杂度:O(N^2), 空间复杂度:O(1)
int SelectMinKey(int a[], int n, int i)
{
int k = i;
for (int j = i + 1; j < n; ++j)
{
if (a[k] > a[j])
k = j;
}
return k;
}
void SelectSort(int a[], int n)
{
int key, tmp;
for (int i = 0; i < n - 1; ++i)
{
key = SelectMinKey(a, n, i); // 选择最小的元素
if (key != i)
{
tmp = a[i];
a[i] = a[key];
a[key] = tmp; // 最小元素与第i位置元素互换
}
}
}
// 堆排序,时间复杂度:O(N*logN),空间复杂度:O(1)
void HeapAdjust(int H[], int s, int length)
{
int tmp = H[s];
int child = 2 * s + 1; // 左孩子结点的位置。(i+1 为当前调整结点的右孩子结点的位置)
while (child < length)
{
if (child + 1 < length && H[child] < H[child + 1])
{ // 如果右孩子大于左孩子(找到比当前待调整结点大的孩子结点)
++child;
}
if (H[s] < H[child])
{ // 如果较大的子结点大于父结点
H[s] = H[child]; // 那么把较大的子结点往上移动,替换它的父结点
s = child; // 重新设置s ,即待调整的下一个结点的位置
child = 2 * s + 1;
}
else
{ // 如果当前待调整结点大于它的左右孩子,则不需要调整,直接退出
break;
}
H[s] = tmp; // 当前待调整的结点放到比其大的孩子结点位置上
}
}
/**
* 初始堆进行调整
* 将H[0..length-1]建成堆
* 调整完之后第一个元素是序列的最小的元素
*/
void BuildingHeap(int H[], int length)
{
// 最后一个有孩子的节点的位置 i= (length -1) / 2
for (int i = (length - 1) / 2; i >= 0; --i)
HeapAdjust(H, i, length);
}
/**
* 堆排序算法
*/
void HeapSort(int H[], int length)
{
// 初始堆
BuildingHeap(H, length);
// 从最后一个元素开始对序列进行调整
for (int i = length - 1; i > 0; --i)
{
// 交换堆顶元素H[0]和堆中最后一个元素
int temp = H[i];
H[i] = H[0];
H[0] = temp;
// 每次交换堆顶元素和堆中最后一个元素之后,都要对堆进行调整
HeapAdjust(H, 0, i);
}
}
// 冒泡排序,时间复杂度:O(N^2),空间复杂度:O(1)
void BubbleSort(int r[], int n)
{
int low = 0;
int high = n - 1; // 设置变量的初始值
int tmp, j;
while (low < high)
{
for (j = low; j < high; ++j) // 正向冒泡,找到最大者
if (r[j] > r[j + 1])
{
tmp = r[j];
r[j] = r[j + 1];
r[j + 1] = tmp;
}
--high; // 修改high值, 前移一位
for (j = high; j > low; --j) // 反向冒泡,找到最小者
if (r[j] < r[j - 1])
{
tmp = r[j];
r[j] = r[j - 1];
r[j - 1] = tmp;
}
++low; // 修改low值,后移一位
}
}
// 快速排序递归实现,时间复杂度:O(N*logN), 空间复杂度:O(logN)
int QuickSort(int *a, int low, int high)
{
int i = low; // 第一位
int j = high; // 最后一位
int key = a[i]; // 将第一个数作为基准值-- 先找到一个基准值
while (i < j)
{
while (i < j && a[j] >= key)
{
j--;
}
a[i] = a[j];
while (i < j && a[i] <= key)
{
i++;
}
a[j] = a[i];
}
a[i] = key;
if (i - 1 > low)
{
QuickSort(a, low, i - 1);
}
if (i + 1 < high)
{
QuickSort(a, i + 1, high);
}
return 0;
}
// 归并排序迭代实现,时间复杂度:O(N*logN), 空间复杂度:O(N)
void merge(int arr[], int start, int mid, int end, int len)
{
int result[len];
int k = 0;
int i = start;
int j = mid + 1;
while (i <= mid && j <= end)
{
if (arr[i] < arr[j])
{
result[k++] = arr[i++];
}
else
{
result[k++] = arr[j++];
}
}
if (i == mid + 1)
{
while (j <= end)
result[k++] = arr[j++];
}
if (j == end + 1)
{
while (i <= mid)
result[k++] = arr[i++];
}
for (j = 0, i = start; j < k; i++, j++)
{
arr[i] = result[j];
}
}
void MergeSort(int arr[], int start, int end, int len)
{
if (start >= end)
return;
int mid = (start + end) / 2;
MergeSort(arr, start, mid, len);
MergeSort(arr, mid + 1, end, len);
merge(arr, start, mid, end, len);
}
int main()
{
int i;
int array[] = {9, 5, 6, 1, 4, 7, 3, 8, 2};
int array2[9];
// InsertSort(array, sizeof(array)/sizeof(array[0]));
// ShellSort(array, sizeof(array)/sizeof(array[0]));
// SelectSort(array, sizeof(array)/sizeof(array[0]));
// HeapSort(array, sizeof(array)/sizeof(array[0]));
// BubbleSort(array, sizeof(array)/sizeof(array[0]));
// QuickSort(array, 0, 9-1);
int len = sizeof(array) / sizeof(array[0]);
MergeSort(array, 0, 9 - 1, len);
printf("sort result is:");
for (i = 0; i < 9; i++)
{
printf("%d", array[i]);
}
return 0;
}
c++#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
struct BstNode
{
int data;
struct BstNode *left;
struct BstNode *right;
};
struct BstNode *GetNewNode(int data)
{
struct BstNode *temp = (struct BstNode *)malloc(sizeof(struct BstNode));
temp->data = data;
temp->left = NULL;
temp->right = NULL;
return temp;
}
// 在二叉搜索树中插入一个节点
struct BstNode *Insert(struct BstNode *root, int data)
{
if (root == NULL)
{
root = GetNewNode(data);
}
else if (data <= root->data)
{
root->left = Insert(root->left, data);
}
else
{
root->right = Insert(root->right, data);
}
return root;
}
// 在二叉搜索树中搜索一个节点
bool Search(struct BstNode *root, int data)
{
if (root == NULL)
return false;
else if (root->data == data)
return true;
else if (data <= root->data)
return Search(root->left, data);
else
return Search(root->right, data);
}
// 二叉搜索树的最小值
int FindMin(struct BstNode *root)
{
struct BstNode *temp = root;
if (temp == NULL)
{
printf("root is empty\n");
return -1;
}
else
{
while (temp->left != NULL)
{
temp = temp->left;
}
return temp->data;
}
}
// 二叉搜索树的最大值
int FindMax(struct BstNode *root)
{
struct BstNode *temp = root;
if (temp == NULL)
{
printf("root is empty\n");
return -1;
}
else
{
while (temp->right != NULL)
{
temp = temp->right;
}
return temp->data;
}
}
// 二叉搜索树的前序遍历
void Preorder(struct BstNode *root)
{
if (root == NULL)
return;
printf("%d,", root->data);
Preorder(root->left);
Preorder(root->right);
}
// 二叉搜索树的中序遍历
void Inorder(struct BstNode *root)
{
if (root == NULL)
return;
Inorder(root->left);
printf("%d,", root->data);
Inorder(root->right);
}
// 二叉搜索树的后序遍历
void Poseorder(struct BstNode *root)
{
if (root == NULL)
return;
Poseorder(root->left);
Poseorder(root->right);
printf("%d,", root->data);
}
// 判断是否是二叉搜索树
bool IsSubtreeLesser(struct BstNode *root, int data)
{
if (root == NULL)
return true;
if (root->data <= data && IsSubtreeLesser(root->left, data) && IsSubtreeLesser(root->right, data))
return true;
else
return false;
}
bool IsSubtreeGreater(struct BstNode *root, int data)
{
if (root == NULL)
return true;
if (root->data >= data && IsSubtreeGreater(root->left, data) && IsSubtreeGreater(root->right, data))
return true;
else
return false;
}
bool IsBinarySearchTree(struct BstNode *root)
{
if (root == NULL)
return true;
if (IsSubtreeLesser(root->left, root->data) && IsSubtreeGreater(root->right, root->data) && IsBinarySearchTree(root->left) && IsBinarySearchTree(root->right))
return true;
else
return false;
}
// 二叉搜索树的高度
int FindHeight(struct BstNode *root)
{
int LeftHeight = 0;
int RightHeight = 0;
if (root == NULL)
{
return -1;
}
LeftHeight = FindHeight(root->left);
RightHeight = FindHeight(root->right);
if (LeftHeight >= RightHeight)
return LeftHeight + 1;
else
return RightHeight + 1;
}
int main()
{
struct BstNode *root = NULL;
printf("input start!\n");
root = Insert(root, 10);
root = Insert(root, 8);
root = Insert(root, 7);
root = Insert(root, 10);
root = Insert(root, 35);
root = Insert(root, 50);
printf("input yes!\n");
printf("%d", FindHeight(root));
// Inorder(root);
// if(IsBinarySearchTree(root) == true)printf("This is IsBinarySearchTree");
// else printf("This is not IsBinarySearchTree");
// if(Search(root,30) == true)printf("Found!\n");
// else printf("Not Found!\n");
// printf("%d\n",FindMax(root));
// printf("%d",FindMin(root));
}
c++#include<iostream>
#include <cstring>
#include <string>
using namespace std; // 为了使用cin和cout
// 定义链表
struct ListNode
{
int a;
string name;
char c;
ListNode* next;
};
ListNode *head;
int main(){
int a;
char b;
char c[8];
string d;
cout<<"please input string"<<endl;
getline(cin, d);
cout<<d<<endl;
return 0;
}
/*
#include <cstring> 的作用:
可以使用string定义字符串
可以使用strlen获得字符串(字符串需要使用char s[10])定义的长度
int strcmp(const char *s1, const char *s2);当s1<s2时,返回为负数;当s1=s2时,返回值= 0;当s1>s2时,返回正数。
*/
/*
cin和getline的区别:
cin>>str不会读取空格; getline(cin, str); 可以读取空格,但是需要#include <string> 并且str需要通过 string定义;
*/
/* *冒泡排序,时间复杂度O(N^2);空间复杂度:O(1);稳定排序 *思路:第一趟:两两元素相比,前一个比后一个大就交换,直到将最大的元素交换到末尾位置;一共进行n-1趟这样的交换将可以把所有的元素排好。 *插入排序,时间复杂度O(N^2);空间复杂度O(1);稳定排序 *思路:假设数组除最后一个元素都有序了,那么将最后一个元素与前面的比较,如果前面的元素大则向右移动。实际过程从数组的第二个元素开始执行插入排序 *希尔排序,时间复杂度O(N^1.5);空间复杂度O(1);不稳定排序。 *思路:先选定一个整数gap,把待排序文件中所有记录分成gap个组,所有距离为gap的记录分在同一组内,并对每一组内的元素进行排序。然后将gap逐渐减小重复上述分组和排序的工作。当到达gap=1时,所有元素在统一组内排好序。 *选择排序,时间复杂度O(N^2); 空间复杂度O(1);不稳定排序。 *思路:第一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始(末尾)位置,然后选出次小(或次大)的一个元素,存放在最大(最小)元素的下一个位置,重复这样的步骤直到全部待排序的数据元素排完。优化:每次最大和最小同时选. *堆排序,时间复杂度O(N*logN);空间复杂度O(1);不稳定排序。 *思路:升序为例,构建一个堆结构,然后每个父节点均替换为子节点的最大值,然后根节点就是最大值,重复这个步骤。 *快速排序,时间复杂度O(N*logN);空间复杂度O(logN);不稳定排序。 *思路:最右边的值为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。确定两个指针left 和right 分别从左边和右边向中间遍历数组。如果选最右边为基准值,那么left指针先走,如果遇到大于基准值的数就停下来。然后右边的指针再走,遇到小于基准值的数就停下来。交换left和right指针对应位置的值。重复以上步骤,直到left = right ,最后将基准值与left(right)位置的值交换。 *归并排序,递归实现,时间复杂度O(N*logN);空间复杂度O(N);稳定排序。 *思路:采用分治法,将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。
c++#include<iostream>
using namespace std;
int len = 10;
int a[10] = {5,7,2,6,4,1,3,9,8,10};
/*
*冒泡排序,时间复杂度O(N^2);空间复杂度:O(1);稳定排序
*思路:第一趟:两两元素相比,前一个比后一个大就交换,直到将最大的元素交换到末尾位置;一共进行n-1趟这样的交换将可以把所有的元素排好。
*/
void MaoPao(int a[]){
int temp;
int flag = 0;
// n-1趟排序
for(int i = 0; i < len - 1; i++){
for(int j = 0; j < len - 1 -i; j++){
if(a[j] > a[j + 1]){
temp = a[j];
a[j] = a[j + 1];
a[j + 1] = temp;
flag = 1;
}
}
// 若某一趟排序中没有元素交换则说明所有元素已经有序,不需要再排序
if(flag == 0)break;
}
}
/*
*插入排序,时间复杂度O(N^2);空间复杂度O(1);稳定排序
*思路:假设数组除最后一个元素都有序了,那么将最后一个元素与前面的比较,如果前面的元素大则向右移动。实际过程从数组的第二个元素开始执行插入排序
*/
void ChaRu(int a[]){
// 从第一轮就从第二个元素开始找,所以n-1轮就可以
for(int i = 0; i < len - 1; i++){
// 已经有序的最后一个元素
int end = i;
// 需要排序的元素
int temp = a[end + 1];
while(end >= 0){
if(a[end] > temp){
// 直接替换 循环外面再将被替换的值放到适当位置
a[end + 1] = a[end];
end--;
}
else{
break;
}
}
a[end + 1] = temp;
}
}
/*
*希尔排序,时间复杂度O(N^1.5);空间复杂度O(1);不稳定排序。
*思路:先选定一个整数gap,把待排序文件中所有记录分成gap个组,所有距离为gap的记录分在同一组内,并对每一组内的元素进行排序。然后将gap逐渐减小重复上述分组和排序的工作。当到达gap=1时,所有元素在统一组内排好序。
*/
void ShellSort(int a[], int n){
int gap = n;
while (gap > 1)
{
//gap /= 2;
gap = gap / 3 + 1;
for (int i = 0; i < n - gap; i++)
{
int end = i;
int x = a[end + gap];
while (end >= 0)
{
if (a[end] > x)
{
a[end + gap] = a[end];
end -= gap;
}
else
{
break;
}
}
a[end + gap] = x;
}
}
}
/*
*选择排序,时间复杂度O(N^2); 空间复杂度O(1);不稳定排序。
*思路:第一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始(末尾)位置,然后选出次小(或次大)的一个元素,存放在最大(最小)元素的下一个位置,重复这样的步骤直到全部待排序的数据元素排完。优化:每次最大和最小同时选.
*/
void SelectSort(int a[], int n){
//保存数组的起始位置
int begin = 0;
//保存换数组的末尾位置
int end = n - 1;
int temp;
while (begin < end)
{
int maxi = begin;//保存最大元素下标
int mini = begin;//保存最小元素下标
//遍历数组寻找最小和最大元素
for (int i = begin; i <= end; i++)
{
if (a[i] < a[mini])
{
mini = i;
}
if (a[i] > a[maxi])
{
maxi = i;
}
}
//将最小元素交换到起始位置
temp = a[begin];
a[begin] = a[mini];
a[mini] = temp;
//判断最大值的位置是否在起始位置
if (maxi == begin)
{
maxi = mini;
}
//将最大元素交换到末尾位置
temp = a[end];
a[end] = a[maxi];
a[maxi] = temp;
//移动数组起始和末尾位置
begin++;
end--;
}
}
/*
*堆排序,时间复杂度O(N*logN);空间复杂度O(1);不稳定排序。
*思路:升序为例,构建一个堆结构,然后每个父节点均替换为子节点的最大值,然后根节点就是最大值,重复这个步骤。
*/
void HeapAdjust(int H[], int s, int length)
{
int tmp = H[s];
// 左孩子结点的位置。(i+1 为当前调整结点的右孩子结点的位置)
int child = 2 * s + 1;
while (child < length)
{
if (child + 1 < length && H[child] < H[child + 1])
{ // 如果右孩子大于左孩子(找到比当前待调整结点大的孩子结点)
child++;
}
if (H[s] < H[child])
{ // 如果较大的子结点大于父结点
H[s] = H[child]; // 那么把较大的子结点往上移动,替换它的父结点
s = child; // 重新设置s ,即待调整的下一个结点的位置
child = 2 * s + 1;
}
else
{ // 如果当前待调整结点大于它的左右孩子,则不需要调整,直接退出
break;
}
H[s] = tmp; // 当前待调整的结点放到比其大的孩子结点位置上
}
}
/**
* 初始堆进行调整
* 将H[0..length-1]建成堆
* 调整完之后第一个元素是序列的最小的元素
*/
void BuildingHeap(int H[], int length)
{
// 最后一个有孩子的节点的位置 i= (length -1) / 2
for (int i = (length - 1) / 2; i >= 0; --i)
HeapAdjust(H, i, length);
}
void HeapSort(int H[], int length)
{
// 初始堆
BuildingHeap(H, length);
// 从最后一个元素开始对序列进行调整
for (int i = length - 1; i > 0; --i)
{
// 交换堆顶元素H[0]和堆中最后一个元素
int temp = H[i];
H[i] = H[0];
H[0] = temp;
// 每次交换堆顶元素和堆中最后一个元素之后,都要对堆进行调整
HeapAdjust(H, 0, i);
}
}
/*
*快速排序,时间复杂度O(N*logN);空间复杂度O(logN);不稳定排序。
*思路:最右边的值为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。确定两个指针left 和right 分别从左边和右边向中间遍历数组。如果选最右边为基准值,那么left指针先走,如果遇到大于基准值的数就停下来。然后右边的指针再走,遇到小于基准值的数就停下来。交换left和right指针对应位置的值。重复以上步骤,直到left = right ,最后将基准值与left(right)位置的值交换。
*/
int PartSort(int a[], int left, int right){
// 选最右面为基准
int key = right;
while(left < right){
//选右边为基准值,左指针先走
while(left < right && a[left] <= a[key]){
left++;
}
//右指针再走
while(left < right && a[right] >= a[key]){
right--;
}
// 交换
int temp = a[right];
a[right] = a[left];
a[left] = temp;
}
int temp = a[key];
a[key] = a[left];
a[left] = temp;
return left;
}
void QuickSort(int a[], int left, int right){
if(left >= right)return;
// 第一次快排
int keyi = PartSort(a, left, right);
// 左子序列快排
QuickSort(a, left, keyi - 1);
// 右子序列快排
QuickSort(a, keyi + 1, right);
}
/*
*归并排序,递归实现,时间复杂度O(N*logN);空间复杂度O(N);稳定排序。
*思路:采用分治法,将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。
*/
void _MergeSort(int* a, int left, int right,int* tmp)
{
//区间中没有元素时不再合并
if (left >= right)
{
return;
}
//划分数组,每次一分为二
int mid = (left + right) / 2;
_MergeSort(a, left, mid,tmp);//划分左区间
_MergeSort(a, mid + 1, right,tmp);//划分右区间
//合并有序序列
int begin1 = left, end1 = mid;//有序序列1
int begin2 = mid + 1, end2 = right;//有序序列2
int i = left;
//注意结束条件为一个序列为空时就停止
while (begin1 <= end1 && begin2 <= end2)
{
if (a[begin1] < a[begin2])
{
tmp[i++] = a[begin1++];
}
else
{
tmp[i++] = a[begin2++];
}
}
//两序列不可能同时为空,将剩余元素合并
while (begin1 <= end1)
{
tmp[i++] = a[begin1++];
}
while (begin2 <= end2)
{
tmp[i++] = a[begin2++];
}
//将合并后的序列拷贝到原数组中
//在这里拷贝的原因是 保证返回到上一层递归后两个子序列中的元素是有序的
int j = 0;
for (j = left; j <= right; j++)
{
a[j] = tmp[j];
}
}
void MergeSort(int* a, int n)
{
//因为需要将两个有序序列合并,需借助额外数组
int* tmp = (int*)malloc(sizeof(int) * n);
if (tmp == NULL)
{
perror("malloc");
exit(-1);
}
_MergeSort(a, 0, n - 1,tmp);
free(tmp);
tmp = NULL;
}
int main(){
// MaoPao(a);
// ChaRu(a);
// ShellSort(a, len);
// SelectSort(a, len);
// HeapSort(a, len);
// QuickSort(a, 0, len - 1);
// MergeSort(a, len);
cout<<"out:"<<endl;
for(int i = 0; i < len; i++){
cout<<a[i];
cout<<',';
}
}
c++vector<int> v;//默认初始化
vector<int> v(v1);//用v1初始化v
vector<int>v(v1.begin(),v1.end());//用v1初始化v
vector<int> v(10);//定义一个大小为10的数组!
vector<int> v(10,1)//定义个全为1而且长度为10的数组
a.front() //返回第一个元素
a.back() //末尾元素
c.begin() 返回一个迭代器,它指向容器的第一个元素
c.end() 返回一个迭代器,它指向容器的最后一个元素的下一个位置
c.rbegin() //返回一个逆序迭代器,它指向容器的最后一个元素
c.rend() 返回一个逆序迭代器,它指向容器的第一个元素前面的位置
v,push_back() //增
v.insert() //插入
1、v.insert(p, t) //将t插到p的前面
2、v.insert(p, n, t) //将n个t插入p之前
3、v.insert(p, i, j) //将区间[i,j)的元素插入到p之前
v.pop_back();//删除最后一个元素
v.erase(t,k)
1、v.erase(t,k)//删除[t,k)之间的元素
2、v.erase(p)//删除p指向的元素
v.chear()==v.erase(begin(),end());//删除所有元素
//下标法
int length = v.size();
for(int i=0;i<length;i++)
{
cout<<v[i];
}
cout<<endl;
//迭代器法
vector<int>::const_iterator iterator = v.begin();
for(;iterator != v.end();iterator++)
{
cout<<*iterator;
}
set底层实现通常是平衡二叉树元素自动排序,这为查找元素提供了良好性能,但同时也造成了一个重要限制:不能直接改变元素值,因为这会打乱原本正确的顺序。
unordered_set底层实现通常是hash-table元素是无序的。
c++#include <set>
/*set 生成*/
set<int> st;
/*set 迭代器*/
set<int>::iterator iter
st.begin()
st.end()
/*set 插入*/
st.insert(2); //插入一个元素
/*set 删除*/
st.erase(st.begin()); //删除迭代器指向元素
st.erase(2); //删除所有为2的元素
/*set 容量*/
st.size()
/*set 查找*/
st.find(2) //从前往后找,若找到,返回指向该处的迭代器;反之,返回迭代器st.end()
st.lower_bound(x) //二分查找大于等于x的元素中最小的一个,并返回指向该元素的迭代器。
st.upper_bound(x) //二分查找大于x的元素中最小的一个,并返回指向该元素的迭代器。
/*set 某元素个数*/
st.count(2); //返回容器里2的个数
/*set 判空*/
st.empty() //返回布尔值
/*set 清空*/
st.clear();
c++#include <map>
/*map 生成*/
map<key_type, value_type> name;
map<int, int> mp;
/*map 迭代器*/
map<int, int>::iterator iter
mp.begin()
mp.end()
/*map 键值*/
iter->first //key
iter->second //value
/*map 插入*/
mp[2] = 5; //直接添加
mp.insert(pair<int, int>(2, 5)); //insert一个pair
/*删除*/
mp.erase(iter); //删除迭代器所指的键值对
/*map 容量*/
mp.size()
/*map 查找*/
mp.find(2) //从前往后找,若找到,返回指向该处的迭代器;反之,返回迭代器mp.end()
/*map 某元素个数*/
st.count(2); //返回key为2的个数(map中只可能是0或者1)
/*map 判空*/
mp.empty() //返回布尔值
/*map 清空*/
mp.clear();
c++#include <stack>
/*stack 生成*/
stack<int> sk;
/*stack 插入*/
sk.push(2); //把一个元素放入栈
/*stack 删除*/
sk.pop(); //删除栈顶的元素
/*stack 栈顶*/
sk.top(); //返回栈顶元素
/*stack 容量*/
sk.size();
/*stack 判空*/
sk.empty()
c++#include <queue>
/*queue 生成*/
queue<int> q;
/*queue 头尾*/
q.front();
q.back();
/*queue 插入*/
q.push(2); //在队尾插入一个元素
/*queue 删除*/
q.pop(); //在队首删除一个元素
/*queue 容量*/
q.size();
/*queue 判空*/
q.empty()
c++/*
头文件queue主要包括循环队列queue和优先队列priority_queue两个容器。其中priority_queue容器相当于大根堆(或者小根堆),大根堆每次堆顶是最大元素,小根堆每次堆顶是最小元素。(以下typename均用int举例)
*/
#include <queue>
/*priority_queue 生成*/
priority_queue<int> q; //大根堆
priority_queue<int, vector<int>, greater<int>> q; //小根堆
/*priority_queue 插入*/
q.push(2); //把一个元素插入堆
/*priority_queue 删除*/
q.pop(); //删除堆顶的元素
/*priority_queue 堆顶*/
q.top(); //返回堆顶元素
/*priority_queue 容量*/
q.size();
/*priority_queue 判空*/
q.empty()
deque是double ended queue的缩写,是一个动态数组,可以向两端发展(双向开口的连续线性空间),因此无论在头部或者尾部安插元素都十分迅速,在中间按插元素则比较费时,因为必须移动其他元素。 双端队列的元素被表示为一个分段数组,容器中的元素分段存放在一个个大小固定的数组中,此外,容器还需要维护一个存放这些数组首地址的索引数组。
初始化与定义已经在序列要求里面,而且方法与vector类似,只是多了push_front()(),pop_front(),这里不做过多的阐述。
c++#include <deque>
/*dequeue 生成*/
dequeue<int> dq;
/*dequeue 头尾*/
dq.front();
dq.back();
/*dequeue 迭代器*/
dq.begin()
dq.end()
/*dequeue 插入*/
dq.push_front(2); //头插入
dq.push_back(2); //尾插入
/*dequeue 删除*/
dq.pop_front(); //头删除
dq.pop_back(); //尾删除
/*dequeue 容量*/
dq.size();
/*dequeue 判空*/
dq.empty()
/*dequeue 清空*/
dq.clear();
c++#include <utility>
/*pair 生成*/
pair<int, int> pr = make_pair(0,1);
pair<int, int> pr(0, 1);
/*pair 两个值*/
pr.first
pr.second
/*pair 多与其他容器结合使用*/
set<pair<int, int>> st;
vector<pair<int, int>> vct(mp.begin(), mp.end());
MQTT协议全称是Message Queuing Telemetry Transport,翻译过来就是消息队列遥测传输协议,它是物联网常用的应用层协议,运行在TCP/IP中的应用层中,依赖TCP协议,因此它具有非常高的可靠性,同时它是基于TCP协议的 <客户端-服务器> 模型发布/订阅主题消息的轻量级协议,也是我们常说的发送与接收数据。
MQTT适合物联网,类似:订阅电视台某个频道。
MQTT报文格式:
程序设计(分层):
客户端程序框架:
1.连接服务器函数调用过程
2.创建线程调用过程
其中创建的线程中的函数mqtt_yield_thread会读网络数据,处理数据包,保持心跳
3.发布消息调用过程
4.订阅消息过程
消息何时到来?不知道!
所以,必定是某个内核线程不断查询网卡:
读网卡数据
在第2步创建的线程,while(1){读网络数据;if(是否是一个发布者消息){判断那个topic,并执行对应的函数;}}
源码直接复现:
shell// 1. 创建文件
$ cat arm-linux.cmake
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_C_COMPILER arm-buildroot-linux-gnueabihf-gcc)
set(CMAKE_CXX_COMPILER arm-buildroot-linux-gnueabihf-g++)
// 2. 修改build.sh
cmake .. "-DCMAKE_TOOLCHAIN_FILE=../arm-linux.cmake"
// 3. 执行
./build.sh
// 4. 编译库,得到:./libmqttclient/lib/libmqttclient.so
./make-libmqttclient.sh
// 5. 修改代码后要先移除build文件夹
rm -rf build
在工程中使用MQTT方法:
相关信息
(待完善,后续加各种传感器实现智能家居)
在imx6ull上运行mjpg推流到本地ip的8080端口:
mjpg_streamer -i "/usr/lib/mjpg-streamer/input_uvc.so -d /dev/video1 -f 30 -q 90 -n" -o "/usr/lib/mjpg-streamer/output_http.so -w /usr/share/mjpg-streamer/www"
音视频编解码流程:
1.摄像头接口(v4l2):
2.声卡接口(ALSA):
移植nginx方法:1.下载源码,手工编译。2.使用Buildroot,配置选择nginx,直接编译生成映像文件。
使用Buildroot:
rtmp协议走的端口经常被防火墙拦截,可以使用http_flv协议(修改配置文件增加一个location节点) rtmp推流给nginx过程:rtmp推流到某个地址,nginx访问的html界面中含有这个地址,就能观看到摄像头视频;
单板下载方式:
新旧固件覆盖模式:
MCU OTA升级过程:
Linux OTA升级过程: 升级过程基本和MCU类似,有以下概念:
RAM、ROM、Flash的区别:
BootLoader 引入的目的:更新系统; 主要作用:
必备知识: 段/重定位 散列文件 异常向量
相关信息
(待完善,后续从0写单片机的bootloader和linux的bootloader,分析现有源码,最后理解linux的uboot)
uboot:universal bootloader;
uboot就是一个裸机程序,功能是用来启动内核,进而启动各种应用程序;作为通用的bootloader能支持很多soc厂家、板卡厂家、开发的不同型号的板;
linux硬件组成:
在flash中有uboot、内核和文件系统的程序。单片机内存比较小一般64KB,可以直接用SRAM(不用初始化),linux内存比较大,几百M,几G,一般用DDR/SDRAM(需要初始化)
uboot有flash的驱动程序,能读flash,上电启动过程:
uboot源码提供了dtb目录用来各种厂家的设备树指定硬件资源,这些dts文件不会编译进uboot的可执行文件,只作为配置文件传给uboot使用。
烧录的uboot = 原始uboot.bin + 某个dtb;
保证uboot源码不臃肿。
XIP(execute in place),RAM和Flash都是XIP设备,cpu可以直接发出地址信号读得到指令并执行,都是cpu读取指令就好像直接在内存上运行的程序,其实执行是在cpu上。SD卡就不是XIP设备,需要CPU通过emmc控制器读取SD上的指令然后运行(这个过程需要通过BootROM,cpu可以直接读BootROM上代码执行,BootROM上的代码可以将SD卡上的uboot上的程序读到内存中)。
因此uboot启动流程,根据uboot代码位置分为两种情况:
uboot源码结构,在u-boot目录下执行"tree . -d > 1.txt",可以得到目录的结构,精简如下:
shell├── arch │ ├── arm // 1. 架构相关 │ │ ├── cpu │ │ │ ├── armv7 │ │ │ │ ├── mx6 │ │ ├── dts │ │ │ └── include │ │ │ └── dt-bindings -> ../../../../include/dt-bindings │ │ ├── include │ │ │ ├── asm │ │ │ │ ├── arch-imx │ │ │ │ ├── arch-imx8 │ │ │ │ ├── arch-imx8m │ │ │ │ ├── imx-common │ │ │ └── debug │ │ ├── lib │ │ ├── mach-rockchip │ │ │ ├── rk3036 │ │ │ ├── rk3288 │ │ │ └── rk3399 │ │ ├── lib ├── board // 单板相关 │ ├── freescale │ │ ├── common │ │ │ └── p_corenet │ │ ├── corenet_ds │ │ ├── mx6ul_14x14_ddr3_arm2 │ │ ├── mx6ul_14x14_evk │ │ ├── mx6ul_14x14_lpddr2_arm2 │ │ ├── mx6ull_ddr3_arm2 │ │ ├── mx6ullevk ├── cmd // 通用的命令 │ ├── fastboot │ └── mvebu ├── common // 通用的 │ ├── eeprom │ ├── init │ └── spl ├── configs ├── disk ├── drivers // 各类驱动 ├── fs // 文件系统 │ ├── cbfs │ ├── cramfs │ ├── ext4 │ ├── fat │ ├── jffs2 │ ├── reiserfs │ ├── sandbox │ ├── ubifs │ ├── yaffs2 │ └── zfs ├── include ├── lib // 库 ├── net // 网络协议
Makefile分析:比如all
规则,clean
规则,include
语法;
vim命令删除注释:g/^#/d
uboot中有很多源码,用编译哪些源码得到最后的uboot.bin文件需要配置。比如IMX6ULL配置命令: make mx6ull_14x14_evk_defconfig
生成了.config文件。
执行过程:
文件.config中含有架构、soc、厂家、单板等配置信息。
以命令mx6ull_14x14_evk_defconfig
为例分析怎么得到的.config文件:
从makefile中分析过程,mx6ull_14x14_evk_defconfig依赖scripts/kconfig/conf,又依赖其他东西...,makefile中以下过程展开:
shellmx6ull_14x14_evk_defconfig: scripts/kconfig/conf
$(Q)$< $(silent) --defconfig=arch/$(SRCARCH)/configs/$@ $(Kconfig)
就是:
shellUBOOTVERSION=2017.03 scripts/kconfig/conf --defconfig=arch/../configs/mx6ull_14x14_evk_defconfig Kconfig
所以要分析scripts/kconfig/conf.c
,该代码整体结构:
shelldefconfig_file = "arch/../configs/mx6ull_14x14_evk_defconfig"; name = "Kconfig" conf_parse(name); // 解析uboot根目录下的Kconfig文件 conf_read(defconfig_file); // 读配置文件 conf_set_all_new_symbols(def_default); // 设置new_symbols为默认值 conf_write(NULL); // 写到.config
分析结果:
uboot config界面语法(增加配置项、菜单(多选、单选),选择了某个配置项后就会把这个功能编进uboot里);
得到的.config用来选择哪些目录/文件被编译,并得到一个.h文件存放.c文件用到的宏等;
得到.config后,使用make
就可以编译uboot了,内部过程:
整体理解uboot编译流程(涉及子文件目录的编译等等,Makefile文件还是很复杂的)
XIP
BootROM
相对寻址、绝对寻址
uboot完整启动流程总结:
Out = (in + 2p - k)/s + 1;
非关系型数据库,不使用SQL作为主要查询语言,而是主要以key-value键值对的形式进行数据存储。这种数据库设计主要针对当前互联网时代的复杂数据,具备高度的可伸缩性和可用性。作用:作为缓存减少IO读操作,减轻CPU和IO压力,通过内存直接读取数据,相对于传统关系型数据库,能够处理超大规模和高并发数据。
如何应用分布式系统进行计算,即把一组计算机通过网络相互连接组成分散系统,然后将需要处理的数据分散成多个部分,交由分散在系统内的计算机组同时计算再将结果最终合并得到最终结果。
LR是一种用于二分类和多分类问题的线性模型。它使用逻辑函数将输入特征与输出概率之间建立关联,通常用于概率建模和分类任务。
准确率(Accuracy)在不平衡数据集中容易误导,因为它不考虑类别之间的不平衡。在类别分布不均匀时,准确率可能不是一个合适的评估指标,需要考虑其他指标如精确度、召回率、F1分数等。
常见的优化算法包括梯度下降法(包括随机梯度下降和批量梯度下降)、Adam、RMSprop、Adagrad等。这些算法用于调整模型参数以最小化损失函数。
常见的特征提取方法包括主成分分析(PCA)、线性判别分析(LDA)、单词嵌入(Word Embeddings)、卷积神经网络(CNN)特征提取等,用于从原始数据中提取有用的特征。
CNN(卷积神经网络)和MLP(多层感知机)都是神经网络模型,但CNN在处理图像和空间数据时具有优势。CNN使用卷积层和池化层可以捕捉局部特征和空间结构,减少了参数数量,并且在图像处理等领域表现出色。
RNN(循环神经网络)是一种适用于序列数据的神经网络,但它存在梯度消失和梯度爆炸的问题。LSTM(长短时记忆网络)是RNN的一种变体,通过门控机制解决了梯度问题,可以更好地捕捉长期依赖性。然而,LSTM相对复杂,训练和计算成本较高。
线性回归、逻辑回归、决策树、支持向量机、朴素贝叶斯等
过拟合是训练效果很好,预测效果很差; 解决:
高斯分布
均匀分布:
六大设计原则:
23种设计模式:
先确定一个规则:const默认与左边结合,左边没有东西则与右边结合。在这个规则下进行分析。 1.const int* a const与int结合,因此变量a是一个指向常量整型的指针。 2.int const * a const与int结合,因此变量a与1同。 3.int* const a const与*结合,因此变量a是一个指向整型的常量指针。 4.const int* const a 第1个const与int结合,第2个const与*结合,因此变量a是一个指向常量整型的常量指针。 5.int const * const a 第1个const与int结合,第2个const与*结合,因此变量a与上同。 6.int const * const * a 第1个const与int结合,第2个const与左边的*结合,而变量a前还有1个多出来的*,因此变量a是一个二级指针,即指向4/5中常量指针的指针。 7.int const * const * const a 第1个const与int结合,第2个const与左边的*结合,第3个const也与左边的*结合,因此变量a是一个常量二级指针,即指向4/5中常量指针的常量指针。 附注:1.常量整型不可改值。2.常量指针不能修改指针的指向。
B树:
定义: B树是一种自平衡的多路搜索树,它可以有多个子节点,不同于二叉树的是,一个节点可以有超过两个的子节点。B树特别适合用于读写相对较大的数据块的存储系统,如磁盘。
数据结构: 一个B树的节点可能包含多个键(数据项)和子指针。节点中的键是有序的,并且每个键的左侧子树包含的键都比它小,右侧子树包含的键都比它大。B树通过重新分布键和指针,分裂和合并节点来维持平衡。
优点: 减少了磁盘I/O操作。保持了树的平衡。对于大型数据集的查找和顺序访问非常高效。
缺点: 节点分裂和合并的过程相对复杂。当数据经常插入和删除时,维护成本较高。
应用: 数据库索引。文件系统。
B+树:
定义: B+树是B树的变种,所有的值都在叶子节点上,并且叶子节点是通过指针连接的,这样就提供了对数据的顺序访问。内部节点(非叶子节点)只存储键值,并作为索引使用。
数据结构: 与B树类似,但B+树有两个不同点:一是非叶子节点不存储数据,仅用于索引;二是所有叶子节点之间都是相互链接的,这样就支持了快速的顺序遍历。
优点: 所有的查询都要查找到叶子节点,查询性能稳定。叶子节点形成了一个有序链表,便于全范围扫描。
缺点: 由于数据只存在于叶子节点,所以可能需要更多的I/O操作来达到叶子节点。
应用: 数据库索引(特别是范围查询和顺序访问)。
红黑树:
定义: 红黑树是一种自平衡的二叉搜索树,它在每个节点上增加了一个存储位来表示节点的颜色,可以是红色或黑色。通过对任何一条从根到叶子的路径上各个节点的颜色进行约束,红黑树确保没有一条路径会比其他路径长出两倍,因而是近似平衡的。
数据结构: 每个节点包含颜色、键值、左右子节点以及指向父节点的指针。红黑树的约束包括:每个节点要么是红色,要么是黑色。根节点是黑色。所有叶子(NIL节点)是黑色。如果一个节点是红色的,则它的子节点必须是黑色的(反之不一定)。从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。
优点: 保证最长路径不会超过最短路径的两倍,因而是平衡的。在实际应用中,插入、删除、查找操作都有很好的性能。
缺点: 算法实现相对复杂。在最坏情况下,可能需要多次颜色变更和树旋转。
应用: 关联数组。高级语言的数据结构库,如C++的STL中的map和set。
B树与B+树与红黑树的区别
信号量、消息队列、共享内存、管道、套接字;
层级 | 名称 | 作用 | 协议 | 关键词 |
---|---|---|---|---|
7 | 应用层 | 各类网络服务 | HTTP、FTP | |
6 | 表示层 | 数据编码、格式转换、加密 | LPP、NBSSP | |
5 | 会话层 | 维护会话 | SSL、TLS、DAP、LDAP | |
4 | 传输层 | 建立主机端到端的连接(应用间的通信) | TCP、UDP | 端口号、TCP、UDP |
3 | 网络层 | 路由选择,控制数据包在设备间的转发(主机间通信) | **IP、ICMP、路由器、**RIP、IGMP、OSPF | IP地址、路由器、ping通 |
2 | 数据链路层 | 将比特流封装成数据帧(数据帧、网卡间通信) | ARP、网卡、交换机、PPTP、L2TP、ATMP | MAC地址、网卡 |
1 | 物理层 | 定义电平、传输介质、物理接口 | 光纤、集线器、中继器等物理器件 |
程序内存布局场景下,堆与栈表示两种内存管理方式。
数据结构场景下,堆与栈表示两种常用的数据结构。
FreeRTOS和Linux是两个不同的嵌入式操作系统,它们在设计理念、架构和功能上存在一些异同。
总体而言,FreeRTOS和Linux在实时性能、资源占用和功能丰富度上存在差异。选择使用哪个操作系统取决于具体的应用需求和资源限制。
Cortex-M内核支持中断优先级配置,中断优先级数值越小,优先级越高,与FreeRTOS任务优先级相反,任务优先级数值越小,优先级越低
。
FreeRTOS中,实际上是没有中断的,中断还是由硬件产生。以STM32为例,中断请求的响应过程还是由STM32的中断服务函数完成,而FreeRTOS在其中的作用可以说成是唤醒某个阻塞或就绪任务。
也就是说,在硬件STM32的中断函数中,需要使用信号量或事件等完成中断与FreeRTOS系统任务的通信,实现通过中断函数来唤醒一次或多次FreeRTOS系统任务。
假设我们正在开发一个嵌入式系统,其中一个传感器通过中断方式通知系统有新的数据可用。我们将使用 FreeRTOS 来处理这个中断事件。在这个示例中,传感器中断触发 vSensorISR 中断服务例程,该例程通过释放信号量 xSemaphore 通知等待的任务有中断事件发生。任务函数 vTaskFunction 通过等待信号量的方式实现对中断事件的处理。这种方式保证了中断处理的实时性,同时避免了在中断服务例程中直接调用 FreeRTOS API。
玩过 MCU 的人都知道,中断服务程序的设计最好是快速完成任务并退出,因为此刻系统处于被中断中。但是在 ISR 中又有一些必须完成的事情,比如:清中断标志,读/写数据,寄存器操作等。
在 Linux 中,同样也是这个要求,希望尽快的完成 ISR。但事与愿违,有些 ISR 中任务繁重,会消耗很多时间,导致响应速度变差。Linux 中针对这种情况,将中断分为了两部分:
上半部(top half):收到一个中断,立即执行,有严格的时间限制,只做一些必要的工作,比如:应答,复位等。这些工作都是在所有中断被禁止的情况下完成的。
底半部(bottom half):能够被推迟到后面完成的任务会在底半部进行。在适合的时机,下半部会被开中断执行。
上半部立即执行,下半部放在工作队列或者内核线程去处理。
freertos: 每个任务都有一个任务控制块(TCB),当任务发生切换后,选择就绪任务列表里优先级最高的任务轮流执行。
任务在就绪状态下,每个优先级都有一个对应的列表,从就绪任务列表中高优先级列表往下找,找到第一个非空列表轮流执行里面的任务。
列表由列表头和列表项组成,列表头和列表项组成一个双向循环链表。
调度过程:在移植时,我们把系统时钟中断xPortSysTickHandler加入到了中断向量表,这个中断周期设置为1ms。这个中断是系统的核心,我们称作调度器,在这里会调用xTaskIncrementTick()把时间计数值加1,并检查有哪些延时任务到期了,将其从延时任务列表里移除并加入到就绪列表里。如果到期的任务优先级>=当前任务则开始一次任务切换。如果当前任务就绪态里有多个任务,也需要切换任务,优先级相同需要在一个系统时钟周期的时间片里轮流执行每个任务。另外在应用程序里也可以通过设置xYieldPending的值来通知调度器进行任务切换。
任务调度方式:
Linux调度
Linux把任务主要分为两类:
当系统中存在实时任务时,调度程序会优先选择实时任务执行,普通任务将得不到执行的机会。同时不同类型的任务有不同的调度策略。
在 CPU 的角度看进程行为的话,可以分为两类:
CFS是 Completely Fair Scheduler 简称,即完全公平调度器。CFS 调度器和以往的调度器不同之处在于没有固定时间片的概念,而是公平分配 CPU 使用的时间。比如:2个优先级相同的任务在一个 CPU 上运行,那么每个任务都将会分配一半的 CPU 运行时间,这就是要实现的公平。
但现实中,必然是有的任务优先级高,有的任务优先级低。CFS 调度器引入权重 weight 的概念,用 weight 代表任务的优先级,各个任务按照 weight 的比例分配 CPU 的时间。比如:2个任务A和B,A的权重是1024,B的权重是2048,则A占 1024/(1024+2048) = 33.3% 的 CPU 时间,B占 2048/(1024+2048)=66.7% 的 CPU 时间。
目前,ARM处理器(内核)分为5类:Cortex-A、Cortex-R、Cortex-M、Machine Learning、SecurCore。前3种我们大部分人都听说过,见下图:
A系列主要用来跑Linux
M系列就是本科玩过的单片机,可以跑FreeRTOS:
保留区 –> 文本段(包含程序和字符串常量)–>初始化的变量(.data,包含全局变量和静态变量)—>未初始化的变量(.bss,包含全局变量和静态变量)—>堆—>共享库或mmap—>栈–>命令函参数(main函数的参数)–>环境变量—>内核空间
推挽输出的最大特点是可以真正能真正的输出高电平和低电平,在两种电平下都具有驱动能力。常说的与推挽输出相对的就是开漏输出,对于开漏输出和推挽输出的区别最普遍的说法就是开漏输出无法真正输出高电平,即高电平时没有驱动能力,需要借助外部上拉电阻完成对外驱动。所谓的驱动能力,就是指输出电流的能力。