@

嵌入式软件工程师面试题目整理(一)

Arm有多少32位寄存器?

  ARM处理器共有37个寄存器。它包含31个通用寄存器和6个状态寄存器。
在这里插入图片描述

通用寄存器的分类
a.未备份寄存器,包括R0-R7
  对每个未备份寄存器来说,在所有的模式下都是指同一个物理寄存器(例如:Usr下的R0与FIQ下的R0是同一个寄存器)。在异常程序中断造成模式切换时,由于不同模式使用的是相同的物理寄存器。这可能导致数据遭到破坏。未备份寄存器没有被系统作为别的用途,任何场合均可采用未备份寄存器。

b.备份寄存器,包括R8-R14
   对于备份寄存器R8-R12来说,除FIQ模式下其它模式均使用相同的物理寄存器。在FIQ模式下R8_fiq,R9_fiq,R10_fiq,R11_fiq,R12_fiq。它有自己的物理寄存器

  对于R13和R14寄存器每种模式都有自己的物理寄存器(System与Usr的寄存器相同)。当异常中断发生时,系统使用相应模式下的物理寄存器,从而可以避免数据遭到破坏。(用户模式和系统模式非异常中断,所以共用R13 R14
  R13也称为SP堆栈指针。
  R14也称为LR寄存器(存放当前子程序的返回地址)

c.程序计数器,PC
  PC寄存器存储指令地址,由于ARM采用流水机制执行指令,故PC寄存器总是存储下一条指令的地址。

  由于ARM是按照字对齐故PC被读取后的值的bit[1:0]总是0b00(thumb的bit[0]是0b0)。

  CPSR(当前程序状态寄存器)可以在任何处理器模式下被访问
  SPSR(备份程序状态寄存器)。当特定的异常中断发生时,这个寄存器用于存放当前程序状态寄存器的内容。在异常中断程序退出时,可以用SPSR中保存的值来恢复CPSR。

  由于用户模式和系统模式不是异常中断模式,所以它们没有SPSR。(用户模式和系统模式的CPSR也是共用的)当在用户模式或系统模式中访问SPSR,将会产生不可预知的结果。

ARM处理器共有37个寄存器。其中包括:31个通用寄存器,包括程序计数器(PC)在内。这些寄存器都是32位寄存器。以及6个32位状态寄存器。但目前只使用了其中12位。ARM处理器共有7种不同的处理器模式,在每一种处理器模式中有一组相应的寄存器组。任意时刻(也就是任意的处理器模式下),可见的寄存器包括15个通用寄存器(R0~R14)、一个或两个状态寄存器及程序计数器(PC)。在所有的寄存器中,有些是各模式共用的同一个物理寄存器;有一些寄存器是各模式自己拥有的独立的物理寄存器。表1列出了各处理器模式下可见的寄存器情况。 

 

表1  各种处理器模式下的寄存器

用户模式

系统模式

特权模式

中止模式

未定义指令模式

外部中断模式

快速中断模式

R0

R0

R0

R0

R0

R0

R0

R1

R1

R1          

R1

R1

R1

R1

R2

R2

R2

R2

R2

R2

R2

R3

R3

R3

R3

R3

R3

R3

R4

R4

R4

R4

R4

R4

R4

R5

R5

R5

R5

R5

R5

R5

R6

R6

R6

R6

R6

R6

R6

R8

R8

R8

R8

R8

R8

R8_fiq

R9

R9

R9

R9

R9

R9

R9_fiq

R10

R10

R10

R10

R10

R10

R10_fiq

R11

R11

R11

R11

R11

R11

R11_fiq

R12

R12

R12

R12

R12

R12

R12_fiq

R13

R13

R13_svc

R13_abt

R13_und

R13_inq

R13_fiq

R14

R14

R14_svc

R14_abt

R14_und

R14_inq

R14_fiq

PC

PC

PC

PC

PC

PC

PC

CPSR

CPSR

CPSR

SPSR_svc

CPSR

SPSR_abt

CPSR

SPSR_und

CPSR

SPSR_inq

CPSR

SPSR_fiq

 

通用寄存器

通用寄存器可以分为下面3类:未备份寄存器(The unbanked registers),包括R0~R7。备份寄存器(The banked registers),包括R8~R14。程序计数器PC,即R15。

未备份寄存器

未备份寄存器包括R0~R7。对于每一个未备份寄存器来说,在所有的处理器模式下指的都是同一个物理寄存器。在异常中断造成处理器模式切换时,由于不同的处理器模式使用相同的物理寄存器,可能造成寄存器中数据被破坏。未备份寄存器没有被系统用于特别的用途,任何可采用通用寄存器的应用场合都可以使用未备份寄存器。

备份寄存器

对于备份寄存器R8~R12来说,每个寄存器对应两个不同的物理寄存器。例如,当使用快速中断模式下的寄存器时,寄存器R8和寄存器R9分别记作R8_fiq、R9_fiq;当使用用户模式下的寄存器时,寄存器R8和寄存器R9分别记作R8_usr、R9_usr等。在这两种情况下使用的是不同的物理寄存器。系统没有将这几个寄存器用于任何的特殊用途,但是当中断处理非常简单,仅仅使用R8~R14寄存器时,FIQ处理程序可以不必执行保存和恢复中断现场的指令,从而可以使中断处理过程非常迅速。对于备份寄存器R13和R14来说,每个寄存器对应6个不同的物理寄存器,其中的一个是用户模式和系统模式共用的;另外的5个对应于其他5种处理器模式。采用记号R13_<mode>来区分各个物理寄存器:

其中,<mode>可以是下面几种模式之一:usr、svc、abt、und、irq及fiq。

寄存器R13在ARM中常用作栈指针。在ARM指令集中,这只是一种习惯的用法,并没有任何指令强制性的使用R13作为栈指针,用户也可以使用其他的寄存器作为栈指

针;而在Thumb指令集中,有一些指令强制性地使用R13作为栈指针。

每一种异常模式拥有自己的物理的R13。应用程序初始化该R13,使其指向该异常模式专用的栈地址。当进入异常模式时,可以将需要使用的寄存器保存在R13所指的栈中;当退出异常处理程序时,将保存在R13所指的栈中的寄存器值弹出。这样就使异常处理程序不会破坏被其中断程序的运行现场。

寄存器R14又被称为连接寄存器(Link Register,LR),在ARM体系中具有下面两种特殊的作用:每一种处理器模式自己的物理R14中存放在当前子程序的返回地址。当通过BL或BLX指令调用子程序时,R14被设置成该子程序的返回地址。在子程序中,当把R14的值复制到程序计数器PC中时,子程序即返回。

当异常中断发生时,该异常模式特定的物理R14被设置成该异常模式将要返回的地址,对于有些异常模式,R14的值可能与将返回的地址有一个常数的偏移量。具体的返回方式与上面的子程序返回方式基本相同。

R14寄存器也可以作为通用寄存器使用。   

程序计数器R15

程序计数器R15又被记作PC。它虽然可以作为一般的通用寄存器使用,但是有一些指令在使用R15时有一些特殊限制。当违反了这些限制时,该指令执行的结果将是不可预料的。

由于ARM采用了流水线机制,当正确读取了PC的值时,该值为当前指令地址值加8个字节。也就是说,对于ARM指令集来说,PC指向当前指令的下两条指令的地址。

由于ARM指令是字对齐的,PC值的第0位和第1位总为0。需要注意的是,当使用指令STR/STM保存R15时,保存的可能是当前指令地址值加8字节,也可能保存的是当前指令地址加12字节。到底是哪种方式,取决于芯片具体设计方式。无论如何,在同一芯片中,要么采用当前指令地址加8,要么采用当前指令地址加12,不能有些指令采用当前指令地址加8,另一些指令采用当前指令地址加12。因此对于用户来说,尽量避免使用STR/STM指令来保存R15的值。当不可避免这种使用方式时,可以先通过一些代码来确定所用的芯片使用的是哪种实现方式。

对于ARM版本4以及更高的版本,程序必须保证写入R15寄存器的地址值的bits[1:0]为0b00;否则将会产生不可预知的结果。

对于Thumb指令集来说,指令是半字对齐的。处理器将忽略bit[0],即写入R15的地址值首先与0XFFFFFFFC做与操作,再写入R15中。

还有—些指令对于R15的用法有一些特殊的要求。比如,指令BX利用bit[0]来确定是ARM指令,还是Thumb指令。这种读取PC值和写入PC值的不对称的操作需要特别注意。

程序状态寄存器

CPSR(当前程序状态寄存器)可以在任何处理器模式下被访问。它包含了条件标志位、中断禁止位、当前处理器模式标志以及其他的一些控制和状态位。每一种处理器模式下都有一个专用的物理状态寄存器,称为SPSR(备份程序状态寄存器)。当特定的异常中断发生时,这个寄存器用于存放当前程序状态寄存器的内容。在异常中断程序退出时,可以用SPSR中保存的值来恢复CPSR。

由于用户模式和系统模式不是异常中断模式,所以它们没有SPSR。当在用户模式或系统模式中访问SPSR,将会产生不可预知的结果。

CPSR的格式如下所示。SPSR格式与CPSR格式相同。

31

30

29

28

27

26

7

6

5

4

3

2

1

0

N

Z

C

V

Q

DNM(RAZ)

I

F

T

M4

M3

M2

M1

M0

 

条件标志位

N(Negative)、Z(Zero)、C(Carry)及V(oVerflow)统称为条件标志位。大部分的ARM指令可以根据CPSR中的这些条件标志位来选择性地执行。各条件标志位的具体含义如表2所示。

 

表2 CPSR中的条件标志位

标志位  

含  义

N

本位设置成当前指令运算结果的bit[31)的值

当两个补码表示的有符号整数运算时,N=I表示运算的结果为负数;N=0表示结果为正数或零

Z

Z=1表示运算的结果为零;Z=0表示运算的结果不为零。

对于CMP指令,Z=1表示进行比较的两个数大小相等。

下面分4种情况讨论C的设置方法:

在加法指令中(包括比较指令CMN),当结果产生了进位,则C=1,表示无符号数运算发生上溢出;其他情况下C=0。

在减法指令中(包括比较指令CMP),当运算中发生借位则C=0表示无符号数运算发生下溢出;其他情况下C=1。

对于包含移位操作的非加/减法运算指令,C中包含最后一次溢出的位数数值。

对于其他非加/减法运算指令,C位的值通常不受影响。

V  

对于加/减法运算指令,当操作数和运算结果为二进制的补码表示的带符号数时V=1表示符号位溢出。

通常其他的指令不影响V位,具体可参考各指令的说明。

 

Q标志位

在ARMv5的E系列处理器中,CPSR的bit[27]称为Q标志位,主要用于指示增强的

DSP指令是否发生了溢出。同样的SPSR中的bit[27]也称为Q标志位,用于在异常中断发生时保存和恢复CPSR中的Q标志位。

在ARM v5以前的版本及ARM v5的非E系列的处理器中,Q标志位没有被定义。CPSR的bit[27]属于DNM(RAZ)。

CPSR中的控制位

CPSR的低8位I、F、T及M[4:0]统称为控制位。当异常中断发生时这些位发生变化。在特权级的处理器模式下,软件可以修改这些控制位。

1)      中断禁止位

