【嵌入式软件开发总结】C语言、内存与寄存器、通信协议、进程、中断、TCP/UDP

学习到一定的阶段一定要做适时的总结,一来是知道自己目前学习到了一个什么样的阶段,二来是避免将来用到所学知识的时候不会花费太多的时间去重新把内容拾起来。有时候觉得自己学的内容很杂,什么都了解了一点但是什么都了解的不是很深入,这也让自己在一段时间里迷茫过。希望自己能够一直保持下来,坚持做好记录,一直很相信一句话:当你觉得现在做的事情很难的时候只要坚持下去,成功的曙光就在不远的将来。

C语言


编译

什么是预编译,何时需要预编译?

1、总是使用且不需要改动的大型代码体

2、程序由多个模块组成,所有模块都使用一组标准的包含文件和相同的编译选项。在这种情况下,可以将所有包含文件预编译为一个预编译头。

预编译指令指示了在程序正式编译前就由编译器进行的操作,可以放在程序中的任何位置。

#ifdefine
#endif
#define
#include
......

关键字

数据类型关键字

基本数据类型

- void:声明函数无返回值或无参数,声明无类型指针,显式丢弃运算结果

  • char:字符型类型数据,属于整型数据的一种
  • int:整型数据,通常为编译器指定的机器字长
  • float:单精度浮点型数据,属于浮点数据的一种
  • double:双精度浮点型数据,属于浮点数据的一种

类型修饰关键字

  • short:修饰int,短整型数据,可省略被修饰的int。
  • long:修饰int,长整形数据,可省略被修饰的int。
  • signed:修饰整型数据,有符号数据类型
    - unsigned:修饰整型数据,无符号数据类型

复杂类型关键字

  • struct:结构体声明
  • union:共用体声明
  • enum:枚举声明
  • typedef:声明类型别名
  • sizeof:得到特定类型或特定类型变量的大小

存储级别关键字

  • auto:指定为自动变量,由编译器自动分配及释放。通常在栈上分配
  • static:指定为静态变量,分配在静态变量区,修饰函数时,指定函数作用域为文件内部
  • register:指定为寄存器变量,建议编译器将变量存储到寄存器中使用,也可以修饰函数形参,建议编译器通过寄存器而不是堆栈传递参数
  • extern:指定对应变量为外部变量,即在另外的目标文件中定义,可以认为是约定由另外文件声明的对象的一个“引用“
  • const:与volatile合称“cv特性”,指定变量不可被当前线程/进程改变(但有可能被系统或其他线程/进程改变)
  • volatile:与const合称“cv特性”,指定变量的值有可能会被系统或其他进程/线程改变,强制编译器每次从内存中取得该变量的值

流程控制关键字

跳转结构

  • return:用在函数体中,返回特定值(或者是void值,即不返回值)
  • continue:结束当前循环,开始下一轮循环
  • break:跳出当前循环或switch结构
  • goto:无条件跳转语句

分支结构

- if:条件语句

  • else:条件语句否定分支(与if连用)
  • switch:开关语句(多重分支语句)
  • case:开关语句中的分支标记
  • default:开关语句中的“其他”分治,可选。

循环结构

  • for:for循环结构,for(1;2;3)4;的执行顺序为1->2->4->3->2…循环,其中2为循环条件
  • do:do循环结构,do 1 while(2);的执行顺序是1->2->1…循环,2为循环条件
  • while:while循环结构,while(1) 2;的执行顺序是1->2->1…循环,1为循环条件

编程

常用函数

sizeof与strlen

sizeof() 操作数所占空间的字节数大小,是C的基本运算符

strlen() 计算字符串的长度,是一种函数。 '\0’作为终止符

main传入参数

int argc 整型变量

char *argv[] 字符指针的数组,通俗一点就是字符串数组,每个元素都是字符串

char *envp[] 字符串数组

strcpy()

char *strcpy(char *dest, const char *src)
//	dest -- 指向用于存储复制内容的目标数组
//  src -- 要复制的字符串

基础定义


头文件中的ifdef/define endif的作用是什么

预编译指令,防止头文件被重复引用


#include <stdio.h>与#include "stdio.h"的区别?

<stdio.h>表示的是该文件存在编译器指定的标准头文件存放处

"stdio.h"表示的是该文件在用户当前的工作目录下


常量和变量的区别

1.常量是只读不可写,变量可读可写常量必须初始化,变量可以不初始化;

2.常量不可以寻址,它的地址不可赋值非常量指针;变量可以寻址,常量的效率比变量高。


