嵌入式知识点搜集

//几种接口
UART	串  异步	慢 波特率设置全双工	 2线 Rx、Tx   星树 远
I2C     串  同步 慢 半双工             2线SDA、SCL  总线仲裁机  近
SPI     串  同步 快 全双工             3线或4线SCLK、SIMO、SOMI、SS(片选)  环  远
usb     串  同步 快 半双工          4线Vbus(5V)、GND、D+、D-(3.3V)	星形   近


在这里插入图片描述
linux用户态切换到内核态的方法:
1.系统调用system_call()
软件中断-》产生一个0x80的异常-》中断描述符表IDT-》system_call(系统调用处理函数)-》系统调用表

2.procfs方式
进程文件系统 伪文件系统,为什么说是 伪 文件系统呢?因为它不占用外部存储空间,只是占用少量的内存,通常是挂载在 /proc 目录下

3.sysctl
sysctl 是一个 Linux 命令,它可以在内核运行过程中,动态修改内核参数。

4.sysfs
和 procfs 不同的是,sysfs 是将一些原本在 procfs 中的,关于设备和驱动的部分,独立出来,以 “设备树” 的形式呈现给用户。

5.netlink
使得它可以用于内核与多种用户进程之间的消息传递系统,比如路由子系统,防火墙(Netfilter),ipsec 安全策略等等。

6.ioctl
ioctl:函数是文件结构中的一个属性分量,就是说如果你的驱动程序提供了对ioctl的支持,用户就可以在用户程序中使用ioctl函数控制设备的I/O通道。

用户态和内核态的转换方式:
*系统调用:这是用户进程主动要求切换到内核态的一种方式,用户进程通过系统调用申请操作系统提供的服务程序完成工作。而系统调用的机制其核心还是使用了操作系统为用户特别开放的一个中断来实现,例如Linux的ine 80h中断;
*异常:当CPU在执行运行在用户态的程序时,发现了某些事件不可知的异常,这是会触发由当前运行进程切换到处理此异常的内核相关程序中,也就到了内核态,比如缺页异常;
*外围设备的中断:当外围设备完成用户请求的操作之后,会向CPU发出相应的中断信号,这时CPU会暂停执行下一条将要执行的指令转而去执行中断信号的处理程序,如果先执行的指令是用户态下的程序,那么这个转换的过程自然也就发生了有用户态到内核态的切换。比如硬盘读写操作完成,系统会切换到硬盘读写的中断处理程序中执行后续操作等。

/tmp:这是让一般使用者或者是正在执行的程序暂时放置档案的地方。
/usr:不是user的缩写,其实usr是Unix Software Resource的缩写, 也就是Unix操作系统软件资源所放置的目录,而不是用户的数据啦
/etc:系统主要的设定档几乎都放置在这个目录内

const int a;
int const a;
const int *a;
int * const a;
const int * const a;
int const * const a;
前两个的作用是一样,a是一个常整型数;
第三个意味着a是一个指向常整型数的指针(也就是,整型数是不可修改的,但指针可以);
第四个意思a是一个指向整型 数的常指针(也就是说,指针指向的整型数是可以修改的,但指针是不可修改的);
最后两个意味着a是一个指向常整型数的常指针(也就是说,指针指向的整型数 是不可修改的,同时指针也是不可修改的)。

如果修改,编译直接报错。

在32位系统中,有如下结构体,那么sizeof(fun)的数值是()

#pragma pack(1)
 
struct fun{
	int i;
	double d;
	char c;
};

解答:13。

可能是一般的内存对齐做习惯了,如果本题采用内存对齐的话,结果就是24(int 4 double char 7)。但是#pragma pack(1)让编译器将结构体数据强制按1来对齐。

每个特定平台上的编译器都有自己的默认“对齐系数”(32位机一般为4,64位机一般为8)。我们可以通过预编译命令#pragma pack(k),k=1,2,4,8,16来改变这个系数,其中k就是需要指定的“对齐系数”。

只需牢记:
第一个数据成员放在offset为0的地方,对齐按照对齐系数和自身占用字节数中,二者比较小的那个进行对齐;
在数据成员完成各自对齐以后,struct或者union本身也要进行对齐,对齐将按照对齐系数和struct或者union中最大数据成员长度中比较小的那个进行;
原文:https://blog.csdn.net/qq_38410730/article/details/80951443

那么为什么要内存对其呢?
如果操作1字节的数据,可以是任意地址,如果是操作2字节的数据,如果开始地址在偶数地址,一次就可以取2字节,如果开始地址在奇数,就要2次内存操作才能完成;如果操作4字节的数据,最好开始地址在能被4整除的数值上,这样可以用一条32位的内存操作指令完成。同样,8字节的开始位置最好的能被8整除的数值上,这样可以用一条64位的内存操作指令完成。
比如32位机,32根地址线,32根数据线,取数时,CPU的32根据地址线与内存的0-3号地址对齐,CPU的32位的数据线也同样,一个读取周期只能取这0-3地址的3个字节。如果你是取3-4地址的数据,CPU会自动把它分解成2次取数据操作,一次取8位的3单元和一次取84单元数据。
只有开始地址是048...32位的数据操作才能一次操作完成,内存不支持从1号单元开始的4字节读,CPU和内存的数据线必须相应数据线对齐才行。
归纳:
1.平台移植的问题。某些平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。 
2.性能的问题。对其的一次就可以读完

 /*int 字节*/
    printf("%d\n",sizeof(int)); //4 
    printf("%d\n",sizeof(__int8)); //1
    printf("%d\n",sizeof(__int16)); //2
    printf("%d\n",sizeof(__int32)); //4
    printf("%d\n",sizeof(__int64)); //8
    /*char 字节*/
    printf("%d\n",sizeof(char)); //1
    /*double 字节*/
    printf("%d\n",sizeof(double)); //8
    /*long 字节*/
    printf("%d\n",sizeof(long)); //4
    printf("%d\n",sizeof(long int)); //4
    printf("%d\n",sizeof(long long)); //8
    /*short 字节*/
    printf("%d\n",sizeof(short)); //2 
    printf("%d\n",sizeof(short int)); //2
    /*unsigned 字节*/
    printf("%d\n",sizeof(unsigned)); //4
    printf("%d\n",sizeof(unsigned int)); //4
    /*signed 字节*/
    printf("%d\n",sizeof(signed)); //4
    printf("%d\n",sizeof(signed int)); //4
    /*const 字节*/
    printf("%d\n",sizeof(const)); //4

