Skip to content

I/O 接口与设备

I/O 设备与接口

通常 I/O 设备由两部分组成:机械部件和电子部件,机械部件即宏观上可见的部分,而电子部件称为设备控制器或适配器。

应用: 8251 UART

通用异步收发器,Universal asynchronous receiver transmitter (UART),包括了 RS232RS449RS423RS422RS485 等接口标准规范和总线标准规范,即 UART 是异步串行通信口的总称。它规定了通信口的电气特性、传输速率、连接特性和接口的机械特性等内容。

通常的 I/O 接口为了能够充当设备与计算机的桥梁,需要多个寄存器:数据寄存器、控制寄存器和状态寄存器

在通信过程中选择控制寄存器或数据缓冲区有三种方法:

  • 独立 I/O 端口:利用独立的端口来进行通信。好处是独立编址可以不占用内存空间,并且可以提高程序的可读性。
  • 内存映射 I/O:将控制寄存器或数据缓冲区映射到内存之中。优点是可以直接使用 C 语言编写驱动程序,但缺点是缓存设备控制寄存器会导致灾难。
  • 混合方案

通常 I/O 设备的分类

利用高级语言控制 I/O

实现存取

c
int peek(char *location) { return *location; }

void poke(char *location, char newval) { (*location) = newval; }

忙等(轮询)模式

c
current_char = mystring;

while (*current_char != ‘\0’) {
	poke(OUT_CHAR,*current_char); 
	while (peek(OUT_STATUS) != 0);
	current_char++;
}

将字符从输入设备拷贝到输出设备

c
while (TRUE) { 
    /* read */
	while (peek(IN_STATUS) == 0); 
    achar = (char)peek(IN_DATA); 
    /* write */ 
    poke(OUT_DATA,achar); 
    poke(OUT_STATUS,1);
	while (peek(OUT_STATUS) != 0); 
}

中断

很显然,忙等模式是非常低效的,因为 CPU 在测试设备时不能做其他工作。因此我们引入了中断机制。中断允许设备改变 CPU 中的控制流(即在周期中加入一个检查中断的步骤),然后调用子例程来处理设备。其主要的流程如下:

  • CPU 与设备之间通过 CPU 总线连接
  • CPU 和设备握手
  • 设备发出中断请求
  • CPU 在能够处理中断时确认中断

此外,有两种机制允许我们使中断更灵活:

  • 优先级:决定哪个中断首先得到 CPU。还提供屏蔽位,来筛选可识别的中断。

  • 向量:决定每种类型的中断调用什么代码。提高灵活性,提供定义服务于来自设备的请求的中断程序的能力。每种类型的中断对应向量表中的一个索引。

尽管中断在大多数情况下提高了效率,但还会产生额外的开销。这些开销包括中断处理程序执行时间、中断机制开销、寄存器的保存/恢复、流水线相关的开销等。

中断服务例程(ISR)的设计准则

  • 列出每个中断,并描述程序应该做什么,同时预算在什么情况下中断需要花费的时间。
  • 粗略估计每个中断的复杂度,ISR 的基本原则是保持处理程序简短。如果 ISR 太长,那就应该把一些步骤放到任务中执行。
  • 避免循环,避免冗长复杂的指令。
  • ISR 中尽快重新启用中断:先做硬件关键和不可重入的事情,然后执行中断启用指令,给其他 ISR 机会去做他们的事情。
  • 使用汇编语言而不是 C
  • 避免 NMI(不可避免的中断):这些中断只用于电源故障、系统关闭和即将发生的灾难,不可滥用。
  • 不要调试。不要启动各种复杂和不可预测的工具。如果 ISR 已经长到不可调试了,那说明违反了前文的简短原则。

丢中断了?

可以使用单个上行/下行计数器构建一个小电路,该计数器对每个中断进行计数,并减少每个中断确认的计数。如果计数器总是显示 01 的值,则一切正常。

一个经验法则将帮助最小化丢失的中断:在最早的安全点重新启用 ISR 中的中断

可重入

例程必须满足以下条件才称为可重入的:

  • 以原子方式使用所有共享变量,除非将每个共享变量分配给函数的特定实例;
  • 不调用不可重入的函数;
  • 不以非原子的方式使用硬件;

原子

第一条和最后一条规则都使用了“原子”这个词,这个词来自希腊语,意思是“不可分割的”。在计算世界中,“原子的”是指不能被中断的操作。一般来说,汇编语言中的一条语句是原子的。

例如,下面的一条汇编语句是原子的:

assembly
mov ax,bx

而下面的 C 代码的每个代码块都不是原子的

c
{temp=foobar; temp+=1; foobar=temp; }
{foobar+=1; }

如果要消除不可重入代码,可以采取以下措施:

  • 避免共享变量。全局变量是没完没了的调试问题和代码失败的根源。使用自动变量或动态分配内存。
  • 最常见的方法是在不可重入代码期间禁用中断
  • 引入信号量

对于不可重入的代码,需要注意竞态条件对执行时序的影响。下面是一个获取计时器的代码,显然它对返回值的精度要求很高,因此一个小小的中断就会造成很大的偏差。

c
int timer_hi; 
interrupt timer(){
	++timer_hi;
} 
long timer_read(void){
	unsigned int low, high;
	low = inword(hardware_register);	//此时,如果返回一个 0xFFFF,会造成溢出中断。
    //中断处理...
	high = timer_hi;	//中断处理完后,计时器的值已经变换。
	return (high<<16+low);	//位移会严重扩大误差。
}