当I=1时禁止IRQ中断。

当F=1时禁止FIQ中断。

2)      T控制位    

T控制位用于控制指令执行的状态,即说明本指令是ARM指令,还是Thumb指令。对与不同版本的ARM处理器,T控制位的含义不同。对于ARMv4以及更高版本的T系列的ARM处理器,

T=0表示执行ARM指令。

T=1表示执行Thumb指令。

对于ARMv5以及更高的版本的非T系列的ARM处理器,T控制位含义如下:

T=0表示执行ARM指令。

T=1表示强制下一条执行的指令产生未定义指令中断。

3)      M控制位

控制位M[4:0]控制处理器模式,具体含义如表3所示。

表3控制位M[4:0] 的含义

M[4:0]

处理器模式

可访问的寄存器

0b10000

User   

PC,R14一R0,CPSR

0b10001

FIQ   

PC,R14_fiq-R8_flq,R7~R0,CPSR,SPSR_nq

0b10010

1RQ

PC,R14 _irq-R13 _irq,R12一R0,CPSR,SPSR_ irq

0b10011

Supervisor

PC,R14_ svc-R13 _svc,R12~R0,CPSR,SPSR_svc

0b10111

Abort

PC,R14_abt-R13_abt,R12~R0,CPSR,SPSR_abt

0b11011

Undefined

PC,R14_und-R13_und,R12~R0,CPSR,SPSR_ und

     

 

CPSR中的其他位

CPSR中的其他位用于将来ARM版本的扩展。应用软件不要操作这些位,以免与ARM将来版本的扩展冲突。

Arm2440和6410有什么区别

  1.主频不同。2440是400M的。6410是533/667M的;(跑安卓操作系统要500MHZ以上)

  2.处理器版本不一样:2440是arm920T内核,6410是arm1176ZJF内核;

  3.6410在视频处理方面比2440要强很多。内部视频解码器,包括MPEG4等视频格式;

  4.6410支持WMV9、xvid、mpeg4、h264等格式的硬解码和编码;

  5. 6410多和很多扩展接口比如:tv-out、CF卡和S-Video输出等;

  6. spi、串口、sd接口也比那两个要丰富;

  7.6410采用的是DDR内存控制器;2440采用的是SDRam内存控制器

  8.6410为双总线架构,一路用于内存总线、一路用于Flash总线;

  9.6410的启动方式更加灵活:主要包括SD、Nand Flash、Nor Flash和OneFlash等设备启动;

  10.6410的Nand Flash支持SLC和MLC两种架构,从而大大扩大存储空间;

  11.6410为双总线架构,一路用于内存总线、一路用于Flash总线;

  12.6410具备8路DMA通道,包括LCD、UART、Camera等专用DMA通道;

  13.6410还支持2D和3D的图形加速;

  14.2440一些参数:256M NAND FLASH,64M SDRAM,2M NOR FLASH

在这里插入图片描述在这里插入图片描述

CPU,MPU,MCU,SOC,SOPC联系与差别

  1.CPU(Central Processing Unit),是一台计算机的运算核心和控制核心。CPU由运算器、控制器和寄存器及实现它们之间联系的数据、控制及状态的总线构成。差不多所有的CPU的运作原理可分为四个阶段:提取(Fetch)、解码(Decode)、执行(Execute)和写回(Writeback)。 CPU从存储器或高速缓冲存储器中取出指令,放入指令寄存器,并对指令译码,并执行指令。所谓的计算机的可编程性主要是指对CPU的编程。

  2.MPU (Micro Processor Unit),叫微处理器(不是微控制器),通常代表一个功能强大的CPU(暂且理解为增强版的CPU吧),但不是为任何已有的特定计算目的而设计的芯片。这种芯片往往是个人计算机和高端工作站的核心CPU。最常见的微处理器是Motorola的68K系列和Intel的X86系列。

  3.MCU(Micro Control Unit),叫微控制器,是指随着大规模集成电路的出现及其发展,将计算机的CPU、RAM、ROM、定时计数器和多种I/O接口集成在一片芯片上,形成芯片级的芯片,比如51,avr这些芯片,内部除了CPU外还有RAM,ROM,可以直接加简单的外围器件(电阻,电容)就可以运行代码了,而MPU如x86,arm这些就不能直接放代码了,它只不过是增强版的CPU,所以得添加RAM,ROM

  MCU MPU 最主要的区别就睡能否直接运行代码。MCU有内部的RAM ROM,而MPU是增强版的CPU,需要添加外部RAM ROM才可以运行代码。

  4.SOC(System on Chip),指的是片上系统,MCU只是芯片级的芯片,而SOC是系统级的芯片,它既MCU(51,avr)那样有内置RAM,ROM同时又像MPU(arm)那样强大的,不单单是放简单的代码,可以放系统级的代码,也就是说可以运行操作系统(将就认为是MCU集成化与MPU强处理力各优点二合一)。

  5.SOPC(System On a Programmable Chip)可编程片上系统(FPGA就是其中一种),上面4点的硬件配置是固化的,就是说51单片机就是51单片机,不能变为avr,而avr就是avr不是51单片机,他们的硬件是一次性掩膜成型的,能改的就是软件配置,说白点就是改代码,本来是跑流水灯的,改下代码,变成数码管,而SOPC则是硬件配置,软件配置都可以修改,软件配置跟上面一样,没什么好说的,至于硬件,是可以自己构建的也就是说这个芯片是自己构造出来的,这颗芯片我们叫“白片”,什么芯片都不是,把硬件配置信息下载进去了,他就是相应的芯片了,可以让他变成51,也可以是avr,甚至arm,同时SOPC是在SOC基础上来的,所以他也是系统级的芯片,所以记得当把他变成arm时还得加外围ROM,RAM之类的,不然就是MPU了。

  顺便再讲一下这个SOPC吧,首先上面讲的“白片”一般指的是FPGA或CPLD这类芯片,由于它是可配置的,所以一旦断电,他的硬件配置就没了,当然,软件配置也没了,什么都没了,比如把他硬件配置成51单片机,软件配置为跑流水灯,结果一断电,这个芯片就什么都不是了,恢复原样“白片”。

  一般有两种用法,一是用它来验证芯片,因为他是可以多次下载配置验证的,成功后再把这硬件配置下载到一次性的芯片上,如果采用基于hardcopy的SOC则成功率100%,不然每次下载硬件配置验证用SOC等到你调试出正确的硬件配置,那得烧多少芯片啊,毕竟这些是一次性的,不成功只能成仁–扔掉!跟调试软件配置一样,一般软件调试很多次才能成功的,所以他是验证技术,行了再将配置配在其他的芯片,第二种方法是,芯片就用这“白片”,然后把配置信息放到flash里,上电后先将这硬件配置信息烧入这“白片”,使其变成自己想要的芯片,然后再调入软件配置。其中硬件配置可以用quartus软件编写,软件配置可以用NIOS软件,这都是altera公司的产品,可以去查看。