Big-endian大端模式 低对高 高对低
Little-endian小端模式 低对低 高对高

栈:由编译器自动分配释放,存放函数的参数值,局部变量等值;
静态存储区:一定会存在且不会消失,这样的数据包括常量、常变量(const 变量)、静态变量、全局变量等;
常量存储区:常量占用内存,只读状态,决不可修改,常量字符串就是放在这里的。
BSS段:保存定义但是未赋值的变量。

volatile应该是在编译阶段,extern在链接阶段起到作用

进程间,文件描述符是独立的

实时操作系统是保证在一定时间限制内完成特定功能的操作系统。实时操作系统有硬实时和软实时之分,硬实时要求在规定的时间内必须完成操作,这是在操作系统设计时保证的;软实时则只要按照任务的优先级,尽可能快地完成操作即可。

实时性最主要的含义是:任务的最迟完成时间是可确认预知的。
比较项目				非实时系统			实时系统
交互能力				较强     			较弱
响应时间				秒集	        		毫秒、微秒级
可靠性				一般	        		较高
进程完成的截止期限		没有	 	    		有
进程切换的要求		一般					快
内核					非可剥夺(体现公平)	可剥夺(体现优先级别)

	内存屏障,也称内存栅栏,内存栅障,屏障指令等, 是一类同步屏障指令,是CPU或编译器在对内存随机访问的操作中的一个同步点,使得此点之前的所有读写操作都执行后才可以开始执行此点之后的操作。
  大多数现代计算机为了提高性能而采取乱序执行,这使得内存屏障成为必须。
  语义上,内存屏障之前的所有写操作都要写入内存;内存屏障之后的读操作都可以获得同步屏障之前的写操作的结果。因此,对于敏感的程序块,写操作之后、读操作之前可以插入内存屏障。
  屏障的分类
  1.编译器引起的内存屏障  volatile
  2.缓存引起的内存屏障
  3.乱序执行引起的内存屏障

//内存顺序
	内存顺序描述了计算机 CPU 获取内存的顺序,内存的排序既可能发生在编译器编译期间,也可能发生在 CPU 指令执行期间。
	为了尽可能地提高计算机资源利用率和性能,编译器会对代码进行重新排序, CPU 会对指令进行重新排序、延缓执行、各种缓存等等,以达到更好的执行效果。当然,任何排序都不能违背代码本身所表达的意义,并且在单线程情况下,通常不会有任何问题。
	但是在多线程环境下,比如无锁(lock-free)数据结构的设计中,指令的乱序执行会造成无法预测的行为。所以我们通常引入内存栅栏(Memory Barrier)这一概念来解决可能存在的并发问题。

//指令序列
你有一台很简单的电脑,它只有两个寄存器X和Y。每个寄存器可以存放一个整数。电脑只有两条指令: 
指令[X]: X=X+Y 
指令[Y]: Y=X+Y 
刚开始的时候两个寄存器里都是1,经过经过一系列的指令,两个寄存器里的内容会发生变化.比如指令序列XXY的执行过程是: 
1 1 -> 2 1 -> 3 1 -> 3 4 
现在给你一个数R,让你输出最短的指令序列使得X寄存器里数是R.如果存在多个最短指令序列,那么输出字典序最小的一个.
Sample Input:
3
10
Sample Output:
XX
XXYYX

//ABA问题
什么是ABA问题?(掉包,替换)
ABA问题的根本在于cas在修改变量的时候,无法记录变量的状态,比如修改的次数,否修改过这个变量。这样就很容易在一个线程将A修改成B时,另一个线程又会把B修改成A,造成casd多次执行的问题。
T1 线程从共享的内存地址读取值 A;
T1 线程被抢占,线程 T2 开始运行;
T2 线程将共享的内存地址中的值由 A 改成 B,然后又改成 A;
T1 线程继续执行,读取共享的内存地址中的值 A,认为没有改变,然后继续执行

//无锁编程Lock-Free 非阻塞同步
有锁编程
	在多线程编程中只要需要共享某些数据,就应当将对它的访问串行化。比如像++count(count是整型变量)这样的简单操作也得加锁,因为即便是增量操作这样的操作,,实际上也是分三步进行的:读、改、写(回)。
	执行的过程中,这两条指令之间也是可以被打断的,而不是一条原子操作。(也就是所谓的写撕裂)
	所以修改共享数据的操作必须以原子操作的形式出现,这样才能保证没有其它线程能在中途插一脚来破坏相应数据。而在使用锁机制的过程中,即便在锁的粒度(granularity),负载(overhead),竞争(contention),死锁(deadlock)等需要重点控制的方面解决的很好,也无法彻底避免这种机制的如下一些缺点:
其次是避免锁的使用引起的错误和问题。
1.死锁(dead lock):两个以上线程互相等待
2.锁护送(lock convoy):多个同优先级的线程反复竞争同一个锁,抢占锁失败后强制上下文切换,引起性能下降
3.优先级反转(priority inversion):低优先级线程拥有锁时被中优先级的线程抢占,而高优先级的线程因为申请不到锁被阻塞