全局变量喝局部变量的区别

1.作用域不同:全局变量的作用域为整个程序,局部变量的作用域为当前语句块;

2.内存储存方式:全局变量存储在全局数据区,局部变量存储在栈上;

3.生命周期不同:全局变量的生命周期和程序一样,局部变量随着语句块的结束而结束;

4.当局部变量与全局变量同名时,优先使用局部变量;

用static声明局部变量,使其变为静态存储方式(静态数据区),作用域不变;用static声明外部变量,其本身就是静态变量,这只会改变其连接方式,使其只在本文件内部有效,而其他文件不可连接或引用该变量。

局部变量和全局变量可以重名,局部变量会屏蔽掉全局变量。同一个函数内也可以定义同名局部变量,内层只在内层有效。


结构体和联合体的区别

1.结构体和联合体都是由不同的数据类型组成,但在任何时刻,联合体只存在一个被选中的成员,结构体所有成员都存在。

2.在结构体中,各成员占有自己的储存空间,总大小等于各成员的大小之和

3.在联合体中,所有成员公用一块储存空间,其大小等于联合体中最大成员的大小


链表与数组的区别

链表是一种上一个元素的引用指向下一个元素的存储结构,链表通过指针来连接元素与元素;

链表分为:单向链表、双向链表、循环链表

链表是链式的存储机构;数组是顺序的存储机构

链表通过指针来连接元素与元素,数组则是把所有元素按次序依次存储。

总结:

1)数组便于查询和修改,但是不方便新增和删除

2)链表适合新增和删除,但是不适合查询,根据业务情况使用合适的数据结构和算法是在大数据量和高并发时必须要考虑的问题


结构化设计程序的基本原则

自顶向下、逐步细化、模块化、结构化编码


宏定义


#define宏定义与typedef的区别

# define宏定义是字符替换,typedef是定义类型,是声明一种新的类型,等同自带的基本类型。

  • #define是宏,处理的时候位于编译前阶段,宏处理器基本上对你的C/C++程序不会有任何的感知。它只处理宏的语法。而编译阶段的“程序”得到的是宏处理完的结果。
  • typedef是编译阶段的一部分。它的意义是单一的。

宏定义只是简单的字符串代换,是在预处理完成的,而typedef是在编译时处理的,它不是作简单的代换,而是对类型说明符进行重新命令。被命名的标识符具有类型定义说明的功能。


使用宏定义求出最小值

#define MIN(A,B) ( (A) <= (B) ? (A) : (B))

三重条件判断符:第一重条件为真则返回第二个表达式,若为假则返回第三个表达式


const与宏的区别

(1)编译检查 宏不会编译检查,const有编译检查

(2)宏的好处 宏可以定义函数、方法等,const不可以

(3)宏的缺点 大量使用宏,会导致预编译的时间过长


指针

指针也是一个变量,其长度取决于系统地址总线的位数。


一个32位的机器指针一共是多少位

指针位数由机器的地址总线位数决定。STM32的地址总线是32位的,一共有四个字节。


引用与指针的区别

多指代在C++程序中

(1)非空区别:指针可以指向NULL,引用必须指向某个对象,引用必须被初始化

(2)可修改区别:指针可以指向不同的对象,引用总是指向初始化的对象,引用初始化后不可以被改变

(3)合法性区别:在使用指针之前要判断是否为NULL,引用不需要判断


指针和数组的区别

1.数组要么在静态存储区,要么在栈上被创建。数组名对应着一块内存,其容量与地址在生命周期内保持不变。

ret *func(args, ...);
\\func是一个函数,args是形参列表,ret *作为一个整体,是 func函数的返回值,是一个指针的形式

2.指针可以随时指向任意类型的内存块,他的特征是可变。比数组灵活,但也危险。

ret (*p)(args, ...);
\\ret为返回值,*p作为一个整体,代表的是指向该函数的指针,args为形参列表。其中p被称为函数指针变量
===============  举例  =======================
	int max(int a, int b)		//设定一个具体函数
	{
    	return a > b ? a : b;
	}
	int (*p)(int, int);		//初始化指针函数
	p = max;	//函数地址赋值
	int ret = p(10, 15); 	//函数指针的调用
	//int ret = (*max)(10,15);
    //int ret = (*p)(10,15);

指针函数和函数指针的区别

1.指针函数是一个函数,它的返回值是一个指针。

2.函数指针是一个指针,这个指针指向的对象是一个函数。