上拉&下拉&高阻态

在这里插入图片描述
  上拉就是将不确定的信号通过一个电阻嵌位在高电平,电阻同时起限流作用。下拉同理。

  上拉电阻是用来解决总线驱动能力不足时提供电流的,一般说法是拉电流。下拉电阻是用来吸收电流的,也就是我们通常所说的灌电流。提升电流和电压的能力是有限的,且弱强只是上拉电阻的阻值不同。

  三态门,是指逻辑门的输出除有高、低电平两种状态外,还有第三种状态——高阻状态的门电路。具备这三种状态的器件就叫做三态(门,总线,…)。

  如果你的设备端口要挂在一个总线上,“必须通过三态缓冲器”。因为在一个总线上同时只能有一个端口作输出,这时其他端口必须在高阻态,同时“可以输入这个输出端口的数据”。所以你还需要有总线控制管理, 访问到哪端口,那个端口的三态缓冲器才可以转入输出状态,这是典型的三态门应用。 如果在线上没有两个以上的输出设备, 当然用不到三态门。

  高阻态,指的是电路的一种输出状态,既不是高电平也不是低电平。高阻态只有电容效应,没有电阻效应;阻抗很高很高,相当于断开。如果高阻态再输入下一级电路的话,对下级电路无任何影响,和没接一样,如果用万用表测的话有可能是高电平也有可能是低电平随它后面接的东西定

  悬空和高阻态的区别 悬空(浮空,floating):就是逻辑器件的输入引脚即不接高电平,也不接低电平。由于逻辑器件的内部结构,当它输入引脚悬空时,相当于该引脚接了高电平。一般实际运用时,引脚不建议悬空,易受干扰。 高阻态:从逻辑器件内部电路结构来说,就是其输出电阻很大,该状态即不是高电平,也不是低电平。当三态门处于高阻态时,无论该门的输入如何变化,都不会对其输出有贡献。

串口协议讲一讲

  串口在嵌入式系统当中是一类重要的数据通信接口,其本质功能是作为 CPU串行设备间的编码转换器。当数据从 CPU 经过串行端口发送出去时,字节数据转换为串行的位;在接收数据时,串行的位被转换为字节数据。应用程序要使用串口进行通信,必须在使用之前向操作系统提出资源申请要求(打开串口),通信完成后必须释放资源(关闭串口)。典型地,串口用于 ASCII 码字符的传输。通信使用3根线完成:(1)地线,(2)发送数据线,(3)接收数据线。

  串口通信最重要的参数是波特率、数据位、停止位和奇偶校验。对于两个进行通行的端口,这些参数必须匹配:波特率是一个衡量通信速度的参数,它表示每秒钟传送的 bit 的个数;数据位是衡量通信中实际数据位的参数,当计算机发送一个信息包,标准的值是 5,7 和 8 位。如何设置取决于你的需求;停止位用于表示单个包的最后一位,典型的值为 1,1.5和 2 位,停止位不仅仅是表示传输的结束,并且提供计算机校正时钟同步的机会;奇偶校验位是串口通信中一种简单的检错方式有四种检错方式——偶、奇、高和低,也可以没有校验位

  奇校验:校核数据完整性的一种方法,一个字节的8个数据位与校验位(parity bit )加起来之和有奇数个1。校验线路在收到数后,通过发生器在校验位填上0或1,以保证和是奇数个1。因此,校验位是0时,数据位中应该有奇数个1;而校验位是1时,数据位应该有偶数个1如果读取数据时发现与此规则不符,CPU会下令重新传输数据。 奇/偶校验(ECC)是数据传送时采用的一种校正数据错误的一种方式,分为奇校验和偶校验两种。 如果是采用奇校验,在传送每一个字节的时候另外附加一位作为校验位,当实际数据中“1”的个数为偶数的时候,这个校验位就是“1”,否则这个校验位就是“0”,这样就可以保证传送数据满足奇校验的要求。在接收方收到数据时,将按照奇校验的要求检测数据中“1”的个数,如果是奇数,表示传送正确,否则表示传送错误。 同理偶校验的过程和奇校验的过程一样,只是检测数据中“1”的个数为偶数。

RS232和RS485通讯接口有什么区别

  1、传输方式不同。 RS-232采取不平衡传输方式,即所谓单端通讯. 而RS485则采用平衡传输,即差分传输方式

  2、传输距离不同。RS-232适合本地设备之间的通信,传输距离一般不超过20m。而RS-485的传输距离为几十米到上千米

  3、RS-232 只允许一对一通信,而RS-485 接口在总线上是允许连接多达128个收发器。 RS232/RS485,是两种不同的电气协议,也就是说,是对电气特性以及物理特性的规定,作用于数据的传输通路上,它并不内含对数据的处理方式。比如,最显著的特征是:RS232使用3-15v有效电平,而UART,因为对电气特性并没有规定,所以直接使用CPU使用的电平,就是所谓的TTL电平(可能在0~3.3V之间)。更具体的,电气的特性也决定了线路的连接方式,比如RS232,规定用电平表示数据,因此线路就是单线路的,两根线才能达到全双工的目的;而RS485, 使用差分电平表示数据,因此,必须用两根线才能达到传输数据的基本要求,要实现全双工,必需用4根线。但是,无论使用RS232还是RS485,它们与UART是相对独立的,但是由于电气特性的差别,必须要有专用的器件和UART接驳,才能完成数据在线路和UART之间的正常流动。

   总结:从某种意义上,可以说,线路上存在的仅仅是电流,RS232/RS485规定了这些电流在什么样的线路上流动和流动的样式;在UART那里,电流才被解释和组装成数据,并变成CPU可直接读写的形式。

IIC时序图画一下,IIC有哪些状态,给一个字节,将它发送出去。IIC有什么注意事项?有没有用I/O模拟IIC,如果有需要注意什么?

  见前ARM部分

为什么2440的内存起始地址是3后面7个0呢?为什么6410的内存起始地址是5后面7个0呢?

内存映射 (256M SDRAM)
在这里插入图片描述

内存管理有什么看法?(MMU)

  见之前Linux部分MMU简介

锁有哪些?有什么注意事项

自旋锁
  上厕所,等待,自旋。
  如果线程A持有自旋锁时间过长,显然会浪费处理器的时间,降低了系统性能。自旋锁只适合短期持有,如果遇到需要长时间持有的情况,我们就要换一种方式了(下文的互斥体)。

读写锁
  当临界区的一个文件可以被同时读取,但是并不能被同时读和写。如果一个线程在读,另一个线程在写,那么很可能会读取到错误的不完整的数据读写自旋锁是可以允许对临界区的共享资源进行并发读操作的。但是并不允许多个线程并发读写操作。如果想要并发读写,就要用到了顺序锁

顺序锁
  顺序锁是读写锁的优化版本,读写锁不允许同时读写,而使用顺序锁可以完成同时进行读和写的操作,但并不允许同时的写。虽然顺序锁可以同时进行读写操作,但并不建议这样,读取的过程并不能保证数据的完整性

信号量和自旋锁区别

信号量
  信号量和自旋锁有些相似,不同的是信号量会发出一个信号告诉你还需要等多久。因此,不会出现傻傻等待的情况。比如,有100个停车位的停车场,门口电子显示屏上实时更新的停车数量就是一个信号量。这个停车的数量就是一个信号量,他告诉我们是否可以停车进去。当有车开进去,信号量加一,当有车开出来,信号量减一。

  比如,厕所一次只能让一个人进去,当A在里面的时候,B想进去,如果是自旋锁,那么B就会一直在门口傻傻等待。如果是信号量,A就会给B一个信号,你先回去吧,我出来了叫你。这就是一个信号量的例子,B听到A发出的信号后,可以先回去睡觉,等待A出来。
  
  因此,信号量显然可以提高系统的执行效率,避免了许多无用功。信号量具有以下特点:
  因为信号量可以使等待资源线程进入休眠状态,因此适用于那些占用资源比较久的场合。
  因此信号量不能用于中断中,因为信号量会引起休眠中断不能休眠
  如果共享资源的持有时间比较短,那就不适合使用信号量了,因为频繁的休眠、切换线程引起的开销要远大于信号量带来的那点优势。

  在驱动程序中,当多个线程同时访问相同的资源时(驱动程序中的全局变量是一种典型的共享资源),可能会引发”竞态“,因此我们必须对共享资源进行并发控制。Linux内核中解决并发控制的最常用方法是自旋锁与信号量(绝大多数时候作为互斥锁使用)。

  自旋锁与信号量”类似而不类”,类似说的是它们功能上的相似性,”不类”指代它们在本质和实现机理上完全不一样,不属于一类。

  自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环查看是否该自旋锁的保持者已经释放了锁,”自旋”就是”在原地打转”。而信号量则引起调用者睡眠,它把进程从运行队列上拖出去,除非获得锁。这就是它们的”不类”。

  但是,无论是信号量,还是自旋锁,在任何时刻,最多只能有一个保持者,即在任何时刻最多只能有一个执行单元获得锁。这就是它们的”类似”。

  鉴于自旋锁与信号量的上述特点,一般而言,自旋锁适合于保持时间非常短的情况,它可以在任何上下文使用;信号量适合于保持时间较长的情况,会只能在进程上下文使用。如果被保护的共享资源只在进程上下文访问,则可以以信号量来保护该共享资源,如果对共享资源的访问时间非常短,自旋锁也是好的选择。但是,如 果被保护的共享资源需要在中断上下文访问(包括底半部即中断处理句柄和顶半部即软中断),就必须使用自旋锁