无锁编程(Lock-Free)就是在某些应用场景和领域下解决以上基于锁机制的并发编程的一种方案。
1.原子操作
2.内存栅栏(memory barriers), 
3.内存顺序冲突(memory order),
4.指令序列一致性(sequential consistency)和顺ABA现象等等。

在现代的 CPU 处理器上,很多操作已经被设计为原子的,比如对齐读(Aligned Read)和对齐写(Aligned Write)等。Read-Modify-Write(RMW)操作的设计让执行更复杂的事务操作变成了原子操作,当有多个写入者想对相同的内存进行修改时,保证一次只执行一个操作。
RMW 操作在不同的 CPU 家族中是通过不同的方式来支持的:
1.x86/64 和 Itanium 架构通过 Compare-And-Swap (CAS) 方式来实现
2.PowerPC、MIPS 和 ARM 架构通过 Load-Link/Store-Conditional (LL/SC) 方式来实现

无锁编程具体使用和考虑到的技术方法包括:原子操作(atomic operations), 内存栅栏(memory barriers), 内存顺序冲突(memory order), 指令序列一致性(sequential consistency)和顺ABA现象等等。
在这其中最基础最重要的是操作的原子性或说原子操作。原子操作可以理解为在执行完毕之前不会被任何其它任务或事件中断的一系列操作。原子操作是非阻塞编程最核心基本的部分,没有原子操作的话,操作会因为中断异常等各种原因引起数据状态的不一致从而影响到程序的正确。
对于原子操作的实现机制,在硬件层面上CPU处理器会默认保证基本的内存操作的原子性,CPU保证从系统内存当中读取或者写入一个字节的行为肯定是原子的,当一个处理器读取一个字节时,其他CPU处理器不能访问这个字节的内存地址。但是对于复杂的内存操作CPU处理器不能自动保证其原子性,比如跨总线宽度或者跨多个缓存行(Cache Line),跨页表的访问等。这个时候就需要用到CPU指令集中设计的原子操作指令,现在大部分CPU指令集都会支持一系列的原子操作。
而在无锁编程中经常用到的原子操作是Read-Modify-Write  (RMW)这种类型的,这其中最常用的原子操作又是 COMPARE AND SWAP(CAS),几乎所有的CPU指令集都支持CAS的原子操作,比如X86平台下中的是 CMPXCHG(Compare Are Exchange)。
继续说一下CAS,CAS操作行为是比较某个内存地址处的内容是否和期望值一致,如果一致则将该地址处的数值替换为一个新值。CAS操作具体的实现原理主要是两种方式:总线锁定和缓存锁定。所谓总线锁定,就是CPU执行某条指令的时候先锁住数据总线的, 使用同一条数据总线的CPU就无法访问内存了,在指令执行完成后再释放锁住的数据总线。锁住数据总线的方式系统开销很大,限制了访问内存的效率,所以又有了基于CPU缓存一致性来保持操作原子性作的方法作为补充,简单来说就是用CPU的缓存一致性的机制来防止内存区域的数据被两个以上的处理器修改。
最后这里随便说一下CAS操作的ABA的问题,所谓的ABA的问题简要的说就是,线程a先读取了要对比的值v后,被线程b抢占了,线程b对v进行了修改后又改会v原来的值,线程1继续运行执行CAS操作的时候,无法判断出v的值被改过又改回来。
解决ABA的问题的一种方法是,一次用CAS检查双倍长度的值,前半部是指针,后半部分是一个计数器;或者对CAS的数值加上版本号。 

中断向量:中断服务程序的入口地址;
中断向量表:把系统中所有的中断类型码及其对应的中断向量按一定的规律存放在一个区域内,这个存储区域就叫做中断向量表;
中断源:软中断/内中断、外中断/硬件中断、异常等。
请求中断→中断响应→保护现场→中断服务→恢复现场→中断返回。

虚拟内存管理(Virtual Memory Management) 机制,这需要MMU
MMU就是负责虚拟地址(virtual address)转化成物理地址(physical address)
CPU看到的用到的只是VA,CPU不管VA最终是怎样到PA的;
而cache、MMU也是看不到VA的,它们使用的是MVA(VA到MVA的转换是由硬件自动完成的);
实际设备看不到VA、MVA(转变后的虚拟地址),读写设备使用的是PA物理地址。

I2c通信协议

在这里插入图片描述
;在总线空闲状态时,这两根线一般被上面所接的上拉电阻拉高,保持着高电平。
I2C通信方式为半双工,只有一根SDA线,同一时间只可以单向通信,485也为半双工,SPI和uart为双工。

  1. I2C总线特征
    I2C总线上的每一个设备都可以作为主设备或者从设备,而且每一个设备都会对应一个唯一的地址(地址通过物理接地或者拉高,可以从I2C器件的数据手册得知,如TVP5158芯片,7位地址依次bit6~bit0:x101 1xxx, 最低三位可配,如果全部物理接地,则该设备地址为0x58, 而之所以7bit因为1个bit要代表方向,主向从和从向主),主从设备之间就通过这个地址来确定与哪个器件进行通信,在通常的应用中,我们把CPU带I2C总线接口的模块作为主设备,把挂接在总线上的其他设备都作为从设备。
    I2C总线上可挂接的设备数量受总线的最大电容400pF 限制,如果所挂接的是相同型号的器件,则还受器件地址位的限制。
    I2C总线数据传输速率在标准模式下可达100kbit/s,快速模式下可达400kbit/s,高速模式下可达3.4Mbit/s。一般通过I2C总线接口可编程时钟来实现传输速率的调整,同时也跟所接的上拉电阻的阻值有关。