指针自增自减与变量自增自减有什么区别?

指针自增自减是改变指针所指向的对象,变量自增自减是改变变量的值


内存与寄存器

内存

运行程序的内存分为堆区、栈区、BSS区、数据区(初始化)、代码区。

  1. 栈:系统分配空间:存放临时创建的局部变量,存放函数调用的形参和返回值;

    局部变量申请的内存空间超过栈的剩余空间就会存在栈的溢出,例如栈中申请很长的数组和递归调用很多次的函数就有可能出现;

  2. 堆:由程序员分配释放,若程序员不释放,结束时由OS回收,

    如果程序员在使用完申请后的堆内存却没有及时把它释放掉,那么这块内存就丢失了,就存在内存泄漏;内存的分配不是连续的。

    通常存放程序运行中动态分配的存储空间;(2)使用 malloc 和new在堆中分配内存;

  3. BSS区:通常是指用来存放程序中未初始化的全局变量和静态变量

    静态分配,在程序开始时通常会被清零。

  4. 数据区:通常是指用来存放程序中已初始化的全局变量和静态变量以及字符串常量

    代码区:通常是指用来存放程序执行代码的一块内存区域,这部分区域的大小在程序运行前就已经确定。

数据类型占用内存大小

在64位系统中占字节数

char 1; short int 2 ; int 4; unsigned int 4; float 4; double 8;

long 8; long long 8; unsigned long 8

一个字节固定是8位,n位的计算机字长就是n位,例如64位计算的字长是64位,含8个字节。

程序的局部变量、全局变量、动态申请的数据存放在哪里?

局部变量存放在栈区;全局变量存放在静态区;动态申请的数据存放在堆;


嵌入式通信协议

USART

通用同步/异步串行接收/发送器,USART是一个全双工通用同步/异步串行收发模块 ,区别于UART,UART是通用异步收发传输器,USART属于UART的增强型,USART比UART多了同步传输功能,他可以为通信设备提供主动时钟

UART

UART使用4线(VCC, GND, RX, TX)

UART使用标准的TTL/CMOS逻辑电平(05v、03.3v、02.5v或01.8v)来表示数据,高电平表示1,低电平表示0。为了增强数据的抗干扰能力、提高传输长度,通常将TTL/CMOD逻辑电平转换为RS-232逻辑电平,312v表示0,-3-12v表示1

UART的帧格式包括线路空闲状态(idle,高电平)、起始位(start bit,低电平)、5~8位数据位(data bits)、校验位(parity bit,可选)和停止位(stop bit,位数可为1、1.5、2位)。低位在前,高位在后(LSB——MSB) ,空闲时为高电平,用下降沿来通知接收方准备接收,紧接着的就是数据位根据设置的5/6/7/8位数据来进行传输,数据位结束后就是奇偶校验为,校验位之后就是停止位,停止位是用高电平来标记一个字符的结束。

RS232

RS-232 接口以9个引脚 (DB-9) 或是25个引脚 (DB-25) 的型态出现,大致上与UART相同,RS-232 一般只使用RXD、TXD、GND三条线 。

RS-232串口通信最远距离是50英尺;

RS232可做到双向传输,全双工通讯,最高传输速率20kbps

RS-232上传送的数字量采用负逻辑,且与地对称。逻辑1:-3 ~-15V 逻辑0:+3~+15V

RS-232-C标准规定的数据传输速率为50、75、100、150、300、600、1200、2400、4800、9600、19200、38400波特

RS485

RS-485接口采用差分方式传输信号方式,并不需要相对于某个参照点来检测信号,系统只需检测两线之间的电位差就可以了 ,RS-485 只有2 根信号线时,所以只能工作在半双工模式

RS485有两线制和四线制两种接线,四线制只能实现点对点的通信方式,现很少采用,现在多采用的是两线制接线方式,这种接线方式为总线式拓扑结构,在同一总线上最多可以挂接32个节点。在RS485通信网络中一般采用的是主从通信方式,即一个主机带多个从机。很多情况下,连接RS-485通信链路时只是简单地用一对双绞线将各个接口的“A”、“B”端连接起来。

两线通信缺陷:共模干扰、EMI的问题

逻辑“1”以两线间的电压差+2V+6V表示,逻辑“0”以两线间的电压差-6V-2V表示

RS232是全双工的,RS485是半双工的


IIC

由数据线SDA和时钟SCL构成的串行总线,可发送和接收数据。在CPU与被控IC之间、IC与IC之间进行双向传送。