区别总结如下:
  1、由于争用信号量的进程在等待锁重新变为可用时会睡眠,所以信号量适用于锁会被长时间持有的情况。
  2、相反,锁被短时间持有时,使用信号量就不太适宜了(自旋锁比较合适),因为睡眠引起的耗时可能比锁被占用的全部时间还要长。
  3、由于执行线程在锁被争用时会睡眠,所以只能在进程上下文中才能获取信号量锁,因为在中断上下文中(使用自旋锁)是不能进行调度的。
   4、你可以在持有信号量时去睡眠(当然你也可能并不需要睡眠),因为当其它进程试图获得同一信号量时不会因此而死锁,(因为该进程也只是去睡眠而已,而你最终会继续执行的)。
  5、在你占用信号量的同时不能占用自旋锁,因为在你等待信号量时可能会睡眠,而在持有自旋锁时是不允许睡眠的。
   6、信号量锁保护的临界区可包含可能引起阻塞的代码,而自旋锁则绝对要避免用来保护包含这样代码的临界区,因为阻塞意味着要进行进程的切换,如果进程被切换出去后(锁定期间被切换出去),另一进程企图获取本自旋锁,死锁就会发生。
   7、信号量不同于自旋锁,它不会禁止内核抢占(自旋锁被持有时,内核不能被抢占),所以持有信号量的代码可以被抢占,这意味着信号量不会对调度的等待时间带来负面影响。
  除了以上介绍的同步机制方法以外,还有BKL(大内核锁),Seq锁等。
  BKL是一个全局自旋锁,使用它主要是为了方便实现从Linux最初的SMP过度到细粒度加锁机制。
  Seq锁用于读写共享数据,实现这样锁只要依靠一个序列计数器。
总结:二者区别主要从持有时间使用环境(进程上下文还是中断上下文),内核抢占

中断能不能睡眠,为什么?下半部能不能睡眠?

  1、 中断处理的时候,不应该发生进程切换,因为在中断context中,唯一能打断当前中断handler的只有更高优先级的中断,它不会被进程打断,如果在中断context中休眠,则没有办法唤醒它,因为所有的wake_up_xxx都是针对某个进程而言的,而在中断context中,没有进程的概念,没 有一个task_struct(这点对于softirq和tasklet一样),因此真的休眠了,比如调用了会导致block的例程,内核几乎肯定会死。

  2、schedule()在切换进程时,保存当前的进程上下文(CPU寄存器的值、进程的状态以及堆栈中的内容),以便以后恢复此进程运行。中断发生后,内核会先保存当前被中断的进程上下文(在调用中断处理程序后恢复);

  但在中断处理程序里,CPU寄存器的值肯定已经变化了吧(最重要的程序计数器PC、堆栈SP等),如果此时因为睡眠或阻塞操作调用了schedule(),则保存的进程上下文就不是当前的进程context了.所以不可以在中断处理程序中调用schedule()。????

  3、2.4内核中schedule()函数本身在进来的时候判断是否处于中断上下文:

if(unlikely(in_interrupt()))

BUG();

  因此,强行调用schedule()的结果就是内核BUG,但我看2.6.18的内核schedule()的实现却没有这句,改掉了。

  4、中断handler会使用被中断的进程内核堆栈,但不会对它有任何影响,因为handler使用完后会完全清除它使用的那部分堆栈,恢复被中断前的原貌。

  5、处于中断context时候,内核是不可抢占的。因此,如果休眠,则内核一定挂起。

为什么软中断中也不能睡眠

  这个问题实际上是一个老生常谈的问题,答案也很简单,Linux在软中断上下文中是不能睡眠的,原因在于Linux的软中断实现上下文有可能是中断上下文,如果在中断上下文中睡眠,那么会导致Linux无法调度,直接的反应是系统Kernel Panic,并且提示dequeue_task出错。所以,在软中断上下文中,我们不能使用信号量等可能导致睡眠的函数,这一点在编写IO回调函数时需要特别注意。  在最近的一个项目中,我们在dm-io的callback函数中去持有semaphore访问竞争资源,导致了系统的kernel panic。其原因就在于dm-io的回调函数在scsi soft irq中执行,scsi soft irq是一个软中断,其会在硬中断发生之后被执行,执行上下文为中断上下文。

  中断上下文中无法睡眠的原因大家一定很清楚,原因在于中断上下文不是一个进程上下文其没有一个专门用来描述CPU寄存器等信息的数据结构,所以无法被调度器调度。如果将中断上下文也设计成进程上下文,那么调度器就可以对其进行调度,如果在开中断的情况下,其自然就可以睡眠了。但是,如果这样设计,那么中断处理的效率将会降低。中断(硬中断、软中断)处理都是些耗时不是很长,对实时性要求很高,执行频度较高的应用,所以,如果采用一个专门的后台daemon对其处理,显然并不合适。

   Linux对中断进行了有效的管理,一个中断发生之后,都会通过相应的中断向量表获取该中断的处理函数。在Linux操作系统中都会调用do_IRQ这个函数,在这个函数中都会执行__do_IRQ(),__do_IRQ函数调用该中断的具体执行函数。在执行过程中,该函数通过中断号找到具体的中断描述结构irq_desc,该结构对某一具体硬件中断进行了描述。在irq_desc结构中存在一条链表irqaction,这条链表中的某一项成员都是一个中断处理方法。这条链表很有意思,其实现了中断共享,例如传统的PCI总线就是采用共享中断的方法,该链表中的一个节点就对应了一个PCI设备的中断处理方法。在PCI设备驱动加载时,都需要注册本设备的中断处理函数,通常会调用request_irq这个函数,通过这个函数会构造一个具体的irq action,然后挂接到某个具体irq_desc的action链表下,实现中断处理方法的注册。在__do_IRQ函数中会通过handle_IRQ_event()函数遍历所有的action节点,完成中断处理过程。到目前为止,中断处理函数do_IRQ完成的都是上半部的工作,也就是设备注册的中断服务程序。在中断上半部中,通常都是关中断的,基本都是完成很简单的操作,否则将会导致中断的丢失。耗时时间相对较长,对实时性要求不是最高的应用都会被延迟处理,都会在中断下半部中执行。所以,在中断上半部中都会触发软中断事件,然后执行完毕,退出服务。

   __do_IRQ完成之后,返回到do_IRQ函数,在该函数中调用了一个非常重要的函数irq_exit(),在该函数中调用invoke_softirq(),invoke_softirq调用do_softirq()函数,执行软中断的操作。此时,程序的执行环境还是中断上下文,但是与中断上半部不同的是,软中断执行过程中是开中断的,能够被硬中断而中断。所以,如果用户的程序在软中断中睡眠,操作系统该如何调度呢?只有kernel panic了。另外,软中断除了上述执行点之外,还有其他的执行点,在内核中还有一个软中断的daemon处理软中断事务,驱动程序也可以自己触发一个软中断事件,并且在软中断的daemon上下文中执行。但是硬中断触发的事件都不会在这个daemon的上下文中执行,除非修改Linux中的do__IRQ代码。

  上述对软中断的执行做了简要分析,我对Linux中的硬中断管理机制做了一些代码分析,这一块代码量不是很大,可移植性非常的好~~建议大家阅读,对我上述的分析和理解存在什么不同意见,欢迎大家讨论。

上下文有哪些?怎么理解?

  见前操作系统部分

死锁产生的原因及四个必要条件

  以按键驱动为例,首先执行的module_init()、module_exit()指定的init函数和exit函数。在init函数中,register_chrdev注册一个字符设备驱动,class_create创建类的名字,class_device_create创建设备。ioremap对管脚进行地址映射。

  在应用程序中调用open函数,就会执行到底层的open函数,在open函数中完成对相应引脚的配置,在应用层调用read函数读取的时候,就会调用底层的read,在里面可以读取按键的值或者点亮LED等等,做一些需要做的事情。最后退出程序的时候就会调用exit函数,unregister_chrdev //卸载驱动,class_device_unregister卸载类设备。class_destroy卸载类,iounmap注销虚拟地址。

LCD驱动如何编写?触摸屏驱动如何编写?

  见之前Linux部分

触摸屏中断做了什么,LCD中断做了什么?

  触摸屏中断中启动ADC转换,ADC转换完成后会产生ADC中断。在ADC中断中读取数据.之后启动定时器,在定时器中断中判断是否继续按下,如果继续按下就会启动ADC继续转换。

什么是交叉编译?为什么需要交叉编译?为什么还要主机编译