2 I2C总线协议
I2C协议规定,总线上数据的传输必须以一个起始信号作为开始条件,以一个结束信号作为传输的停止条件。起始和结束信号总是由主设备产生(意味着从设备不可以主动通信?所有的通信都是主设备发起的,主可以发出询问的command,然后等待从设备的通信)。
起始和结束信号产生条件:总线在空闲状态时,SCL和SDA都保持着高电平,当SCL为高电平而SDA由高到低的跳变,表示产生一个起始条件;当SCL为高而SDA由低到高的跳变,表示产生一个停止条件。
在起始条件产生后,总线处于忙状态,由本次数据传输的主从设备独占,其他I2C器件无法访问总线;而在停止条件产生后,本次数据传输的主从设备将释放总线,总线再次处于空闲状态。起始和结束如图所示:
在这里插入图片描述
在了解起始条件和停止条件后,我们再来看看在这个过程中数据的传输是如何进行的。前面我们已经提到过,数据传输以字节为单位。主设备在SCL线上产生每个时钟脉冲的过程中将在SDA线上传输一个数据位,当一个字节按数据位从高位到低位的顺序传输完后,紧接着从设备将拉低SDA线,回传给主设备一个应答位, 此时才认为一个字节真正的被传输完成。当然,并不是所有的字节传输都必须有一个应答位,比如:当从设备不能再接收主设备发送的数据时,从设备将回传一个否 定应答位。数据传输的过程如图所示:
在这里插入图片描述
在前面我们还提到过,I2C总线上的每一个设备都对应一个唯一的地址,主从设备之间的数据传输是建立在地址的基础上,也就是说,主设备在传输有效数据之前要先指定从设备的地址,地址指定的过程和上面数据传输的过程一样,只不过大多数从设备的地址是7位的,然后协议规定再给地址添加一个最低位用来表示接下来数据传输的方向,0表示主设备向从设备写数据,1表示主设备向从设备读数据。向指定设备发送数据的格式如图所示:(每一最小包数据由9bit组成,8bit内容+1bit ACK, 如果是地址数据,则8bit包含1bit方向)
在这里插入图片描述
在这里插入图片描述
4 I2C总线操作
对I2C总线的操作实际就是主从设备之间的读写操作。大致可分为以下三种操作情况:
主设备往从设备中写数据。数据传输格式如下:
在这里插入图片描述
主设备从从设备中读数据。数据传输格式如下:
在这里插入图片描述
主设备往从设备中写数据,然后重启起始条件,紧接着从从设备中读取数据;或者是主设备从从设备中读数据,然后重启起始条件,紧接着主设备往从设备中写数据。数据传输格式如下:
在这里插入图片描述
第三种操作在单个主设备系统中,重复的开启起始条件机制要比用STOP终止传输后又再次开启总线更有效率。

时钟同步和仲裁
如果两个master都想在同一条空闲总线上传输,此时必须能够使用某种机制来选择将总线控制权交给哪个master,这是通过时钟同步和仲裁来完成的,而被迫让出控制权的master则需要等待总线空闲后再继续传输。在单一master的系统上无需实现时钟同步和仲裁。

总线仲裁
总线仲裁和时钟同步类似,当所有master在SDA上都写1时,SDA的数据才是1,只要有一个master写0,那此时SDA上的数据就是0。一个master每发送一个bit数据,在SCL处于高电平时,就检查看SDA的电平是否和发送的数据一致,如果不一致,这个master便知道自己输掉仲裁,然后停止向SDA写数据。也就是说,如果master一直检查到总线上数据和自己发送的数据一致,则继续传输,这样在仲裁过程中就保证了赢得仲裁的master不会丢失数据。
输掉仲裁的master在检测到自己输了之后也不再产生时钟脉冲,并且要在总线空闲时才能重新传输。

SPI通信

是一种高速的,全双工,同步的通信总线。
缺点
没有指定的流控制,没有应答机制确认是否接收到数据,所以跟IIC总线协议比较在数据 可靠性上有一定的缺陷。
(1)SDO/MOSI – 主设备数据输出,从设备数据输入;
(2)SDI/MISO – 主设备数据输入,从设备数据输出;
(3)SCLK – 时钟信号,由主设备产生;
(4)CS/SS – 从设备使能信号,由主设备控制。当有多个从设备的时候,因为每个从设
备上都有一个片选引脚接入到主设备机中,当我们的主设备和某个从设备通信时将需
要将从设备对应的片选引脚电平拉低或者是拉高。
在这里插入图片描述
这样传输的特点:这样的传输方式有一个优点,与普通的串行通讯不同,普通的串行通讯一次连续传送至少8位数据,而SPI允许数据一位一位的传送,甚至允许暂停,因为SCK时钟线由主控设备控制,当没有时钟跳变时,从设备不采集或传送数据,也就是说,主设备通过对SCK时钟线的控制可以完成对通讯的控制。SPI还是一个数据交换协议:因为SPI的数据输入和输出线独立,所以允许同时完成数据的输入和输出。不同的SPI设备的实现方式不尽相同,主要是数据改变和采集的时间不同,在时钟信号上沿或下沿采集有不同定义,具体请参考相关器件的文档。
在点对点的通信中,SPI接口不需要进行寻址操作,且为全双工通信,显得简单高效。在多个从设备的系统中,每个从设备需要独立的使能信号,硬件上比I2C系统要稍微复杂一些。
最后,SPI接口的一个缺点:没有指定的流控制,没有应答机制确认是否接收到数据。
在这里插入图片描述
在主设备这边配置SPI接口时钟的时候一定要弄清楚从设备的时钟要求,因为主设备这边的时钟极性和相位都是以从设备为基准的。因此在时钟极性的配置上一定要搞清楚从设备是在时钟的上升沿还是下降沿接收数据,是在时钟的下降沿还是上升沿输出数据。但要注意的是,由于主设备的SDO连接从设备的SDI,从设备的SDO连接主设备的SDI,从设备SDI接收的数据是主设备的SDO发送过来的,主设备SDI接收的数据是从设备SDO发送过来的,所以主设备这边SPI时钟极性的配置(即SDO的配置)跟从设备的SDI接收数据的极性是相反的,跟从设备SDO发送数据的极性是相同的。下面这段话是Sychip Wlan8100 Module Spec上说的,充分说明了时钟极性是如何配置的:

The 81xx module will always input data bits at the rising edge of the clock, and the host will always output data bits on the falling edge of the clock.

意思是:主设备在时钟的下降沿发送数据,从设备在时钟的上升沿接收数据。因此主设备这边SPI时钟极性应该配置为下降沿有效。

又如,下面这段话是摘自LCD Driver IC SSD1289:

SDI is shifted into 8-bit shift register on every rising edge of SCK in the order of data bit 7, data bit 6 …… data bit 0.

意思是:从设备SSD1289在时钟的上升沿接收数据,而且是按照从高位到地位的顺序接收数据的。因此主设备的SPI时钟极性同样应该配置为下降沿有效。
clk低电平代表空闲状态

SPI有四种工作模式,取决于两个参数:
(这两个参数其实就是控制了CLK这一根线,SPI通信不像UART或IIC那样有专门的通信周期,有专门的通信起始信号和结束信号。所以SPI协议能够通过控制时钟信号线在没有数据交流的时候保持的状态,要么是高电平,要么是低电平)
1、 CPOL,clock polarity,译作时钟极性。
2、 CPHA,clock phase,译作时钟相位。
CPOL具体说明:
CPOL用于定义时钟信号在空闲状态下处于高电平还是低电平,为1代表高电平,0为低电平。
CPHA具体说明:
首先,在同步接口中,肯定存在一个接口时钟,用来同步采样接口上数据的。
CPHA就是用来定义数据采样在第几个边沿的,数据的采样时刻。为1代表第二个边沿采样,为0代表第一个边沿采样。
以上两个参数,总共有四种组合:
CPOL=0,CPHA=0:此时空闲态时,SCLK处于低电平,数据采样是在第1个边沿,也就是
SCLK由低电平到高电平的跳变,所以数据采样是在上升沿,数据发送是在下降沿。
CPOL=0,CPHA=1:此时空闲态时,SCLK处于低电平,数据发送是在第1个边沿,也就是
SCLK由低电平到高电平的跳变,所以数据采样是在下降沿,数据发送是在上升沿。
CPOL=1,CPHA=0:此时空闲态时,SCLK处于高电平,数据采集是在第1个边沿,也就是
SCLK由高电平到低电平的跳变,所以数据采集是在下降沿,数据发送是在上升沿。
CPOL=1,CPHA=1:此时空闲态时,SCLK处于高电平,数据发送是在第1个边沿,也就是
SCLK由高电平到低电平的跳变,所以数据采集是在上升沿,数据发送是在下降沿。

MSB最高有效位
在这里插入图片描述在这里插入图片描述
在这里插入图片描述
进程间通信方式:
有名管道:看见这个名字就能知道个大概了,它于管道的不同的是它有名字了。这就不同与管道只能在具有亲缘关系的进程间通信了。它提供了一个路径名与之关联,有了自己的传输格式。有名管道和管道的不同之处还有一点是,有名管道是个设备文件,存储在文件系统中,没有亲缘关系的进程也可以访问,但是它要按照先进先出的原则读取数据。同样也是单双工的。
管道
特点:
它是半双工的(即数据只能在一个方向上流动),具有固定的读端和写端。
它只能用于具有亲缘关系的进程之间的通信(也是父子进程或者兄弟进程之间)。
它可以看成是一种特殊的文件,对于它的读写也可以使用普通的read、write 等函数。但是它不是普通的文件,并不属于其他任何文件系统,并且只存在于内存中。
1 #include <unistd.h>
2 int pipe(int fd[2]); // 返回值:若成功返回0,失败返回-1
当一个管道建立时,它会创建两个文件描述符:fd[0]为读而打开,fd[1]为写而打开。如下图:
在这里插入图片描述
要关闭管道只需将这两个文件描述符关闭即可。
单个进程中的管道几乎没有任何用处。所以,通常调用 pipe 的进程接着调用 fork,这样就创建了父进程与子进程之间的 IPC 通道。如下图所示:

FIFO
FIFO,也称为命名管道,它是一种文件类型。
1、特点
FIFO可以在无关的进程之间交换数据,与无名管道不同。
FIFO有路径名与之相关联,它以一种特殊设备文件形式存在于文件系统中。
1 #include <sys/stat.h>
3 int mkfifo(const char *pathname, mode_t mode);
其中的 mode 参数与open函数中的 mode 相同。一旦创建了一个 FIFO,就可以用一般的文件I/O函数操作它。
当 open 一个FIFO时,是否设置非阻塞标志(O_NONBLOCK)的区别:
若没有指定O_NONBLOCK(默认),只读 open 要阻塞到某个其他进程为写而打开此 FIFO。类似的,只写 open 要阻塞到某个其他进程为读而打开它。
若指定了O_NONBLOCK,则只读 open 立即返回。而只写 open 将出错返回 -1 如果没有进程已经为读而打开该 FIFO,其errno置ENXIO。
FIFO的通信方式类似于在进程中使用文件来传输数据,只不过FIFO类型文件同时具有管道的特性。在数据读出时,FIFO管道中同时清除数据,并且“先进先出”。下面的例子演示了使用 FIFO 进行 IPC 的过程:

消息队列
消息队列,是消息的链接表,存放在内核中。一个消息队列由一个标识符(即队列ID)来标识。
特点
消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级。
消息队列独立于发送与接收进程。进程终止时,消息队列及其内容并不会被删除。
消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取。