I2C总线在传送数据过程中共有三种类型信号, 它们分别是:开始信号、结束信号和应答信号。

​ 开始信号:SCL为高电平时,SDA由高电平向低电平跳变,开始传送数据。

​ 结束信号:SCL为高电平时,SDA由低电平向高电平跳变,结束传送数据。

​ 应答信号:接收数据的IC在接收到8bit数据后,向发送数据的IC发出特定的低电平脉冲,表示已收到数据。

CPU向受控单元发出一个信号后,等待受控单元发出一个应答信号,CPU接收到应答信号后,根据实际情况作出是否继续传递信号的判断。若未收到应答信号,由判断为受控单元出现故障。主发>从应答>主处理

这些信号中,起始信号是必需的,结束信号和应答信号,都可以不要。

总线时序

  • 空闲状态:**I2C总线总线的SDA和SCL两条信号线同时处于高电平时,规定为总线的空闲状态。**此时各个器件的输出级场效应管均处在截止状态,即释放总线,由两条信号线各自的上拉电阻把电平拉高。
  • 起始信号:当SCL为高期间,SDA由高到低的跳变;启动信号是一种电平跳变时序信号,而不是一个电平信号。
  • 停止信号:当SCL为高期间,SDA由低到高的跳变;停止信号也是一种电平跳变时序信号,而不是一个电平信号
  • 数据的传送:在I2C总线上传送的每一位数据都有一个时钟脉冲相对应(或同步控制),即在SCL串行时钟的配合下,在SDA上逐位地串行传送每一位数据。数据位的传输是边沿触发。

SPI

SPI接口主要应用在EEPROM,FLASH,实时时钟,AD转换器,还有数字信号处理器和数字信号解码器之间。只占用四根线,SPI接口一般使用4条线通信:

  • MISO 主设备数据输入,从设备数据输出。
  • MOSI 主设备数据输出,从设备数据输入。
  • SCLK时钟信号,由主设备产生。
  • CS从设备片选信号,由主设备控制

主机和从机都有一个串行移位寄存器,主机通过向它的SPI串行寄存器写入一个字节来发起一次传输。寄存器通过MOSI信号线将字节传送给从机,从机也将自己的移位寄存器中的内容通过MISO信号线返回给主机。这样,两个移位寄存器中的内容就被交换。外设的写操作和读操作是同步完成的。如果只进行写操作,主机只需忽略接收到的字节;反之,若主机要读取从机的一个字节,就必须发送一个空字节来引发从机的传输。

SPI 有四种工作模式,通过串行时钟极性(CPOL)和相位(CPHA)的搭配来得到四种工作模式:

①、CPOL=0,串行时钟空闲状态为低电平。

②、CPOL=1,串行时钟空闲状态为高电平,此时可以通过配置时钟相位(CPHA)来选择具

体的传输协议。

③、CPHA=0,串行时钟的第一个跳变沿(上升沿或下降沿)采集数据。

④、CPHA=1,串行时钟的第二个跳变沿(上升沿或下降沿)采集数据。


中断

CPU在正常执行程序的过程中,由于内部/外部事件的触发或程序的预先安排引起CPU暂时中断当前正在运行的程序,而转去执行中断服务子程序,待中断服务子程序执行完毕后,CPU继续执行原来的程序,这一过程称为中断

中断流程

中断处理过程(汇编层面解释)

第一步:保护现场,将当前位置的PC地址压栈;

第二步:跳转到中断服务程序,执行中断服务程序;

第三步:恢复现场,将栈顶的值回送给PC;

第四步:跳转到被中断的位置开始执行下一个指令;

中断处理函数

相对于正常子函数,中断服务函数有以下需要注意的地方:

1.中断服务函数不能传入参数;

2.中断服务函数不能有返回值;

3.中断服务函数应该做到短小精悍;

4.不要在中断函数中使用printf函数,会带来重入和性能问题。


进程与线程

进程与线程的区别

**根本区别:**进程是操作系统资源分配的基本单位,而线程是任务调度和执行的基本单位

**在开销方面:**每个进程都有独立的代码和数据空间(程序上下文),程序之间的切换会有较大的开销;线程可以看做轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器(PC),线程之间切换的开销小。

所处环境:在操作系统中能同时运行多个进程(程序);而在同一个进程(程序)中有多个线程同时执行(通过CPU调度,在每个时间片中只有一个线程执行)