什么是交叉编译
  在一种计算机环境中运行的编译程序,能编译出在另外一种环境下运行的代码,我们就称这种编译器支持交叉编译。这个编译过程就叫交叉编译。简单地说,就是在一个平台上生成另一个平台上的可执行代码。这里需要注意的是所谓平台,实际上包含两个概念:体系结构(Architecture)、操作系统(OperatingSystem)。同一个体系结构可以运行不同的操作系统;同样,同一个操作系统也可以在不同的体系结构上运行。举例来说,我们常说的x86 Linux平台实际上是Intel x86体系结构和Linux for x86操作系统的统称;而x86 WinNT平台实际上是Intel x86体系结构和Windows NT for x86操作系统的简称。

   交叉编译这个概念的出现和流行是和嵌入式系统的广泛发展同步的。我们常用的计算机软件,都需要通过编译的方式,把使用高级计算机语言编写的代码(比如C代码)编译(compile)成计算机可以识别和执行的二进制代码。比如,我们在Windows平台上,可使用Visual C++开发环境,编写程序并编译成可执行程序。这种方式下,我们使用PC平台上的Windows工具开发针对Windows本身的可执行程序,这种编译过程称为native compilation,中文可理解为本机编译。然而,在进行嵌入式系统的开发时,运行程序的目标平台通常具有有限的存储空间和运算能力,比如常见的ARM 平台,其一般的静态存储空间大概是16到32MB,而CPU的主频大概在100MHz到500MHz之间。这种情况下,在ARM平台上进行本机编译就不太可能了,这是因为一般的编译工具链(compilation tool chain)需要很大的存储空间,并需要很强的CPU运算能力。为了解决这个问题,交叉编译工具就应运而生了。通过交叉编译工具,我们就可以在CPU能力很强、存储控件足够的主机平台上(比如PC上)编译出针对其他平台的可执行程序。

  要进行交叉编译,我们需要在主机平台上安装对应的交叉编译工具链(crosscompilation tool chain),然后用这个交叉编译工具链编译我们的源代码,最终生成可在目标平台上运行的代码。常见的交叉编译例子如下:
  1、在Windows PC上,利用ADS(ARM 开发环境),使用armcc编译器,则可编译出针对ARM CPU的可执行代码。
  2、在Linux PC上,利用arm-linux-gcc编译器,可编译出针对Linux ARM平台的可执行代码。
  3、在Windows PC上,利用cygwin环境,运行arm-elf-gcc编译器,可编译出针对ARM CPU的可执行代码。

为什么要使用交叉编译
  有时是因为目的平台上不允许或不能够安装我们所需要的编译器,而我们又需要这个编译器的某些特征;有时是因为目的平台上的资源贫乏,无法运行我们所需要编译器;有时又是因为目的平台还没有建立,连操作系统都没有,根本谈不上运行什么编译器。

简述linux系统启动过程

  可以答之前的答之前的分析uboot的过程的部分。

Linux设备中字符设备和块设备有什么主要区别?分别举例。

  字符设备:字符设备是个能够像字节流(类似文件)一样被访问的设备,由字符设备驱动程序来实现这种特性。字符设备驱动程序通常至少实现open, close,read和 write系统谓用。字符终端、串口、鼠标、键盘、摄像头、声卡和显卡等就是典型的字符设备。

  块设备:和字符设备类似,块设备也是通过/dev目录下的文件系统节点来访问。块设备上能够容纳文件系统,如:u盘,SD卡,磁盘等。

  字符设备和块设备的区别仅仅在于内核内部管理数据的方式,也就是内核及驱动程序之间的些不同对用户来讲是透明的。

主设备号和次设备号的用途
在这里插入图片描述
  Linux各种设备都以文件的形式存放在/dev目录下,称为设备文件。

  应用程序可以打开、关闭和读写这些设备文件,完成对设备的操作,就像操作普通的数据文件一样。为了管理这些设备,系统为设备编了号,每个设备号又分为主设备号和次设备号。主设备号用来区分不同种类的设备,而次设备号用来区分同一类型的多个设备。对于常用设备,Linux有约定俗成的编号,如硬盘的主设备号是3。

   一个字符设备或者块设备都有一个主设备号和次设备号。主设备号和次设备号统称为设备号。主设备号用来表示一个特定的驱动程序。次设备号用来表示使用该驱动程序的各设备。例如一个嵌入式系统,有两个LED指示灯,LED灯需要独立的打开或者关闭。那么,可以写一个LED灯的字符设备驱动程序,可以将其主设备号注册成5号设备,次设备号分别为1和2。这里,次设备号就分别表示两个LED灯。

同步通信和异步通信

同步通信和异步通信
   串行通信可以分为两种类型,一种叫同步通信,另一种叫异步通信。

   同步通信方式,是把许多字符组成一个信息组,这样,字符可以一个接一个地传输,但是,在每组信息(通常称为信息帧)的开始要加上同步字符,在没有信息要传输时,要填上空字符,因为同步传输不允许有间隙。同步方式下,发送方除了发送数据,还要传输同步时钟信号,信息传输的双方用同一个时钟信号确定传输过程中每1位的位置。见右图5.2所示。
在这里插入图片描述

在这里插入图片描述

   在异步通信方式中,两个数据字符之间的传输间隔是任意的,所以,每个数据字符的前后都要用一些数位来作为分隔位。

   从下图中可以看到,按标准的异步通信数据格式(叫做异步通信帧格式),1个字符在传输时,除了传输实际数据字符信息外,还要传输几个外加数位。具体说,在1个字符开始传输前,输出线必须在逻辑上处于“1”状态,这称为标识态。传输一开始,输出线由标识态变为“0”状态,从而作为起始位。起始位后面为5~8个信息位,信息位由低往高排列,即先传字符的低位,后传字符的高位。信息位后面为校验位,校验位可以按奇校验设置,也可以按偶校验设置,或不设校验位。最后是逻辑的“1”作为停止位,停止位可为1位、1.5位或者2位。如果传输完1个字符以后,立即传输下一个字符,那么,后一个字符的起始位便紧挨着前一个字符的停止位了,否则,输出线又会进入标识态。在异步通信方式中,发送和接收的双方必须约定相同的帧格式,否则会造成传输错误。在异步通信方式中,发送方只发送数据帧,不传输时钟,发送和接收双方必须约定相同的传输率。当然双方实际工作速率不可能绝对相等,但是只要误差不超过一定的限度,就不会造成传输出错。图5.3是异步通信时的标准数据格式。

   比较起来,在传输率相同时,同步通信方式下的信息有效率要比异步方式下的高,因为同步方式下的非数据信息比例比较小。

在这里插入图片描述
传输率
  所谓传输率就是指每秒传输多少位,传输率也常叫波特率。在计算机中,每秒传输多少位和波特率的含义是完全一致的,但是,在最初的定义上,每秒传输多少位和波特率是不同的,前者是指每秒钟传输的数位是多少,而波特率是指每秒钟传输的离散信号的数目。所谓离散信号,就是指不均匀的、不连续的也不相关的信号。在计算机里,只允许高电平和低电平两种离散信号,它们分别表示l和0,于是,造成了波特率与每秒传输数位这两者的吻合。但在其他一些场合,就未必如此。比如,采用脉冲调制时,可以允许取4种相位,而每种相位代表2个数位,这种情况下,按每秒传输多少位(bps)计算的传输率便是波特率的两倍。

  国际上规定了一个标准波特率系列,标准波特率也是最常用的波特率,标准波特率系列为110、300、600、1200、1800、2400、4800、9600、19200......。

  大多数接口的波特率可以通过编程来指定。

  作为例子,我们可以考虑这样一个异步传输过程:设每个字符对应1个起始位、7个数据位、1个奇/偶校验位和1个停止位,如果波特率为1200,那么,每秒钟能传输的最大字符数为1200/10=120个。

  作为比较,我们再来看一个同步传输的例子。假如也用1200的波特率工作,每个字符为7位,用4个同步字符作为信息帧头部,但不用奇/偶校验,那么,传输100个字符所用的时间为7×(100+4)/1200=0.6067,这就是说,每秒钟能传输的字符数可达到100/0.6067=165个。
异步通信的差错类型
  异步通信过程中,可能发生通信错,一般有3种错误:
  1、帧格式错:在应该接收到停止位的时候,接收到逻辑的“0”,便产生帧格式错误。
  2、奇偶错:接收到的奇偶校验位错。
  3、覆盖错:通信接口接收到数据并存放到数据输入寄存器中,但是CPU没有及时来取,后面新接收的数据覆盖了前面收到的数据,叫做覆盖错。
  发生帧格式错和奇偶错的原因可能为下面几种:
  ◆ 发送和接收双方采用了不同的传输率,或虽然双方约定了相同的传输率,但传输率不可能绝对相等。在通信的速率比较高的情况下,如果双方的传输率误差达到一定的程度,也会造成通信出错;
  ◆ 通信双方采用了不相同的帧格式;
  ◆ 干扰。