信号量
信号量(semaphore)与已经介绍过的 IPC 结构不同,它是一个计数器。信号量用于实现进程间的互斥与同步,而不是用于存储进程间通信数据。
特点
信号量用于进程间同步,若要在进程间传递数据需要结合共享内存。
信号量基于操作系统的 PV 操作,程序对信号量的操作都是原子操作。
每次对信号量的 PV 操作不仅限于对信号量值加 1 或减 1,而且可以加减任意正整数。
支持信号量组。
最简单的信号量是只能取 0 和 1 的变量,这也是信号量最常见的一种形式,叫做二值信号量(Binary Semaphore)。而可以取多个正整数的信号量被称为通用信号量。
Linux 下的信号量函数都是在通用的信号量数组上进行操作,而不是在一个单一的二值信号量上进行操作。

共享内存
共享内存(Shared Memory),指两个或多个进程共享一个给定的存储区。
1、特点
共享内存是最快的一种 IPC,因为进程是直接对内存进行存取。
因为多个进程可以同时操作,所以需要进行同步。
信号量+共享内存通常结合在一起使用,信号量用来同步对共享内存的访问。

套接字( socket ) :
套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同机器间的进程通信。

信号:信号是在软件层次上对中断机制的一种模拟,在原理上,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的。信号是异步的,一个进程不必通过任何操作来等待信号的到达,事实上,进程也不知道信号到底什么时候到达。信号是进程间通信机制中唯一的异步通信机制,可以看作是异步通知,通知接收信号的进程有哪些事情发生了。信号机制经过POSIX实时扩展后,功能更加强大,除了基本通知功能外,还可以传递附加信息。信号事件的发生有两个来源:硬件来源(比如我们按下了键盘或者其它硬件故障);软件来源。信号分为可靠信号和不可靠信号,实时信号和非实时信号。进程有三种方式响应信号1.忽略信号2.捕捉信号3.执行缺省操作。

自旋锁:
何谓自旋锁?它是为实现保护共享资源而提出一种锁机制。其实,自旋锁与互斥锁比较类似,它们都是为了解决对某项资源的互斥使用。无论是互斥锁,还是自旋锁,在任何时刻,最多只能有一个保持者,也就说,在任何时刻最多只能有一个执行单元获得锁。但是两者在调度机制上略有不同。对于互斥锁,如果资源已经被占用,资源申请者只能进入睡眠状态。但是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,"自旋"一词就是因此而得名。

基址寻址方式中,操作数的有效地址是基址寄存器内容加上形式地址(位移量)

512mb内存的机器,将4G的文件数字重新排大小。
bitmap问题

线程可以被杀死吗
可以,但是不推荐这么做,会导致锁无法被解锁,造成其他线程被死锁。

进程和线程的关系,区别。
线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),堆是共享的,也就是说,(全局变量,堆,静态变量是共享的)但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。
线程与进程的区别归纳:
线程和进程的区别在于,子进程和父进程有不同的代码和数据空间,而多个线程则共享数据空间,每个线程有自己的执行堆栈和程序计数器为其执行上下文。多线程主要是为了节约CPU时间,发挥利用,根据具体情况而定。线程的运行中需要使用计算机的内存资源和CPU。
a.地址空间和其它资源:进程间相互独立,同一进程的各线程间共享。某进程内的线程在其它进程不可见。
b.通信:进程间通信IPC,线程间可以直接读写进程数据段(如全局变量)来进行通信——需要进程同步和互斥手段的辅助,以保证数据的一致性。
c.调度和切换:线程上下文切换比进程上下文切换要快得多。
d.在多线程OS中,进程不是一个可执行的实体。

Linux进程间的通信方式:管道、有名管道、信号量、消息队列、共享内存、信号、socket
Windows进程间的通信方式:管道、信号量、消息队列、共享内存、socket
Linxu线程间的通信方式:互斥量、条件变量、信号量、信号
Windows线程间的通信方式:互斥量、信号量、事件(Event)、临界区(Critical Section)

linux文件操作
在Linux下使用文件描述符来表示设备文件和普通文件。文件描述符是一个整型的数据,所有对文件的操作都通过文件描述符实现。文件描述符的范围是0~OPEN_MAX,系统中有3个已经分配的文件描述符,即标准输入、标准输出、和标准错误,他们的文件描述符的值分别为0、1、2。

打开文件 头文件:<fcntl.h>
int open(const char *pathname, int flags, mode_t mode);
open()函数根据用户设置的标志flags和模式mode在路径pathname下建立或者打开一个文件。当函数成功时,返回一个整型的文件描述符,出错时返回-1.

关闭文件close()函数 头文件: <unistd.h>
#include<unistd.h>
int close(int fd);
close()函数关闭一个文件描述符,关闭以后此文件描述符不再指向任何文件,从而描述符可以再次使用。当函数执行成功的时候返回0,如果有错误发生,返回-1.
如果每次打开文件不关闭,则会将系统的文件描述符耗尽,导致不能再打开文件:

读取文件read()函数
头文件: <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
read()函数从文件描述符fd对应的文件中读取count字节,放到buf开始的缓冲区。如果count的值为0,read()函数返回0,不进行其他操作;读取成功时,文件对应的读取位置指针向后移动,移动的大小为读取的字节数。
如果read()函数读取成功,返回读取的字节数;当返回值为-1的时候,读取函数有错误发生;如果已经达到文件的末尾,返回0.

写文件write()函数 头文件:<unistd.h> 
ssize_t write(int fd, const void *buf, size_t count);
与read()函数的含义类似,write()函数向文件描述符fd写入数据,数据的大小有count指定,buf为要写入数据的指针。写入成功返回写入数据的字节数,写入出错则返回-1。当操作的对象是普通文件时,写文件的位置从文件的当前开始,如果在打开文件时指定了O_APPEND项,每次写操作时,会将写操作的位置移到文件的结尾处。

