I/O 接口与设备
I/O 设备与接口
通常 I/O 设备由两部分组成:机械部件和电子部件,机械部件即宏观上可见的部分,而电子部件称为设备控制器或适配器。
应用: 8251
通用异步收发器,Universal asynchronous receiver transmitter (RS232
、RS449
、RS423
、RS422
和 RS485
等接口标准规范和总线标准规范,即
通常的 I/O 接口为了能够充当设备与计算机的桥梁,需要多个寄存器:数据寄存器、控制寄存器和状态寄存器。
在通信过程中选择控制寄存器或数据缓冲区有三种方法:
- 独立 I/O 端口:利用独立的端口来进行通信。好处是独立编址可以不占用内存空间,并且可以提高程序的可读性。
- 内存映射 I/O:将控制寄存器或数据缓冲区映射到内存之中。优点是可以直接使用
C
语言编写驱动程序,但缺点是缓存设备控制寄存器会导致灾难。 - 混合方案
通常 I/O 设备的分类
利用高级语言控制 I/O
实现存取
int peek(char *location) { return *location; }
void poke(char *location, char newval) { (*location) = newval; }
2
3
忙等(轮询)模式
current_char = mystring;
while (*current_char != ‘\0’) {
poke(OUT_CHAR,*current_char);
while (peek(OUT_STATUS) != 0);
current_char++;
}
2
3
4
5
6
7
将字符从输入设备拷贝到输出设备
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);
}
2
3
4
5
6
7
8
9
中断
很显然,忙等模式是非常低效的,因为
与设备之间通过 总线连接 和设备握手- 设备发出中断请求
在能够处理中断时确认中断
此外,有两种机制允许我们使中断更灵活:
优先级:决定哪个中断首先得到
。还提供屏蔽位,来筛选可识别的中断。向量:决定每种类型的中断调用什么代码。提高灵活性,提供定义服务于来自设备的请求的中断程序的能力。每种类型的中断对应向量表中的一个索引。
尽管中断在大多数情况下提高了效率,但还会产生额外的开销。这些开销包括中断处理程序执行时间、中断机制开销、寄存器的保存/恢复、流水线相关的开销等。
中断服务例程( )的设计准则
- 列出每个中断,并描述程序应该做什么,同时预算在什么情况下中断需要花费的时间。
- 粗略估计每个中断的复杂度,
的基本原则是保持处理程序简短。如果 太长,那就应该把一些步骤放到任务中执行。 - 避免循环,避免冗长复杂的指令。
- 在
中尽快重新启用中断:先做硬件关键和不可重入的事情,然后执行中断启用指令,给其他 机会去做他们的事情。 - 使用汇编语言而不是
C
。 - 避免 NMI(不可避免的中断):这些中断只用于电源故障、系统关闭和即将发生的灾难,不可滥用。
- 不要调试。不要启动各种复杂和不可预测的工具。如果
已经长到不可调试了,那说明违反了前文的简短原则。
丢中断了?
可以使用单个上行/下行计数器构建一个小电路,该计数器对每个中断进行计数,并减少每个中断确认的计数。如果计数器总是显示
一个经验法则将帮助最小化丢失的中断:在最早的安全点重新启用
可重入
例程必须满足以下条件才称为可重入的:
- 以原子方式使用所有共享变量,除非将每个共享变量分配给函数的特定实例;
- 不调用不可重入的函数;
- 不以非原子的方式使用硬件;
原子
第一条和最后一条规则都使用了“原子”这个词,这个词来自希腊语,意思是“不可分割的”。在计算世界中,“原子的”是指不能被中断的操作。一般来说,汇编语言中的一条语句是原子的。
例如,下面的一条汇编语句是原子的:
mov ax,bx
而下面的 C
代码的每个代码块都不是原子的
{temp=foobar; temp+=1; foobar=temp; }
{foobar+=1; }
2
如果要消除不可重入代码,可以采取以下措施:
- 避免共享变量。全局变量是没完没了的调试问题和代码失败的根源。使用自动变量或动态分配内存。
- 最常见的方法是在不可重入代码期间禁用中断。
- 引入信号量。
对于不可重入的代码,需要注意竞态条件对执行时序的影响。下面是一个获取计时器的代码,显然它对返回值的精度要求很高,因此一个小小的中断就会造成很大的偏差。
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); //位移会严重扩大误差。
}
2
3
4
5
6
7
8
9
10
11