Uart和IIC和SPI的区别(提示:关于异步和同步,电子器件上的)?

  UART、SPI、IIC是经常用到的几个数据传输标准,下面分别总结一下:

  UART(Universal Asynchronous Receive Transmitter):也就是我们经常所说的串口,基本都用于调试。

  主机和从机至少要接三根线,RX、TX和GND。TX用于发送数据,RX用于接受数据(收发不是一根线,所以是全双工方式)。注意A和B通信A.TX要接B.RX,A.RX要接B.TX(A用TX发B当然要用RX来收了!)

  如果A是PC机,B是单片机,A和B之间还要接一块电平转换芯片,用于将TTL/CMOS(单片机电平)转换为RS232(PC机电平)。因为TTL/CMOS电平范围是0 ~ 1.8/2.5/3.3/5V(不同单片机范围不同),高电压表示1,低电压表示0。而RS232逻辑电平范围-12V~ 12V-5~ -12表示高电平,+5~+12V表示低电平(对!你没有听错)。为什么这么设置?这就要追溯到调制解调器出生时代了,有兴趣自己去查资料!

  数据协议:以PC机A给单片机B发数据为例(1为高电平,0为低电平):A.TX to B.RX。刚开始B.RX的端口保持1,当A.TX发来一个0作为起始位告诉B我要发数据了!然后就开始发数据,发多少呢?通常一次是5位、6位、7位、8位,这个双方事先要用软件设置好。PC机一般会用串口助手设置,单片机会在uart的驱动中设置。一小帧数据发送完了以后,A.TX给个高电平告诉B.RX我发完了一帧。如果还有数据,就再给个0然后重复上一步。如果双方约定由校验位,还要在发停止位1之前发送个校验位,不过现在一般都不需要校验位了,因为出错的概率太小了,而且一般用于调试。

  一般在串口助手上还有个RTS/CTS流控选项,也叫握手,我从来没用过。搬一段我能理解的介绍:RTS(请求发送),CTS(清除发送)。如果要用这两个功能,那就至少要接5根线:RX+TX+GND+RTS+CTS。当A要发送数据时,置RTS有效(可能是置1),告诉B我要发送数据了。当B准备好接受数据后,置CTS有效,告诉A你可以发了。然后他们就实现了两次握手!挺耽误时间是不是?这个RTS还可以当电源使用,如果你不用它的握手功能,且电源电流在50mA以下时,就可以把它置为高电平可以当电源用喔~!

  SPI(Serial Peripheral Interface, 同步外设接口)是由摩托罗拉公司开发的全双工同步串行总线,该总线大量用在与EEPROM、ADC、FRAM和显示驱动器之类的慢速外设器件通信。

  SPI是一种串行同步通讯协议,由一个主设备和一个或多个从设备组成,主设备启动一个与从设备的同步通讯,从而完成数据的交换。SPI 接口由SDI(串行数据输入),SDO(串行数据输出),SCK(串行移位时钟),CS(从使能信号)四种信号构成,CS 决定了唯一的与主设备通信的从设备,片选信号低电平有效。如没有CS 信号,则只能存在一个从设备,主设备通过产生移位时钟来发起通讯。通讯时,数据由SDO 输出,SDI 输入,数据在时钟的上升或下降沿由SDO 输出,在紧接着的下降或上升沿由SDI 读入,这样经过8/16 次时钟的改变,完成8/16 位数据的传输。(极性 空闲高低电平 相位 那个跳边沿采样)

  IIC(Inter Integrated Circuit):两根线:一个时钟线SCL和一个数据线SDA。只有一根数据线,所以是半双工通信。接线不难,而且两根线上也可以挂很多设备(每个设备的IIC地址不同),数据协议比较麻烦:

  还是假设A给B发数据(这里A.SCL接B.SCL, A.SDA接B.SDA)。起初SDA和SCL上的电平都为高电平。然后A先把SDA拉低,等SDA变为低电平后再把SCL拉低(以上两个动作构成了iic的起始位),此时SDA就可以发送数据了,与此同时,SCL发送一定周期的脉冲(周期和PCLK有关,一般会在IIC的控制寄存器中设置)。SDA发送数据和SCL发送脉冲的要符合的关系是:SDA必须在SCL是高电平是保持有效,在SCL是低电平时发送下一位(SCL会在上升沿对SDA进行采样)。规定一次必须传8位数据,8位数据传输结束后A释放SDA,但SCL再发一个脉冲(这是第九个脉冲),这会触发B通过将SDA置为低电平表示确认(该低电平称为ACK)。最后SCL先变为高电平,SDA再变为高电平(以上两个动作称为结束标志)如果B没有将SDA置为0,则A停止发送下一帧数据。IIC总线(即SDA和SCL)上的每个设备都有唯一地址,数据包传输时先发送地址位,接着才是数据。一个地址字节由7个地址位(可以挂128个设备)和1个指示位组成(7位寻址模式)。指示位是0表示写,1表示读。还有10位寻址模式,使用两个字节来保存地址,第一个字节的最低两位和第二个字节的8位合起来构成10位地址。

  在I2C总线的应用中应注意的事项总结为以下几点 :
  1) 严格按照时序图的要求进行操作,
  2) 若与口线上带内部上拉电阻的单片机接口连接,可以不外加上拉电阻。
  3) 程序中为配合相应的传输速率,在对口线操作的指令后可用NOP指令加一定的延时。
  4) 为了减少意外的干扰信号将EEPROM内的数据改写可用外部写保护引脚(如果有),或者在EEPROM内部没有用的空间写入标志字,每次上电时或复位时做一次检测,判断EEPROM是否被意外改写。

  关于IIC总线的操作注意事项
  1、对IIC总线的一次操作完之后,需要等待一段时间才能进行第二次操作。否则是启动不了总线的:)
  2、在时钟线(SCL)为高电平的时候,一定不能动数据线(SDA)状态,除非是启动或者结束总线

UART, SPI, IIC的区别与联系:

  第一个区别当然是名字:
  UART(Universal Asynchronous Receiver Transmitter:通用异步收发器)
  SPI(Serial Peripheral Interface:串行外设接口);
  I2C(INTER IC BUS)

  第二,区别在电气信号线上:
  SPI总线由三条信号线组成:串行时钟(SCLK)、串行数据输出(SDO)、串行数据输入(SDI)。SPI总线可以实现 多个SPI设备互相连接。提供SPI串行时钟的SPI设备为SPI主机或主设备(Master),其他设备为SPI从机或从设备(Slave)。主从设备间可以实现全双工通信当有多个从设备时,还可以增加一条从设备选择线。

  如果用通用IO口模拟SPI总线,必须要有一个输出口(SDO),一个输入口(SDI),另一个口则视实现的设备类型而定,如果要实现主从设备,则需输入输出口,若只实现主设备,则需输出口即可,若只实现从设备,则只需输入口即可。

  I2C总线是双向、两线(SCL、SDA)、串行、多主控(multi-master)接口标准,具有总线仲裁机制,非常适合在器件之间进行近距离、非经常性的数据通信。在它的协议体系中,传输数据时都会带上目的设备的设备地址,因此可以实现设备组网。
如果用通用IO口模拟I2C总线,并实现双向传输,则需一个输入输出口(SDA),另外还需一个输出口(SCL)。(注:I2C资料了解得比较少,这里的描述可能很不完备)

  UART总线是异步串口,因此一般比前两种同步串口的结构要复杂很多,一般由波特率产生器(产生的波特率等于传输波特率的16倍)、UART接收器、UART发送器组成,硬件上由两根线,一根用于发送,一根用于接收。

  显然,如果用通用IO口模拟UART总线,则需一个输入口,一个输出口。

  第三,从第二点明显可以看出,SPI和UART可以实现全双工,但I2C不行;

  第四,看看牛人们的意见吧!

  wudanyu:I2C线更少,我觉得比UART、SPI更为强大,但是技术上也更加麻烦些,因为I2C需要有双向IO的支持,而且使用上拉电阻,我觉得抗干扰能力较弱,一般用于同一板卡上芯片之间的通信,较少用于远距离通信。SPI实现要简单一些,UART需要固定的波特率,就是说两位数据的间隔要相等,而SPI则无所谓,因为它是有时钟的协议。

quickmouse:I2C的速度比SPI慢一点,协议比SPI复杂一点,但是连线也比标准的SPI要少。
  UART一帧可以传5/6/7/8位,IIC必须是8位。IIC和SPI都从最高位开始传。
  SPI用片选信号选择从机,IIC用地址选择从机。

  速率与传输距离
  SPI 速率与芯片有关,有的400K,有的到几兆
  rs232速率一般最大115200
  iic一般应用400K,CAN最高可到1M;
  spi和iic一般应用在芯片之间通讯,RS232可应用与设备与设备之间短距离通讯,最大15米,CAN适用设备间通讯,抗干扰能力强,理论上通讯距离可到10KM

用串口发送十个字节就丢失一个两个你会怎样检查;发送的时候对方设备不响应你该怎么办

  1.发送方自己发自己收,看有无问题
  2.接收方自己发自己收,看有无问题
  3.都没问题就是在发送过程中出现问题,