**内存分配方面:**系统在运行的时候会为每个进程分配不同的内存空间;而对线程而言,除了CPU外,系统不会为线程分配内存(线程所使用的资源来自其所属进程的资源),线程组之间只能共享资源。

包含关系:没有线程的进程可以看做是单线程的,如果一个进程内有多个线程,则执行过程不是一条线的,而是多条线(线程)共同完成的;线程是进程的一部分,所以线程也被称为轻权进程或者轻量级进程。

总结一下:进程:1、操作系统会分配地址空间 2、一个进程当中可以有多个线程

线程:1、没有独立的地址空间 2、一个进程里的多个线程可以共享该进程的所有资源 3、线程有自己的栈、堆和局部变量 4、线程是不能独立执行的


同步和互斥的区别

当有多个线程的时候,经常需要去同步这些线程以访问同一个数据或资源。例如,假设有一个程序,其中一个线程用于把文件读到内存,而另一个线程用于统计文件中的字符数。当然,在把整个文件调入内存之前,统计它的计数是没有意义的。但是,由于每个操作都有自己的线程,操作系统会把两个线程当作是互不相干的任务分别执行,这样就可能在没有把整个文件装入内存时统计字数。为解决此问题,你必须使两个线程同步工作。

所谓同步,是指散步在不同进程之间的若干程序片断,它们的运行必须严格按照规定的某种先后次序来运行,这种先后次序依赖于要完成的特定的任务。如果用对资源的访问来定义的话,同步是指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。在大多数情况下,同步已经实现了互斥,特别是所有写入资源的情况必定是互斥的。少数情况是指可以允许多个访问者同时访问资源。

所谓互斥,是指散布在不同进程之间的若干程序片断,当某个进程运行其中一个程序片段时,其它进程就不能运行它们之中的任一程序片段,只能等到该进程运行完这个程序片段后才可以运行。如果用对资源的访问来定义的话,互斥某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的。


进程间通信的方式

(1)管道(pipe)及有名管道(named pipe):管道可用于具有亲缘关系的父子进程间的通信,有名管道除了具有管道所具有的功能外,它还允许无亲缘关系进程间的通信。

(2)信号(signal):信号是在软件层次上对中断机制的一种模拟,它是比较复杂的通信方式,用于通知进程有某事件发生,一个进程收到一个信号与处理器收到一个中断请求效果上可以说是一致的。

(3)消息队列(message queue):消息队列是消息的链接表,它克服了上两种通信方式中信号量有限的缺点,具有写权限得进程可以按照一定得规则向消息队列中添加新信息;对消息队列有读权限得进程则可以从消息队列中读取信息。

(4)共享内存(shared memory):可以说这是最有用的进程间通信方式。它使得多个进程可以访问同一块内存空间,不同进程可以及时看到对方进程中对共享内存中数据得更新。这种方式需要依靠某种同步操作,如互斥锁和信号量等。

(5)信号量(semaphore):主要作为进程之间及同一种进程的不同线程之间得同步和互斥手段。

(6)套接字(socket):这是一种更为一般得进程间通信机制,它可用于网络中不同机器之间的进程间通信,应用非常广泛。


产生死锁的原因

多个并发进程因为争夺系统资源而产生相互等待的现象。

产生死锁的原因:1)系统资源有限;2)进程推进顺序不合理

死锁的4个必要条件

  1. 互斥:某种资源一次只允许一个进程访问,即该资源一旦分配给某个进程,其他进程就不能再访问,直到该进程访问结束。

  2. 占有且等待:一个进程本身占有资源(一种或多种),同时还有资源未得到满足,正在等待其他进程释放该资源。

  3. 不可抢占 :已经被占用的资源不可以被抢占

  4. 循环等待:存在一个进程链,使得每个进程都占有下一个进程所需的至少一种资源。


TCP\UDP

​ TCP/IP协议栈

应用层

  1. 超文本传输协议(HTTP)

  2. 文件传输(TFTP简单文件传输协议)

  3. 远程登录(Telnet)

  4. 网络管理(SNMP简单网络管理协议)

  5. 域名系统(DNS)

网络层

  1. Internet协议(IP)
  2. Internet控制信息协议(ICMP)
  3. 地址解析协议(ARP)
  4. 反向地址解析协议(RARP)

TCP

TCP(Transmission Control Protocol,传输控制协议)是面向连接的协议 ,

UDP

UDP(User Data Protocol,用户数据报协议)UDP是一个非连接的协议,传输数据之前源端和终端不建立连接

猜你喜欢

转载自blog.csdn.net/weixin_47407066/article/details/128629760