文件偏移lseek()函数 头文件:<unistd.h>
文件偏移量指的是当前文件操作位置相对于文件开始位置的偏移。当打开一个文件时,如果没有指定O_APPEND参数,文件的偏移量为0。如果指定了O_APPEND选项,文件的偏移量与文件的长度相等,即文件的当前操作位置移到了末尾。
off_t lseek( int fildes, off_t offset, int whence)
函数对文件描述符fildes所代表的文件,按照操作模式whence和偏移量的大小off_t,重新设定文件偏移量。如果lseek()函数操作成功,则返回新的文件偏移量的值;如果失败返回-1。由于文件的偏移量可以为负值,判断lseek()是否操作成功时,不要使用小于0的判断,要使用是否等于-1来判断。参数offet和whence搭配使用,具体含义如下:
  whence值为SEEK_SET时,offset为相对文件开始处的值;
  whence值为SEEK_CUR时,offset为相对当前位置的值;
  whence值为SEEK_END时,offset为相对文件结尾的值;
 
建立内存映射函数mmap()
mmap()函数将普通文件映射到内存中,普通文件被映射到进程地址空间后,进程可以像访问普通内存一样对文件进行访问,不必再调用read(),write()等操作。 mmap()系统调用使得进程之间通过映射同一个普通文件实现共享内存。
mmap()映射后,让用户程序直接访问设备内存,相比较在用户控件和内核空间互相拷贝数据,效率更高。在要求高性能的应用中比较常用。mmap映射内存必须是页面大小的整数倍,面向流的设备不能进行mmap,mmap的实现和硬件有关。

设备文件输入输出控制ioctl()函数
ioctl()函数通过对文件描述符的发送命令来控制设备。
头文件:<sys/ioctl.h>
int ioctl(int d, int rquest, …);
ioctl()函数通过对文件描述符发送特定的命令来控制文件描述符所代表的设备。参数d时一个已经打开的设备。通常情况下ioctl()函数出错会返回-1,成功返回0或者大于1的值,取决于对应设备的驱动程序对命令的处理。
使用ioctl()像其他的系统调用一样:打开文件,发送命令,查询结果。ioctl()函数像一个杂货铺,对设备的控制通常都通过这个函数来执行。具体对设备的操作方式取决于设备驱动程序的编写。

SMP的全称是"对称多处理"(Symmetrical Multi-Processing)技术,是指在一个计算机上汇集了一组处理器(多CPU),各CPU之间共享内存子系统以及总线结构。

linux下五种IO模型:
阻塞IO模型
非阻塞IO模型
IO复用模型
信号驱动IO
异步IO模型

异步IO
异步IO的概念和同步IO相对。当一个异步过程调用发出后,调用者不能立刻得到结果。实际处理这个调用的部件在完成后,通过状态、通知和回调来通知调用者。在一个CPU密集型的应用中,有一些需要处理的数据可能放在磁盘上。预先知道这些数 据的位置,所以预先发起异步IO读请求。等到真正需要用到这些数据的时候,再等待异步IO完成。使用了异步IO,在发起IO请求到实际使用数据这段时间 内,程序还可以继续做其他事情。
同步IO

  1. 同步,就是我调用一个功能,该功能没有结束前,我死等结果。
  2. 异步,就是我调用一个功能,不需要知道该功能结果,该功能有结果后通知我(回调通知)
  3. 阻塞, 就是调用我(函数),我(函数)没有接收完数据或者没有得到结果之前,我不会返回。
  4. 非阻塞, 就是调用我(函数),我(函数)立即返回,通过select通知调用者

同步IO和异步IO的区别就在于:数据拷贝的时候进程是否阻塞!
阻塞IO和非阻塞IO的区别就在于:应用程序的调用是否立即返回!

内存溢出:(Out Of Memory—OOM)
系统已经不能再分配出你所需要的空间,比如你需要100M的空间,系统只剩90M了,这就叫内存溢出
内存泄漏: (Memory Leak)----》强引用所指向的对象不会被回收,可能导致内存泄漏,虚拟机宁愿抛出OOM也不会去回收他指向的对象
意思就是你用资源的时候为他开辟了一段空间,当你用完时忘记释放资源了,这时内存还被占用着,一次没关系,但是内存泄漏次数多了就会导致内存溢出

内存分配方式有三种:
(1) 从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static变量。
(2) 在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
(3) 从堆上分配,亦称动态内存分配。程序在运行的时候用malloc或new申请任意多少的内存,程序员自己负责在何时用free或delete释放内存。动态内存的生存期由我们决定,使用非常灵活,但问题也最多。

可重入内核 & 可重入函数
可重入内核在ULK(深入理解linux内核)中的定义是指若干个进程可以同时在内核态下执行,也就是说多个进程可以在内核态下并发执行内核代码。在单处理器上,只能实现
微观上的串行,宏观上的并行,即任意时刻,只有一个进程真正执行,其他进程处于阻塞或者等待状态。这里的可重入,是指可以多个进程进入内核,并不是重复/重新进入内核
对于linux来说,可重入内核代码包含可重入函数和非可重入函数。
可重入函数是指运行时只改变局部数据结构,不改变全局数据结构;
不可重入函数是指运行该函数时也需要改变全局数据结构。
如果有多个进程进入不可重入函数时,需要相应的锁机制(互斥锁,自旋锁)来保证同一时刻只有一个进程改变涉及到的全局数据。
可重入函数的理解其实比较麻烦,可以从以下阐述:
1.可重入是与多线程无关的,一个函数被同一个线程调用2次以上,得到的结果具有可再现性。则这个函数是可重入的。
2.可重入讲究的是结果可再现性,因此,使用全局(静态)变量的函数,再次调用其函数的结果是不可再现的,这就是前面说的为何要求该函数只修改局部变量
故可重入函数,描述的是函数被多次调用但是结果具有可再现性
可重入函数条件:
1,不在函数内部使用静态或者全局数据
2,不返回静态或者全局数据,所有的数据都由函数调用者提供
3,使用本地数据,或者通过制作全局数据的本地拷贝来保护全局数据
4, 如果必须访问全局数据,使用互斥锁(自旋锁)来保护
5,不调用不可重入函数
6,可重入函数必须是线程安全的
补充一点,可重入函数必定是线程安全的,但是线程安全的,不一定是可重入的。不可重入函数,函数调用结果不具有可再现性,可以通过互斥锁等机制,使之能安全的同时被多个线程调用,那么,这个不可重入函数就是转换成了线程安全。
可重入函数主要用于多任务环境中,一个可重入的函数简单来说就是可以被中断的函数,也就是说,可以在这个函数执行的任何时刻中断它,转入OS调度下去执行另外一段代码,而返回控制时不会出现什么错误;而不可重入的函数由于使用了一些系统资源,比如全局变量区,中断向量表等,所以它如果被中断的话,可能会出现问题,这类函数是不能运行在多任务环境下的。
可重入函数也可以这样理解,重入即表示重复进入,首先它意味着这个函数可以被中断,其次意味着它除了使用自己栈上的变量以外不依赖于任何环境(包括 static),这样的函数就是purecode(纯代码)可重入,可以允许有该函数的多个副本在运行,由于它们使用的是分离的栈,所以不会互相干扰。