发送和接收的参数是否一致,比如波特率和奇偶校验位
打印发送buffer的数据和接收buffer的数据
  4.检查丢失的字节有什么规律
  5.逻辑分析仪查看发送和接收的时序,导出发送的数据和接收的数据
什么是逻辑分析仪?逻辑分析仪的参数、使用步骤和优势

内核链表为什么具有通用性?

  内核中由于要管理大量的设备,但是各种设备各不相同,必须将他们统一起来管理,于是内核设计者就想到了使用通用链表来处理,通用链表看似神秘,实际上就是双向循环链表,这个链表的每个节点都是只有指针域,没有任何数据域。

  使用通用链表的好处是:1.通用链表中每个节点中没有数据域,也就是说无论数据结构有多复杂在链表中只有前后级指针。2.如果一个数据结构(即是描述设备的设备结构体)想要用通用链表管理,只需要在结构体中包含节点的字段即可。3.双向链表可以从任意一个节点的前后遍历整个链表,遍历非常方便。4.使用循环链表使得可以不断地循环遍历管理节点,像进程的调度:操作系统会把就绪的进程放在一个管理进程的就绪队列的通用链表中管理起来,循环不断地,为他们分配时间片,获得cpu进行周而复始的进程调度。

通用(内核)链表详解

分配内存哪些函数?kmalloc有两个参数,各个作用是什么?

  见前Arm部分

有哪些锁,各自的效率问题?自选锁怎样实现的?

  锁是线程同步时的一个重要的工具,然而操作系统中包含了多种不同的锁,各种锁之间有什么不同呢?

信号量(Semaphore)

  信号量分为二元信号量和多元信号量,所谓二元信号量就是指该信号量只有两个状态,要么被占用,要么空闲;而多元信号量则允许同时被N个线程占有,超出N个外的占用请求将被阻塞。信号量是“系统级别”的,即同一个信号量可以被不同的进程访问。
因为信号量可以使等待资源线程进入休眠状态,因此适用于那些占用资源比较久的场合。如果共享资源的持有时间比较短,那就不适合使用信号量了,因为频繁的休眠、切换线程引起的开销要远大于信号量带来的那点优势。

互斥量 (Mutex)

  和二元信号量类似(一元信号量就是互斥体), 唯一不同的是,互斥量的获取和释放必须是在同一个线程中进行的。如果一个线程去释放一个并不是它所占有的互斥量是无效的。而信号量是可以由其它线程进行释放的。

临界区(Critical Section)

  术语中,把临界区的锁的获取称为进入临界区,而把锁的释放称为离开临界区。临界区是“进程级别”的,即它只在本进程的所有线程中可见,其它性质与互斥量相同(即谁获取,谁释放)

读写锁(Read-Write Lock)

  适 用于一个特定的场合。比如对于一段线程间访问的数据,如果程序大部分时间都是在读取,而只有很少的时间才会写入,那么使用前面几种锁时,每次读取也是同样 要申请锁的,而这时其它的线程就无法再对此段数据进行读取。可是,多个线程同时对一段数据进行读取时,是不存在同步问题的,那么这些读取时设置的锁就影响了程序的性能。读写锁的出现就是为了解决这个问题的。

  对于一个读写锁,有两种获取方式:共享(Shared)或独占 (Exclusive)。如果当前读写锁处于空闲状态,那么当多个线程同时以共享方式访问该读写锁时,都可以成功;而此时如果一个线程以独占的方式访问该 读写锁,那么它会等待所有共享访问都结束后才可以成功。在读写锁被独占访问的过程中,再次共享和独占请求访问该锁,都会进行等待状态。

  当临界区的一个文件可以被同时读取,但是并不能被同时读和写。如果一个线程在读,另一个线程在写,那么很可能会读取到错误的不完整的数据。读写自旋锁是可以允许对临界区的共享资源进行并发读操作的。但是并不允许多个线程并发读写操作。如果想要并发读写,就要用到了顺序锁。
顺序锁
  顺序锁是读写锁的优化版本,读写锁不允许同时读写,而使用顺序锁可以完成同时进行读和写的操作,但并不允许同时的写。虽然顺序锁可以同时进行读写操作,但并不建议这样,读取的过程并不能保证数据的完整性。

  顺序锁和读写锁都是自旋锁的一种,因为在等待自旋锁的时候处于“自旋”状态,因此锁的持有时间不能太长,一定要短,否则的话会降低系统性能。如果临界区比较大,运行时间比较长的话要选择其他的并发处理方式,比如信号量和互斥体。

条件变量(Condition Variable)

  条件变量相当于一种通知机制。多个线程可以设置等待该条件变量,而一旦另外的线程设置了该条件变量(相当于唤醒条件变量)后,多个等待的线程就可以继续执行了。

Linux内核硬中断 / 软中断的原理和实现

一、概述

  从本质上来讲,中断是一种电信号,当设备有某种事件发生时,它就会产生中断,通过总线把电信号发送给中断控制器。

  如果中断的线是激活的,中断控制器就把电信号发送给处理器的某个特定引脚。处理器于是立即停止自己正在做的事,跳到中断处理程序的入口点,进行中断处理。
  (1)硬中断
  由与系统相连的外设(比如网卡、硬盘)自动产生的。主要是用来通知操作系统系统外设状态的变化。比如当网卡收到数据包的时候,就会发出一个中断。我们通常所说的中断指的是硬中断(hardirq)。

  (2)软中断
  为了满足实时系统的要求,中断处理应该是越快越好。linux为了实现这个特点,当中断发生的时候,硬中断处理那些短时间就可以完成的工作,而将那些处理事件比较长的工作,放到中断之后来完成,也就是软中断(softirq)来完成。

  (3)中断嵌套

  Linux下硬中断是可以嵌套的,但是没有优先级的概念,也就是说任何一个新的中断都可以打断正在执行的中断,但同种中断除外。软中断不能嵌套,但相同类型的软中断可以在不同CPU上并行执行。

  (4)软中断指令
  int是软中断指令。
  中断向量表是中断号和中断处理函数地址的对应表。
  int n — 触发软中断n。相应的中断处理函数的地址为:中断向量表地址 + 4 * n。

  (5)硬中断和软中断的区别
  软中断是执行中断指令产生的,而硬中断是由外设引发的。
  硬中断的中断号是由中断控制器提供的,软中断的中断号由指令直接指出,无需使用中断控制器。
  硬中断是可屏蔽的,软中断不可屏蔽。
  硬中断处理程序要确保它能快速地完成任务,这样程序执行时才不会等待较长时间,称为上半部。
  软中断处理硬中断未完成的工作,是一种推后执行的机制,属于下半部。
二、开关

  (1)硬中断的开关

  简单禁止和激活当前处理器上的本地中断:

local_irq_disable();

local_irq_enable();

  保存本地中断系统状态下的禁止和激活:

unsigned long flags;

local_irq_save(flags);

local_irq_restore(flags);

  (2)软中断的开关
  禁止下半部,如softirq、tasklet和workqueue等:

local_bh_disable();

local_bh_enable();

  需要注意的是,禁止下半部时仍然可以被硬中断抢占。

  (3)判断中断状态

#define in_interrupt() (irq_count()) // 是否处于中断状态(硬中断或软中断)

#define in_irq() (hardirq_count()) // 是否处于硬中断

#define in_softirq() (softirq_count()) // 是否处于软中断

三、硬中断

  (1)注册中断处理函数

  注册中断处理函数:

/**
 * irq: 要分配的中断号
 * handler: 要注册的中断处理函数
 * flags: 标志(一般为0)
 * name: 设备名(dev->name)
 * dev: 设备(struct net_device *dev),作为中断处理函数的参数
 * 成功返回0
 */
 
int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, 
    const char *name, void *dev);
中断处理函数本身:


typedef irqreturn_t (*irq_handler_t) (int, void *);
 
/**
 * enum irqreturn
 * @IRQ_NONE: interrupt was not from this device
 * @IRQ_HANDLED: interrupt was handled by this device
 * @IRQ_WAKE_THREAD: handler requests to wake the handler thread
 */
enum irqreturn {
    IRQ_NONE,
    IRQ_HANDLED,
    IRQ_WAKE_THREAD,
};
typedef enum irqreturn irqreturn_t;
#define IRQ_RETVAL(x) ((x) != IRQ_NONE)

  (2)注销中断处理函数

/**
 * free_irq - free an interrupt allocated with request_irq
 * @irq: Interrupt line to free
 * @dev_id: Device identity to free
 *
 * Remove an interrupt handler. The handler is removed and if the
 * interrupt line is no longer in use by any driver it is disabled.
 * On a shared IRQ the caller must ensure the interrupt is disabled
 * on the card it drives before calling this function. The function does
 * not return until any executing interrupts for this IRQ have completed.
 * This function must not be called from interrupt context.
 */
 
void free_irq(unsigned int irq, void *dev_id);

