嵌入式复习
嵌入式复习
ubuntu(x86架构、64位、版本18.04.6)【lsb_release -a】
S3C2440开发板(ARMv4T架构、32位,ARM920T芯片)
现代计算机(X64架构、64位)
linux系统版本:5.4.0?【uname -a】 Linux内核源码:2.6.32.6
//嵌入式编程和X86编程
嵌入式:32位开发板,资源比较紧张,交叉编译
X86:64位系统,资源丰富,本地编译
信锐:专注于基础网络与物联网联接领域的产品及服务提供商
映翰通:公司专注于机器设备间通信技术的行业应用及工业信息化,提供工业无线路由器、物联网智能网关、设备云平台等产品,覆盖电力、工控、智能交通等十余个行业。
一、C语言基础
1*、C语言编译的阶段
预处理:宏替换、头文件展开、去掉注释、特殊符号处理
编译:C语言转换汇编文件
汇编:将汇编文件转换成二进制文件
链接:关联各种符号信息,归并文件
(main.c C源代码-->main.i完全符合C语言语法的文本文件-->main.s汇编文件-->main.o可执行的二进制文件---mainapp可直接执行的二进制文件>)
2、结构体,枚举,共用体大小计算
字节对齐:编译器在内存中排布数据时,按照一定规则,使数据的起始地址位于某个值的整数倍原因:
硬件限制:很多CPU在访问未对齐内存地址时会触发硬件异常,导致程序崩溃
性能:未对齐对性能消耗,(可能需要两次内存总线周期)
【用空间换时间】
// 结构体对齐规则:
// 1. 成员相对于结构体首地址的偏移量是其自身大小的整数倍
// 2. 结构体总大小是最大成员大小的整数倍
// 3. 编译器支持#pragma pack(n)修改对齐方式
【char a;int b;short c;】//12字节
【int a;char b; short c;】//8字节//枚举对齐规则
//通常是4字节//共用体对齐
//联合体中最大成员所占内存的大小且必须为最大类型所占字节的最小倍数。
【char ch[10];int i;double d;】//16字节
【char ch[7];int i;double d;】//8字节
3*、static
//针对两部分来说:(存储在静态存储区)
全局变量(或函数):限制作用域为本文件(从定义的位置开始到文件结束)
局部变量: 延长生命周期至整个程序结束,被static修饰过的局部变量仅会初始化1次
4*、const
//作用:修饰变量的属性为只读
const char *P和char const *P:两者一样,*P不能被修改,P可以改变指向
char *const P:P指向不能改变,*P可以被修改
char const * const P:P和*P均不能被修改
5*、Volatile
//作用
易失性修饰符,每次访问变量都从内存地址中访问【防止编译器优化】
强制编译器每次需要该变量的值时,都必须从它的内存地址中重新读取,
每次修改该变量时,都必须立刻写回它的内存地址。
【而未被Volatile修饰的变量,变量可能存寄存器取,CPU缓存】
//实例
内存映射硬件寄存器:硬件设备(如 GPIO 引脚、状态寄存器)的状态会映射到特定的内存地址。程序通过读写这些内存地址来控制硬件,而这些地址的值会由硬件自行改变
在中断服务程序 (ISR) 中修改的全局变量
多线程应用中的共享变量
6、inline
为了解决一些频繁调用的小函数大量消耗栈空间(栈内存)的问题,特别的引入了 inline 修饰符,表示为内联函数。编译器会尝试在每个调用点上将其函数体展开
//inline和宏的区别:
inline:编译阶段处理,比较安全
宏:预处理阶段处理,不太安全
7*、extern
声明一个外部符号(变量和函数)可以在本文件使用
8、冒泡排序
思想:重复地遍历要排序的列表,依次比较相邻的两个元素,每一轮遍历都会将当前未排序部分中的最大(或最小)元素“浮”到其正确的位置上。时间复杂度:最好O(n),最坏O(n^2)稳定性好。
稳定:稳定是在排序过程中,相对元素的相对位置因交换操作不改变
void bubblesort(int *a,int n)
{for(int i=1;i<n;i++)//控制趟数{for(int j=0;j<n-i;j++)//一趟的比较的过程{if(a[j]>a[j+1]){swap();}}}
}
9、选择排序
思想:每次从未排序的部分中选出最小(或最大)的元素,放到已排序部分的末尾
void choiceshort(int *a,int n)
{for(int i=0;i<n-1;i++){for(int j=i+1;j<n;j++){if(a[i]>a[j]){swap();}}}
}
10、插入排序
思想:将待排序的序列看作两部分:前面是已经排好序的部分,后面是未排序的部分。然后逐个将未排序部分的元素插入到前面已排序部分的正确位置上,直到所有元素都插入完毕。
void insertsort(int *a,int n)
{for(int i=0;i<n;i++)//控制拿数{t=a[i];j=i;while(j>0&&a[j-1]>t){a[j]=a[j-1];j--;}a[j]=t;}
}
11、快速排序
思想:分治法,选择基准值,将数组分为两部分,左边均小于基准,右边均大于基准,将基准左右两侧的子数组重复上述步骤。时间复杂度:最好O(nlogn),最坏O(n^2)
void QuitSort(int *begin,int *end)
{if(begin>=end){return;}int *left=begin;int *right=end;int k=*begin;while(left<right){while(left<right && *right>=k){right--;}while(left<right && *left<=k){left++;}if (left < right) {swap(left, right);}}swap(begin, left);QuitSort(begin, left - 1);QuitSort(left + 1, end);
}
12、内存大小端
32位的int整数0x12345678存放起始地址0x1000内存中:小端:高高低低;大端:高低高低
小端
| 0x1000(低地址) | 0x1001 | 0x1002 | 0x1003(高地址) |
|---|---|---|---|
| 0x78(低字节) | 0x56 | 0x34 | 0x12(高字节) |
大端
| 0x1000(低地址) | 0x1001 | 0x1002 | 0x1003(高地址) |
|---|---|---|---|
| 0x12(高字节) | 0x34 | 0x56 | 0x78(低字节) |
int IsLittleEdian(void)
{unsigned int a=1;//大端:0x00 0x00 0x00 0x01//小端:0x01 0x00 0x00 0x00unsigned char *p = (unsigned char *)&a;return *p;
}
union{short s;char c[2];
}endianTest;
endianTest.s=0x0102;
//因为联合体共享一片内存,所以short和char都占同一片的2字节数据
if(endianTest.c[0]==0x01) printf("大端\n");
else printf("小端\n");
13、数组、指针、函数结合
//指针常量
int *const p
//常量指针
const int *p//指针数组:一个有10个指针的数组,
int *a[10]
//数组指针:一个指向有10个整型数组的指针
int (*a)[10]//函数指针:定义了一个名为fun的函数指针,指向一个int类型的参数且返回值类型为int的函数。
int sum(){}
int main(){int (*fun)(int a);fun=sum
}
//指针函数:返回值类型为指针的函数
int *fun()//函数指针数组:一个有10个指针的数组,该指针指向一个函数
int (*a[10])(int a);
int (*a[10])(int a)={fun1,fun2...func10};
指针数组 int *a[10]:本质是数组,数组每个元素都是int *指针,内存占用10*sizeof(int *),
数组指针 int(*a)[10]:本质是指针,指针指向一个包含3个int的指针,内存占用sizeof(int),
14、C语言数据类型的大小
//在32位系统上,(1个字节8位数据)
int 4个字节,char 1个字节,short 2个字节,long 4个字节,float 4个字节,double 8个字节,指针4个字节
//在64位系统
int 4个字节,char 1个字节,short 2个字节,long 8个字节(64位windows4个字节),float 4个字节,double 8个字节,指针8个字节
15、C语言函数的形参和实参
形参:函数定义中声明的变量,它们作为占位符存在,用于接收在函数调用时传递的值。形参只在函数体内部有效,当函数调用结束后,形参所占用的内存就会被释放。
实参:函数调用时提供给形参的具体值。而实参则在函数调用之外定义,它们在函数调用期间保持不变,即使形参的值在函数内部发生改变,也不会影响实参的值。没有形参和实参的机制,函数就无法成为通用的逻辑模板。
指针传参:
void swap(int *a,int *b)
{
int temp=*a;
*a=*b;
*b=temp;
}
int x=10,y=20;
swap(&x,&y);
举例:
将数组传入函数时,实际上传递的是数组首元素的地址,而不是整个数组的拷贝。
// 方式一:最直观,明确表示需要一个数组(但编译器仍会将其视为指针)
void myFunction(int arr[], int size) {
}
// 方式二:最真实,明确表示这是一个指针
void myFunction(int *arr, int size) {
}
函数内部无法通过数组形参得知原数组的长度,因此必须额外传递数组大小
16*、if not define一般有啥用
#ifndef _XXX_H
#define _XXX_H
//----
#endif
条件编译:防止重复包含的宏定义(重复定义变量,结构体,函数等),也称为头文件保护
17*、头文件“”和<>有啥区别
#include<stdio.h>:默认在系统PATH下查找头文件
#include"fun.h":默认在当前目录下查找头文件
18、二级指针
用法:
//修改指针指向:
int change_pointer(int **q)
{int value2=20;*q=&value2;//修改外部指针的指向
}
int main()
{int value1=10;int *p=&value1; //初始值printf("%d\n",*p); //10change_pointer(&p); //传入指针的地址,为二级指针printf("%d\n",*p); //20
}
//处理指针数组://函数内部分配内存并传参
int alloc_memoy(char **q,int size)
{*q=malloc(size);if(q!=NULL){strcpy(*q,"hello");}
}
int main()
{char *p=NULL;printf("%p\n",p); //为nilalloc_memoy(&p,100);printf("%p\n",p); //为一个地址printf("%d\n",p); //hello
}
19、枚举
//在C语言中,enum 是一种用户自定义的数据类型,用于定义一组具名的整型常量。
enum week
{ Mon = 0,
Tues,
Wed,
Thurs,
Fri,
Sat,
Sun };
enum week today = wed;
//today=2;
20、函数的声明和定义的区别
声明:告诉编译器类型、名称、参数(这个名字是什么),保证程序能通过编译。不分配内存
定义:告诉编译器为它分配内存或提供实现体(这个名字在哪里),保证程序能被链接和运行。分配内存
21、strcpy、strncpy和memcpy有什么区别
strcpy:将源字符串src(包括结束符\0)全部复制到目标字符串。会有缓冲区溢出的风险,极不安全
strncpy:从源字符串src中复制最多n个字符到目标字符串。可能不以\0结尾,相对安全,要注意n的选择
memcpy:内存复制,复制指定的字节数
22、已知有一个int型大小的内存及其地址,如何向该地址存入数据
int *p=(int *)0x3a47ba14; // 地址
*p=10
23、结构体有几个成员,知道成员的地址,怎么得到结构体的起始地址
//结构体起始地址=成员地址-该成员在结构体中的偏移量
24、自增的i++和++i
//int j = i++; 相当于 j = i; i = i + 1;先使用变量a的当前值参与表达式运算,然后再将a的值加1。
//int j = ++i; 相当于 i = i + 1; j = i;先将变量a的值加1,然后再使用新的值参与表达式运算。
int a = 10;
int b = a++; // b = 10, 然后 a = 11
int c = ++a; // a = 12, 然后 c = 12
int d = 5 * a++; // d = 5 * 12 = 60, 然后 a = 13
//a=13,b=10,c=12,d=60
25、auto和static
//auto 是C语言中默认的存储类别,用于声明自动变量(局部变量)
26、位运算请除一位和置一位
//&与:一假则假
//|或:一真则真
//^异或:相同为0,不同为1
a |= (1<<3) //用于将a的第3位置1。
a &= ~(1<<3) //用于将a的第3位清0。
27、有符号和无符号运算
28、求字符串的最大连续字符串,最大的数字连续字符串
29、字符数组
//一维
char *str="hello";
char str[100]="hello";
//二维
char *str[3]={"hello","world","love"};
char str[10][3]={"hello","world","love"};
30*、库(静态库,动态库)
库:一堆可执行二进制文件的集合, 由若干个.o文件归并生成 静态(链接)库: libxxx.a
优:生成独立的可执行程序(运行时仅需一个文件即可)、使用方便,不需要安装
缺:文件体积大、多个程序使用同一个静态库时,每个程序独立链接(库文件改动需要重新编译整个工程)
gcc -c fun.c -o fun.o //编译目标文件
ar -rcv libfun.a fun.o fun1.o fun2.o//ar归档工具,把fun.o fun1.o等打包成libfun.a静态库动态(链接)库: libxxx.so。
优:可执行程序小、多个程序共享同一库文件、库文件更新,只需替换库文件,无需重新编译程序
缺:程序执行时,需要可执行文件和库文件同时存在,并且可执行文件能找到库文件(动态库必须存在于系统PATH目录下、指定链接路径)
gcc -fPIC -shared fun.c fun1.c -o libfun.so
//-fPIC:生成位置无关代码,这是动态库的必要条件,因为动态库可能被加载到不同进程的不同内存地址
//-shared:告诉编译器要生成共享库(动态库)
-I:指定头文件路径
-L:指定链接库的路径
31、strlen和sizeof
sizeof返回数组的总大小(包括空字符)。
strlen返回字符串的长度,直到空字符为止(不包括空字符)。
char *p="abcde";sizeof(p)=4 strlen=5反斜杠零(\0)是字符串的结束符,在内存中占一个字节,内容为0,长度:作为一个字符,长度为1
32、野指针和空指针
野指针是指向未知或已释放内存(无效内存)的指针避免方法:
(1)检查指针有效性后再使用
(2)声明时立即初始化
(3)释放后立即置空
(4)不要返回局部变量的地址(栈地址),因为函数结束后,该地址无效,那返回一块无效的内存空指针是不指向任何有效内存地址的指针,int *p=NULL
33、memset、memcpy、memmove、memcmp
使用memset初始化所有字节为0:memset(arr, 0, n * sizeof(int));
memcpy和memmove都是内存复制,后者考虑内存重叠了
void *Memmove(void *dest, const void *src, int n)
{if(dest==NULL || src==NULL || n==0){return src; }char *p_dest=(char *)dest;const char *p_src=(const char *)src;if(p_dest > p_src && p_dest < p_src+n){p_dest+=n-1;p_src+=n-1;for(int i=0;i<n;i++){*p_dest=*p_src;p_dest--;p_src--;}}else{for(int i=0;i<n;i++){*p_dest=*p_src;p_dest++;p_src++;}}return dest;
}
memcmp ⽤于按字节⽐较两块内存区域的内容
34、typedef和define
typedef:为已有的数据类型定义一个新的别名。是编译时的行为,只影响类型
define:用于定义宏或者常量,预处理会在编译前进行文本替换。是预处理阶段的替换,适用于常量和代码片段。
35、break和continue
break:立即跳出循环或switch语句
continue:跳出当前循环的剩余部分,继续下一次循环
二、数据结构
1、什么是时间复杂度,常用的排序算法时间复杂度
算法时间复杂度:执行算法所花时间的度量
推导时间复杂度(用1取代所有常数,只保留最高阶,最高阶存在且不是1则去除最高阶的常数)
O(1)<O(logn)<O(N)<O(nlogn)<O(n^2)<O(n^3)<O(2^n)<O(n!)<O(n^n)(最好到最坏)冒泡O(n^2),选择O(n^2),插入O(n^2)
快排O(nlogn),堆排O(nlogn),归并O(nlogn)
2、链表和顺序表(数组)的区别和优缺点
| 顺序表(数组) | 链表 |
|---|---|
| 连续的存储单元 | 物理结构(在内存中的表现形式)不连续 |
| 需要预先分配空间,大小固定 | 不需要预先分配,大小可变,动态分配 |
| 查找为O(1);插入删除为O(n) | 查找为O(n);插入删除为O(1) |
3、单链表和双链表的区别
| 单链表 | 双链表 |
|---|---|
| 每个节点有一个指向后继节点的指针,只能单向遍历 | 每个节点有一个指向前驱和后继节点的指针,可以双向遍历 |
| 尾插,尾删,指定节点前删除:O(n) | 尾插,尾删,指定节点前删除:O(1) |
| 头插,头删,指定节点后删除:O(1) | 头插,头删,指定节点后删除:O(1) |
| 查找,遍历:O(n) | 查找,遍历:O(n) |
4、双向链表的插入、遍历、查找、逆序
[先新节点往老节点指]
插入:
int InsertHeadDouLinkList(DouLinkList *dl, DATATYPE *data)
{DouLinkNode* newnode=malloc(sizeof(DouLinkNode));if(newnode==NULL){fprintf(stderr, "CreateDouLinkList malloc\n");return 1;}memcpy(&newnode->data, data, sizeof(DATATYPE));newnode->next=NULL;newnode->prev=NULL;if(IsEmptyDouLinkList(dl)){dl->head=newnode;//?}else//?{newnode->next=dl->head;// 新节点的 next 指向原头节点dl->head->prev=newnode;// 原头节点的 prev 指向新节点dl->head=newnode;// 更新链表头指针为新节点}dl->clen++;return 0;
}
// i
遍历:
int GetSizeDouLinkList(DouLinkList *dl)
{return dl->clen;
}
int ShowDouLinkList(DouLinkList *dl,SHOW_DIR dir)
{int size=GetSizeDouLinkList(dl);DouLinkNode* tmp=dl->head;if(DIR_FORWARD==dir){for(int i=0;i<size;i++){printf("name:%s age:%d sex:%c score:%d\n",tmp->data.name,tmp->data.age,tmp->data.sex,tmp->data.score);tmp= tmp->next;//tmp++;}}else{while (tmp->next) {tmp=tmp->next;}for(int i=0;i<size;i++){printf("name:%s age:%d sex:%c score:%d\n",tmp->data.name,tmp->data.age,tmp->data.sex,tmp->data.score);tmp= tmp->prev;//tmp++;}}return 0;
}
查找:
DouLinkNode *FindDouLinkList(DouLinkList *dl, char *name)
{if(IsEmptyDouLinkList(dl)){return NULL;}DouLinkNode* tmp=dl->head;while(tmp){if(strcmp(tmp->data.name,name)==0){return tmp;}tmp=tmp->next;}return NULL;
}
逆序:
DouLinkList *RverseDouLinkList(DouLinkList *dl)
{int size=GetSizeDouLinkList(dl);if(size<2){return dl;} DouLinkNode* Prev=NULL;DouLinkNode* Tmp=dl->head;DouLinkNode* Next=Tmp->next;while (1) {Tmp->next=Prev;Tmp->prev=Next;Prev=Tmp;Tmp=Next;if(Tmp==NULL){break;}Next=Next->next;}dl->head=Prev;return dl;
}
5、简单说一下链表查重(有无环)结束的条件是什么
快慢指针相等了
int has_cycle(LinkList *list)
{if(list==NULL || list->head==NULL){return 0;//空表无环}LinkNode *fast=list->head;LinkNode *slow=list->head;
while(fast!=NULL && fast->next==NULL)
{slow=slow->next;fast=fast->next-next;if(slow==fast){return 1;}
}
return 0;
}
6、如何调试段错误?gdb和valgrind

gcc -g main.c
gdb a.out
b 36(行数)
r 运行
n 下一步
p 查看数据
c 跳出循环(continue)
where 显示栈结构,函数调用关系
bt
7、malloc中有多少空间没有被覆掉怎么办
malloc分配的空间中,所有未被初始化的字节都包含垃圾数据,这些数据是未被覆写的,不可预测是,直接使用它们是危险的。
第一:手动初始化
int *arr = (int*)malloc(n * sizeof(int));
// 总是检查malloc是否成功
if (arr == NULL) {perror("malloc failed");exit(1);
}
// 关键步骤:使用memset初始化所有字节为0
memset(arr, 0, n * sizeof(int));
第二:使用calloc 是另一个内存分配函数,它的特点是在分配的同时会自动将内存初始化为全零。
void* calloc(size_t num, size_t size);
8、栈、队列之间的区别?
| 栈 | 队列 |
|---|---|
| 先进后出 | 先进先出 |
| 仅在一端增删改查 | 一端增,另一端删 |
| 函数调用 | 消息队列,缓冲区 |
9、二叉树
满二叉树:一个深度为k,且有2^k-1个节点的二叉树。每一层都是满的,所有叶子节点都在最后一层
完全二叉树:一个深度为k,前k-1层是满的,且最后一层的节点从左到右连续排列,中间没有空缺
广度优先遍历(BFS):按层级顺序,从上到下、从左到右访问每个节点。
深度优先遍历(DFS):前序、中序、后序
10、Makefile
DST = dht11_app
SRC = dht11_app.c
SRC +=queue.c
SRC +=list.c
# 编译器
ARMCC = arm-linux-gcc
# 编译选项
FLAGS = -g -Wall -std=gnu99// 库路径
LIBS = -L ./lib \-L /home/linux/nfs/paho.mqtt.c-master/build/output \-L /home/linux/nfs/openssl-1.0.0s/__install/lib \-L /home/linux/nfs/rootfs/lib \// 头文件
# SQLite3 头文件路径 - 需要根据你的实际路径修改
SQLITE_INC = -I /home/linux/nfs/rootfs # 或者你的sqlite3头文件所在路径
# 其他头文件路径
INC = -I /usr/local/include $(SQLITE_INC)// 链接库
LDFLAGS = -lrt -lsqlite3 -lssl -lcrypto -lpaho-mqtt3c -lpthread# ARM版本编译规则
arm: $(SRC)$(ARMCC) $(FLAGS) $(SRC) -o $(DST) $(LIBS) $(INC) $(LDFLAGS)
clean:rm -f $(DST)
.PHONY: arm clean
show:echo $(LIBS) $(INC) $(LDFLAGS)//目标体:
arm、clean、show
//目标体所依赖的文件:
arm的依赖文件:$(SRC)即 dht11_app.c queue.c list.c。clean和show无
//目标体时需要运行的命令(规则):arm目标:$(ARMCC) $(FLAGS) $(SRC) -o $(DST) $(LIBS) $(INC) $(LDFLAGS) clean目标:rm -f $(DST)show目标:echo $(LIBS) $(INC) $(LDFLAGS)
11、哈希表
哈希表:
哈希表是一种通过哈希函数将键映射到值的数据结构。插入、删除、查找操作都是O(1)
通常会碰到两个关键字 key1≠key2,但是却有f(key1)=f(key2),这种现象我们称为冲突查找过程:(查找一个Key时)
将Key扔进哈希函数
立即得到一个索引
直接访问数组的该索引的位置,查看key是否存在解决冲突:
1. 开放定址法:发现冲突后寻找下一个空闲散列表位置
2. 再哈希法:利用不同的哈希函数再次计算哈希值(多轮)
3. 链地址法:每个哈希表节点都有一个next指针,多个哈希表节点可以用next指针构成一个单向链
表,被分配到同一个索引上的多个节点可以用这个单向链表连接起来,因而查找、插入和删除主要
在同义词链中进行。
4. 公共溢出区法:冲突放入溢出表
12、内存碎片、内存泄露、内存溢出、内存越界
内存碎片:是指在反复的内存分配和释放过程中,堆中产生了许多无法使用的小内存块,导致无法满足大块内存的分配请求。【有空间,但无法有效利用】
内存泄漏:是指程序中分配的内存没有被释放,导致内存无法被重新分配使用,最终可能耗尽系统内存。【资源被永久占用而无法回收】
char *ptr = (char*)malloc(100); // 占用了100字节
ptr = (char*)malloc(200);// 重写ptr指针!之前100字节的地址丢失了,无法再释放。
内存溢出:
(1)缓冲区溢出
char buffer[10]; // 只能容纳10个字符
strcpy(buffer, "This string is definitely longer than 10 characters!"); // 溢出!
(2)栈溢出
比如深层次的递归调用,消耗尽了栈区空间,导致栈溢出。
(3)分配失败
int *huge_array = (int*)malloc(1000000000000 * sizeof(int)); // 申请一个巨大的空间,很可能失败
内存越界:指程序访问了未分配给它的内存空间。
数组越界访问、访问野指针指向的空间、访问已经释放的堆区空间、访问已经释放的局部变量空间
13、用过哪些调试手段?检测内存泄漏的原理吗?踩内存是什么?越界访问?发生越界访问,怎么调试?
调试方法:GDB,valgrind检测内存管理检测内存泄漏原理:内存泄漏检测的基本原理是记录程序中内存分配和释放的情况,并在程序结束时检查是否有未释放的内存块。踩内存:越界写操作,破坏了其他数据;越界访问:程序访问不属于它的内存区域。堆溢出,栈溢出GDB和valgrind调试越界访问
valgrind --tool=memcheck ./a.out
14、有序链表合并成一个有序链表
struct ListNode* mergeTwoLists(struct ListNode* l1, struct ListNode* l2)
{struct ListNode *dummy;struct ListNode *current=&dummy;dummy.next=NULL;ListNode *tmp1=l1;ListNode *tmp2=l2;while(tmp1 != NULL && tmp2 !=NULL){if(tmp1->val<tmp2->val){current->next=tmp1;tmp1=tmp1->next;}else{current->next=tmp2;tmp2=tmp2->next;}current=current->next;}return dummy.next;
}
三、文件编程
1、对目录的操作流程是什么
打开opendir 读操作readdir 关闭closedir。
2*、目录操作函数
创建mkdir() 、删除空目录rmdir() 、获取当前的工作路径getcwd() 、改变当前的工作路径chdir()、获得文件属性stat()
3、目录的遍历怎么实现
#include <dirent.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
void cat_ls(char *ls)
{DIR *dir=opendir(ls);if(dir==NULL){return;}while (1){struct dirent *info=readdir(dir);if(info==NULL){break;}if(strcmp(info->d_name, ".")==0||strcmp(info->d_name, "..")==0){continue;}char cs[1024];sprintf(cs, "%s/%s", ls, info->d_name);printf("%s\n", cs);if(info->d_type==DT_DIR){cat_ls(cs);}}closedir(dir);
}
int main(int argc,char **argv)
{if(argc<2){printf("输入错误\n");return 1;}cat_ls(argv[1]);return 0;
}
4*、IO学了什么
标准IO:有缓冲区,效率更高,文件指针(流是通过 FILE*类型的对象来管理的),Windows, Linux都支持(标准库的接口stdio.h)
fopen(), fread()/fgetc(), fwrite()/fputc(), fclose(), fseek()
文件IO:无缓冲区,效率偏低,文件描述符,仅在Linux下可以使用(系统接口)
open(),read(),write(),close(),lseek()
5、在Linux操作系统中怎么获得内存的大小
free -h
cat /proc/meminfo
6、库函数用man1和man2查询有什么区别
man1是用于查询普通的用户命令(shell终端命令),man2用于查询系统调用(内核函数)
7、fgets和fputs文件的拷贝是怎么实现的
#include <stdio.h>
int main(int argc, char *argv[])
{if(argc<3){printf("usage:%s srcfile dstfile\n",argv[0]);return 1;}FILE* src = fopen(argv[1],"r");// 读取目标文件权限 ,目标文件必须存在FILE* dst = fopen(argv[2],"w");// 读取目标文件权限 ,目标文件必须存在if(NULL == src||NULL== dst){printf("fopen error\n");return 1;}char buf[256]={0};while(1){char* tmp = fgets(buf,sizeof(buf),src);if(NULL == tmp) //end of file {break;}fputs(buf,dst);}fclose(dst);fclose(src);return 0;
}
8、linux常用的命令功能
ls cd pwd mkdir rmdir rm cp mv touch cat tail chmod man tar zip
进程:ps kill top
网络:ping ifconfig netstatgrep:查找字符串(grep -n "hello" 1.txt)top: 动态实时查看进程状态和系统资源占用(查CPU占用率 {perf})kill PID: 通过进程 ID 杀死进程。pkill 进程名:通过进程名杀死进程。【kill -9 PID: 强制终止一个进程】> example.txt:重定向符号 > 将空内容输出到文件ps aux:查看系统中所有进程的详细信息。netstat -tunap | grep 2691:查看某进程(2091)占用了哪些端口pstree: 以树状图形式显示进程关系。
9、怎么实现文件的拷贝
cp 源文件 目标文件
fgetc/fputc
#include <stdio.h>
int main(int argc, char *argv[])
{if(argc<3){printf("usage:%s srcfile dstfile\n",argv[0]); return 1;}FILE* src = fopen(argv[1],"r");FILE* dst = fopen(argv[2],"w");if(NULL == src ||NULL == dst){fprintf(stderr,"open error\n");return 1;}while(1) {int c = fgetc(src);if(EOF == c){break;}fputc(c,dst);}fclose(dst);fclose(src);return 0;
}
fread/fwrite
#include <stdio.h>
#include <string.h>
#include <stdlib.h>int main(int argc, char *argv[])
{if(argc<4){printf("usage: %s srcfile dstfile srcfile_size\n",argv[0]);return 1;}FILE* src = fopen(argv[1],"r");FILE* dst = fopen(argv[2],"w");if(NULL == src|| NULL == dst){printf("fopen error\n");return 1;}int size = atoi(argv[3]);char * p = malloc(size);bzero(p,size);fread(p,size,1,src);fwrite(p,size,1,dst);fclose(dst);fclose(src);free(p);p= NULL;return 0;
}
read/write
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>int main(int argc, char **argv)
{if(argc<3){printf("usage:%s srcfile dstfile\n",argv[0]);return 1;}int srcfd = open(argv[1],O_RDONLY);int dstfd = open(argv[2],O_WRONLY|O_CREAT|O_TRUNC,0666);if (-1 == srcfd || -1 == dstfd){printf("open error\n");return 1;}while(1){char buf[4096]={0};int ret = read(srcfd,buf,sizeof(buf));if(0 == ret){break;}write(dstfd,buf,ret);}close(dstfd);close(srcfd);return 0;
}
10*、缓冲的类型
缓冲区是解决速度快慢不匹配的问题行缓冲:1k,遇到\n刷新、缓冲区满刷新、正常结束刷新、fflush来刷新,关于终端的操作,主要用于人机交互
全缓冲:4k,缓冲区满刷新、正常结束刷新、fflush来刷新,主要用于文件的读写
无缓冲:0k,不对数据缓存直接刷新,主要用于出错处理信息的输出
四、进程线程
1*、线程和进程的概念,区别,以及什么时候用线程什么时候用进程
线程使用场景:
需要频繁创建和销毁大量执行单元时
因为线程共享内存空间,通信极其方便和快速,无需经过内核,所以要求高性能和大量数据共享时
在多核CPU上执行并行计算任务时
一个程序能够“同时”做多件事情,从而最大限度地利用CPU资源,提高程序的执行效率和响应能力。
进程使用场景:
要求高安全性和稳定性时
需要运行在不同机器上(分布式系统)时
任务之间不需要大量数据共享或通信时
| 进程 | 线程 |
|---|---|
| 资源分配的基本单位 | CPU调度的基本单位 |
| 进程有独立的3G空间(内核1G共享) | 线程空间是共享的(8M栈区独立) |
| 创建开销大,稳定性强 | 创建开销小,稳定性弱 |
2*、进程间通信方式
原因:
进程空间独立,一个进程无法直接访问另一个进程的变量或数据。(进程间通信: IPC )
方式:
(1)有名管道fifo
//任意两个进程
创建有名管道mkfifo()->打开有名管道open()->读写管道read()->write()->关闭管道close()->卸载有
(2)无名管道pipe
//父子进程
创建无名管道pipe()->读写管道-read() write()>关闭管道close() //注意:无名管道在fork()前进行
使用方便、大小固定且有限、一般用于少量数据的交互
(3)共享内存
速度快,大小可以自行申请,一般用于大量数据交互,使用时需要其他的通信方式做同步,通信方式最快
写
// 1 获取相同的Key
key_t key = ftok("./",'!');
// 2 创建共享内存
int shmid = shmget(key,4096,IPC_CREAT|0666);
// 3 连接
void* p = shmat(shmid,NULL,!SHM_RDONLY);
// 4 写入
strcpy((char*)p,"hello, this is mem test");
// 5 断开连接
shmdt(p);读
// 1 获取相同的Key
key_t key = ftok("./",'!');
// 2 创建共享内存
int shmid = shmget(key,4096,IPC_CREAT|0666);
// 3 连接
void* p = shmat(shmid,NULL,!SHM_RDONLY);
// 4 读取
printf("%s\n",(char*)p);
// 5 断开连接
shmdt(p);
shmctl(shmid,IPC_RMID,NULL);
(4)消息队列
内核中的一个消息链表,进程可以向队列中写入有类型、有结构的数据块(消息),也可以从队列中读取指定类型的消息。
(5)信号
//异步事件通知机制
信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。使用相对固定,无法新增新的信号
(6)信号量
//不是用于传输数据,而是用于进程同步。
信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
(7)套接字
用于不同主机间的通进程信,最通用,可跨网络通信
| 信号 | 信号量 |
|---|---|
| 异步的进程间通信机制 | 进程/线程同步机制 |
| 通知事件发生 | 控制资源访问 |
3*、线程间通信方式
共享内存:多个线程共享同一块内存区域,通过读写共享内存来实现信息交流和数据共享。需要考虑线程安全问题,可以使用互斥锁、信号量等机制来保证数据的一致性。信号量:通过信号量来实现线程之间的同步和互斥。通过P操作和V操作来改变信号量的值,当信号量的值为0时,线程需要等待;当信号量的值大于0时,线程可以继续执行。互斥锁:通过互斥锁来实现线程之间的互斥访问共享资源。当一个线程获取到互斥锁时,其他线程需要等待;当一个线程释放互斥锁时,其他线程可以竞争获取锁。条件变量:通过条件变量来实现线程之间的等待和唤醒。当线程需要等待某个条件满足时,可以调用条件变量的等待函数使自己进入等待状态;当条件满足时,可以调用条件变量的唤醒函数唤醒等待的线程。
4、同步和互斥
互斥是大圆,同步是小圆(同步是有顺序的排他性访问,互斥是排他性访问)
互斥:在多线程中对临界资源的排他性访问。(禁止同时访问)
用互斥:当多个线程需要访问同一个共享资源,且这个资源在一次操作中不能被破坏(即操作不具备原子性)时。互斥锁同步:有一定先后顺序的对资源的排他性访问。(先后顺序访问)
用同步:一个线程的执行需要依赖另一个线程的中间结果或某个任务完成的事件。信号量补充:临界资源(一次仅允许一个线程使用的共享资源)排他性(在任意时刻,最多只允许一个线程对临界资源进行操作)
5、父与子运行期间的关系是什么(重点推导)
-
fork():
功能: 一次调用,会返回两次 变量不共享 子进程复制父进程的0到3g空间和父进程内核中的PCB,但id号不同 子进程是父进程的完全拷贝。 子进程的执行过程是从fork函数之后执行。 子进程与父进程具有相同的代码逻辑。返回值: >0:父进程 =0:子进程 -1:表示创建进程失败 -
资源管理:写时复制,相互独立
-
执行过程:子进程和父进程运行的顺序不确定,如果非要确定那个要先运行,需要IPC机制
-
生命周期:父进程应该wait()子进程
僵尸进程:一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中。这种进程称之为僵尸进程。 //处理: 具体做法是接管SIGCHLD信号。子进程死后,会发送SIGCHLD信号给父进程,父进程收到此信号后,执行waitpid()函数为子进程收尸。孤儿进程:父进程如果不等待子进程退出,在子进程之前就结束了自己的“生命”此时的子进程叫做孤儿进程。init进程收留孤儿进程,变成孤儿进程的父进程。
6*、进程内存分布
| 空间 | |
|---|---|
| 内核空间 | 占内存地址的3G-4G,各个进程共享,完成进程管理 |
| 栈(stack) | 编译器自动管理,局部变量、函数返回地址、函数参数,向低地址增长(8M) |
| //内存映射 | map/share 映射/共享 外部库函数, 内存共享。 |
| 堆(heap) | 程序员自己管理(申请/释放),向高地址增长(2.9G) |
| BSS段 | 未初始化的全局/静态变量,初始化为0的全局/静态变量 |
| 数据(Data) | 已初始化的全局/静态变量 |
| 代码(Code) | 程序指令 |
程序内存主要分为五个区域:栈区:存放局部变量,函数参数,由编译器自动分配和释放。
堆区:用于动态分配内存
全局/静态存储区:[bss+data]
常量区:存放字符串变量和const修饰的常量
代码区:存放函数体的二进制代码
【栈溢出:栈的使用量超过了为其分配的内存空间。情况:函数调用过深;变量超过栈的大小;无限递归】
7、进程的状态图

基本操作系统:–./a.out–>就绪态–调度–>运行态–条件不满足–>阻塞态–满足–>就绪态
Liunx系统:0(运行态、就绪态),睡眠态(可中断、不可中断),僵死态,暂停态
8、编程序的时候什么时候用多进程,什么时候用多线程
多线程:做I/O相关的事情(等网络,读文件)想并发(单核),需要频繁通信和共享数据、I/O密集型应用
多进程:做计算相关的事情(算数,处理图片)想并行(多核),多个独立任务,需要强隔离、需要利用多核CPU进行并行计算
9、进程的命令
ps:报告当前系统的进程状态
kill:向指定进程发送一个信号
top/htop:提供一个实时动态的视图来监控系统的进程活动、CPU和内存使用情况等
pstree:用树状图的形式来显示进程之间的关系
nice/renice:修改进程的调度优先级
10、为什么多线程需要互斥
线程间默认共享内存。当多个线程不加控制地同时读写同一份共享资源时,就会发生竞争条件,导致数据不一致、程序出错。
共享资源有:全局变量或静态(static)变量、堆内存、文件、数据库连接、网络连接等外部设备
举例:
两线程对全局变量count操作100次,理论count应为200次,实际小于200次
11、多线程解决资源竞争
互斥锁:考虑性能和死锁现象
信号量:考虑线程数量
12、信号量
//二值信号量(类似于互斥锁)
//计数信号量
1、信号量定义 sem_t sem;
2、信号量初始化 sem_init(sem_t *sem, int pshared, unsigned int value);
3、信号量的PV操作(控制顺序)
sem_wait();申请资源-1 如果sem有资源(==1),则申请该资源,程序继续运行;如果sem没有资源(==0),则线程阻塞等待,一旦有资源,则自动申请资源并继续运行程序
sem_post();释放资源+1 函数可以将指定的sem信号量资源释放,线程在该函数上不会阻塞。
4、信号量销毁 sem_destroy(sem_t *sem);
12、你知道锁吗?自旋锁、互斥锁和读写锁
锁是一种同步机制,控制对共享资源的访问,保证在任意时刻,只有一个执行流能进入被锁保护的代码区域,解决资源竞争
互斥锁创建:保证同一时间只有一个线程操作临界资源
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;(静态初始化)
pthread_mutex_init(&mutex, NULL);(动态初始化)。pthread_mutex_lock()加锁
pthread_mutex_unlock()解锁
pthread_mutex_destroy()销毁
互斥锁:休眠等待,互斥锁是一种独占锁。比如当线程 A 加锁成功后,此时互斥锁已经被线程 A 独占了,只要线程 A 没有释放手中的锁,线程 B 加锁就会失败,失败的线程B于是就会释放 CPU 让给其他线程,既然线程 B 释放掉了 CPU,自然线程 B 加锁的代码就会被阻塞。自旋锁:忙等待,循环不停的检查锁是否被释放,占着CPU。用于操作系统内核读写锁:读读之间不互斥,写和读之间互斥,写写之间互斥
13、死锁
定义:
死锁是指两个或两个以上的进程(或线程)在执行过程中,由于资源竞争或彼此通信而造成的一种相互等待的现象产生条件:
互斥条件:一个资源每次只能被一个进程使用
请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放
不剥夺条件:进程已获得资源,在未使用完之前,不能强行剥夺,只能由自己释放
循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系预防:
破坏上述四个条件
互斥条件:将互斥资源改造成可共享使用的资源
请求与保持条件:实行 “一次性申请所有资源” 的策略
不剥夺条件:如果一个进程已经持有了一些资源,但又无法立即获得新的资源,那么它必须释放所有已持有的资源,以后需要时再重新申请。
循环等待条件:给所有资源类型定义一个全局的线性顺序(例如,锁1 -> 锁2 -> 锁3)。要求每个进程都必须按照这个递增的顺序申请资源。释放资源时则按相反顺序进行。场景:
#include <pthread.h>
pthread_mutex_t A = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t B = PTHREAD_MUTEX_INITIALIZER;
void* t1(void* arg) { pthread_mutex_lock(&A); pthread_mutex_lock(&B); return NULL; }
void* t2(void* arg) { pthread_mutex_lock(&B); pthread_mutex_lock(&A); return NULL; }
int main() {pthread_t tid1, tid2;pthread_create(&tid1, NULL, t1, NULL);pthread_create(&tid2, NULL, t2, NULL);pthread_join(tid1, NULL); // 永远等不到返回pthread_join(tid2, NULL); // 永远等不到返回return 0;
}
t1 获得锁A,t2 获得锁B
t1 等待锁B(被t2持有)
t2 等待锁A(被t1持有)互相等待 → 死锁
14、什么是阻塞
一个程序或线程为了等待某个操作完成(比如读数据、等网络回复),而被迫暂停执行,直到它要等的东西就绪为止场景:
read(), write() - 文件I/O
recv(), send() - 网络I/O
15、什么是原子操作
一个或多个指令的序列,这个序列在执行时对外表现为一个不可分割的、连续的整体。不可中断
实现:引入了 `<stdatomic.h>` 和 `<atomic>` 头文件,提供了`atomic_int`、`atomic_fetch_add` 等类型和函数。
16、实际场景理解:一方从键盘敲”hello“字符到显示到另一方屏幕这一过程
1、发送A处理部分:
键盘输入和硬件中断:
用户A输入“hello”,键盘控制器会产生一个中断请求;CPU收到中断请求,暂停当前任务,保存当前状态,跳转键盘中断处理程序;中断处理程序从键盘控制器的缓冲区读取扫描码,转换对应的字符;接着,字符被放到操作系统的输入缓冲区应用程序读取输入:
应用程序通过系统调用从输入缓冲区读取字符,并放入自己的缓冲区,同时回显到发送A的屏幕上网络程序发送数据:
应用程序将缓冲区的的数据通过系统调用(send)发送到网络套接字,数据进入网络协议栈协议栈处理:
协议栈中,数据首先经过应用层,在传输层给数据加上TCP头部形成TCP段;网络层加上IP头部形成IP数据报,接口层加上帧头部和帧尾部形成帧数据,然后转换成比特流,通过网卡发送到网络。2、网络传输
数据包通过网络传输(路由器)传输,到达接受B所在的网络。3、接收B处理部分:
网络接口接收:
网卡将比特流组装成帧;网卡检查帧的目的MAC地址是否是自己,如果是,则产生一个中断;CPU响应中断,调用网络驱动程序处理该帧。协议栈处理:
接口层处理帧,去掉帧头部和帧尾部形成,将IP数据报交给网络层;网络层去掉IP头部,将TCP段交给传输层;传输层去掉TCP头部,传到应用层,将数据放到对应网络套接字缓冲区应用程序读取数据:
B的网络应用程序通过系统调用从套接字缓冲区读取数据,将数据显示到B的屏幕上
17、信号和信号处理函数
信号:信号是在软件层次上 是⼀种通知机制, 对中断机制的⼀种模拟,是⼀种异步通信⽅式两种使用方法:注册信号处理函数( void ( *signal(int signum, void (*handler)(int)) ) (int);)
或者发送信号(命令行kill)信号处理函数是在主程序执行的任意点被异步调用的。这意味着它可能打断非常脆弱的代码段(如正在执行 malloc 的内部结构、正在修改全局数据结构等)。如果使用就会导致死锁或内存破坏。
18、线程调度
线程调度就是:先运行哪个线程,线程要运行多久,什么时候切换。调度算法:
先来先服务(可能导致短任务比长任务阻塞)
最短任务优先
优先级调度
时间片轮转:每个线程分配一个固定的时间片,当时间片用完后,调度器会强制回收CPU,把它放回就绪队列末尾,然后调度下一个线程。
19、线程饥饿
线程饥饿:线程饥饿是指一个或多个线程因为某种原因长期无法获得所需的系统资源(主要是CPU时间片)两个线程,共用一个互斥锁,它们都会进行加锁和解锁操作,每个线程执行的概率各是50%,其中一个线程就会一直能获取到锁并执行,另一个线程就获取不到只能空等待,这个线程就饥饿了解决办法:
使用公平锁:维护一个等待队列,锁被释放时,总是分配给等待时间最长的线程
主动让出CPU
使用条件变量:"定义一个条件变量和一个条件判断标志,线程先判断条件是否成立,如果条件成立,就执行工作,如果条件不成立,就等待条件变量,当其他线程改变条件标志并通知条件变量时,等待的线程被唤醒重新检查条件"
五、网络
1*、TCP/IP分几层,每层的核心任务是什么
| 层数 | 作用 | 协议 |
|---|---|---|
| 应用层 | 为应用程序提供网络服务接口,负责处理具体的应用细节 | HTTP(超文本传输协议)、FTP(文件传输协议)、DNS、DHCP【从网页/网盘下载用HTTPS,专业/大型文件传输用FTP/SFTP】 |
| 传输层 | 为两台主机上的进程提供端到端的通信服务,负责数据的分段、流量控制、差错控制和复用 | TCP(传输控制协议)、UDP(用户数据报) |
| 网络层 | 负责将数据包从源主机经过多个网络(路由)传输到目的主机。核心工作是逻辑寻址和路由选择。 | IP(互联网协议)、ICMP(互联网控制消息协议,检查目标主机是否可达)ARP(ip->mac)RARP(mac->ip) MAC:媒体访问控制地址(物理地址) |
| 接口层 | 负责在局域网内,通过物理网络硬件(如网线、网卡)传输数据帧。 | 以太网协议 |
路由选择是数据包在多跳网络中传递时,由路由器为其选择最佳路径的决策过程。
route查看路由表
osi七层模型:
应用、表示、会话、传输、网络、链路、物理
2*、TCP、UDP的区别
| TCP(传输控制协议) | UDP(用户数据报协议) |
|---|---|
| 数据单位是字节流(无边界) | 数据单位的数据报(有边界) |
| 有连接 | 无连接 |
| 一对一 | 一对多(多对多) |
| 可靠性高 | 可靠性低 |
| 效率低 | 效率高 |
| 流式套接字:(有顺序、发送和接受的次数不需要对应,发的太快会出现写阻塞,数据之间没有边界) | 数据报:数据间有边界,收发次数需要对应 |
| 包头:【20字节-60字节:源端口(16位-2)、目的端口(16位-2)、序列号(32位-4)、确认号(32位-4)、数据长度、校验(16位-2)、滑动窗口标志】 | 包头:【8字节:源端口(2)、目的端口(2)、数据长度(2)、校验(2)】 |
| http, https;ftp, sftp | 视频; DNS:使用UDP进行域名解析,将域名映射到IP地址,通常使用53号端口。 |
针对于粘包:
1、数据末尾加一个标识符
2、使用串口的方式,起始位,数据位、校验位、停止位
3*、三次握和四次挥手的过程是怎么样的?
三次握手:
客户端–>SYN(请求连接)–>服务器
服务器–>SYN/ACK–>客户端
客户端–>ACK–>服务器
为什么是三次:防止已失效的连接请求报文突然又传送到服务器
服务器回复RST:RST(Reset)包表示重置连接。
四次挥手:
客户端–> FIN(断开连接)/ACK -->服务器
服务器–> ACK -->客户端
服务器–> FIN -->客户端
客户端–> ACK -->服务器
因为 TCP 是全双工的,双方需要分别关闭自己的发送通道,所以需要四次交互。
四次挥手time_wait,主动发起关闭一方,发送最后一个ack进入的状态,是2MSL
客户端和服务器基于tcp通信,那如果我把服务器进程关掉,客户端继续发,会收到什么报文1、正常关闭(服务器进程正常退出,操作系统发送FIN)
客户端先收到FIN,如果客户端继续发送数据,服务器会回复RST2、强制杀死进程(服务器进程被强制终止,操作系统会关闭套接字并发送FIN)
客户端会收到FIN,如果继续发数据,会收到RST(TCP协议栈(操作系统内核)发出的)3、如果服务器主机崩溃或网络
客户端发送的数据不会收到任何响应,会触发重传机制,直到超时
4、TCP为什么安全可靠
超时重传,确认应答:每个字节都有序列号,接收方通过ACK确认号告知发送方已成功接收的数据;发送方在一定时间内未收到ACK,会重新发送数据。三次握手,四次挥手拥塞控制:根据网络状况(如丢包)动态调整发送速率,以避免网络崩溃。拥塞窗口流量控制:接收方告诉发送方窗口大小,从而控制发送速度。滑动窗口
5、http报文格式以及交互过程
概念:
HTTP:超文本传输协议
http标准端口号是80,HTTPS(加密的HTTP)的标准端口号是 443
IP地址 (Address):在网络中唯一定位一台主机。
端口号 (Port):在一台主机上唯一标识一个正在运行的应用程序/进程。
代码:
http://<主机>:<端口>/<路径>HTTP 是一种应用层协议,它是万维网(World Wide Web)的数据通信基础。它的主要作用是在Web浏览器(客户端)和Web服务器之间建立通信桥梁,从而传输Web内容。
请求报文:
请求行:方法(get/post)+URL+HTTP版本号+回车换行\r\n
请求头:状态说明信息(键值对)
请求体:请求实体(connection连接状态、host对方主机信息)GET:GET请求主要用于从服务器获取数据。它将请求参数附加到URL中
POST:POST请求通常用于向服务器提交数据以进行处理。POST请求将数据包含在请求体中。
响应报文:
响应行:HTTP版本+状态码+原因短语
响应头:响应信息(键值对)
响应体:服务器返回的实际内容
交互过程:
- 客户端与服务器的指定端口(HTTP是80,HTTPS是443)建立TCP连接(三次握手)
- 客户端发送请求报文
- 服务器收到请求,发送响应报文+加对应数据
- 断开连接(四次挥手)(长连接保持2分钟不会断,短连接会断)
6、套接字编程
TCP:
服务器:
socket()创建监听套接字,检测客户端连接请求
--->设置服务器地址,sockaddr_in结构体
--->bind()绑定套接字与ip地址和端口号
--->listen()开始监听,等待客户端连接,同时设置最大连接请求
--->acppet()从连接请求取出一个,创建新的套接字与客户端通信(阻塞等待)
--->read()--->write()--->close()
客户端:
socket()创建套接字
->设置服务器地址
->connect()连接服务器,触发三次握手
->write()->read()->close()
UDP
服务器:socket()创建UDP套接字--->设置服务器地址--->bind()绑定地址--->recvfrom()->sendto()->close()
客户端:socket()创建UDP套接字--->设置服务器地址--->sendto()->recvfrom()->close()
概念
socket(地址族,套接字类型,0)
地址族:AF_UNIX(本地通信)、AF_INET(IPV4网络协议)、AF_INET6(IPV6网络协议)
套接字类型:SOCK_STREAM(有连接,可靠)、SOCK_DGRAM(无连接,不可靠)
7、子网掩码

子网掩码是一串与IP地址长度相同的二进制数字(通常是32位的IPv4),它有两个作用:区分网络:指明IP地址中哪一部分代表网络号,哪一部分代表主机号。
划分网段:将一个大的IP网络划分成若干个小的、更易于管理的子网络。
8、ipv4和ipv6
| IPV4 | IPV6 |
|---|---|
| 32位 | 128位 |
| 点分十进制表示 | 冒号分隔的十六进制数字 |
9、网络相关命令
netstat -at//查看TCP连接
netstat -au//查看UDP连接ping 用于检测网络通断。网络是否能够正常上网。
ifconfig 查看网卡的ip地址。
netstat -anp 查看linux上,所有的网络连接信息。
10、telnet端口 web端口
Telnet(远程登录和管理计算机)默认端口: 23
Web服务默认端口:
HTTP: 80
HTTPS: 443
11、说一下wireshark,他有哪些功能
网络抓包工具.功能:数据包捕获、数据包协议解析、过滤功能启动步骤:
1. 启动wireshark软件(sudo)
2. 选择设备,ens33(网络数据与互联网交互),loopback(本机自己测试),any(所有本机与本机通信的数据)
3. 设置过滤条件,大多数,使用端口号,和ip地址。设置条件
12、epoll
概念:epoll 是Linux内核为处理大量并发连接而提出的I/O多路复用机制。
步骤:
创建epoll实例:epoll_create()
添加要监听的fd到实例中:add_fd()
准备事件数组:struct epoll_event rev[2]
等待事件发生:epoll_wait()
处理就绪事件:
13、select
步骤:
创建集合并初始化:fd_set rd_set;FD_ZERO(&rd_set);
添加要监听的fd到集合中:FD_SET(fd,&rd_set);
阻塞等待:select(fd+1,&rd_set,NULL,NULL,NULL);
检查哪些准备就绪:
14、epoll和poll和select的区别
| poll | epoll | select |
|---|---|---|
| 无 | 不限制fd数量 | 最大1024fd |
| 轮询方式 | 基于回调函数(当某个文件描述符就绪时,内核会直接将其放入一个就绪列表) | 轮询方式 |
| 仅水平触发 | 边缘触发和水平触发都行 | 仅水平触发 |
| 每次调用需将整个fd_set从用户态拷贝到内核态 | 每次调用需将整个fd_set从用户态拷贝到内核态 | 内核维护事件表,仅需一次注册,无需重复拷贝全集 |
边缘触发:必须一次性读完/写完所有数据
水平触发:如果一次没有读完所有数据,epoll_wait() 会不断提醒
15、同步事件和异步事件
同步:发起一个任务后,调用者必须在这个调用点等待,直到这个任务执行完毕并返回结果,才能继续执行后续代码。顺序等待。
异步:发起一个任务后,调用者立即返回,不必等待任务完成,可以立刻去执行其他操作。当被调用的任务完成后,它会通过一种机制(如回调函数、信号、消息)来通知调用者任务完成并提供结果。通知回调。
16、知道哪几种IO模型? (写代码)
(1)阻塞IO
应用程序在发起IO调用后会一直等待,直到数据准备好并完成传输。
代码流程:
//写端
mkfifo("myfifo", 0666)//创建管道
int fd = open("myfifo", O_WRONLY); // 以只写方式打开(会阻塞直到有读端打开)
while (1) {write(fd, "hello", 5); // 写入数据sleep(3); // 每隔3秒写一次
}
//读端
mkfifo("myfifo", 0666)//创建管道
int fd = open("myfifo", O_RDONLY); // 以只读方式打开(会阻塞直到有写端打开)
while (1) {char buf[1024] = {0};read(fd, buf, sizeof(buf)); // 读取数据(阻塞直到有数据可读)printf("fifo:%s\n", buf); // 打印从 FIFO 读取的数据// 额外功能:从终端读取输入并打印(不影响 FIFO 通信)fgets(buf, sizeof(buf), stdin);printf("terminal:%s", buf);
}
关键特性:
FIFO是半双工,单向流动,双方必须同时打开才能继续执行(否则open阻塞),read 和 write 在管道(包括 FIFO)、套接字等这类面向字节流的文件描述符上默认是阻塞 I/O。
(2)非阻塞IO EAGAIN 忙等待 errno
应用程序需要不断轮询,直到数据就绪,然后进行数据拷贝
代码流程:
// 给文件描述符设置非阻塞特性
int flag = fcntl(fd, F_GETFL, 0); // 获取当前文件状态标志
fcntl(fd, F_SETFL, flag | O_NONBLOCK); // 在原标志基础上增加非阻塞标志
(3)信号驱动IO SIGIO 用的相对少(了解)
进程不再主动检查 I/O 是否就绪,而是向内核注册一个信号处理函数。当 I/O 条件就绪(如数据可读)时,内核主动向进程发送一个信号(如 SIGIO),进程在信号处理函数中进行实际的 I/O 操作。
代码流程:
//把设备 设置为 信号驱动的方式int flag = fcntl(fd,F_GETFL,0);fcntl(fd,F_SETFL,flag|O_ASYNC);// 指定由本进程接收SIGIO信号fcntl(fd,F_SETOWN,getpid());//注意:不要在信号处理函数printf()
(4)IO多路复用 select、poll、epoll
select:
// 1 创建集合
fd_set rd_set,tmp_set;
//2 add fd
FD_ZERO(&rd_set);
FD_ZERO(&tmp_set);
FD_SET(fd,&tmp_set);
FD_SET(0,&tmp_set);
while(1)
{//5 清除标志位rd_set = tmp_set;// 3.阻塞等待, 直到至少有一个设备可以读取select(fd+1,&rd_set,NULL,NULL,NULL);char buf[1024] = {0};//4 找到对应的fdif(FD_ISSET(fd,&rd_set)){read(fd, buf, sizeof(buf));printf("fifo:%s\n", buf);}if(FD_ISSET(0,&rd_set)){bzero(buf,sizeof(buf));fgets(buf,sizeof(buf),stdin);printf("teminal:%s",buf);}
}
epoll:
int add_fd(int epfd,int fd)
{ struct epoll_event ev;ev.events = EPOLLIN;ev.data.fd = fd;int ret = epoll_ctl(epfd,EPOLL_CTL_ADD,fd,&ev);if(-1 == ret){perror("add_fd");return 1;}return 0;
}
int del_fd(int epfd,int fd)
{ struct epoll_event ev;ev.events = EPOLLIN;ev.data.fd = fd;int ret = epoll_ctl(epfd,EPOLL_CTL_DEL,fd,&ev);if(-1 == ret){perror("add_fd");return 1;}return 0;
}
// 创建epoll实例
int epfd = epoll_create(2);
if(-1 == epfd)
{perror("epoll_create");exit(1);
}
// 添加要监听的fd到实例中
add_fd(epfd,0);
add_fd(epfd,fd);
//准备事件数组
struct epoll_event rev[2];
while(1)
{char buf[1024] = {0};// 等待事件发生int ep_ret = epoll_wait(epfd,rev,2, -1);for(int i = 0 ;i<ep_ret;++i){if(rev[i].data.fd == fd){int rd_ret=read(fd, buf, sizeof(buf));// 处理就绪事件if(rd_ret<=0){del_fd(epfd,fd);close(fd);continue;}printf("fifo:%s\n", buf);}if(0 == rev[i].data.fd ){bzero(buf,sizeof(buf));fgets(buf,sizeof(buf),stdin);printf("teminal:%s",buf);}}
}
(5)异步IO
应用只需要向内核发送一个read 请求,告诉内核它要读取数据后即刻返回;内核收到请求后会建立一个信号联系,当数据准备就绪,内核会主动把数据从内核复制到用户空间,等所有操作都完成之后,内核会发起一个通知告诉应用
17、路由器和交换机
路由器:基于IP地址进行数据包转发,连接不同网络
交换机:基于MAC地址进行数据帧转发交换机工作原理:
(1)学习:交换机通过检查进入端口的数据帧的源MAC地址来学习MAC地址与端口的映射关系,并将其存储到MAC地址表
(2)转发:当交换机收到一个数据帧时,检查目的MAC地址,,并查找MAC地址表,如果找到对应端口,则将该帧从该端口转发出去,如果找不到,则向所有端口广播该帧
(3)过滤:如果数据帧的目的MAC地址与源MAC地址在同一端口上,则交换机过滤,因为这是同一网段内的通信
(4)避免环路:交换机使用生成树协议防止环路产生,从而避免广播风暴和多帧复制。
(16、什么情况发生泛洪?
17、交换机数据包什么时候会被丢弃?
18、MAC地址表的学习过程?
19、MAC地址表刷新时间大概多久?
20、你了解的交换机是二层交换机还是三层交换机?
21、vlan可以重复吗,vlan数量限制多少个?
22、如果设备数量过多vlan不够用了,怎么办?
vxlan解封装流程?)
18、浏览器访问一个网站,背后的通信协议和通信过程
1. DNS解析:将域名解析为IP地址。
2. 建立TCP连接:与服务器通过三次握手建立TCP连接。
//3. 如果使用HTTPS,则进行TLS握手。
4. 发送HTTP请求:浏览器发送HTTP请求到服务器。
5. 服务器处理请求并返回HTTP响应。
6. 浏览器接收响应并解析渲染页面。
7. 关闭连接(或保持连接以供后续请求使用)。
19、当网卡接收到一个数据包,cpu是怎么跟网卡交互的,整个流程是怎样的?
数据包到达网卡 -> 网卡通过DMA将数据包写入内存环形缓冲区 -> 网卡发出中断 -> CPU执行中断处理函数 -> 中断处理函数屏蔽中断并触发软中断 -> 软中断处理函数(轮询方式)从环形缓冲区读取多个数据包 -> 构建sk_buff并送入网络协议栈 -> 协议栈层层处理 -> 数据包到达套接字缓冲区 -> 应用程序读取数据
20、ping的流程
ping:DNS(UDP)【将域名解析IP地址】->ARP【根据IP地址转换成对应的MAC地址】 ->ICMP【发送回显请求和接收回显应答】
21、NTP网络时间协议
时间同步
22、ICMP的延迟怎么计算
ping 程序在发送 ICMP Echo Request 包时,会在其数据部分(Data)存⼊⼀个时间戳,记录发送的时刻(T1)。
当它收到 ICMP Echo Reply 包时,会读取当前时刻(T2)。
延迟(RTT,Round-Trip Time) = T2 - T1。
23*、TCP/IP协议栈的数据封装结构
数据(应用层):
以太网头、IP头、TCP头、数据、以太网校验
TCP(传输层):
16位源端口、16位目的端口、32位序列号、32位确认号……16位校验
IP(网络层):
4位版本号、4位头部长度、8位服务类型、16位总长、16位标识……32位源IP、32位目的IP
帧数据(接口层):
6字节目的物理地址、6字节源物理地址、2字节类型、46-1500字节数据、4字节CRC校验
六、数据库
1、SQL语句分类
DDL(数据定义语言):CREATE: 创建数据库对象(库、表、索引、视图等)。ALTER: 修改已存在的数据库对象的结构。DROP: 删除数据库对象(整个结构都会消失,包括里面的数据)。TRUNCATE: 清空表中的所有数据,但保留表的结构(相当于快速格式化,比DELETE更快)。
DML(数据操纵语言):INSERT: 向表中插入新的数据行。UPDATE: 修改表中已存在的数据。DELETE: 删除表中的数据行。
DQL(数据查询语言):SELECT: 从数据库中检索数据。它是DQL乃至整个SQL中最复杂和强大的命令。
2、都了解那些数据库?各个数据库的异同?
关系型数据库:MySQL,Oracle,SQL Serve,SQLite。数据被预先定义并存储在规整的表格中,表格之间可以通过关系(如外键)连接。强调数据的一致性和完整性。
适合:结构化数据,复杂查询,事务处理
【事务是数据库提供的一种安全机制,它确保一组相关的数据库操作能够安全、可靠地执行】
非关系型数据库:MongoDB,Redis。数据存储方式灵活多样,追求高性能、高并发和可扩展性。
适合:非结构化数据、高吞吐量
3、查数据库如何升序打印
select * from user order by age asc
4、数据库的工作原理
存储:存储管理数据
索引:快速定位数据
查询处理:SQL语句查询数据
事务管理:保证数据在多用户操作下的正确性
5、Sqlite3常用
create table user(id int ,name char,age int);//创建表
drop table 表名;//删除整个表insert into user values(3,"wang",11);
delete from user where id = 1 or id = 2;//删除表内容
update user set id = 2 where name = "li" or name = "zhao";
select id,name from user where not age <30
6、Sqlite3提供的函数接口
sqlite3_open()
sqlite3_exec()
sqlite3_close()
7、数据库并发(事务)
BEGIN TRANSACTION;
-- 多条SQL语句
COMMIT; -- 或 ROLLBACK;
七、ARM+51单片机+2440
1、单片机相关概念
1、单片机最小系统
单片机芯片(MCU)、电源电路、复位电路、时钟电路
ARM的最小系统多了内存和Flash2、ROM和RAM
RAM:随机存储器,访问速率快,掉电数据丢失(内存)--运行程序
ROM:只读寄存器,访问速率慢,掉电数据不丢失(flash)--存储程序3、51单片机的中断
INT0(外部中断0)、T0(定时器0中断)、INT1(外部中断1)、T1(定时器1中断)、T1/R1(串口中断)
步骤:
初始化中断源->设置中断优先级->打开中断允许->编写中断服务函数4、计数器和定时器
本质:16位加法计数器,高8位THx,低8位TLx
计外部脉冲->计数器模式;计内部时钟->定时器模式
当计数器从最大值 `0xFFFF` (65535) 再加1时,会变为 `0x0000`。这个现象称为“溢出”
2、通信方式
按传输介质分类:
有线通信,无线通信按数据传输方向分类:
单工:数据只能单向传输
半双工:数据可以双向传输,但不能同时进行
全双工:数据可以同时双向传输按数据位传输方式分类:
串行:按照单个字节发送/接收数据的通信方式
并行:同时发送一个甚至多个字节的通信方式按时钟同步方式分类:
同步:通信双方使用同一个时钟
异步:通信双方使用不同的时钟
3、示波器使用
波特率计算:
1位数据的大小:1v
1位数据的时间:100us
波特率:10000(100us=1/10000s)示波器使用场景:看IIC和UART时序
4、CPU内部组成

针对于ARM920T版本:
CPU:中央处理单元
ALU:运算单元,实现基本的运算功能
RO~R12:通用寄存器,存储数据
PC:程序计数器,指向正在执行的下下条指令,上电后值为0,默认做自加运算
LR:链接寄存器, 保存函数的返回地址
SP:栈指针寄存器,指向栈顶
CPSR:当前程序状态寄存器,运算的结果为0、正、负等,运算中产生的进位、借位等;中断使能、工作状态、工作模式。SPSR:保存程序状态寄存器,存放CPSR的备份:
Cache:CPu和内存之间的缓存,访问速率远高于内存
MMU:内存管理单元,做虚拟地址到物理地址的转换四种栈:空增 空减 满增 满减
增栈:栈指针(SP)向高内存地址移动
减栈:SP向低内存地址移动
满栈:SP指向栈中最后一个已使用的单元
空栈:SP指向栈中第一个未使用的单元
5、处理器结构

ARM的外设:ARM内核是大脑,外设就是五官四肢,编写的程序(大脑)通过配置和驱动这些外设,来感知环境(输入)并控制外部设备(输出),从而完成特定的嵌入式任务。相关外设:
输入输出:GPIO
定时器
通信接口:UART、I2C、SPI
连接模拟数据:ADC
性能加速:DMA
6、ARM9处理器的七种工作模式
特权模式:
FIQ:高优先级中断产生会进入这个模式--快中断模式
IRQ:低优先级中断产生会进入这个模式--中断模式
Supervisor:当复位或软中断指令执行时会进入这个模式--SVC
Abort:当存取异常时会进入这个模式--中止模式(内存访问失败)
Undef:当执行未定义指令时会进入这个模式--未定义模式
System:使用和User模式相同寄存器集的特权模式--系统模式(比user权限高)
非特权模式:
User:大部分任务执行在这种模式
7、异常处理流程
当异常产生时:
保存现场:拷贝CPSR(当前程序状态寄存器)到SPSR(保存程序状态寄存器),
模式切换与配置:设置适当的CPSR位(T:改变处理器状态进入ARM态、M[4:0]模式位:改变处理器模式进入相应的异常模式、I和F位:设置中断禁止位禁止相应中断)
保存返回地址:将下一条指令的地址(返回地址)保存到当前异常模式下的链接寄存器(LR_<mode>) 中
跳转:PC设置为异常向量表中对应的地址返回时,异常处理需要:(在ARM态执行)
恢复状态:从SPSR_<mode>恢复CPSR
恢复执行:从LR_<mode>恢复PC
异常向量表:

8、函数调用规则
前四个参数使用r0-r3传递,剩余参数使用栈传递,返回值存放在r0中
9、硬件接口使用引脚
2440平台:
配置引脚功能为输入/输出
控制引脚输出对应电平(写数据)/读取引脚电平状态(读数据)//申请GPIO资源
//配置引脚功能为输入/输出
//控制引脚输出对应电平(写数据)/读取引脚状态(读数据)
//释放GPIO资源
imx6ull平台:
设置引脚功能(GPIO)
设置电器属性(上拉/输出频率)
设置引脚方向 输入/输出
控制引脚输出对应电平
GPIO输入:
上拉输入、下拉输入、模拟输入、浮空输入
GPIO输出:
推挽输出(可以真正的输出高电平和低电平,在两种电平下都具有驱动能力)
开漏输出(无法真正输出高电平,即高电平时没有驱动能力,需要借助外部上拉电阻完成对外驱动。)
10、时钟管理
PLL(锁相环):用来倍频
根据模式控制引脚OM3、OM2的电平状态来选择时钟源S3c2440中有5个定时器,都是16位减定时器,首先向某个定时器设置工作频率,之后向该定时器设置一个初值,启动定时器后定时器将会按照之前设定好的频率减这个数,直到减到0时再减一就会发送一个中断请求。
11、中断

中断的执行流程是什么:
中断源发出中断请求;
Soc检查该中断源是否被屏蔽,处理器内核是否允许处理中断;
查询中断优先级;
保护现场;
执行中断服务函数
恢复现场
12、PWM定时器
PWM(脉宽调制):利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术(pwm属于定时器的一种?)
周期:一次高电平开始到下次高电平开始的时间
频率:1/T
占空比:高电平占整个周期的比例
极性:先高后低 先低后高?
pwm定时器使用:
1、计数寄存器和比较寄存器初始化(使用PWM)
2、配置时钟
3、设置工作模式(单次/自动重载)
4、设置极性(使用PWM)
5、使能中断(如果需要)
6、启动定时器
13、ADC模数转换
Adc是模拟-数字转换器的简称,大多数adc采用的是一种称为逐次逼近法的方式将模拟信号转换为数字信息。Adc对待测电压进行多次必要,每次比较时都会按照参考电压(量程)的值计算出一个对比值,继而得到一组连续的0、1序列。

相关概念:
采样,保持,量化,编码(采样率:)
量程:0~3v
精度:10位
转换速率:500ksps
最重要的两个参数:步骤:
adc初始化
设置通道
读数据

14、UART(异步全双工,从低位开始读)
两条线:Txd(发)和Rxd(收)
空闲时总线保持高电平(9600/1/8/n/1)
波特率:每秒传输的比特数(9600/115200)
起始信号:1位,由高到低
数据位:8位
校验位:奇校验(数据位+校验位1的个数为奇数);偶校验(数据位+校验位1的个数为偶数);n无校验
停止信号:1位,由低到高传输的字节数:9600bit/s ÷ 10bit/字节=960字节/s(无校验)
| TTL | RS232 | RS485 |
|---|---|---|
| 单端传输 | 单端传输 | 差分信号传输 |
| 0:3./5v 1:0v | 0:+3v~+15v 1:-3v~-15v | 0:-2v~-6v 1:+2v~+6v |
| 抗干扰差,传输短 | 抗干扰一般,传输一般 | 抗干扰强,传输远 |
15、I2C(同步半双工,从高位开始读)
原理图:

工作流程:
空闲时总线总保持高电平
起始信号,当SCL为高时,SDA由高到低传输数据时:
SCL低电平期间SDA可以任意改变(该期间接收方不采集SD的电平)
SCL高电平期间SD保持不变(该期间接收方采集SDA电平)
应答位(ACK):数据位后(第9个)的时钟周期由接收方拉低SDA停止信号,当SCL为高时,SDA由低到高典型时钟频率:100k 400k 3.4M
每帧固定8bit
通信七位从机地址的IIC总线最多可连接的从设备数量为127个。因为七位地址共有2^7=128种组合,但其中地址0x00被保留用于通用广播呼叫,所以实际可用的从机地址数量为127个。
IIC需要接上拉电阻,因为是开漏输出,且驱动能力弱,为了保证空闲状态为高
因为IIC线与特性
16、SPI(同步全双工)
同步全双工,一主机多从机
SCLK:时钟线、
MISI:主设备发送数据,从设备接受数据、
MISO:从设备发送数据,主设备接受数据 、
CSS:片选信号,主设备拉低这根线选中与之通信的从设备
时序:
空闲:SCLK为低电平,CS为高电平(表示未选中)起始通信:主机将CS拉低,表示选中从机数据传输:
SCLK第一个边沿(上升沿),主机和从机采样读取数据线
主机在上升沿采样MSIO线,获取从机发来的数据位
从机在上升沿采样MOSI线,获取主机发来的数据位
数据线的值是SCLK的下降沿发生切换结束通信:8位数据传输完毕后,主机将CS拉高
17、IIC\SPI\UART 传输速率、几根线、全双工还是半双工,做一个全面的对比
| UART | IIC | SPI |
|---|---|---|
| TX RX | SCL SDA | SCL MISI MISO CSS |
| 全双工 | 半双工 | 全双工 |
| 波特率:9600/115200 | 100k/400k/3.4M | 1-50M |
| 点对点 |
18、DMA直接存储器存取
不通过CPU来实现外设之间的数据直接搬移,节省CPU,CPU可以去执行其他任务
19、汇编语言
mov:赋值
add/sub/mul/div:加减乘除
cmp:比较位运算:AND与, ORR或, EOR异或, BIC位清除
20、看门狗
看门狗定时器:在系统因软件故障而死机或进入无限循环时,强制将其复位,使系统恢复正常运行。
步骤:
初始化:系统上电或复位后,软件会启用看门狗定时器,并设置一个定时时间
计时:看门狗定时器独立从零开始计数
喂狗:系统正常运行时,软件必须在看门狗超时之前,定时执行一条指令将看门狗计时器值清零
超时和复位
21、矩阵键盘
【在没有按键按下时,行和列之间是断开的。】
将所有列设置为输出高电平,行设置为输入上拉。此时如果没有按键按下,所有行都读到高电平。然后我们开始逐列扫描:
第一列:将C0设置为低电平,其他列设置为高电平。然后读取所有行(R0-R3)的电平。如果R0读到低电平,则按键K00按下;如果R1读到低电平,则按键K10按下;以此类推。
第二列:将C1设置为低电平,其他列设置为高电平。然后读取所有行,如果R0低电平,则K01按下;R1低电平,则K11按下;等等。
第三列:同理。
第四列:同理。如果K00按下,则C0的低电平会通过按键传递到R0,因此R0的输入电平从高电平变为低电平。因为C0输出低电平,而R0通过上拉电阻本来是高电平,但当按键按下时,C0和R0连接在一起,由于C0是输出低电平(相当于强低电平),它会将R0的电平拉低(克服上拉电阻的作用)。所以R0读到的就是低电平。
22、电阻的作用
上拉/下拉电阻:确保输入引脚在悬空时有确定的逻辑电平,防止电平漂移
限流电阻:保护GPIO引脚不被过电流损坏
分压电阻:将较高的外部电压转换到MCU的安全输入范围
23、CPU访问硬件地址
• 对于内存映射I/O,CPU使用虚拟地址(经过MMU转换)来访问硬件,这些虚拟地址最终由内存管理单元(MMU)转换为设备寄存器的物理地址。
• 对于端口I/O,CPU使用端口号(可以看作是一种专门的地址)和专门的I/O指令来访问硬件。
物理地址转虚拟地址:ioremap()
八、内核驱动
1、ARM的裁剪
ARM的裁剪:移除系统(主要是Linux内核和根文件系统)中不需要的功能,以减小体积、降低内存占用、提高启动速度和运行效率
(1)移除系统LED、KEY,自己编写驱动时LED、KEY和系统中的驱动冲突,通过make menuconfig将LED、KEY配置选项选为N,保存到.config文件,重新编译内核生成zImage
(2)adc->系统adc->内核源码->make menuconfig去掉系统adc
2、内核启动流程
uboot是一段裸机程序,为Linux内核启动初始化相关软硬件,准备合适的环境引导内核启动
uboot向内核传参:在bootargs设置环境变量setenv
通过老师提供的uboot源码编译生成uboot的可执行二进制文件uboot.bin
uboot:初始化硬件,初始化串口网口,向内核传参(bootargs),将内核镜像文件搬移到内存
初始化硬件:初始化cpu工作模式是SVC,初始化内存,初始化异常向量表,关闭中断,关闭DCache,关闭看门狗
向内核传参:根文件系统的类型/位置,init进程,控制台波特率Kernel-Linux内核:跳转到内核的入口点,准备启动内核,执行start_Kernel函数
start_kernel(完成内核初始化工作):开启内核调度机制、使能中断、开启MMU、初始化控制台、初始化init进程rootfs:挂载根文件系统init进程:用户空间的第一个进程init(比如linuxrc),执行rcS脚本文件
如果板子启动后自动加载,把程序,命令写到rcS文件
系统移植做了哪些工作:
移植:将操作系统(通常是Liunx内核)适配到一块特定的ARM开发板或芯片上,使其能够正常运行uboot移植:uboot源码->编译uboot->uboot.bin->jlink仿真器jflash软件(将uboot烧写到2440flash 0 地址)内核移植:linux内核源码->拷贝.config文件->make menuconfig->make uImage->配置tftp服务->uImage到tftp
【配置Ubuntu网络为桥接模式,通过网线与开发板连接,设置主机ip和开发板ip到同一网段(192.168.xx.xx)】
【tftp 0x30008000 uImage 通过tftp服务将serverip的uImage下载到内存的0x30008000地址处】
【bootm 0x30008000 启动0x30008000地址处的内核】配置nfs服务->根文件系统到nfs目录
3、uboot的两种启动方式
从norflash启动,bootloader使用norflash中的,kernel和rootfs存放在ubuntu主机上。可线性寻址,访问方式和内存相同(1)系统上电后,直接执行norflash中的bootloader程序(norflash被接在0地址处)bootloader向内核传参,通过tftp服务下载内核到内存的0x30008000地址处,启动内核
(2)内核启动到最后阶段通过nfs服务挂载ubuntu中的rootfs
(3)根文件系统被挂载完成后,内核会启动根文件系统中的Init进程进一步启动shell及用户程序从nandflash启动,bootloader、kernel、rootfs均存储在nandflash上。不可线性寻址,访问需要专用的程序控制
4、交叉编译
在一台主机编译另一台主机需要运行的程序
编译arm(2440)运行的程序——arm-linux-gcc原因:因为我编译代码的Ubuntu平台是x86架构,运行代码的平台是arm架构。两个平台的环境不同,所以为了确保在x86平台上编译的程序能够在arm平台运行,就需要通过arm编译器去编译。安装编译器:
从官网下载安装包(或源码)
解压安装包或编译源码
配置PATH,将arm-linux-gcc所在路径添加到系统的PATH下,修改家目录下.bashrc文件编译器选择:
gcc:编译在本地运行的程序
arm-linux-gcc:ARM架构嵌入式设备
arm-linux-gnueabihf-gcc:ARM架构嵌入式设备版本号:
arm-linux-gnueabihf-gcc -v(4.9.4)
arm-linux-gcc -v(4.4.3)
gcc -v(7.5.0)
5、uboot命令
help/?(查看帮助手册)、print(打印环境变量)、setenv(设置环境变量)、saveenv(保存环境变量)、
ping serverip(检查网络连接)[uboot是单向的,只能从uboot ping主机]
6、 Linux 内核启动参数(bootargs)的详细解释
bootargs console=ttysAc0,115200 root=/dev/nfs nfsroot=192.168.1.3:/home/linux/nfs/rootfs ip=192.168.1.123 init=/linuxrc//consle=ttysAc0,115200 控制台使用串口0,波特率是115200
//root=/dev/nfs 根文件系统类型,通过nfs挂载
//nfsroot=192.168.1.3:/home/linux/nfs/rootfs 根文件系统位置,挂载到ip为192.168.1.3的主机的rootfs目录为开发板的根目录
//ip=192.168.1.123 内核启动阶段,开发板的ip
//init=/linuxrc 指定init进程
7、重重重点(驱动部分)
Linux系统想要移植到开发板上,我们需要移植三部分: U-Boot(uboot是bootloader的一种)、 zImage(内核镜
像)、 rootfs(文件系统)1、环境准备
安装tftp服务,并配置tftp服务目录(开发板需要内核镜像文件,就可以使用tftp从服务器下载)
安装nfs服务,并配置nfs服务目录(将开发板的文件系统挂载到Ubuntu的指定目录下,形成共享文件,这样我们在Ubuntu下编写软件,编译软件,将可执行程序放到共享目录下,开发板即可拿到代码,并执行代码。)2、uboot烧写:下载uboot.bin到norflash的0地址处
uboot是2019.2后的版本?3、拷贝uImage(一个具体的内核)到tftp服务目录下
Image(可以直接使用的内核映像),zImage(一段解压程序(代码)+Image的压缩包),uImage(64字节的头信息+zImage)mkimage -l(查看头部信息)4、拷贝rootfs到nfs目录下(拷贝rootfs.tar.gz到nfs服务目录下并解压, 使用命令 + sudo) 5、编译一个内核
linux-2.6.32.2-mini2440-20150709.tgz:内核源码包(内核版本2.6.32,老师提供)
修改Makefile(增加条件变量)、Kconfig(增加内容使menuconfig可以看到)、make menuconfig(配置内核)、make、生成uImage文件、复制到tftp6、驱动编写,编译内核(两种编译方式)
静态编译:配置:=Y,驱动代码被直接编译链接进内核(uImage,zImage),make uImage
动态编译:配置:=M,.ko是内核模块文件,可以被动态加载到正在运行的内核中,make modules
步骤:
向driver/char创建.c文件、修改driver/char/Makefile、修改driver/char/Kconfig、源码目录make menuconfig、make
8、设备驱动程序
设备驱动分类:
字符设备(以字节流形式顺序读写,无缓存,操作输出输出设备鼠标键盘)
块设备(按固定大小的块512k读写,有高速缓存,操作硬盘)
网络设备(按数据包收发,无固定大小)设备号:
主设备号:12位,标识设备类型;次设备号:20位,区分同一驱动下不同个体设备设备节点:
创建命令:mknod /dev/led c 255 0编写设备驱动的核心步骤:
实现供应用程序调用的接口
向内核注册该驱动程序
创建设备节点
9、标准字符驱动模块和杂项驱动模板(两个驱动模板)
最基础、最完整的字符设备驱动编写方式,你需要手动管理设备号、cdev 结构体、设备类等多个步骤。
步骤:
定义module_init()驱动入口函数,module_exit()驱动注销函数
然后定义file_operations结构体,存放open,read,write操作函数的集合
构建cdev结构体用来描述字符型设备信息(设备号、设备名称)在入口函数动态申请设备号(alloc_chrdev_region)静态申请(register_chrdev_region);初始化并添加 cdev;创建设备类(class_create);在 /dev/ 下创建设备节点(device_create);完成硬件初始化
在注销函数与初始化相反的顺序释放资源,完善file_operation结构体中的操作函数,如open,read,write,release函数read函数按DHT11时序读5个字节的数据,并校验
校验后通过copy_to_from把数据传到用户层
用户层打开dev/下的设备节点读数据
“杂项设备”是内核提供的一种简化版的字符设备驱动框架。它的主要特点是固定使用主设备号 10,内核帮你完成了大部分标准字符设备的注册工作。
基于Linux杂项设备驱动框架实现DHT11驱动
步骤:
定义module_init()驱动入口函数,module_exit()驱动注销函数
然后定义file_operations结构体,存放open,read,write操作函数的集合
然后misc_device杂项设备信息在入口函数调用misc_register注册杂项设备,完成硬件初始化
在注销函数调用misc_deregister注销杂项设备read函数按DHT11时序读5个字节的数据,并校验
校验后通过copy_to_from把数据传到用户层
用户层打开dev/下的设备节点读数据
init(__修饰):insmod xx.ko加载完自动释放init函数空间
exit(__修饰):rmmod xxx优先执行exit函数
10、字符设备和杂项设备区别
字符设备:自己定义设备号;手动创建设备节点(mknod);更适合鼠标、键盘有输入输出的设备
杂项设备:设备号固定为10;自动生成节点;操作简单;适合没有明确总线分类的设备1、字符设备模板(静态编译,make menuconfig配置Y)
register_chrdev_regin()函数,make生成image文件,复制到tftpboot,编译创建设备节点mknod,执行用户程序
alloc_chrdev_register()函数,make生成image文件,复制到tftpboot,执行用户程序2、杂项设备模板(动态编译,make menuconfig配置M)
make modules生成.ko文件,复制.ko文件到文件系统,编译时insmod led.ko,运行
11、platform平台总线(第三种驱动模板)
驱动(driver):只实现操作方法
设备(device):只保存设备使用的资源加载的过程:(以adc为:注册一个adc的设备,也就是给platform的设备列表里面加adc)
1、platform识别后遍历左边的驱动列表是否有adc,有的话就开始注册完整的设备驱动程序,没有的话就结束了。
2、当安装好设备驱动的时候,platform遍历设备列表是否有adc设备,有的就匹配成功了(match)
3、接着执行一个probe的程序,以后就在probe里面注册完整的驱动(有设备也有驱动)。
4、当二者缺失一个的时候,就会执行remove的程序,恢复到不能使用adc的状态。这里靠名字来匹配。
12、ioctl
用于实现设备特定的控制命令,主要处理设置和属性获取方面的功能。dev:魔幻数,区分不同设备(8bit)
nr:命令编号,设备内的不同功能(8bit)
dir:数据流向(2bit)
size:数据大小(14bit)
13、中断
中断的使用:
初始化中断源、配置中断优先级、使能中断、编写中断服务程序中断顶半部:
中断服务程序【request_irq(irq,handler,flags,name,dev_id);】:短小、快速退出、调度底半部、仅自旋锁。中断底半部:
三种方式:软中断、tasklet(不能被打断)、workqueue工作队列(可以被阻塞)
处理具体的中断服务的业务
硬件中断是中断源发出中断请求而引发的,中断请求发出后如果没有屏蔽该中断,则CPU会立即处理该函数;
Linux下的中断通常可以理解为软中断,软中断不是中断源请求而发生的,而是由中断指令产生的。
中断处理机制
ARM CPU 硬件处理流程
中断发生->通用中断控制器GIC集中管理所有中断源->CPU核心响应(切换IRQ模式和FIQ模式、跳转到异常向量表)
Linux内核软件处理流程
汇编入口->通用中断处理->中断处理函数-上半部->下半部机制
14、进程上下文和中断上下文
进程上下文:内核在执行进程(或线程)时所处的环境,代表一个进程在CPU上的执行状态进程在执行过程中被打断时,操作系统需要**保存的该进程的状态信息**。当内核需要**代表进程**执行操作(系统调用(read,write))时,它就在进程上下文运行中断上下文:内核在处理硬件中断或软件异常时所处的执行环境当**硬件设备**(如网卡、硬盘、定时器)需要CPU立即处理时,会发出一个中断信号。CPU会暂停当前正在执行的指令,转去执行与**该中断对应的处理程序**【注意:不能被休眠,执行要快】
15、两种挂载方式
tftp:简单文件传输协议
主机1(服务端):
设置tftp服务目录,只有该目录下的文件能被客户端下载。客户端上传的文件也会存放该目录(如果配置支持上传)
主机2(客户端):
运行tftp客户端,连接服务器,可以下载服务器的文件到本地。也可以上传本地文件到tftp服务端(如果服务端配置)nfs:网络文件系统
主机1(服务端):
设置nfs服务的目录,该目录的所有文件共享给挂载该目录的客户端
主机2(客户端):
需要手动挂载远端服务器,确定服务器的ip和目录,确定本地挂载的目录,可以在本地目录访问服务器nfs的所有文件
步骤:
下载nfs服务,sudo apt-get install nfs-kernel-server
通过nfs配置文件指定根文件系统路径:sudo vim /etc/exports
重启nfs服务:sudo /etc/init.d/nfs-kernel-server restart
16、设备树
以.dts源文件和.dtsi头文件编写,编译成.dtb二进制文件供内核使用
设备树结构:节点(硬件组成部分)和属性(节点的配置参数)uboot加载dtb文件到内存,内核解析设备树并初始化匹配的驱动
内核通过compatible属性查找对应的驱动
17、IIC子系统(第四种驱动模板)
IIC子系统是Linux提供的一种IIC操作驱动框架
物理硬件层
IIC适配器驱动层
IIC核心层:实现IIC总线上设备和驱动的匹配/注册方法,具体IIC总线传输API接口
IIC设备驱动层
用户层
18、为什么uImage下载到0x30008000地址??
0x30004000 0x300008000
19、示波器、逻辑分析仪和示波器的用法
示波器
横轴表示的时间,纵轴表示的电压

20、设备和驱动的匹配、驱动和用户的交互
杂项设备和字符设备:主设备号标识驱动类别,次设备号标识具体设备实例用户打开的是设备节点,内核通过设备节点中的设备号(主设备号+次设备号)找到驱动
platfrom平台总线:platform_device_register() // 设备注册;platform_driver_register() // 驱动注册
调用platform_match() 进行匹配
比较 platform_device.name 和 platform_driver.driver.name,如果匹配成功,调用驱动的.probe() 函数
??
设备树:
在设备树文件中定义一个设备节点,并指定一个或多个compatible 属性。
在驱动代码中,定义一个 of_device_id 结构体数组
21、为什么uImage下载到0x30008000内存?
将uImage下载到0x30008000(对于S3C2400)是为了避免覆盖Bootloader存放在SDRAM起始部分的重要代码和数据,同时确保内核被加载到正确的链接地址上。
22、根文件系统包含哪些文件
/dev:存放设备相关文件目录
/lib: 存放应用程序和系统运行所需要的库文件
/usr:存放用户相关的应用程序、库文件、头文件等目录
/etc:存放系统配置文件目录
/mnt:存放临时挂载目录
/tmp:存放临时文件目录
/bin:存放二进制相关指令(如ls)
/home:家目录
/root:超级用户目录
/var:存放经常变化的数据,如日志
/proc:虚拟文件系统,不实际存储在磁盘上,而是反映了系统当前的运行状态和内核信息。
/sys:虚拟文件系统,主要用于提供内核和设备驱动程序的信息
23、如何制作根文件系统
busybox,下载busybox源码,配置busybox,编译busybox,创建根文件系统目录,拷贝相关库到lib目录,构建根文件系统的其他目录。
buildroot,原理类似。
来源:老师提供的
24、如何手动挂载根文件系统
mount -o nolock,rw 192.168.1.100:/home/linux/nfs/rootfs /mnt
nolock:非阻塞
rw:读写
192.168.1.100:ubuntu ip
/home/linux/nfs/rootfs:根文件路径
/mnt:挂载到板子的mnt目录通过mount命令将ubuntu ip下的根文件系统挂载到板子的mnt目录中,板子的mnt目录和rootfs可以看作是同一个目录。
25、静态注册设备号和动态注册设备号
静态注册register_chrdev_region :事先定义好设备号,将定义好的设备号注册到系统(mknod)
mknod /dev/led c 255 0
动态注册alloc_chrdev_region:让内核自动分配一个未被占用的设备号(推荐,避免和内核中的设备号冲突)
26、用户和内核的交互
设备文件:用户态程序可以通过打开设备文件(如/dev/下的设备)并执行read、write、ioctl等操作来与内核驱动交互。
系统调用:在 Linux 操作系统中,系统调用是用户程序与内核交互的关键接口
27、GPIO点灯(驱动和用户)
九、ARM项目
1、数据库移植
1、解压源码压缩包
2、配置编译:./configure --host=arm-linux
3、make
如果报错在shell.c加入
#ifndef LLONG_MIN
#define LLONG_MIN (-9223372036854775807LL - 1)
#endif
#ifndef LLONG_MAX
#define LLONG_MAX 9223372036854775807LL
#endif
在压缩包目录生成:libsqlite3.so(库) sqlite3(可执行程序)、sqlite3.h(头文件)
4、libsqlite3复制到rootfs/lib/、sqlite3复制到rootfs/
5、在压缩包目录编写.c文件(因为头文件在压缩包目录下)
6、运行:arm-linux-gcc testsql.c -L. -lsqlite3 -o sql
-L找库名
2、显示framebuffer
步骤:
(1)open(/dev/fb0)
(2)获取显示的相关信息--显示器分辨率、显存分辨率、位深度ioctl(fd, FBIOGET_VSCREENINFO, &info);
(3)获取显存地址(以内存映射的方式)p_fb = mmap(void* start,size_t length,int prot,int flags,int fd,off_t offset);【内存映射:mmap 可以将文件的内容映射到进程的虚拟地址空间,使得对这段内存的修改可以直接反映到文件中。用mmap建立内存映射, 并返回映射首地址指针start】参数:start:指向欲映射的内存起始地址,通常设为 NULL。length:代表将文件中多大的部分映射到内存。prot:映射区域的保护方式。flags:影响映射区域的各种特性。fd:要映射到内存中的文件描述符。offset:文件映射的偏移量,通常设置为0。
(4)解除映射、closemunmap(p_fb, fb_size);close(fb0);
相关信息:
LVGL是基于framebuffer,使用guiguider UI设计器
TD35、240*320分辨率、色深RGB565
LVGL:
开始用的是framebuffer比较底层的做UI,后来了解到LVGL图形库,从官网下载源码编译,把相关库移植到2440上,用UI设计器guiguide设计生成UI代码,拷贝到工程里
3、线程邮箱
概念:
线程之间的一个数据通信和数据共享,是采用的一种链表加队列的数据结构,链表存线程函数,id,队列头尾等信息,队列缓存数据,优势是数据缓存(避免数据覆盖丢失)和解耦合(每个线程操作自己的队列)
为什么选择多线程而不是多进程:
(1)因为线程空间是共享的,使传感器数据,等需要在模块间频繁传递的大量数据,可以通过全局变量直接访问,非常高效,而进程需要用到进程间通信,开销大
(2)线程的创建比进程轻量得多,对资源有限的嵌入式系统比较有利
开始线程间通过全局变量进行通信,会出现资源竞争(多个线程访问同一个资源),加互斥锁;
线程处理频率不一样(3秒采集一次,5秒存储一次),数据会丢失,采用缓存机制,采用队列;
为了解决解耦合度高的问题,给每个线程搭配一个队列;为了把队列关联到一起,通过链表关联到一起,根据tid和线程名字遍历链表,找到对应的线程,向对应要传数据的队列入队,向自己的队列出队(send_msg和recv_msg)
原理图:

关键函数:

项目通信流程:
线程邮箱:
改变:
定义传感器数据的结构体,在MAIL_DATA结构体添加传感器数据的结构体。
修改send_msg和recv_msg函数,使其能够发送和接收传感器数据。邮箱系统内部互斥锁:
保护:因为多个线程可能同时访问同一个消息队列,比如存储线程向报警线程,上传线程,显示线程发数据send_msg(),同时报警线程、显示线程、上传线程向数据存储线程recv_msg(),没有锁可能导致消息丢失或队列损坏。
因此:邮箱系统的锁保证了消息队列的线程安全,进而保证了传感器数据在传递过程中的安全。
4、mqtt
概念:
MQTT协议全称是消息队列遥测传输协议,是一种基于发布/订阅模式的轻量级通讯协议,是应用层的协议
通信模型:
MQTT协议中有三种身份:发布者(Publish)、代理(Broker)、订阅者(Subscribe)。其中,消息的发布者和订阅者都是客户端,消息代理是服务器,消息发布者可以同时是订阅者。

关键技术点:
流程:
建立TCP连接、建立MQTT连接、broker返回ACK确认连接、发送主题到broker
建立TCP连接、建立MQTT连接、broker返回ACK确认连接、代理将订阅该主题的订阅者发送消息报文头格式:
国定头
可变头
payload(消息体):QOS服务质量、Topic订阅主题,客户端信息(password,id)Qos决定消息传递的可靠性
Qos0:最多一次
Qos1:至少一次
Qos2:恰好一次遗嘱:
断网前把之前的数据发过去
设置遗嘱:
willTopic:遗嘱主题、willMessage:遗嘱内容、willQos:消息传递次数、willRetain:返回值心跳包:
服务器检测客户端是否还在线,在空闲时间向服务器发一个心跳包移植:
要实现mqtt客户端,服务器是云平台,客户端需要调用函数接口,需要编译paho库(有函数接口)
依赖于openssl,paho 的编译(C语言实现MQTT的开源库),makeonenet云操作:
创建产品,创建设备(包括产品id、设备id、物模型),物模型(标识符、类型、取值范围)
实现:
1、mqtt开源代码
mqtt初始化->订阅主题发布主题->创建mqtt客户端->设置回调函数->连接mqtt服务器->循环发送数据
回调函数:连接断开时的处理、接收到消息时的确认、消息发送成功的确认2、mqtt库移植:
openssl源码:配置,make编译,sudo make install
【OpenSSL 为 MQTT 通信提供 安全加密,确保数据传输的保密性和完整性。】
paho库:配置,make编译,sudo make install
【Paho 库让你用简单的函数调用就能完成复杂的 MQTT 通信,不用自己处理底层的网络协议细节。】3、适配到本项目
5、DHT11驱动介绍
1、查DHT11中文手册
指标参数:
温度:范围(0-50),误差(-2~+2)
湿度:范围(20%-90%),误差(-5%~+5%)
接线方式:GPIO单总线
时序:起始+应答+传输数据(0,1)
启动传感器->等待传感器响应->读取数据位->解析数据
数据格式:Byte0:湿度整数部分;Byte1:湿度小数部分(DHT11总是0);Byte2: 温度整数部分;Byte3:温度小数部分(DHT11总是0);Byte4:校验和(前4字节相加)2、S3C2440芯片手册,查询引脚,原理图,引脚连接
3、基于杂项设备驱动框架实现DHT11驱动
4、应用层读取DHT11数据
问题:DHT11与GPIO引脚接的线作为输入还是输出?DHT11的通信需要GPIO在输入和输出模式之间切换
主机发送起始信号时:设置为输出,
等待DHT11响应和读取数据时:设置为输入
6、RS485介绍
1、通信协议ModbusRTU
2、数据格式:地址、功能码、数据、CRC校验????
7、项目介绍:
项目名称:是基于 ARM 的农业大棚土壤钾含量及空气温湿度监测系统,
项目来源:协会之前的老师提供的,我和另一个同学拿来练手的项目,分工合作完成
项目功能:
在2440平台移植Linux操作系统,通过多路传感器采集土壤钾含量、温湿度数据,移植sqlite3实现数据存储,移植LVGL实现LCD显示,还实现声光报警和MQTT上传功能
模块功能:
采集模块,用rs485土壤钾含量传感器和DHT11温湿度传感器,编写驱动采集数据
存储模块,移植sqlite3,设计温湿度表和土壤钾含量表存数据
显示模块,利用LVGL图形库将采集的数据显示在LCD屏幕上
上传模块,并通过MQTT协议上传至ONENET云
报警模块,当数据超过预设的阈值,蜂鸣器和LED灯会发出警示
最后各模块用多线程并发实现,线程间数据通信和共享用链表加队列的数据结构,实现线程邮箱。
8、技术难点
现象:调试DHT11时候,读取的数据全部为0问题排查过程:
检查硬件没问题,把DHT11接到STM32,用网上历程代码测试,确认硬件没问题
然后用逻辑分析仪抓波形,发现驱动延时不准确(要求延时18ms,实际超了),原因是Linux是分时系统,调度其他任务会耗时间。导致延时不准解决方案:调整延时为15-16ms,最后数据正常。
dht11_start()//主机先保持高电平,主机再拉低低电平至少18ms,主机再拉高20-40us
dht11_response()//dht11拉低至少80us,dht11再拉高80us,dht11再次拉低准备传输数据
get_bit()//等待低电平开始至少50us,延时35us,如果是低电平了就是0,反之是高电平就是1
get_data()//循环5次,每次读取8位,组成一个字节mdelay()忙等待,占CPU,不会去调度其他任务
msleep()睡眠延时函数,不占cpu,CPU会调度其他任务,(所以要降低延时)
逻辑分析仪:
采样率(每秒采集的样点数)、采样深度(一次采集的样点总数)、触发方式……
9、可以优化的问题
加一个摄像头模块,检查农业大棚的图像信息
每天早中晚拍摄三张照片,以图片形式保存本地的数据库并记录时间戳,然后上传到云端
10、得到的收获
对嵌入式技能得到提升,是技能落实在项目上,学会对传感器手册阅读
11、gdb(ARM调试)
远程调试:
十、网站项目
1、介绍
该项目是一个基于TCP的socket套接字编程的Web服务器,使用epoll多路I/O复用技术实现并发。客户端浏览器发送请求报文,服务器接收请求数据,解析URL,提取参数并解码,然后根据路径进行不同的处理:对于静态文件直接读取发送;对于动态请求(如登录、注册、搜索等),服务器操作数据库,通过回调函数将查询结果拼接到HTML模板中,生成动态网页并发送给客户端。
2、协议解析
客户端和服务器基于tcp套接字编程连接(socket,bind,listen)
//创建epoll实例
循环
//epoll_wait等待事件对于每个事件:
a. 如果是监听套接字上的事件,接受新的连接(accept),并将新连接的套接字加入epoll。
b. 如果是已连接套接字上的事件,则接收数据(recv),然后解析HTTP请求。服务器recv接受客户端发来的请求报文(定义一个1024的buf数组)
把请求报文放进buf数组
strtok分割请求行(方法,url,版本号,\r\n)根据不同的URL路径,执行不同的操作
a.对于静态文件(HTML、图片),直接读取文件并发送;
b.对于动态请求(如登录、注册、搜索等),服务器会操作数据库,并根据结果生成HTML页面发送给客户端。
(创建模板文件和发送文件,然后复制模板文件内容给要发送的文件,直到遇到标记(如"%SEARCH_RESULTS%"),然后执行数据库查询,通过回调函数将查询结果写入新文件(在标记处插入),然后再复制模板文件剩余的部分)然后使用send_file函数发送HTML、PNG、JPG文件。这个函数先调用send_head发送HTTP响应头(6个数组),然后读取文件内容并发送。
十一、自我介绍
过去的我:面试官好,我叫饶佛强,毕业于西南民族大学计算机科学与技术专业,我应聘的是公司嵌入式软件开发工程师岗位,我在校期间获得中国计算机设计大赛二等奖、蓝桥杯省级三等奖等竞赛奖励,这是我学习能力还不错的一个代表,同时我也与协会成员合作基于 ARM 的农业大棚土壤钾含量及空气温湿度监测系统项目的开发,负责数据采集、存储、上传等模块,以及简单驱动代码编写,这是我专业能力还不错的一个表现。同时去年的测试实习经历,也让我对项目研发流程、团队合作和技术文档的编写等有了相关经验。
现在的我:我了解到公司对这个职位的要求,我自己也熟练掌握C语言、Linux系统编程,sqlite3数据库,熟悉ARM体系架构、UART、IIC 等硬件通信协议,这些和公司的职位是比较匹配的,同时,我在面试之前,对公司的行业进行了一些了解,知道这个行业是非常朝阳的行业,我非常感兴趣。
未来的我:我期待我毕业的第一份工作是在这样一个成熟的平台开始的,我也有信心进入公司后很快上手项目研发工作,我的自我介绍完毕,谢谢你。
十二、人事面
1、校园经历:
比赛项目经历:
蓝桥杯:大二(23年3-4月)软件测试赛道
23年9月-12月:网站项目
设计大赛:协会指导老师给的项目,拿去参加计算机设计大赛,物联网应用赛道(24年4月-24年9月)大四经历:
24年9月准备去秋招:大四上,想准备好秋招,开发测试都找,找到测试岗位,便尝试测试实习,在做测试这段时间,觉得开发更适合自己(因为开发更能提升自己,不局限于测试,),所以选择辞职。
团队合作:
2人,分工,
首先讨论,了解每个人的优势,做好分工成员不配合:
私下沟通,了解每个人的不配合的原因,并给予解决办法
团队协作发生分歧的案例:
项目传感器的选择,温湿度传感器选择DHT11,或者选择其他传感器让项目更新颖,但是项目时间紧张,所以沟通选择DHT11
与团队成员沟通,要以更好的完成项目为目标
自豪的事,成就感的事:没有荒废学业,一直没有落下自己的课程
最遗憾的事:在大三寒暑假期间,可以去投简历,丰富自己的实践经历,现在我希望在工作中丰富自己的实践经历
压力最大的事:大四在面对多条路的选择,找工作压力大
2、个人情况:
性格优缺点:善于倾听做事严谨;不太自信内向
兴趣爱好:骑行和游泳
稳定度:
没有女朋友;没有遇到合适的家里情况:
家乡行业发展不是很好,父母在家,二姐考上家里的公务员,自己想在外面好好发展,并且父母也支持找工作情况:
没有offer,刚开始找工作,收到几个笔试面试,在等后续了解公司情况:
广联智通:物联网智能网关及解决方案供应商,创立GL.inet品牌职业规划:
1、在进入公司后学习,尽快上手公司的项目,承担项目一些模块
2、争取在半年左右独立做项目
3、给自己2-3年时间做嵌入式岗位的专业开发人员
抗压情况:
加班:可以接受加班
排解压力的方式:运动跑步
多任务同时:优先级,处理紧急的抗压案例:
感觉自己找工作压力大,因为自己25届身份很严峻,但是自己会调整好作息,适当锻炼,合理安排时间规划。
为什么计算机软件转嵌入式,为什么从测试转开发:
更喜欢软硬件结合,
希望让自己更具备竞争力为什么从上一家公司离职:
秋招的时候也是想往开发发展,当时人事和我说岗位是测试开发工程师,自己觉得是个不错的机会,也能做开发,但是在岗位发现是纯测试岗位,觉得不太适合,就放弃转正机会(三方是随时可以签订,但是自己想选择)为什么现在才找工作:
因为春节前,回学校的时候腿摔断了,医生建议卧床,父母就把我接回家修养,毕设导师又要求一周一次组会,没有大规模的进行春招,回学校已经快4月底了,这时候毕业答辩和毕业设计要开始了,就错过春招。和家人老师商量,老师建议可以准备一下(因为应届生身份还在),准备秋招,家人也支持,所以在暑假对学校所学的知识复习,以及项目再写了一遍,准备秋招
3、求职情况:
岗位理解:
“在我看来,嵌入式岗位远不止是‘写单片机代码’。它是一个要求软硬兼修的复合型岗位。工程师需要具备硬件思维来理解底层基础,用软件能力构建功能实现,并用系统思维来统筹全局、做出权衡。最终的目标,是在各种严苛的约束下,打造出高效、稳定、可靠的智能产品。这正是这个岗位最具挑战性和吸引力的地方。期望薪资:8000+
找工作最看重哪些方面:第一岗位,第二行业,第三公司稳定度为什么不选择考研:
因为自己觉得投入工作更好锻炼发展自己,自己也有明确的职业规划反问:
技术:聊技术,入职的具体的工作内容,方向是:偏上层还是偏下层
人事:培训机制,入职的培训,能不能提前来实习,职位发展通道
1.简单讲一下你面试这个岗位的优势
技能优势:掌握岗位所具备的基本要求
实战优势:有相关的项目开发的经验和互联网公司项目的经验,
团队优势:对团队沟通合作比较
2.项目来源、队友选择、分工如何交流的
原协会的会长提供指导的项目
在协会选择平时比较认真的学生共同合作
我负责收集、存储、上传,报警模块以及模块间通信,另外一个成员负责显示模块,还有一个成员负责ppt及演示
3.产生分歧如何解决
沟通,以更好的完成项目为目标
4.项目是怎么验收的
完成项目所需的基本功能,在其后,我们拿项目参加计算机设计大赛的物联网应用的赛道
5.需要其他人帮助或帮助其他人,自己怎么看待边界问题
给我提供一个解决方案的思路,具体过程自己去完成,而非直接代劳
6.如果自己的核心工作没完成,团队同事需要帮助,怎么取舍
评估两者的工作紧急度,如果同事的比较紧急,我会快速给出建议性的方案,否则,先完成自己的核心工作。
7.在做项目过程中有遇到老师或同学的强硬的批评和指责吗?自己怎么消化?
认清自己的问题,反思犯错的根源,作为一个教训以后激励自己
8.在团队里如果被他人误会,怎么自证?
沟通,然后把相关记录与他澄清,主要是保证团队的信任,而非分辨出对错
9.如果进入公司给你一个项目,快截止交稿了但资源分配不足项目进展不顺利,要怎么解决?
评估剩余工作的优先级,申请调整资源的分配。但自己同时也做好风险的准备
10.拿到一个项目怎么团队分工?
首先给项目按模块拆分,了解成员的优势做好分工,自己也要对项目的整体完成做好把握
11.为什么想入职这个行业?是有什么契机吗?
因为大学在协会,在前任会长机遇下,并带领我们入门了嵌入式,并给了我们相关项目,并拿来做了比赛。
后期也对嵌入式充满了兴趣,以后也想往嵌入式发展
本科期间,学的C语言,数据结构,操作系统,计算机网络也是嵌入式开发的软件基础
最后,计算机虽然更注重软件,但自己更喜欢软硬件结合,用代码来控制硬件实现。只有不断提升自己,才能在技术高速发展的社会不被淘汰。
12.你在你的协会负责什么?为什么当时加入的是这个部门?
前期学习阶段:C语言,嵌入式,web开发
后期负责课程教学:C语言编程,Web开发想要在课外也学习新的知识,提高自己。也想多认识一些同样热爱技术的同学。
13.职业规划是怎样的?后续有考虑往什么方面发展吗?
一直想在嵌入式行业做研发人员
1、在进入公司后学习,尽快上手公司的项目,承担项目一些模块
2、争取在半年左右独立做项目
3、给自己2-3年时间做嵌入式岗位的专业开发人员
14.如果手上有多个offer,怎么考虑先后问题(给了我三个选项排序,平台(机会)、薪资、城市)
看重平台:好的平台给自己的锻炼机会,可以更好的提升自己的技能
然后是薪资和城市并列:因为都是决定生活成本,可能上海北京生活成本高同时工资也高,其他城市工资较低但是生活成本也低,所以我更看重平台与机会,更好的提升自己
15.你觉得你在公司主要能负责什么?
了解到公司是物联网智能网关及解决方案供应商,以及在招聘软件大概浏览了嵌入式岗位职责负责路由器核心功能模块的开发与优化,我具备扎实的C/C++编程能力和网络基础知识,对TCP/IP协议族有深入的理解,能够进行这些网络应用功能的代码实现、问题排查和性能调优
技术文档编写,技术支持
16.反问
//如果我有幸加入,这个岗位(或我们团队)目前在技术上面临的最大挑战是什么请问这个岗位主要负责的产品线是什么? (是安全网关、无线AP 还是其他? ),团队的技术栈是怎样的?
想问一下还有哪些需要补足的技能点?来提升自己
附录:
一、Git
1、基本命令
git init 初始化仓库
git add . 添加文件到暂存区
git commit -m 提交更改
git remote 远程仓库管理
git pull 拉取远程更新
git push 推送到远程仓库
二、Javascript
1、基本规则
HTML 中的 Javascript 脚本代码必须位于 标签之间。
2、数据类型
字符串(String)、数字(Number)、布尔(Boolean)、空(Null)、未定义(Undefined)、Symbol。
对象(Object)、数组(Array)、函数(Function),还有两个特殊的对象:正则(RegExp)和日期(Date)。
尊敬的招聘负责人:您好!我是西南民族大学25届毕业生饶佛强,应聘的是贵公司的嵌入式软件工程师岗位,附件是我的简历和毕业成绩单。感谢您在百忙之中阅读我的邮件,并期待您的回复,谢谢!顺祝:工作顺利!应聘人:饶佛强联系电话:17311268401项目描述:
本项目是一个基于ARM的农业大棚环境信息监测系统,采用S3C2440处理器,运行Linux操作系统。系统通过RS-485
土壤钾含量监测传感器和 DHT11 温湿度传感器采集环境数据,对大棚环境进行实时监测。通过多线程模型搭建并发
服务器,将采集的数据,存储到移植在 ARM 板上的 SQLite3 数据库中,并通过 MQTT 协议将数据上传至 OneNET
云平台,供工作人员线上监测,同时使用 LVGL 技术在 LCD 屏幕显示。系统还实现了报警功能,当钾含量低于阈值
或者温湿度高于阈值时,本地会进行报警提示,各模块间通信使用线程邮箱,确保系统的实时性和稳定性
相关模块:
1、数据采集模块:通过 RS-485 土壤钾含量监测传感器和 DHT11 空气温湿度传感器实时采集农业大棚环境数据,使用
单总线的方式获取传感器数据,并进行数据校验,每 30 分钟采集一次数据,确保数据实时性。
2、存储模块:将采集的土壤钾含量数据和空气温湿度数据存储到 ARM 板上移植的 SQLite3 数据库中。
3、显示模块:将 LVGL 轻量级图形库移植至嵌入式平台,负责将底层采集的传感器数据显示在 LCD 屏幕上,并且每 30
分钟刷新环境数据。
4、报警模块:在程序中设定钾含量和温湿度的阙值,当检测数据超过该阙值,通过控制 LED 灯和蜂鸣器进行本地报警
提示
5、网络模块:利用 MQTT 协议将大棚环境信息上传至 OneNET 云平台,实现云端数据可视化和远程监控。
6、线程间通信:通过线程邮箱(主要用到线程间通信、链表、队列的相关技术),实现数据采集、存储、上传,显示和报
警等模块之间的通信
关键技术:
Sqlite3 数据库、MQTT 通信协议、线程邮箱、LVGL 图形库、Linux 驱动开发
三、相关概念:
1、基本
MCU:将CPU、内存、闪存、各种I/O接口集成到一颗芯片上的微控制器。
SoC:功能更强大的MCU,片上系统。ARM系统:精简指令集计算机(RISC)架构
RISCV嵌入式系统:精简指令集的指令集架构。它不是一块具体的芯片,而是一套设计芯片的“标准语言”。RK、海思、国科、君正:芯片厂商(基于ARM或RISC-V架构设计出SoC芯片)
SDK:购买SoC芯片,芯片原厂会提供SDK,SDK包含(BSP启动代码和驱动、交叉编译工具链、系统源码和镜像、温度工具)下位机:整个嵌入式系统,负责执行具体的控制任务,是“被控制”的一方。
上位机:通常是计算机上运行的软件,负责监控、管理、调试和显示下位机的状态和数据,是“发命令”的一方。AutoSAR_AP:为汽车行业制定的开放式、标准化的软件架构,运行在SoC上的汽车软件平台。openwrt:SoC上一个非常流行的应用程序运行时环境,专门为嵌入式设备(通常是网络设备)设计的高度可扩展的GNU/Linux发行版。我理解的是MCU就是单片机和STM32(一般基于ARM Cortex-M架构),SoC是集完整系统的芯片(一般基于ARM Cortex-A架构),而ARM和RISCV都是精简指令集的架构,许多微控制器(MCU、SoC)都是基于ARM和RISCV架构设计的,RK海思国科君正这些都是微控制器也就是芯片的厂商,SDK就是芯片厂商提供的相关工具集合,openwrt是针对于路由器等嵌入式设备的linux发行版,AutoSAR_AP是一个汽车电子领域的软件设计的规则和框架。
2、MCU、ARM、Linux
三个概念:
MCU(微控制器):是一种集成了处理器、内存、输入/输出接口等的小型计算机
ARM:是一种处理器架构,以其低功耗和高效率著称
Linux:是一个开源的操作系统内核关系:
MCU可以是基于ARM架构的,也可以是其他架构(如RISC-V、MIPS等)。
Linux操作系统可以运行在具有MMU(内存管理单元)的ARM处理器上(通常是比较强大的MCU或MPU)。
【注意:不是所有的ARM MCU都能运行Linux,因为Linux通常需要MMU来支持虚拟内存管理。因此,只有具有MMU的ARM处理器(如Cortex-A系列)才能运行Linux,而没有MMU的ARM处理器(如Cortex-M系列)通常运行实时操作系统(RTOS)或裸机程序。】基于ARM架构可以设计出各种不同的MCU/SoC,而其中功能强大的那些(通常叫SoC)可以用来运行Linux操作系统,从而支撑复杂的应用程序。
3、其他相关平台
IPC:网络摄像机。每个IPC都有一个独立的IP地址,在现场采集视频信号,并进行编解码(H.264和H.265),然后通过网络进行传输。
NVR:网络视频录像机。不连接摄像机,只处理网络数据,接收来自IPC的直播流,并进行存储和管理。H.264和H.265:决定了视频的清晰度、流畅度和存储空间。(通过FFmpeg)RTSP实时流协议:建立控制连接,告诉服务器我要连接。
RTP:建立数据通道,通过RTP协议将音视频数据包发送到客户端指定的端口。
RTCP实时传输控制协议:同步质量反馈,客户端和服务器互相发送质量报告。ISP图像信号处理器:处理图像噪声和图像的锐度等图像问题
4、电路相关知识
二极管和三极管:
四、vim
1、向下插入一个空行
普通模式(normal):o键,并切换插入模式
2、复制粘贴怎么做
可视模式:
y
插入模式:
p
自己。也想多认识一些同样热爱技术的同学。
13.职业规划是怎样的?后续有考虑往什么方面发展吗?
一直想在嵌入式行业做研发人员
1、在进入公司后学习,尽快上手公司的项目,承担项目一些模块
2、争取在半年左右独立做项目
3、给自己2-3年时间做嵌入式岗位的专业开发人员
**14.如果手上有多个offer,怎么考虑先后问题(给了我三个选项排序,平台(机会)、薪资、城市)**
看重平台:好的平台给自己的锻炼机会,可以更好的提升自己的技能
然后是薪资和城市并列:因为都是决定生活成本,可能上海北京生活成本高同时工资也高,其他城市工资较低但是生活成本也低,所以我更看重平台与机会,更好的提升自己
**15.你觉得你在公司主要能负责什么?**
了解到公司是物联网智能网关及解决方案供应商,以及在招聘软件大概浏览了嵌入式岗位职责
负责路由器核心功能模块的开发与优化,我具备扎实的C/C++编程能力和网络基础知识,对TCP/IP协议族有深入的理解,能够进行这些网络应用功能的代码实现、问题排查和性能调优
技术文档编写,技术支持
16.反问
//如果我有幸加入,这个岗位(或我们团队)目前在技术上面临的最大挑战是什么
请问这个岗位主要负责的产品线是什么? (是安全网关、无线AP 还是其他? ),团队的技术栈是怎样的?
想问一下还有哪些需要补足的技能点?来提升自己
### 附录:### 一、Git##### 1、基本命令
git init 初始化仓库
git add . 添加文件到暂存区
git commit -m 提交更改
git remote 远程仓库管理
git pull 拉取远程更新
git push 推送到远程仓库
### 二、Javascript##### 1、基本规则HTML 中的 Javascript 脚本代码必须位于 **<script>** 与 **</script>** 标签之间。##### 2、数据类型字符串(String)、数字(Number)、布尔(Boolean)、空(Null)、未定义(Undefined)、Symbol。
对象(Object)、数组(Array)、函数(Function),还有两个特殊的对象:正则(RegExp)和日期(Date)。
尊敬的招聘负责人:
您好!
我是西南民族大学25届毕业生饶佛强,应聘的是贵公司的嵌入式软件工程师岗位,附件是我的简历和毕业成绩单。感谢您在百忙之中阅读我的邮件,并期待您的回复,谢谢!
顺祝:工作顺利!应聘人:饶佛强联系电话:17311268401
项目描述:
本项目是一个基于ARM的农业大棚环境信息监测系统,采用S3C2440处理器,运行Linux操作系统。系统通过RS-485
土壤钾含量监测传感器和 DHT11 温湿度传感器采集环境数据,对大棚环境进行实时监测。通过多线程模型搭建并发
服务器,将采集的数据,存储到移植在 ARM 板上的 SQLite3 数据库中,并通过 MQTT 协议将数据上传至 OneNET
云平台,供工作人员线上监测,同时使用 LVGL 技术在 LCD 屏幕显示。系统还实现了报警功能,当钾含量低于阈值
或者温湿度高于阈值时,本地会进行报警提示,各模块间通信使用线程邮箱,确保系统的实时性和稳定性
相关模块:
1、数据采集模块:通过 RS-485 土壤钾含量监测传感器和 DHT11 空气温湿度传感器实时采集农业大棚环境数据,使用
单总线的方式获取传感器数据,并进行数据校验,每 30 分钟采集一次数据,确保数据实时性。
2、存储模块:将采集的土壤钾含量数据和空气温湿度数据存储到 ARM 板上移植的 SQLite3 数据库中。
3、显示模块:将 LVGL 轻量级图形库移植至嵌入式平台,负责将底层采集的传感器数据显示在 LCD 屏幕上,并且每 30
分钟刷新环境数据。
4、报警模块:在程序中设定钾含量和温湿度的阙值,当检测数据超过该阙值,通过控制 LED 灯和蜂鸣器进行本地报警
提示
5、网络模块:利用 MQTT 协议将大棚环境信息上传至 OneNET 云平台,实现云端数据可视化和远程监控。
6、线程间通信:通过线程邮箱(主要用到线程间通信、链表、队列的相关技术),实现数据采集、存储、上传,显示和报
警等模块之间的通信
关键技术:
Sqlite3 数据库、MQTT 通信协议、线程邮箱、LVGL 图形库、Linux 驱动开发
### 三、相关概念:##### 1、基本
MCU:将CPU、内存、闪存、各种I/O接口集成到一颗芯片上的微控制器。
SoC:功能更强大的MCU,片上系统。
ARM系统:精简指令集计算机(RISC)架构
RISCV嵌入式系统:精简指令集的指令集架构。它不是一块具体的芯片,而是一套设计芯片的“标准语言”。
RK、海思、国科、君正:芯片厂商(基于ARM或RISC-V架构设计出SoC芯片)
SDK:购买SoC芯片,芯片原厂会提供SDK,SDK包含(BSP启动代码和驱动、交叉编译工具链、系统源码和镜像、温度工具)
下位机:整个嵌入式系统,负责执行具体的控制任务,是“被控制”的一方。
上位机:通常是计算机上运行的软件,负责监控、管理、调试和显示下位机的状态和数据,是“发命令”的一方。
AutoSAR_AP:为汽车行业制定的开放式、标准化的软件架构,运行在SoC上的汽车软件平台。
openwrt:SoC上一个非常流行的应用程序运行时环境,专门为嵌入式设备(通常是网络设备)设计的高度可扩展的GNU/Linux发行版。
我理解的是MCU就是单片机和STM32(一般基于ARM Cortex-M架构),SoC是集完整系统的芯片(一般基于ARM Cortex-A架构),而ARM和RISCV都是精简指令集的架构,许多微控制器(MCU、SoC)都是基于ARM和RISCV架构设计的,RK海思国科君正这些都是微控制器也就是芯片的厂商,SDK就是芯片厂商提供的相关工具集合,openwrt是针对于路由器等嵌入式设备的linux发行版,AutoSAR_AP是一个汽车电子领域的软件设计的规则和框架。
##### 2、MCU、ARM、Linux
三个概念:
MCU(微控制器):是一种集成了处理器、内存、输入/输出接口等的小型计算机
ARM:是一种处理器架构,以其低功耗和高效率著称
Linux:是一个开源的操作系统内核
关系:
MCU可以是基于ARM架构的,也可以是其他架构(如RISC-V、MIPS等)。
Linux操作系统可以运行在具有MMU(内存管理单元)的ARM处理器上(通常是比较强大的MCU或MPU)。
【注意:不是所有的ARM MCU都能运行Linux,因为Linux通常需要MMU来支持虚拟内存管理。因此,只有具有MMU的ARM处理器(如Cortex-A系列)才能运行Linux,而没有MMU的ARM处理器(如Cortex-M系列)通常运行实时操作系统(RTOS)或裸机程序。】
基于ARM架构可以设计出各种不同的MCU/SoC,而其中功能强大的那些(通常叫SoC)可以用来运行Linux操作系统,从而支撑复杂的应用程序。
##### 3、其他相关平台
IPC:网络摄像机。每个IPC都有一个独立的IP地址,在现场采集视频信号,并进行编解码(H.264和H.265),然后通过网络进行传输。
NVR:网络视频录像机。不连接摄像机,只处理网络数据,接收来自IPC的直播流,并进行存储和管理。
H.264和H.265:决定了视频的清晰度、流畅度和存储空间。(通过FFmpeg)
RTSP实时流协议:建立控制连接,告诉服务器我要连接。
RTP:建立数据通道,通过RTP协议将音视频数据包发送到客户端指定的端口。
RTCP实时传输控制协议:同步质量反馈,客户端和服务器互相发送质量报告。
ISP图像信号处理器:处理图像噪声和图像的锐度等图像问题
##### 4、电路相关知识
二极管和三极管:
### 四、vim##### 1、向下插入一个空行
普通模式(normal):o键,并切换插入模式
##### 2、复制粘贴怎么做
可视模式:
y
插入模式:
p