死锁的一些结论:
参与死锁的进程数至少为两个
参与死锁的所有进程均等待资源
参与死锁的进程至少有两个已经占有资源
死锁进程是系统中当前进程集合的一个子集
死锁会浪费大量系统资源,甚至导致系统崩溃。

死锁产生的原因
竞争不可抢占资源引起死锁
通常系统中拥有的不可抢占资源,其数量不足以满足多个进程运行的需要,使得进程在运行过程中,会因争夺资源而陷入僵局,如磁带机、打印机等。只有对不可抢占资源的竞争 才可能产生死锁,对可抢占资源的竞争是不会引起死锁的。
竞争可消耗资源引起死锁
进程推进顺序不当引起死锁
进程在运行过程中,请求和释放资源的顺序不当,也同样会导致死锁。例如,并发进程 P1、P2分别保持了资源R1、R2,而进程P1申请资源R2,进程P2申请资源R1时,两者都会因为所需资源被占用而阻塞。
信号量使用不当也会造成死锁。进程间彼此相互等待对方发来的消息,结果也会使得这 些进程间无法继续向前推进。例如,进程A等待进程B发的消息,进程B又在等待进程A 发的消息,可以看出进程A和B不是因为竞争同一资源,而是在等待对方的资源导致死锁。

产生死锁的四个必要条件:
5.1 互斥条件:
5.2 不可剥夺条件:
5.3 请求与保持条件:
5.4 循环等待条件:

处理死锁的方法:
预防死锁:通过设置某些限制条件,去破坏产生死锁的四个必要条件中的一个或几个条件,来防止死锁的发生。
避免死锁:在资源的动态分配过程中,用某种方法去防止系统进入不安全状态,从而避免死锁的发生。
检测死锁:允许系统在运行过程中发生死锁,但可设置检测机构及时检测死锁的发生,并采取适当措施加以清除。
解除死锁:当检测出死锁后,便采取适当措施将进程从死锁状态中解脱出来。

I/O交通管制程序:记录设备,控制器以及通道的状态,从而达到独立I/O调度。

I/O接口与CPU之间的I/O总线有数据线,控制线和地址线。数据线和地址线都是单向传输的,从CPU传送给I/O接口。

当CPU发现所请求的内存地址中没有指令,就会发出缺页中断。

ARM寄存器简介:
R0‐R7 也被称为低组寄存器。所有指令都能访问它们。它们的字长全是 32 位,复位后
的初始值是不可预料的。
R8‐R12 也被称为高组寄存器。这是因为只有很少的 16 位 Thumb 指令能访问它们, 32位的指令则不受限制。它们也是 32 位字长,且复位后的初始值是不可预料的 。
在这里插入图片描述
 主堆栈指针(MSP),或写作 SP_main。这是缺省的堆栈指针,它由 OS 内核、异常服务例程 以及所有需要特权访问的应用程序代码来使用。  进程堆栈指针(PSP),或写作 SP_process。用于常规的应用程序代码(不处于异常服用例 程中时)。

CPSR 状态寄存器就是
在这里插入图片描述
CPSR的低8位(包括I、F、T和M[4:0])称为控制位,程序无法修改,除非CPU运行于特权模式下,程序才能修改控制位!
N、Z、C、V均为条件码标志位。它们的内容可被算术或逻辑运算的结果所改变,并且可以决定某条指令是否被执行!意义重大!
它包含了条件标志位、中断禁止位、当前处理器模式标志以及其他的一些控制和状态位。

PC寄存器的指向问题
如上图所示,在执行add r0, r1, #5指令时,第二条指令正在译码阶段,而第三条指令正在取指阶段。在执行第一条指令时,PC寄存器应指向第三条指令。也即,当处理器为三级流水线结构时,PC寄存器总是指向随后的第三条指令。
当处理器处于ARM状态时,每条ARM指令为4个字节,所以PC寄存器的值为当前指令地址 + 8字节
当处理器处于Thumb状态时,每条Thumb指令为2字节,所以PC寄存器的值为当前指令地址 + 4字节
此外,在ARM9中,采用了五级流水线结构,是在ARM7的三级流水线结构后面添加了两个新的过程。因此,指令的执行过程和取指过程还是相隔一个译码过程,因而PC还是指向当前指令随后的第三条指令。

SPSR:程序状态保存寄存器
SPSR用来进行异常处理,有以下功能
1.保存ALU中的当前操作信息。
2.控制允许和禁止中断。
3.设置处理器的运行模式。

发布了27 篇原创文章 · 获赞 20 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_33479881/article/details/97520662