四、软中断

  (1)定义

  软中断是一组静态定义的下半部接口,可以在所有处理器上同时执行,即使两个类型相同也可以。

  但一个软中断不会抢占另一个软中断,唯一可以抢占软中断的是硬中断。

  软中断由softirq_action结构体实现:

struct softirq_action {
    void (*action) (struct softirq_action *); /* 软中断的处理函数 */
};
目前已注册的软中断有10种,定义为一个全局数组:

static struct softirq_action softirq_vec[NR_SOFTIRQS];
 
enum {
    HI_SOFTIRQ = 0, /* 优先级高的tasklets */
    TIMER_SOFTIRQ, /* 定时器的下半部 */
    NET_TX_SOFTIRQ, /* 发送网络数据包 */
    NET_RX_SOFTIRQ, /* 接收网络数据包 */
    BLOCK_SOFTIRQ, /* BLOCK装置 */
    BLOCK_IOPOLL_SOFTIRQ,
    TASKLET_SOFTIRQ, /* 正常优先级的tasklets */
    SCHED_SOFTIRQ, /* 调度程序 */
    HRTIMER_SOFTIRQ, /* 高分辨率定时器 */
    RCU_SOFTIRQ, /* RCU锁定 */
    NR_SOFTIRQS /* 10 */
};

  (2)注册软中断处理函数

/**
 * @nr: 软中断的索引号
 * @action: 软中断的处理函数
 */
 
void open_softirq(int nr, void (*action) (struct softirq_action *))
{
    softirq_vec[nr].action = action;
}
例如:

open_softirq(NET_TX_SOFTIRQ, net_tx_action);

open_softirq(NET_RX_SOFTIRQ, net_rx_action);

  (3)触发软中断

&emsp;&emsp;调用raise_softirq()来触发软中断。

void raise_softirq(unsigned int nr)
{
    unsigned long flags;
    local_irq_save(flags);
    raise_softirq_irqoff(nr);
    local_irq_restore(flags);
}
 
/* This function must run with irqs disabled */
inline void rasie_softirq_irqsoff(unsigned int nr)
{
    __raise_softirq_irqoff(nr);
 
    /* If we\'re in an interrupt or softirq, we\'re done
     * (this also catches softirq-disabled code). We will
     * actually run the softirq once we return from the irq
     * or softirq.
     * Otherwise we wake up ksoftirqd to make sure we
     * schedule the softirq soon.
     */
    if (! in_interrupt()) /* 如果不处于硬中断或软中断 */
        wakeup_softirqd(void); /* 唤醒ksoftirqd/n进程 */
}
Percpu变量irq_cpustat_t中的__softirq_pending是等待处理的软中断的位图,通过设置此变量即可告诉内核该执行哪些软中断。

static inline void __rasie_softirq_irqoff(unsigned int nr)
{
    trace_softirq_raise(nr);
    or_softirq_pending(1UL << nr);
}
 
typedef struct {
    unsigned int __softirq_pending;
    unsigned int __nmi_count; /* arch dependent */
} irq_cpustat_t;
 
irq_cpustat_t irq_stat[];
#define __IRQ_STAT(cpu, member) (irq_stat[cpu].member)
#define or_softirq_pending(x) percpu_or(irq_stat.__softirq_pending, (x))
#define local_softirq_pending() percpu_read(irq_stat.__softirq_pending)

  唤醒ksoftirqd内核线程处理软中断。

static void wakeup_softirqd(void)
{
    /* Interrupts are disabled: no need to stop preemption */
    struct task_struct *tsk = __get_cpu_var(ksoftirqd);
 
    if (tsk && tsk->state != TASK_RUNNING)
        wake_up_process(tsk);
}

  在下列地方,待处理的软中断会被检查和执行:

  a. 从一个硬件中断代码处返回时

  b. 在ksoftirqd内核线程中

  c. 在那些显示检查和执行待处理的软中断的代码中,如网络子系统中

  而不管是用什么方法唤起,软中断都要在do_softirq()中执行。如果有待处理的软中断,do_softirq()会循环遍历每一个,调用它们的相应的处理程序。

  在中断处理程序中触发软中断是最常见的形式。中断处理程序执行硬件设备的相关操作,然后触发相应的软中断,最后退出。内核在执行完中断处理程序以后,马上就会调用do_softirq(),于是软中断开始执行中断处理程序完成剩余的任务。

  下面来看下do_softirq()的具体实现。

asmlinkage void do_softirq(void)
{
    __u32 pending;
    unsigned long flags;
 
    /* 如果当前已处于硬中断或软中断中,直接返回 */
    if (in_interrupt()) 
        return;
 
    local_irq_save(flags);
    pending = local_softirq_pending();
    if (pending) /* 如果有激活的软中断 */
        __do_softirq(); /* 处理函数 */
    local_irq_restore(flags);
}
/* We restart softirq processing MAX_SOFTIRQ_RESTART times,
 * and we fall back to softirqd after that.
 * This number has been established via experimentation.
 * The two things to balance is latency against fairness - we want
 * to handle softirqs as soon as possible, but they should not be
 * able to lock up the box.
 */
asmlinkage void __do_softirq(void)
{
    struct softirq_action *h;
    __u32 pending;
    /* 本函数能重复触发执行的次数,防止占用过多的cpu时间 */
    int max_restart = MAX_SOFTIRQ_RESTART;
    int cpu;
 
    pending = local_softirq_pending(); /* 激活的软中断位图 */
    account_system_vtime(current);
    /* 本地禁止当前的软中断 */
    __local_bh_disable((unsigned long)__builtin_return_address(0), SOFTIRQ_OFFSET);
    lockdep_softirq_enter(); /* current->softirq_context++ */
    cpu = smp_processor_id(); /* 当前cpu编号 */
 
restart:
    /* Reset the pending bitmask before enabling irqs */
    set_softirq_pending(0); /* 重置位图 */
    local_irq_enable();
    h = softirq_vec;
    do {
        if (pending & 1) {
            unsigned int vec_nr = h - softirq_vec; /* 软中断索引 */
            int prev_count = preempt_count();
            kstat_incr_softirqs_this_cpu(vec_nr);
 
            trace_softirq_entry(vec_nr);
            h->action(h); /* 调用软中断的处理函数 */
            trace_softirq_exit(vec_nr);
 
            if (unlikely(prev_count != preempt_count())) {
                printk(KERN_ERR "huh, entered softirq %u %s %p" "with preempt_count %08x,"
                    "exited with %08x?\n", vec_nr, softirq_to_name[vec_nr], h->action, prev_count,
                    preempt_count());
            }
            rcu_bh_qs(cpu);
        }
        h++;
        pending >>= 1;
    } while(pending);
 
    local_irq_disable();
    pending = local_softirq_pending();
    if (pending & --max_restart) /* 重复触发 */
        goto restart;
 
    /* 如果重复触发了10次了,接下来唤醒ksoftirqd/n内核线程来处理 */
    if (pending)
        wakeup_softirqd(); 
 
    lockdep_softirq_exit();
    account_system_vtime(current);
    __local_bh_enable(SOFTIRQ_OFFSET);
}

  (4)ksoftirqd内核线程

  内核不会立即处理重新触发的软中断。当大量软中断出现的时候,内核会唤醒一组内核线程来处理。这些线程的优先级最低(nice值为19),这能避免它们跟其它重要的任务抢夺资源。但它们最终肯定会被执行,所以这个折中的方案能够保证在软中断很多时用户程序不会因为得不到处理时间而处于饥饿状态,同时也保证过量的软中断最终会得到处理。

  每个处理器都有一个这样的线程,名字为ksoftirqd/n,n为处理器的编号。

static int run_ksoftirqd(void *__bind_cpu)
{
    set_current_state(TASK_INTERRUPTIBLE);
    current->flags |= PF_KSOFTIRQD; /* I am ksoftirqd */
 
    while(! kthread_should_stop()) {
        preempt_disable();
 
        if (! local_softirq_pending()) { /* 如果没有要处理的软中断 */
            preempt_enable_no_resched();
            schedule();
            preempt_disable():
        }
 
        __set_current_state(TASK_RUNNING);
 
        while(local_softirq_pending()) {
            /* Preempt disable stops cpu going offline.
             * If already offline, we\'ll be on wrong CPU: don\'t process.
             */
             if (cpu_is_offline(long)__bind_cpu))/* 被要求释放cpu */
                 goto wait_to_die;
 
            do_softirq(); /* 软中断的统一处理函数 */
 
            preempt_enable_no_resched();
            cond_resched();
            preempt_disable();
            rcu_note_context_switch((long)__bind_cpu);
        }
 
        preempt_enable();
        set_current_state(TASK_INTERRUPTIBLE);
    }
 
    __set_current_state(TASK_RUNNING);
    return 0;
 
wait_to_die:
    preempt_enable();
    /* Wait for kthread_stop */
    set_current_state(TASK_INTERRUPTIBLE);
    while(! kthread_should_stop()) {
        schedule();
        set_current_state(TASK_INTERRUPTIBLE);
    }
 
    __set_current_state(TASK_RUNNING);
    return 0;
}

版权声明:本文为dongxb原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/dongxb/p/14361045.html