计算机系统输入输出接口与数据传输机制
引言
在计算机系统中,处理器与外部世界的交互是通过各种输入输出(I/O)接口实现的。无论是键盘输入、显示器输出,还是硬盘存储、网络通信,都离不开I/O接口的支持。本文将深入探讨计算机I/O接口的基本原理、架构设计和数据传输机制,并结合具体代码实例,帮助读者理解这一计算机系统中至关重要的组成部分。通过掌握I/O接口技术,我们可以更好地理解计算机系统的工作原理,为嵌入式系统开发、设备驱动程序编写以及系统优化提供坚实的理论基础。
7.1 输入输出接口基础知识
输入输出接口是计算机系统与外部设备进行数据交换的桥梁,它将处理器的电气信号、时序要求与外部设备的信号特性进行匹配,实现数据的可靠传输。
7.1.1 输入输出接口的核心功能
I/O接口担负着多种重要功能,确保计算机系统能够与外部设备进行高效、可靠的数据交换:
地址解码:识别CPU发出的地址,确定是否选中该I/O设备。
数据缓冲:暂存输入或输出的数据,解决设备与CPU之间的速度不匹配问题。
信号转换:将CPU的电气信号转换为外设可识别的信号,反之亦然。
控制和状态管理:管理设备的工作状态,执行CPU发出的控制命令。
错误检测:检测数据传输过程中的错误,确保数据完整性。
中断处理:生成中断请求信号,通知CPU有数据需要处理或其他事件发生。
以下是一个简单的C语言示例,演示I/O接口的基本功能概念:
c
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
// 模拟I/O接口的结构
typedef struct {
unsigned char data_register; // 数据寄存器
unsigned char status_register; // 状态寄存器
unsigned char control_register; // 控制寄存器
bool interrupt_enabled; // 中断使能标志
bool data_ready; // 数据准备好标志
bool busy; // 设备忙标志
} IOInterface;
// 初始化I/O接口
void init_io_interface(IOInterface* io) {
io->data_register = 0;
io->status_register = 0;
io->control_register = 0;
io->interrupt_enabled = false;
io->data_ready = false;
io->busy = false;
}
// 向设备写数据
bool write_data(IOInterface* io, unsigned char data) {
// 检查设备是否忙
if (io->busy) {
printf("设备忙,无法写入数据
");
return false;
}
// 设置忙标志
io->busy = true;
// 写入数据到数据寄存器
io->data_register = data;
// 模拟设备处理数据
printf("写入数据: 0x%02X
", data);
// 清除忙标志
io->busy = false;
// 设置状态寄存器中的"写完成"位
io->status_register |= 0x02;
// 如果中断使能,触发中断
if (io->interrupt_enabled) {
printf("生成写完成中断
");
}
return true;
}
// 从设备读数据
bool read_data(IOInterface* io, unsigned char* data) {
// 检查数据是否准备好
if (!io->data_ready) {
printf("数据未准备好,无法读取
");
return false;
}
// 读取数据寄存器
*data = io->data_register;
printf("读取数据: 0x%02X
", *data);
// 清除数据准备好标志
io->data_ready = false;
// 清除状态寄存器中的"数据可用"位
io->status_register &= ~0x01;
return true;
}
// 模拟设备生成新数据
void device_generate_data(IOInterface* io, unsigned char data) {
// 设置数据寄存器
io->data_register = data;
// 设置数据准备好标志
io->data_ready = true;
// 设置状态寄存器中的"数据可用"位
io->status_register |= 0x01;
// 如果中断使能,生成数据可用中断
if (io->interrupt_enabled) {
printf("生成数据可用中断
");
}
}
// 设置控制寄存器
void set_control_register(IOInterface* io, unsigned char value) {
io->control_register = value;
// 解析控制寄存器的各个位
io->interrupt_enabled = (value & 0x01) ? true : false;
printf("设置控制寄存器: 0x%02X (中断%s)
",
value, io->interrupt_enabled ? "使能" : "禁用");
}
// 读取状态寄存器
unsigned char get_status_register(IOInterface* io) {
printf("读取状态寄存器: 0x%02X
", io->status_register);
return io->status_register;
}
int main() {
IOInterface io_device;
unsigned char data;
// 初始化I/O接口
init_io_interface(&io_device);
printf("I/O接口功能演示
");
printf("================
");
// 设置控制寄存器,启用中断
set_control_register(&io_device, 0x01);
// 尝试读取数据(应该失败,因为还没有数据)
read_data(&io_device, &data);
// 模拟设备生成新数据
printf("
设备生成新数据
");
device_generate_data(&io_device, 0x42);
// 读取状态
get_status_register(&io_device);
// 读取数据
printf("
");
read_data(&io_device, &data);
// 写入数据到设备
printf("
");
write_data(&io_device, 0xA5);
// 读取状态
get_status_register(&io_device);
return 0;
}
7.1.2 输入输出接口的典型结构
I/O接口通常包含多种硬件组件,它们协同工作,实现处理器与外部设备之间的数据交换。一个典型的I/O接口结构包括:
数据寄存器:存储待传输的数据,可以是输入寄存器或输出寄存器。
状态寄存器:反映设备的当前工作状态,如设备忙、数据准备好、错误状态等。
控制寄存器:存储控制设备工作方式的命令和参数,如工作模式、中断使能等。
地址译码器:识别CPU发出的地址信号,判断是否选中该I/O接口。
数据缓冲器:暂存输入输出数据,协调CPU与设备间的速度差异。
控制逻辑:根据CPU命令和设备状态,产生控制信号,协调数据传输过程。
以下是一个更完整的结构示例,模拟一个简单的串行通信接口:
c
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
// 模拟串行通信接口的结构
typedef struct {
// 数据寄存器
unsigned char tx_data_register; // 发送数据寄存器
unsigned char rx_data_register; // 接收数据寄存器
// 状态寄存器位定义
union {
unsigned char value; // 状态寄存器的完整值
struct {
unsigned char rx_ready : 1; // 接收数据准备好
unsigned char tx_empty : 1; // 发送寄存器空
unsigned char parity_error : 1; // 奇偶校验错误
unsigned char framing_error : 1; // 帧错误
unsigned char overrun_error : 1; // 数据溢出错误
unsigned char reserved : 3; // 保留位
} bits;
} status_register;
// 控制寄存器位定义
union {
unsigned char value; // 控制寄存器的完整值
struct {
unsigned char rx_enable : 1; // 接收使能
unsigned char tx_enable : 1; // 发送使能
unsigned char rx_int_enable : 1; // 接收中断使能
unsigned char tx_int_enable : 1; // 发送中断使能
unsigned char parity_enable : 1; // 奇偶校验使能
unsigned char parity_odd : 1; // 1=奇校验,0=偶校验
unsigned char stop_bits : 1; // 1=2个停止位,0=1个停止位
unsigned char char_length : 1; // 1=8位,0=7位
} bits;
} control_register;
// 波特率设置寄存器
unsigned char baud_divisor_low; // 波特率除数低字节
unsigned char baud_divisor_high; // 波特率除数高字节
// 内部缓冲区
unsigned char rx_buffer[16]; // 接收缓冲区
int rx_buffer_head; // 接收缓冲区头指针
int rx_buffer_tail; // 接收缓冲区尾指针
unsigned char tx_buffer[16]; // 发送缓冲区
int tx_buffer_head; // 发送缓冲区头指针
int tx_buffer_tail; // 发送缓冲区尾指针
// 内部状态标志
bool transmitting; // 正在发送标志
bool receiving; // 正在接收标志
} SerialInterface;
// 初始化串行接口
void init_serial_interface(SerialInterface* serial) {
// 清零所有寄存器
serial->tx_data_register = 0;
serial->rx_data_register = 0;
serial->status_register.value = 0;
serial->control_register.value = 0;
serial->baud_divisor_low = 0;
serial->baud_divisor_high = 0;
// 初始化缓冲区
memset(serial->rx_buffer, 0, sizeof(serial->rx_buffer));
memset(serial->tx_buffer, 0, sizeof(serial->tx_buffer));
serial->rx_buffer_head = 0;
serial->rx_buffer_tail = 0;
serial->tx_buffer_head = 0;
serial->tx_buffer_tail = 0;
// 设置初始状态
serial->status_register.bits.tx_empty = 1; // 发送寄存器为空
serial->transmitting = false;
serial->receiving = false;
}
// 写入控制寄存器
void write_control_register(SerialInterface* serial, unsigned char value) {
serial->control_register.value = value;
printf("设置控制寄存器: 0x%02X
", value);
printf(" 接收使能: %s
", serial->control_register.bits.rx_enable ? "是" : "否");
printf(" 发送使能: %s
", serial->control_register.bits.tx_enable ? "是" : "否");
printf(" 接收中断: %s
", serial->control_register.bits.rx_int_enable ? "使能" : "禁用");
printf(" 发送中断: %s
", serial->control_register.bits.tx_int_enable ? "使能" : "禁用");
printf(" 校验位: %s
", serial->control_register.bits.parity_enable ? "使能" : "禁用");
if (serial->control_register.bits.parity_enable) {
printf(" 校验模式: %s
", serial->control_register.bits.parity_odd ? "奇校验" : "偶校验");
}
printf(" 停止位: %d位
", serial->control_register.bits.stop_bits ? 2 : 1);
printf(" 字符长度: %d位
", serial->control_register.bits.char_length ? 8 : 7);
}
// 设置波特率
void set_baud_rate(SerialInterface* serial, unsigned short divisor) {
serial->baud_divisor_low = divisor & 0xFF;
serial->baud_divisor_high = (divisor >> 8) & 0xFF;
// 计算波特率(假设输入时钟为1.8432MHz)
unsigned int baud_rate = 1843200 / (16 * divisor);
printf("设置波特率: %u bps (除数: %u)
", baud_rate, divisor);
}
// 读取状态寄存器
unsigned char read_status_register(SerialInterface* serial) {
printf("读取状态寄存器: 0x%02X
", serial->status_register.value);
printf(" 接收数据可用: %s
", serial->status_register.bits.rx_ready ? "是" : "否");
printf(" 发送寄存器空: %s
", serial->status_register.bits.tx_empty ? "是" : "否");
printf(" 奇偶校验错误: %s
", serial->status_register.bits.parity_error ? "是" : "否");
printf(" 帧错误: %s
", serial->status_register.bits.framing_error ? "是" : "否");
printf(" 溢出错误: %s
", serial->status_register.bits.overrun_error ? "是" : "否");
return serial->status_register.value;
}
// 向发送数据寄存器写入数据
bool write_tx_data(SerialInterface* serial, unsigned char data) {
// 检查发送是否使能
if (!serial->control_register.bits.tx_enable) {
printf("发送未使能,无法发送数据
");
return false;
}
// 检查发送寄存器是否为空
if (!serial->status_register.bits.tx_empty) {
printf("发送寄存器满,等待发送完成
");
return false;
}
// 写入数据到发送寄存器
serial->tx_data_register = data;
serial->status_register.bits.tx_empty = 0; // 发送寄存器不再为空
// 模拟开始发送过程
serial->transmitting = true;
printf("开始发送数据: 0x%02X ('%c')
", data,
(data >= 32 && data <= 126) ? data : '.');
// 模拟发送完成
serial->status_register.bits.tx_empty = 1; // 发送完成,寄存器再次为空
serial->transmitting = false;
printf("数据发送完成
");
// 如果发送中断使能,触发中断
if (serial->control_register.bits.tx_int_enable) {
printf("生成发送完成中断
");
}
return true;
}
// 模拟接收数据
void simulate_data_reception(SerialInterface* serial, unsigned char data) {
// 检查接收是否使能
if (!serial->control_register.bits.rx_enable) {
printf("接收未使能,丢弃接收到的数据
");
return;
}
// 检查接收寄存器是否已满(有未读取的数据)
if (serial->status_register.bits.rx_ready) {
printf("接收寄存器已满,设置溢出错误标志
");
serial->status_register.bits.overrun_error = 1;
return;
}
// 模拟接收过程
serial->receiving = true;
printf("开始接收数据...
");
// 存储接收到的数据
serial->rx_data_register = data;
serial->status_register.bits.rx_ready = 1; // 设置数据可用标志
serial->receiving = false;
printf("接收完成: 0x%02X ('%c')
", data,
(data >= 32 && data <= 126) ? data : '.');
// 如果接收中断使能,触发中断
if (serial->control_register.bits.rx_int_enable) {
printf("生成接收数据中断
");
}
}
// 读取接收数据寄存器
unsigned char read_rx_data(SerialInterface* serial) {
// 检查是否有数据可读
if (!serial->status_register.bits.rx_ready) {
printf("警告: 接收寄存器无数据,读取的值可能无效
");
return 0;
}
// 读取数据
unsigned char data = serial->rx_data_register;
// 清除数据可用标志
serial->status_register.bits.rx_ready = 0;
// 清除错误标志
serial->status_register.bits.parity_error = 0;
serial->status_register.bits.framing_error = 0;
serial->status_register.bits.overrun_error = 0;
printf("读取接收数据: 0x%02X ('%c')
", data,
(data >= 32 && data <= 126) ? data : '.');
return data;
}
int main() {
SerialInterface serial_port;
printf("串行通信接口模拟
");
printf("==============
");
// 初始化串行接口
init_serial_interface(&serial_port);
// 配置串行接口
write_control_register(&serial_port, 0x9B); // 启用发送和接收,带中断,8位数据,1个停止位,奇校验
set_baud_rate(&serial_port, 96); // 设置波特率为1200bps (1843200/(16*96))
printf("
");
// 发送一个字符
write_tx_data(&serial_port, 'A');
printf("
");
// 模拟接收到一个字符
simulate_data_reception(&serial_port, 'B');
printf("
");
// 读取状态寄存器
read_status_register(&serial_port);
printf("
");
// 读取接收到的数据
read_rx_data(&serial_port);
printf("
");
// 再次读取状态寄存器
read_status_register(&serial_port);
return 0;
}
7.1.3 输入输出端口的编址方式
计算机系统中,I/O端口的编址方式主要有两种:独立编址和内存映射编址。
独立编址(I/O映射):
使用专门的I/O地址空间,与内存地址空间分开需要特殊的I/O指令(如x86的IN/OUT指令)访问I/O端口I/O地址空间较小,但简化了硬件设计
内存映射编址:
I/O设备的寄存器映射到内存地址空间使用普通内存访问指令(如MOV)操作I/O设备可以使用完整的内存寻址能力,提供更大的地址空间可以对I/O寄存器进行更复杂的操作
以下是使用这两种方式访问I/O设备的示例:
c
#include <stdio.h>
#include <stdlib.h>
// 模拟独立编址I/O操作(x86架构)
#ifdef __GNUC__
static inline unsigned char inb(unsigned short port) {
unsigned char value;
__asm__ __volatile__ ("inb %1, %0" : "=a"(value) : "Nd"(port));
return value;
}
static inline void outb(unsigned short port, unsigned char value) {
__asm__ __volatile__ ("outb %0, %1" : : "a"(value), "Nd"(port));
}
#else
// 如果不是GCC,提供模拟函数
unsigned char inb(unsigned short port) {
printf("从端口0x%04X读取数据
", port);
return 0; // 模拟返回值
}
void outb(unsigned short port, unsigned char value) {
printf("向端口0x%04X写入数据0x%02X
", port, value);
}
#endif
// 模拟内存映射I/O
volatile unsigned char* create_memory_mapped_io(size_t size) {
// 在实际系统中,这通常是通过mmap或类似函数映射物理设备内存
volatile unsigned char* memory = (volatile unsigned char*)malloc(size);
printf("创建%zu字节的内存映射I/O区域
", size);
return memory;
}
// 释放模拟的内存映射I/O
void free_memory_mapped_io(volatile unsigned char* memory) {
free((void*)memory);
printf("释放内存映射I/O区域
");
}
int main() {
printf("I/O端口编址方式演示
");
printf("=================
");
// 1. 独立编址I/O示例
printf("独立编址I/O操作:
");
printf("--------------
");
// 定义一些模拟的I/O端口
const unsigned short CONTROL_PORT = 0x3F8; // 串行端口控制寄存器
const unsigned short DATA_PORT = 0x3F8; // 串行端口数据寄存器
const unsigned short STATUS_PORT = 0x3FD; // 串行端口状态寄存器
// 读取状态
unsigned char status = inb(STATUS_PORT);
printf("读取状态寄存器: 0x%02X
", status);
// 写入控制值
outb(CONTROL_PORT, 0x80); // 设置DLAB位,允许设置波特率
printf("写入控制寄存器完成
");
// 写入数据
outb(DATA_PORT, 'A');
printf("写入数据完成
");
printf("
");
// 2. 内存映射I/O示例
printf("内存映射I/O操作:
");
printf("--------------
");
// 创建一个模拟的内存映射I/O区域(例如,图形卡内存)
volatile unsigned char* vga_memory = create_memory_mapped_io(0x10000); // 64KB
// 定义寄存器偏移
const int REG_CONTROL = 0x00;
const int REG_STATUS = 0x04;
const int REG_DATA = 0x08;
const int FRAME_BUFFER = 0x1000;
// 写入控制寄存器
vga_memory[REG_CONTROL] = 0x01; // 开启显示
printf("写入控制寄存器: 0x%02X
", vga_memory[REG_CONTROL]);
// 读取状态寄存器
unsigned char vga_status = vga_memory[REG_STATUS];
printf("读取状态寄存器: 0x%02X
", vga_status);
// 写入数据寄存器
vga_memory[REG_DATA] = 0xA5;
printf("写入数据寄存器: 0x%02X
", vga_memory[REG_DATA]);
// 写入帧缓冲区(模拟在屏幕上显示字符)
printf("写入屏幕缓冲区...
");
const char* message = "Hello, Memory-Mapped I/O!";
for (int i = 0; message[i] != ''; i++) {
vga_memory[FRAME_BUFFER + i * 2] = message[i]; // 字符
vga_memory[FRAME_BUFFER + i * 2 + 1] = 0x07; // 属性(白色文本)
}
printf("屏幕上显示: %s
", message);
// 释放模拟的内存映射I/O
free_memory_mapped_io(vga_memory);
return 0;
}
7.1.4 8088/8086处理器的输入输出指令系统
8088/8086处理器提供了专门的I/O指令,用于独立编址方式的I/O操作。这些指令包括IN和OUT及其变体,允许处理器直接与I/O端口进行数据交换。
主要I/O指令:
IN AL, DX – 从DX指定的端口读取一个字节到ALIN AX, DX – 从DX指定的端口读取一个字到AXOUT DX, AL – 将AL中的字节输出到DX指定的端口OUT DX, AX – 将AX中的字输出到DX指定的端口IN AL, imm8 – 从立即数指定的端口读取一个字节到ALOUT imm8, AL – 将AL中的字节输出到立即数指定的端口
以下是一个使用8086汇编语言的I/O操作示例:
asm
; 8086汇编语言I/O操作示例
; 可以使用NASM等汇编器编译
section .data
message db 'I/O操作示例', 0
section .text
global _start
_start:
; 使用直接端口地址的I/O操作
; 从端口0x60读取键盘扫描码
in al, 0x60
; 将读取的值写入到端口0x80(通常用于延时)
out 0x80, al
; 使用变量端口地址的I/O操作
; 设置DX为串口数据端口
mov dx, 0x3F8
; 从串口读取一个字节
in al, dx
; 向串口写入一个字符'A'
mov al, 'A'
out dx, al
; 读取串口状态寄存器
mov dx, 0x3FD
in al, dx
; 字I/O操作
; 设置DX为16位I/O端口
mov dx, 0x200
; 读取一个字(16位)
in ax, dx
; 写入一个字
mov ax, 0x1234
out dx, ax
; 字符串I/O操作(使用REP前缀)
; 设置源和目标
mov si, message ; 源地址
mov cx, 13 ; 字符数
mov dx, 0x3F8 ; 串口数据端口
io_loop:
mov al, [si] ; 获取下一个字符
out dx, al ; 输出到串口
inc si ; 增加源指针
loop io_loop ; 减少计数并循环
; 程序退出
mov eax, 1 ; 系统调用号(exit)
xor ebx, ebx ; 退出码0
int 0x80 ; 调用内核
下面是一个使用C语言内联汇编实现的8086 I/O操作示例:
c
#include <stdio.h>
#include <stdlib.h>
// 模拟8086/8088的I/O指令
// 从指定端口读取一个字节
unsigned char io_read_byte(unsigned short port) {
unsigned char value;
#ifdef __GNUC__
__asm__ __volatile__ (
"in %0, %1"
: "=a" (value) /* 输出操作数 */
: "Nd" (port) /* 输入操作数 */
);
#else
// 模拟读取操作
printf("从端口0x%04X读取一个字节
", port);
value = 0xAA; // 模拟返回值
#endif
return value;
}
// 向指定端口写入一个字节
void io_write_byte(unsigned short port, unsigned char value) {
#ifdef __GNUC__
__asm__ __volatile__ (
"out %0, %1"
: /* 无输出操作数 */
: "Nd" (port), /* 端口号 */
"a" (value) /* 值在AL中 */
);
#else
// 模拟写入操作
printf("向端口0x%04X写入字节0x%02X
", port, value);
#endif
}
// 从指定端口读取一个字(16位)
unsigned short io_read_word(unsigned short port) {
unsigned short value;
#ifdef __GNUC__
__asm__ __volatile__ (
"in %0, %1"
: "=a" (value) /* 输出到AX */
: "Nd" (port) /* 输入端口号 */
);
#else
// 模拟读取操作
printf("从端口0x%04X读取一个字
", port);
value = 0xBBCC; // 模拟返回值
#endif
return value;
}
// 向指定端口写入一个字(16位)
void io_write_word(unsigned short port, unsigned short value) {
#ifdef __GNUC__
__asm__ __volatile__ (
"out %0, %1"
: /* 无输出操作数 */
: "Nd" (port), /* 端口号 */
"a" (value) /* 值在AX中 */
);
#else
// 模拟写入操作
printf("向端口0x%04X写入字0x%04X
", port, value);
#endif
}
// 使用REP INS指令从端口读取多个字节到内存
void io_read_string(unsigned short port, void* buffer, unsigned int count) {
#ifdef __GNUC__
__asm__ __volatile__ (
"rep insb"
: /* 无显式输出操作数 */
: "d" (port), /* 端口号在DX */
"D" (buffer), /* 目标地址在EDI */
"c" (count) /* 计数在ECX */
: "memory" /* 告诉编译器内存被修改 */
);
#else
// 模拟字符串读取操作
printf("从端口0x%04X读取%u字节到内存
", port, count);
unsigned char* buf = (unsigned char*)buffer;
for (unsigned int i = 0; i < count; i++) {
buf[i] = 0xCC; // 填充模拟值
}
#endif
}
// 使用REP OUTS指令将内存数据写入端口
void io_write_string(unsigned short port, const void* buffer, unsigned int count) {
#ifdef __GNUC__
__asm__ __volatile__ (
"rep outsb"
: /* 无显式输出操作数 */
: "d" (port), /* 端口号在DX */
"S" (buffer), /* 源地址在ESI */
"c" (count) /* 计数在ECX */
);
#else
// 模拟字符串写入操作
const unsigned char* buf = (const unsigned char*)buffer;
printf("向端口0x%04X写入%u字节: ", port, count);
for (unsigned int i = 0; i < (count > 8 ? 8 : count); i++) {
printf("%02X ", buf[i]);
}
if (count > 8) printf("...");
printf("
");
#endif
}
int main() {
printf("8086/8088 I/O指令演示
");
printf("====================
");
// 定义一些常用的I/O端口(仅用于演示)
const unsigned short KEYBOARD_DATA = 0x60; // 键盘数据端口
const unsigned short SERIAL_DATA = 0x3F8; // 串行端口数据
const unsigned short SERIAL_STATUS = 0x3FD; // 串行端口状态
const unsigned short PRINTER_DATA = 0x378; // 并行打印机数据
const unsigned short PRINTER_STATUS = 0x379; // 并行打印机状态
// 1. 基本字节I/O操作
printf("基本字节I/O操作:
");
// 从键盘端口读取一个字节
unsigned char kb_data = io_read_byte(KEYBOARD_DATA);
printf("键盘数据: 0x%02X
", kb_data);
// 向串行端口写入一个字符
io_write_byte(SERIAL_DATA, 'A');
printf("
");
// 2. 字(16位)I/O操作
printf("字(16位)I/O操作:
");
// 读取一个16位值
unsigned short word_value = io_read_word(0x200);
printf("读取的16位值: 0x%04X
", word_value);
// 写入一个16位值
io_write_word(0x200, 0x1234);
printf("
");
// 3. 字符串I/O操作
printf("字符串I/O操作:
");
// 准备缓冲区
unsigned char buffer[32];
const char* message = "Hello, I/O World!";
// 从串口读取多个字节
io_read_string(SERIAL_DATA, buffer, 16);
// 显示读取的数据
printf("读取的数据: ");
for (int i = 0; i < 16; i++) {
printf("%02X ", buffer[i]);
}
printf("
");
// 将一个字符串写入到打印机端口
io_write_string(PRINTER_DATA, message, strlen(message));
return 0;
}
7.1.5 输入输出地址的解码机制
I/O地址解码是I/O接口设计中的关键环节,它决定了当CPU发出特定地址时,哪个I/O设备会被选中。地址解码通常通过地址译码器或可编程逻辑器件实现。
地址解码的基本方法:
完全解码:
每个I/O设备仅响应分配给它的特定地址实现较为复杂,但不会有地址冲突
部分解码:
仅解码地址的一部分位简化硬件设计,但可能导致地址别名(多个地址映射到同一设备)
可编程解码:
使用可编程逻辑设备(如PAL、GAL)实现灵活的地址解码允许系统配置和重新映射I/O地址
以下是一个模拟I/O地址解码器的C语言示例:
c
#include <stdio.h>
#include <stdbool.h>
#include <stdint.h>
// I/O设备定义
typedef struct {
char name[32]; // 设备名称
uint16_t base_address; // 基地址
uint16_t address_mask; // 地址掩码(用于部分解码)
uint16_t address_range; // 地址范围大小
} IODevice;
// 完全解码检查
bool is_address_fully_decoded(uint16_t address, IODevice* device) {
return (address >= device->base_address &&
address < device->base_address + device->address_range);
}
// 部分解码检查
bool is_address_partially_decoded(uint16_t address, IODevice* device) {
// 只检查掩码中的有效位
return ((address & device->address_mask) ==
(device->base_address & device->address_mask));
}
// 模拟地址解码器
IODevice* decode_io_address(uint16_t address, IODevice* devices, int device_count, bool use_partial_decoding) {
for (int i = 0; i < device_count; i++) {
if (use_partial_decoding) {
if (is_address_partially_decoded(address, &devices[i])) {
return &devices[i];
}
} else {
if (is_address_fully_decoded(address, &devices[i])) {
return &devices[i];
}
}
}
return NULL; // 没有找到匹配的设备
}
// 打印设备信息
void print_device_info(IODevice* device) {
if (device) {
printf("设备: %s
", device->name);
printf(" 基地址: 0x%04X
", device->base_address);
printf(" 地址范围: 0x%04X - 0x%04X
",
device->base_address,
device->base_address + device->address_range - 1);
printf(" 地址掩码: 0x%04X
", device->address_mask);
} else {
printf("未找到匹配的设备
");
}
}
int main() {
// 定义一些I/O设备
IODevice devices[5] = {
{"串行端口COM1", 0x3F8, 0xFFF8, 8},
{"串行端口COM2", 0x2F8, 0xFFF8, 8},
{"并行端口LPT1", 0x378, 0xFFC0, 8},
{"键盘控制器", 0x060, 0xFFF0, 16},
{"实时时钟", 0x070, 0xFFF0, 16}
};
printf("I/O地址解码示例
");
printf("===============
");
// 测试地址
uint16_t test_addresses[] = {
0x3F8, // COM1数据端口
0x3F9, // COM1中断使能寄存器
0x3FF, // 部分解码可能匹配COM1
0x378, // LPT1数据端口
0x37A, // LPT1控制寄存器
0x060, // 键盘数据端口
0x061, // 键盘控制寄存器
0x040, // 计时器寄存器(未定义在我们的列表中)
};
int address_count = sizeof(test_addresses) / sizeof(test_addresses[0]);
// 测试完全解码
printf("完全解码模式:
");
printf("------------
");
for (int i = 0; i < address_count; i++) {
printf("地址0x%04X: ", test_addresses[i]);
IODevice* device = decode_io_address(test_addresses[i], devices, 5, false);
print_device_info(device);
printf("
");
}
// 测试部分解码
printf("
部分解码模式:
");
printf("------------
");
for (int i = 0; i < address_count; i++) {
printf("地址0x%04X: ", test_addresses[i]);
IODevice* device = decode_io_address(test_addresses[i], devices, 5, true);
print_device_info(device);
printf("
");
}
// 展示地址别名问题
printf("
地址别名示例(部分解码):
");
printf("----------------------
");
// 对于COM1(基地址0x3F8,掩码0xFFF8),以下地址都会匹配
for (int offset = 0; offset < 8; offset++) {
uint16_t alias_address = 0x3F8 + offset + 0x0100; // 加上0x100创建一个别名
printf("别名地址0x%04X: ", alias_address);
IODevice* device = decode_io_address(alias_address, devices, 5, true);
if (device) {
printf("匹配设备 %s (部分解码)
", device->name);
} else {
printf("未匹配任何设备
");
}
}
return 0;
}
7.1.6 数据传输方式概述
在计算机系统中,处理器与I/O设备之间的数据传输有几种基本方式,每种方式都有其特定的应用场景和优缺点。
四种基本数据传输方式:
无条件传送方式(程序控制I/O):
CPU通过轮询I/O设备状态,直接控制数据传输简单但效率低,占用CPU时间
查询传送方式:
CPU定期检查I/O设备状态,在设备就绪时进行数据传输比无条件方式效率略高,但仍占用大量CPU时间
中断传送方式:
I/O设备就绪时向CPU发出中断请求CPU暂停当前程序,执行中断服务程序处理I/O提高CPU利用率,适合中低速I/O设备
直接内存访问(DMA)方式:
数据传输由DMA控制器管理,无需CPU参与每次传输CPU只需初始化DMA控制器,然后继续其他任务高效率,适合高速I/O设备和大量数据传输
以下是一个展示不同数据传输方式的C语言示例:
c
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
// 模拟I/O设备
typedef struct {
char name[32]; // 设备名称
bool busy; // 设备忙状态
bool data_ready; // 数据就绪标志
bool interrupt_pending; // 中断挂起标志
unsigned char* buffer; // 设备缓冲区
int buffer_size; // 缓冲区大小
int buffer_pos; // 当前缓冲区位置
int transfer_speed; // 传输速度(字节/秒)
clock_t last_operation_time; // 上次操作时间
} IODevice;
// 初始化I/O设备
IODevice* init_io_device(const char* name, int buffer_size, int speed) {
IODevice* device = (IODevice*)malloc(sizeof(IODevice));
strncpy(device->name, name, sizeof(device->name) - 1);
device->name[sizeof(device->name) - 1] = '';
device->busy = false;
device->data_ready = false;
device->interrupt_pending = false;
device->buffer = (unsigned char*)malloc(buffer_size);
device->buffer_size = buffer_size;
device->buffer_pos = 0;
device->transfer_speed = speed;
device->last_operation_time = clock();
memset(device->buffer, 0, buffer_size);
return device;
}
// 释放I/O设备资源
void free_io_device(IODevice* device) {
free(device->buffer);
free(device);
}
// 模拟设备操作时间
void simulate_device_operation(IODevice* device, int bytes) {
// 计算操作需要的时间(微秒)
int operation_time = (bytes * 1000000) / device->transfer_speed;
// 如果是真实环境,可能会真正等待,但这里只是模拟
printf("设备 '%s' 操作中... (%d微秒)
", device->name, operation_time);
// 在实际应用中可能使用以下代码真正等待
// usleep(operation_time);
// 更新操作时间
device->last_operation_time = clock();
}
// 检查设备是否准备好
bool is_device_ready(IODevice* device) {
// 如果设备忙,检查是否操作已完成
if (device->busy) {
// 简化模拟:假设设备总是很快就准备好
device->busy = false;
device->data_ready = true;
// 如果设备支持中断,设置中断挂起标志
device->interrupt_pending = true;
}
return device->data_ready;
}
// 模拟中断处理器
typedef struct {
bool interrupt_enabled; // 中断使能标志
bool interrupt_in_service; // 中断服务中标志
} InterruptController;
// 初始化中断控制器
InterruptController* init_interrupt_controller() {
InterruptController* controller = (InterruptController*)malloc(sizeof(InterruptController));
controller->interrupt_enabled = true;
controller->interrupt_in_service = false;
return controller;
}
// 检查中断请求
bool check_interrupt_request(InterruptController* controller, IODevice* devices[], int device_count) {
if (!controller->interrupt_enabled || controller->interrupt_in_service) {
return false;
}
for (int i = 0; i < device_count; i++) {
if (devices[i]->interrupt_pending) {
return true;
}
}
return false;
}
// 获取触发中断的设备
IODevice* get_interrupt_device(IODevice* devices[], int device_count) {
for (int i = 0; i < device_count; i++) {
if (devices[i]->interrupt_pending) {
return devices[i];
}
}
return NULL;
}
// 执行中断服务
void service_interrupt(InterruptController* controller, IODevice* device) {
if (!device) return;
printf("处理设备 '%s' 的中断
", device->name);
// 设置中断服务中标志
controller->interrupt_in_service = true;
// 清除设备的中断挂起标志
device->interrupt_pending = false;
// 模拟中断服务例程的操作
// 例如,读取就绪的数据
if (device->data_ready) {
printf(" 读取设备数据: 0x%02X
", device->buffer[device->buffer_pos]);
device->buffer_pos = (device->buffer_pos + 1) % device->buffer_size;
device->data_ready = false;
}
// 完成中断服务
controller->interrupt_in_service = false;
}
// DMA控制器结构
typedef struct {
bool active; // DMA活动状态
unsigned char* source; // 源地址
unsigned char* destination; // 目标地址
int count; // 传输计数
int position; // 当前位置
int transfer_speed; // 传输速度(字节/秒)
} DMAController;
// 初始化DMA控制器
DMAController* init_dma_controller(int speed) {
DMAController* dma = (DMAController*)malloc(sizeof(DMAController));
dma->active = false;
dma->source = NULL;
dma->destination = NULL;
dma->count = 0;
dma->position = 0;
dma->transfer_speed = speed;
return dma;
}
// 设置DMA传输参数
void setup_dma_transfer(DMAController* dma, unsigned char* source, unsigned char* destination, int count) {
dma->source = source;
dma->destination = destination;
dma->count = count;
dma->position = 0;
dma->active = true;
printf("设置DMA传输: %d字节
", count);
}
// 执行一步DMA传输
bool process_dma_transfer(DMAController* dma) {
if (!dma->active || dma->position >= dma->count) {
return false;
}
// 模拟传输一个字节
dma->destination[dma->position] = dma->source[dma->position];
dma->position++;
// 检查是否完成
if (dma->position >= dma->count) {
printf("DMA传输完成: 已传输%d字节
", dma->count);
dma->active = false;
return false;
}
return true; // 继续传输
}
// 主函数
int main() {
printf("I/O数据传输方式演示
");
printf("=================
");
// 初始化两个模拟设备
IODevice* keyboard = init_io_device("键盘", 16, 1000); // 1KB/s
IODevice* disk = init_io_device("硬盘", 4096, 10000000); // 10MB/s
IODevice* devices[] = {keyboard, disk};
// 初始化中断控制器
InterruptController* interrupt_ctrl = init_interrupt_controller();
// 初始化DMA控制器
DMAController* dma_ctrl = init_dma_controller(5000000); // 5MB/s
// 准备系统内存
unsigned char system_memory[8192];
memset(system_memory, 0, sizeof(system_memory));
// 模拟数据
memset(keyboard->buffer, 'A', keyboard->buffer_size); // 键盘缓冲区填充'A'
memset(disk->buffer, 'B', disk->buffer_size); // 磁盘缓冲区填充'B'
printf("1. 无条件传送方式(程序控制I/O)
");
printf("---------------------------
");
// 模拟写入数据到设备
printf("写入数据到键盘缓冲区...
");
keyboard->buffer[0] = 'X';
simulate_device_operation(keyboard, 1);
// 模拟从设备读取数据(无条件方式)
printf("从键盘读取数据(无条件方式):
");
unsigned char data = keyboard->buffer[0];
printf("读取的数据: %c (0x%02X)
", data, data);
printf("
2. 查询传送方式
");
printf("-------------
");
// 模拟查询方式读取键盘数据
printf("查询键盘状态...
");
int poll_count = 0;
while (!is_device_ready(keyboard) && poll_count < 5) {
printf("键盘未就绪,继续查询...
");
poll_count++;
}
if (is_device_ready(keyboard)) {
data = keyboard->buffer[keyboard->buffer_pos];
keyboard->buffer_pos = (keyboard->buffer_pos + 1) % keyboard->buffer_size;
keyboard->data_ready = false;
printf("键盘就绪,读取的数据: %c (0x%02X)
", data, data);
} else {
printf("键盘未就绪,放弃查询
");
}
printf("
3. 中断传送方式
");
printf("-------------
");
// 准备键盘数据,触发中断
keyboard->buffer[keyboard->buffer_pos] = 'K';
keyboard->data_ready = true;
keyboard->interrupt_pending = true;
// 模拟CPU执行主程序
printf("CPU执行主程序...
");
// 检查中断请求
if (check_interrupt_request(interrupt_ctrl, devices, 2)) {
IODevice* int_device = get_interrupt_device(devices, 2);
printf("收到中断请求,来自设备: %s
", int_device->name);
// 处理中断
service_interrupt(interrupt_ctrl, int_device);
}
printf("
4. DMA传送方式
");
printf("-------------
");
// 设置DMA传输(从磁盘到内存)
printf("从磁盘读取数据到内存(使用DMA):
");
setup_dma_transfer(dma_ctrl, disk->buffer, system_memory, 1024);
// 模拟DMA传输过程
int transfer_steps = 0;
while (process_dma_transfer(dma_ctrl) && transfer_steps < 10) {
transfer_steps++;
printf("DMA传输进行中...已传输%d字节
", dma_ctrl->position);
// 在实际应用中,CPU可以同时执行其他任务
printf("CPU同时执行其他任务...
");
}
// 检查传输结果
printf("
DMA传输结果:
");
printf(" 系统内存前10字节: ");
for (int i = 0; i < 10; i++) {
printf("%c ", system_memory[i]);
}
printf("
");
// 释放资源
free_io_device(keyboard);
free_io_device(disk);
free(interrupt_ctrl);
free(dma_ctrl);
return 0;
}
7.2 无条件传送方式与接口设计
无条件传送方式是最基本的I/O数据传输方法,也称为程序控制I/O。在这种模式下,CPU直接控制数据传输过程,不考虑设备状态,假设设备总是就绪的。
无条件传送的特点与接口实现
无条件传送的主要特点:
直接控制:
CPU直接发出I/O命令不检查设备状态假设设备随时可用
接口简单:
硬件接口电路简单不需要状态检测逻辑不需要中断或DMA控制电路
适用场景:
简单的输入输出设备固定延迟的设备始终就绪的设备(如某些类型的存储器)
局限性:
不能处理设备忙或未就绪的情况CPU无法同时执行其他任务效率低,尤其是对于速度较慢的设备
无条件传送接口的典型结构:
数据寄存器:存储输入或输出的数据地址解码器:识别CPU发出的地址信号控制信号逻辑:处理读/写控制信号
以下是一个使用无条件传送方式的C语言和汇编代码示例,展示了如何直接操作I/O端口:
c
#include <stdio.h>
#include <stdint.h>
// 模拟x86架构下的I/O操作
// 在实际的x86系统上,这些函数会直接使用IN/OUT指令
// 这里使用模拟实现,方便理解原理
// 向指定端口写入一个字节
void outb(uint16_t port, uint8_t value) {
printf("向端口0x%04X写入数据: 0x%02X
", port, value);
// 在实际x86系统中,会使用如下汇编指令:
// __asm__ __volatile__ ("outb %0, %1" : : "a"(value), "Nd"(port));
}
// 从指定端口读取一个字节
uint8_t inb(uint16_t port) {
uint8_t value = 0x55; // 模拟从端口读取的值
printf("从端口0x%04X读取数据: 0x%02X
", port, value);
// 在实际x86系统中,会使用如下汇编指令:
// __asm__ __volatile__ ("inb %1, %0" : "=a"(value) : "Nd"(port));
return value;
}
// 向指定端口写入一个字(16位)
void outw(uint16_t port, uint16_t value) {
printf("向端口0x%04X写入数据: 0x%04X
", port, value);
// 在实际x86系统中,会使用如下汇编指令:
// __asm__ __volatile__ ("outw %0, %1" : : "a"(value), "Nd"(port));
}
// 从指定端口读取一个字(16位)
uint16_t inw(uint16_t port) {
uint16_t value = 0xAA55; // 模拟从端口读取的值
printf("从端口0x%04X读取数据: 0x%04X
", port, value);
// 在实际x86系统中,会使用如下汇编指令:
// __asm__ __volatile__ ("inw %1, %0" : "=a"(value) : "Nd"(port));
return value;
}
// 使用无条件传送方式操作简单的LED显示设备
void control_leds(uint8_t pattern) {
// 假设LED控制端口地址为0x378(标准并行端口)
const uint16_t LED_PORT = 0x378;
// 直接写入LED模式到端口
outb(LED_PORT, pattern);
printf("LED显示模式已设置: 0x%02X
", pattern);
}
// 使用无条件传送方式读取简单的开关状态
uint8_t read_switches() {
// 假设开关状态端口地址为0x379(标准并行端口状态)
const uint16_t SWITCH_PORT = 0x379;
// 直接读取开关状态
uint8_t switches = inb(SWITCH_PORT);
printf("开关状态: 0x%02X
", switches);
return switches;
}
// 使用无条件传送方式操作简单的数码管显示器
void set_7segment_display(uint16_t value) {
// 假设数码管显示器端口地址为0x300
const uint16_t DISPLAY_PORT = 0x300;
// 直接写入值到数码管显示器
outw(DISPLAY_PORT, value);
printf("数码管显示值已设置: %d
", value);
}
int main() {
printf("无条件传送方式I/O操作示例
");
printf("=====================
");
// 1. 控制LED灯
printf("1. 控制LED灯:
");
control_leds(0xAA); // 设置交替的LED模式(10101010)
control_leds(0x55); // 设置另一种交替的LED模式(01010101)
control_leds(0xFF); // 点亮所有LED(11111111)
control_leds(0x00); // 关闭所有LED(00000000)
printf("
2. 读取开关状态:
");
uint8_t switch_state = read_switches();
// 根据开关状态执行不同操作
if (switch_state & 0x01) {
printf("开关1打开,执行操作A
");
} else {
printf("开关1关闭,执行操作B
");
}
printf("
3. 设置数码管显示:
");
set_7segment_display(1234); // 显示数字1234
set_7segment_display(5678); // 显示数字5678
printf("
无条件传送方式的局限性:
");
printf("如果设备未就绪,CPU无法得知,可能会导致数据错误或丢失
");
printf("CPU无法同时执行其他任务,必须等待I/O操作完成
");
printf("特别适用于总是就绪的简单设备,如LED、简单显示器等
");
return 0;
}
下面是一个使用8086汇编实现的无条件传送方式I/O操作示例:
asm
; 无条件传送方式I/O操作 - 8086汇编示例
; 可以使用NASM等汇编器编译
section .data
led_patterns db 0xAA, 0x55, 0xFF, 0x00 ; LED显示模式数组
display_values dw 1234, 5678 ; 数码管显示值
section .text
global _start
_start:
; 1. 控制LED灯
mov cx, 4 ; 设置循环计数为4(模式数量)
mov si, led_patterns ; SI指向模式数组
led_control_loop:
mov al, [si] ; 获取当前LED模式
mov dx, 0x378 ; LED控制端口地址
out dx, al ; 发送数据到LED端口
inc si ; 指向下一个模式
loop led_control_loop ; 循环直到所有模式都显示
; 2. 读取开关状态
mov dx, 0x379 ; 开关状态端口地址
in al, dx ; 读取开关状态
; 根据开关状态执行不同操作
test al, 1 ; 测试最低位(开关1)
jz switch1_off ; 如果为0(关闭),跳转
switch1_on: ; 开关1打开
; 执行开关1打开时的操作
jmp switch_done
switch1_off: ; 开关1关闭
; 执行开关1关闭时的操作
switch_done:
; 3. 设置数码管显示
mov cx, 2 ; 设置循环计数为2(显示值数量)
mov si, display_values ; SI指向显示值数组
display_loop:
mov ax, [si] ; 获取当前显示值(16位)
mov dx, 0x300 ; 数码管显示器端口地址
out dx, ax ; 发送数据到显示器端口
add si, 2 ; 指向下一个显示值(每个值2字节)
loop display_loop ; 循环直到所有值都显示
; 程序退出
mov eax, 1 ; 系统调用号(exit)
xor ebx, ebx ; 返回值0
int 0x80 ; 调用内核
7.3 查询传送方式及其接口实现
查询传送方式是对无条件传送方式的改进,它通过检查设备状态,确保设备就绪后再进行数据传输,从而提高了可靠性。
7.3.1 查询输入接口设计与实现
查询输入接口允许程序检查输入设备的状态,确认数据是否已准备好,然后再读取数据。这种设计能够避免尝试从未就绪的设备读取数据导致的错误。
查询输入接口的典型结构:
数据寄存器:存储从设备接收的数据状态寄存器:表示设备的当前状态,如数据就绪标志控制寄存器:控制设备的工作模式
查询输入的基本流程:
读取设备状态寄存器检查数据就绪标志如果数据就绪,从数据寄存器读取数据如果数据未就绪,继续查询或延迟后再查询
以下是一个模拟查询输入接口的C语言实现:
c
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <time.h>
#include <unistd.h>
// 模拟键盘控制器
typedef struct {
uint8_t data_register; // 数据寄存器
uint8_t status_register; // 状态寄存器
uint8_t control_register; // 控制寄存器
} KeyboardController;
// 状态寄存器位定义
#define STATUS_DATA_READY 0x01 // 数据就绪标志
#define STATUS_BUFFER_FULL 0x02 // 输入缓冲区满
#define STATUS_SELF_TEST 0x04 // 自测试通过标志
#define STATUS_COMMAND 0x08 // 数据是命令结果(1)还是键盘数据(0)
#define STATUS_ERROR 0x80 // 错误标志
// 初始化键盘控制器
KeyboardController* init_keyboard_controller() {
KeyboardController* kb = (KeyboardController*)malloc(sizeof(KeyboardController));
kb->data_register = 0;
kb->status_register = 0;
kb->control_register = 0;
return kb;
}
// 模拟从键盘端口读取状态
uint8_t read_keyboard_status(KeyboardController* kb) {
// 在实际系统中,这将是对状态端口的直接读取
printf("读取键盘状态寄存器: 0x%02X
", kb->status_register);
return kb->status_register;
}
// 模拟从键盘端口读取数据
uint8_t read_keyboard_data(KeyboardController* kb) {
// 在实际系统中,这将是对数据端口的直接读取
uint8_t data = kb->data_register;
// 清除数据就绪标志
kb->status_register &= ~STATUS_DATA_READY;
printf("读取键盘数据寄存器: 0x%02X (ASCII: '%c')
",
data, (data >= 32 && data <= 126) ? data : '.');
return data;
}
// 模拟键盘产生按键
void simulate_keypress(KeyboardController* kb, uint8_t scancode) {
// 将扫描码存入数据寄存器
kb->data_register = scancode;
// 设置数据就绪标志
kb->status_register |= STATUS_DATA_READY;
printf("模拟按键: 扫描码 0x%02X 放入数据寄存器
", scancode);
}
// 使用查询方式读取键盘输入
bool read_keyboard_input_polling(KeyboardController* kb, uint8_t* data, int max_attempts) {
for (int attempt = 1; attempt <= max_attempts; attempt++) {
// 读取键盘状态
uint8_t status = read_keyboard_status(kb);
// 检查数据就绪标志
if (status & STATUS_DATA_READY) {
// 数据已就绪,读取数据
*data = read_keyboard_data(kb);
printf("查询成功: 第%d次尝试
", attempt);
return true;
}
// 数据未就绪,延迟一段时间后重试
printf("查询: 数据未就绪,等待后重试 (尝试 %d/%d)
", attempt, max_attempts);
usleep(10000); // 延迟10毫秒
}
printf("查询超时: 达到最大尝试次数
");
return false; // 达到最大尝试次数,仍未读到数据
}
// 模拟简单的串行接收器
typedef struct {
uint8_t rx_buffer[16]; // 接收缓冲区
int rx_head; // 接收缓冲区头索引
int rx_tail; // 接收缓冲区尾索引
uint8_t status; // 状态寄存器
} SerialReceiver;
// 串行接收器状态位定义
#define SERIAL_STATUS_DATA_READY 0x01
#define SERIAL_STATUS_OVERRUN 0x02
#define SERIAL_STATUS_PARITY_ERR 0x04
#define SERIAL_STATUS_FRAMING_ERR 0x08
#define SERIAL_STATUS_BREAK 0x10
#define SERIAL_STATUS_THR_EMPTY 0x20
#define SERIAL_STATUS_TIMEOUT 0x80
// 初始化串行接收器
SerialReceiver* init_serial_receiver() {
SerialReceiver* serial = (SerialReceiver*)malloc(sizeof(SerialReceiver));
memset(serial->rx_buffer, 0, sizeof(serial->rx_buffer));
serial->rx_head = 0;
serial->rx_tail = 0;
serial->status = 0;
return serial;
}
// 检查串行接收器是否有数据可读
bool is_serial_data_available(SerialReceiver* serial) {
// 检查缓冲区是否有数据
bool data_available = (serial->rx_head != serial->rx_tail);
// 更新状态寄存器
if (data_available) {
serial->status |= SERIAL_STATUS_DATA_READY;
} else {
serial->status &= ~SERIAL_STATUS_DATA_READY;
}
return data_available;
}
// 读取串行接收器状态
uint8_t read_serial_status(SerialReceiver* serial) {
// 检查缓冲区状态并更新状态寄存器
is_serial_data_available(serial);
printf("读取串行接收器状态: 0x%02X
", serial->status);
return serial->status;
}
// 从串行接收器读取数据
uint8_t read_serial_data(SerialReceiver* serial) {
// 检查是否有数据可读
if (!is_serial_data_available(serial)) {
printf("警告: 尝试从空缓冲区读取数据
");
return 0;
}
// 读取缓冲区中的数据
uint8_t data = serial->rx_buffer[serial->rx_tail];
// 更新尾索引
serial->rx_tail = (serial->rx_tail + 1) % sizeof(serial->rx_buffer);
// 如果缓冲区现在为空,清除数据就绪标志
if (serial->rx_head == serial->rx_tail) {
serial->status &= ~SERIAL_STATUS_DATA_READY;
}
printf("从串行接收器读取数据: 0x%02X (ASCII: '%c')
",
data, (data >= 32 && data <= 126) ? data : '.');
return data;
}
// 模拟接收一个字符到串行接收器
void simulate_serial_reception(SerialReceiver* serial, uint8_t data) {
// 计算缓冲区的下一个位置
int next_head = (serial->rx_head + 1) % sizeof(serial->rx_buffer);
// 检查缓冲区是否已满
if (next_head == serial->rx_tail) {
// 缓冲区溢出
serial->status |= SERIAL_STATUS_OVERRUN;
printf("串行接收器缓冲区溢出
");
return;
}
// 存储数据到缓冲区
serial->rx_buffer[serial->rx_head] = data;
// 更新头索引
serial->rx_head = next_head;
// 设置数据就绪标志
serial->status |= SERIAL_STATUS_DATA_READY;
printf("模拟串行接收: 数据 0x%02X (ASCII: '%c')
",
data, (data >= 32 && data <= 126) ? data : '.');
}
// 使用查询方式从串行接收器读取所有可用数据
int read_serial_input_polling(SerialReceiver* serial, uint8_t* buffer, int buffer_size) {
int count = 0;
while (count < buffer_size) {
// 读取状态
uint8_t status = read_serial_status(serial);
// 检查是否有数据可读
if (status & SERIAL_STATUS_DATA_READY) {
// 读取数据
buffer[count++] = read_serial_data(serial);
} else {
// 无更多数据可读
break;
}
}
printf("查询读取了%d个字节的数据
", count);
return count;
}
int main() {
printf("查询输入接口示例
");
printf("==============
");
// 1. 键盘输入查询示例
printf("1. 键盘输入查询:
");
printf("--------------
");
KeyboardController* kb = init_keyboard_controller();
uint8_t key_data;
// 尝试读取(应该失败,因为没有按键)
bool result = read_keyboard_input_polling(kb, &key_data, 3);
printf("查询结果: %s
", result ? "成功" : "失败");
// 模拟按键
printf("
模拟用户按下'A'键
");
simulate_keypress(kb, 'A');
// 再次尝试读取(应该成功)
result = read_keyboard_input_polling(kb, &key_data, 3);
printf("查询结果: %s
", result ? "成功" : "失败");
if (result) {
printf("读取到的键值: 0x%02X (ASCII: '%c')
", key_data, key_data);
}
printf("
2. 串行接收器查询:
");
printf("----------------
");
SerialReceiver* serial = init_serial_receiver();
uint8_t serial_buffer[64];
// 模拟接收一些数据
printf("模拟接收一系列数据...
");
simulate_serial_reception(serial, 'H');
simulate_serial_reception(serial, 'e');
simulate_serial_reception(serial, 'l');
simulate_serial_reception(serial, 'l');
simulate_serial_reception(serial, 'o');
// 查询读取所有可用数据
int bytes_read = read_serial_input_polling(serial, serial_buffer, sizeof(serial_buffer));
// 显示读取的数据
if (bytes_read > 0) {
printf("
读取到%d字节的数据: ", bytes_read);
for (int i = 0; i < bytes_read; i++) {
printf("%c", serial_buffer[i]);
}
printf("
");
}
// 清理资源
free(kb);
free(serial);
return 0;
}
7.3.2 查询输出接口设计与实现
查询输出接口允许程序检查输出设备的状态,确认设备已准备好接收数据后再发送数据。这种设计可以防止数据因设备未准备好而丢失。
查询输出接口的典型结构:
数据寄存器:存储要发送到设备的数据状态寄存器:表示设备的当前状态,如设备就绪标志控制寄存器:控制设备的工作模式
查询输出的基本流程:
读取设备状态寄存器检查设备就绪标志如果设备就绪,向数据寄存器写入数据如果设备未就绪,继续查询或延迟后再查询
以下是一个模拟查询输出接口的C语言实现:
c
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
// 模拟串行发送器
typedef struct {
uint8_t tx_buffer[16]; // 发送缓冲区
int tx_head; // 发送缓冲区头索引
int tx_tail; // 发送缓冲区尾索引
uint8_t status; // 状态寄存器
uint8_t data_register; // 数据寄存器
bool transmitting; // 发送中标志
clock_t last_tx_time; // 上次发送时间
int tx_delay_ms; // 模拟发送延迟(毫秒)
} SerialTransmitter;
// 串行发送器状态位定义
#define SERIAL_TX_STATUS_READY 0x01 // 发送器就绪标志
#define SERIAL_TX_STATUS_BUSY 0x02 // 发送器忙标志
#define SERIAL_TX_STATUS_EMPTY 0x04 // 发送缓冲区空标志
#define SERIAL_TX_STATUS_OVERFLOW 0x08 // 发送缓冲区溢出标志
#define SERIAL_TX_STATUS_ERROR 0x80 // 发送错误标志
// 初始化串行发送器
SerialTransmitter* init_serial_transmitter(int tx_delay_ms) {
SerialTransmitter* tx = (SerialTransmitter*)malloc(sizeof(SerialTransmitter));
memset(tx->tx_buffer, 0, sizeof(tx->tx_buffer));
tx->tx_head = 0;
tx->tx_tail = 0;
tx->status = SERIAL_TX_STATUS_READY | SERIAL_TX_STATUS_EMPTY;
tx->data_register = 0;
tx->transmitting = false;
tx->last_tx_time = 0;
tx->tx_delay_ms = tx_delay_ms;
return tx;
}
// 检查发送器是否准备好接收新数据
bool is_transmitter_ready(SerialTransmitter* tx) {
// 检查是否有正在进行的发送
if (tx->transmitting) {
// 检查是否已经过了足够的时间
clock_t current_time = clock();
double elapsed_ms = (double)(current_time - tx->last_tx_time) * 1000 / CLOCKS_PER_SEC;
if (elapsed_ms >= tx->tx_delay_ms) {
// 发送完成
tx->transmitting = false;
tx->status |= SERIAL_TX_STATUS_READY;
tx->status &= ~SERIAL_TX_STATUS_BUSY;
}
}
return (tx->status & SERIAL_TX_STATUS_READY) != 0;
}
// 读取发送器状态
uint8_t read_transmitter_status(SerialTransmitter* tx) {
// 更新发送器状态
is_transmitter_ready(tx);
printf("读取发送器状态: 0x%02X
", tx->status);
return tx->status;
}
// 向发送器写入数据
bool write_transmitter_data(SerialTransmitter* tx, uint8_t data) {
// 检查发送器是否就绪
if (!is_transmitter_ready(tx)) {
printf("发送器未就绪,无法写入数据
");
return false;
}
// 写入数据到数据寄存器
tx->data_register = data;
// 开始发送
tx->transmitting = true;
tx->last_tx_time = clock();
// 更新状态
tx->status &= ~SERIAL_TX_STATUS_READY;
tx->status |= SERIAL_TX_STATUS_BUSY;
printf("向发送器写入数据: 0x%02X (ASCII: '%c')
",
data, (data >= 32 && data <= 126) ? data : '.');
return true;
}
// 使用查询方式发送单个字符
bool send_char_polling(SerialTransmitter* tx, uint8_t data, int max_attempts) {
for (int attempt = 1; attempt <= max_attempts; attempt++) {
// 读取发送器状态
uint8_t status = read_transmitter_status(tx);
// 检查发送器是否就绪
if (status & SERIAL_TX_STATUS_READY) {
// 发送器就绪,写入数据
write_transmitter_data(tx, data);
printf("查询发送成功: 第%d次尝试
", attempt);
return true;
}
// 发送器未就绪,延迟一段时间后重试
printf("查询: 发送器未就绪,等待后重试 (尝试 %d/%d)
", attempt, max_attempts);
usleep(10000); // 延迟10毫秒
}
printf("查询超时: 达到最大尝试次数
");
return false; // 达到最大尝试次数,仍未能发送
}
// 使用查询方式发送字符串
int send_string_polling(SerialTransmitter* tx, const char* str, int max_attempts_per_char) {
int chars_sent = 0;
while (*str != '') {
if (send_char_polling(tx, *str, max_attempts_per_char)) {
chars_sent++;
str++;
} else {
// 发送失败,中断发送
break;
}
}
return chars_sent;
}
// 模拟打印机控制器
typedef struct {
uint8_t data_register; // 数据寄存器
uint8_t status_register; // 状态寄存器
uint8_t control_register; // 控制寄存器
int busy_time_ms; // 处理每个字符的时间(毫秒)
clock_t busy_until; // 忙状态结束时间
} PrinterController;
// 打印机状态位定义
#define PRINTER_STATUS_READY 0x01 // 打印机就绪
#define PRINTER_STATUS_BUSY 0x02 // 打印机忙
#define PRINTER_STATUS_PAPER_OUT 0x04 // 缺纸
#define PRINTER_STATUS_ONLINE 0x08 // 联机
#define PRINTER_STATUS_ERROR 0x10 // 错误状态
#define PRINTER_STATUS_SELECTED 0x20 // 打印机已选中
// 初始化打印机控制器
PrinterController* init_printer_controller(int busy_time_ms) {
PrinterController* printer = (PrinterController*)malloc(sizeof(PrinterController));
printer->data_register = 0;
printer->status_register = PRINTER_STATUS_READY | PRINTER_STATUS_ONLINE | PRINTER_STATUS_SELECTED;
printer->control_register = 0;
printer->busy_time_ms = busy_time_ms;
printer->busy_until = 0;
return printer;
}
// 检查打印机状态
uint8_t read_printer_status(PrinterController* printer) {
// 如果打印机处于忙状态,检查是否已经过了忙状态时间
if (printer->status_register & PRINTER_STATUS_BUSY) {
clock_t current_time = clock();
if (current_time >= printer->busy_until) {
// 打印完成,恢复就绪状态
printer->status_register &= ~PRINTER_STATUS_BUSY;
printer->status_register |= PRINTER_STATUS_READY;
}
}
printf("读取打印机状态: 0x%02X
", printer->status_register);
return printer->status_register;
}
// 向打印机写入数据
bool write_printer_data(PrinterController* printer, uint8_t data) {
// 检查打印机是否就绪
if (!(printer->status_register & PRINTER_STATUS_READY)) {
printf("打印机未就绪,无法打印
");
return false;
}
// 检查打印机是否联机
if (!(printer->status_register & PRINTER_STATUS_ONLINE)) {
printf("打印机离线,无法打印
");
return false;
}
// 写入数据
printer->data_register = data;
// 设置忙状态
printer->status_register |= PRINTER_STATUS_BUSY;
printer->status_register &= ~PRINTER_STATUS_READY;
// 设置忙状态结束时间
printer->busy_until = clock() + (printer->busy_time_ms * CLOCKS_PER_SEC / 1000);
printf("打印机开始打印字符: 0x%02X (ASCII: '%c')
",
data, (data >= 32 && data <= 126) ? data : '.');
return true;
}
// 使用查询方式打印字符
bool print_char_polling(PrinterController* printer, uint8_t data, int max_attempts) {
for (int attempt = 1; attempt <= max_attempts; attempt++) {
// 读取打印机状态
uint8_t status = read_printer_status(printer);
// 检查打印机是否就绪
if (status & PRINTER_STATUS_READY) {
// 打印机就绪,写入数据
write_printer_data(printer, data);
printf("查询打印成功: 第%d次尝试
", attempt);
return true;
}
// 打印机未就绪,延迟后重试
printf("查询: 打印机未就绪,等待后重试 (尝试 %d/%d)
", attempt, max_attempts);
usleep(20000); // 延迟20毫秒
}
printf("查询超时: 达到最大尝试次数
");
return false; // 达到最大尝试次数,仍未能打印
}
// 使用查询方式打印字符串
int print_string_polling(PrinterController* printer, const char* str, int max_attempts_per_char) {
int chars_printed = 0;
while (*str != '') {
if (print_char_polling(printer, *str, max_attempts_per_char)) {
chars_printed++;
str++;
} else {
// 打印失败,中断打印
break;
}
}
return chars_printed;
}
int main() {
printf("查询输出接口示例
");
printf("==============
");
// 1. 串行发送器查询示例
printf("1. 串行发送器查询:
");
printf("-----------------
");
SerialTransmitter* tx = init_serial_transmitter(100); // 每个字符发送延迟100毫秒
// 使用查询方式发送单个字符
printf("发送单个字符...
");
send_char_polling(tx, 'A', 5);
// 等待一段时间,确保上一个字符已发送完成
printf("等待发送完成...
");
usleep(150000); // 等待150毫秒
// 使用查询方式发送字符串
printf("
发送字符串...
");
const char* message = "Hello, World!";
int sent = send_string_polling(tx, message, 5);
printf("发送完成: 共发送%d个字符,消息长度%zu
", sent, strlen(message));
printf("
2. 打印机控制器查询:
");
printf("-------------------
");
PrinterController* printer = init_printer_controller(200); // 每个字符打印延迟200毫秒
// 使用查询方式打印单个字符
printf("打印单个字符...
");
print_char_polling(printer, '*', 5);
// 等待一段时间,确保上一个字符已打印完成
printf("等待打印完成...
");
usleep(250000); // 等待250毫秒
// 使用查询方式打印字符串
printf("
打印字符串...
");
const char* print_text = "This is a test document.";
int printed = print_string_polling(printer, print_text, 5);
printf("打印完成: 共打印%d个字符,文本长度%zu
", printed, strlen(print_text));
// 清理资源
free(tx);
free(printer);
return 0;
}
7.3.3 使用查询方式对EEPROM进行编程
EEPROM(电可擦除可编程只读存储器)是一种非易失性存储器,可以电擦除和重编程。由于EEPROM写入操作需要一定时间,通常使用查询方式确认写入完成。
EEPROM编程查询的特点:
写入操作启动后,EEPROM进入忙状态程序需要定期查询状态寄存器以确定写入是否完成写入完成后,EEPROM恢复就绪状态,可以进行下一个操作
以下是一个使用查询方式对EEPROM进行编程的C语言示例:
c
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
// 模拟EEPROM芯片
typedef struct {
uint8_t* memory; // 存储数组
uint32_t size; // 存储容量
uint8_t status_register; // 状态寄存器
uint16_t address_register; // 地址寄存器
uint8_t data_register; // 数据寄存器
uint8_t command_register; // 命令寄存器
int write_time_ms; // 写入时间(毫秒)
clock_t busy_until; // 忙状态结束时间
int write_cycles; // 擦写周期计数
int max_write_cycles; // 最大擦写周期
} EEPROM;
// EEPROM状态寄存器位定义
#define EEPROM_STATUS_READY 0x01 // 就绪标志
#define EEPROM_STATUS_BUSY 0x02 // 忙标志
#define EEPROM_STATUS_WRITE_OK 0x04 // 写入成功标志
#define EEPROM_STATUS_WRITE_ERR 0x08 // 写入错误标志
#define EEPROM_STATUS_WP 0x10 // 写保护标志
// EEPROM命令
#define EEPROM_CMD_READ 0x01 // 读命令
#define EEPROM_CMD_WRITE 0x02 // 写命令
#define EEPROM_CMD_WRITE_ENABLE 0x04 // 写使能命令
#define EEPROM_CMD_WRITE_DISABLE 0x08 // 写禁止命令
#define EEPROM_CMD_STATUS_READ 0x10 // 读状态命令
// 初始化EEPROM
EEPROM* init_eeprom(uint32_t size, int write_time_ms, int max_write_cycles) {
EEPROM* eeprom = (EEPROM*)malloc(sizeof(EEPROM));
eeprom->memory = (uint8_t*)malloc(size);
eeprom->size = size;
eeprom->status_register = EEPROM_STATUS_READY;
eeprom->address_register = 0;
eeprom->data_register = 0;
eeprom->command_register = 0;
eeprom->write_time_ms = write_time_ms;
eeprom->busy_until = 0;
eeprom->write_cycles = (int*)calloc(size, sizeof(int));
eeprom->max_write_cycles = max_write_cycles;
// 初始化存储区域(全FF)
memset(eeprom->memory, 0xFF, size);
return eeprom;
}
// 释放EEPROM资源
void free_eeprom(EEPROM* eeprom) {
free(eeprom->memory);
free(eeprom);
}
// 读取EEPROM状态寄存器
uint8_t read_eeprom_status(EEPROM* eeprom) {
// 检查是否仍处于忙状态
if (eeprom->status_register & EEPROM_STATUS_BUSY) {
clock_t current_time = clock();
if (current_time >= eeprom->busy_until) {
// 写入完成
eeprom->status_register &= ~EEPROM_STATUS_BUSY;
eeprom->status_register |= EEPROM_STATUS_READY;
}
}
printf("读取EEPROM状态寄存器: 0x%02X
", eeprom->status_register);
return eeprom->status_register;
}
// 设置EEPROM地址
void set_eeprom_address(EEPROM* eeprom, uint16_t address) {
if (address < eeprom->size) {
eeprom->address_register = address;
printf("设置EEPROM地址: 0x%04X
", address);
} else {
printf("错误: 地址超出EEPROM范围 (0x%04X > 0x%04X)
", address, eeprom->size - 1);
}
}
// 读取EEPROM数据
uint8_t read_eeprom_data(EEPROM* eeprom) {
// 检查地址是否有效
if (eeprom->address_register >= eeprom->size) {
printf("错误: 读取地址超出范围
");
return 0;
}
// 从存储阵列读取数据
eeprom->data_register = eeprom->memory[eeprom->address_register];
printf("读取EEPROM地址0x%04X: 0x%02X
", eeprom->address_register, eeprom->data_register);
return eeprom->data_register;
}
// 写入数据到EEPROM
bool write_eeprom_data(EEPROM* eeprom, uint8_t data) {
// 检查是否处于就绪状态
if (!(eeprom->status_register & EEPROM_STATUS_READY)) {
printf("错误: EEPROM未就绪,无法写入
");
return false;
}
// 检查是否写保护
if (eeprom->status_register & EEPROM_STATUS_WP) {
printf("错误: EEPROM写保护已启用
");
return false;
}
// 检查地址是否有效
if (eeprom->address_register >= eeprom->size) {
printf("错误: 写入地址超出范围
");
return false;
}
// 检查擦写周期
if (eeprom->write_cycles[eeprom->address_register] >= eeprom->max_write_cycles) {
printf("错误: 地址0x%04X已达最大擦写次数(%d次)
",
eeprom->address_register, eeprom->max_write_cycles);
eeprom->status_register |= EEPROM_STATUS_WRITE_ERR;
return false;
}
// 存储数据
eeprom->data_register = data;
// 设置忙状态
eeprom->status_register |= EEPROM_STATUS_BUSY;
eeprom->status_register &= ~EEPROM_STATUS_READY;
// 设置忙结束时间
eeprom->busy_until = clock() + (eeprom->write_time_ms * CLOCKS_PER_SEC / 1000);
printf("开始写入数据0x%02X到地址0x%04X (预计耗时%dms)...
",
data, eeprom->address_register, eeprom->write_time_ms);
return true;
}
// 使用查询方式完成EEPROM写入
bool write_eeprom_polling(EEPROM* eeprom, uint16_t address, uint8_t data, int max_attempts) {
// 设置EEPROM地址
set_eeprom_address(eeprom, address);
// 启动写入操作
if (!write_eeprom_data(eeprom, data)) {
return false;
}
// 使用查询方式等待写入完成
for (int attempt = 1; attempt <= max_attempts; attempt++) {
// 读取状态寄存器
uint8_t status = read_eeprom_status(eeprom);
// 检查是否就绪
if (status & EEPROM_STATUS_READY) {
// 写入成功,更新存储阵列
eeprom->memory[eeprom->address_register] = eeprom->data_register;
eeprom->write_cycles[eeprom->address_register]++;
printf("写入完成: 地址0x%04X = 0x%02X (擦写周期: %d/%d)
",
eeprom->address_register, eeprom->data_register,
eeprom->write_cycles[eeprom->address_register],
eeprom->max_write_cycles);
// 设置写入成功标志
eeprom->status_register |= EEPROM_STATUS_WRITE_OK;
return true;
}
// 还在忙状态,等待一段时间后重试
printf("查询: EEPROM仍在写入,等待后重试 (尝试 %d/%d)
", attempt, max_attempts);
usleep(eeprom->write_time_ms * 1000 / 10); // 等待总写入时间的1/10
}
printf("查询超时: 达到最大尝试次数
");
return false; // 超时
}
// 使用查询方式写入一块数据到EEPROM
int write_eeprom_block_polling(EEPROM* eeprom, uint16_t start_address,
const uint8_t* data, uint16_t length, int max_attempts_per_byte) {
int bytes_written = 0;
for (int i = 0; i < length; i++) {
uint16_t current_address = start_address + i;
// 检查地址是否超出范围
if (current_address >= eeprom->size) {
printf("错误: 地址0x%04X超出EEPROM范围
", current_address);
break;
}
// 使用查询方式写入一个字节
if (write_eeprom_polling(eeprom, current_address, data[i], max_attempts_per_byte)) {
bytes_written++;
} else {
printf("写入块数据失败于地址0x%04X
", current_address);
break;
}
}
return bytes_written;
}
// 读取EEPROM的一块数据
int read_eeprom_block(EEPROM* eeprom, uint16_t start_address, uint8_t* buffer, uint16_t length) {
int bytes_read = 0;
for (int i = 0; i < length; i++) {
uint16_t current_address = start_address + i;
// 检查地址是否超出范围
if (current_address >= eeprom->size) {
printf("错误: 地址0x%04X超出EEPROM范围
", current_address);
break;
}
// 设置地址并读取数据
set_eeprom_address(eeprom, current_address);
buffer[i] = read_eeprom_data(eeprom);
bytes_read++;
}
return bytes_read;
}
// 显示EEPROM内容
void display_eeprom_content(EEPROM* eeprom, uint16_t start_address, uint16_t length) {
printf("
EEPROM内容 (0x%04X - 0x%04X):
", start_address, start_address + length - 1);
printf("地址 | 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F | ASCII
");
printf("--------+-----------------------------------------------+----------------
");
// 确保长度不超出EEPROM范围
if (start_address + length > eeprom->size) {
length = eeprom->size - start_address;
}
for (uint16_t base = start_address; base < start_address + length; base += 16) {
printf("0x%04X | ", base);
// 显示十六进制值
for (int i = 0; i < 16; i++) {
uint16_t addr = base + i;
if (addr < start_address + length) {
printf("%02X ", eeprom->memory[addr]);
} else {
printf(" ");
}
}
printf("| ");
// 显示ASCII值
for (int i = 0; i < 16; i++) {
uint16_t addr = base + i;
if (addr < start_address + length) {
char c = eeprom->memory[addr];
if (c >= 32 && c <= 126) {
printf("%c", c);
} else {
printf(".");
}
} else {
printf(" ");
}
}
printf("
");
}
}
int main() {
printf("EEPROM查询编程示例
");
printf("================
");
// 初始化一个16KB的EEPROM,每次写入耗时10毫秒,最大擦写周期100000
EEPROM* eeprom = init_eeprom(16384, 10, 100000);
// 1. 显示初始EEPROM内容
printf("初始EEPROM内容:
");
display_eeprom_content(eeprom, 0, 32);
// 2. 使用查询方式写入单个字节
printf("
使用查询方式写入单个字节:
");
write_eeprom_polling(eeprom, 0x10, 'A', 10);
write_eeprom_polling(eeprom, 0x11, 'B', 10);
write_eeprom_polling(eeprom, 0x12, 'C', 10);
// 3. 显示写入后的内容
printf("
单字节写入后的EEPROM内容:
");
display_eeprom_content(eeprom, 0, 32);
// 4. 使用查询方式写入一块数据
printf("
使用查询方式写入一块数据:
");
const char* test_data = "This is a test of EEPROM block write using polling method.";
int written = write_eeprom_block_polling(eeprom, 0x20, (const uint8_t*)test_data, strlen(test_data), 10);
printf("写入完成: %d/%zu 字节
", written, strlen(test_data));
// 5. 显示写入块数据后的内容
printf("
块数据写入后的EEPROM内容:
");
display_eeprom_content(eeprom, 0, 80);
// 6. 读取一块数据
printf("
读取块数据:
");
uint8_t read_buffer[64];
int read = read_eeprom_block(eeprom, 0x20, read_buffer, 50);
printf("读取完成: %d字节
数据: ", read);
for (int i = 0; i < read; i++) {
printf("%c", read_buffer[i]);
}
printf("
");
// 7. 测试多次写入同一地址(磨损测试)
printf("
磨损测试 - 多次写入同一地址:
");
for (int i = 0; i < 5; i++) {
printf("
迭代 %d:
", i + 1);
write_eeprom_polling(eeprom, 0x50, 0xA0 + i, 10);
}
// 释放EEPROM资源
free_eeprom(eeprom);
return 0;
}
7.4 中断传送方式及其特性
中断传送方式是一种更高效的I/O数据传输方法,允许CPU在I/O设备未就绪时执行其他任务,当设备就绪时通过中断机制通知CPU。
7.4.1 中断传送机制与接口设计
中断机制是现代计算机系统中非常重要的功能,它允许外部设备在需要服务时主动通知CPU,而不需要CPU不断查询设备状态。
中断传送的核心组件:
中断控制器:管理多个中断源,实现优先级排序和中断屏蔽中断请求线(IRQ):设备用来发送中断请求的硬件信号线中断向量表:存储各种中断对应的处理程序地址中断服务程序(ISR):响应特定中断的代码
中断接口设计的关键要素:
中断产生电路:在特定条件下生成中断请求信号中断状态寄存器:表示中断的原因和状态中断控制寄存器:允许软件启用/禁用特定中断中断确认机制:允许软件通知设备中断已被处理
下面是一个模拟中断传送机制的C语言示例:
c
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <signal.h>
// 模拟中断控制器
typedef struct {
uint8_t imr; // 中断屏蔽寄存器
uint8_t irr; // 中断请求寄存器
uint8_t isr; // 中断服务寄存器
int current_int; // 当前正在处理的中断
void (*handlers[8])(void*); // 中断处理程序数组
void* handler_args[8]; // 中断处理程序参数
} InterruptController;
// 初始化中断控制器
InterruptController* init_interrupt_controller() {
InterruptController* ic = (InterruptController*)malloc(sizeof(InterruptController));
ic->imr = 0xFF; // 所有中断默认屏蔽
ic->irr = 0; // 无中断请求
ic->isr = 0; // 无中断服务
ic->current_int = -1; // 无当前中断
// 初始化处理程序为NULL
for (int i = 0; i < 8; i++) {
ic->handlers[i] = NULL;
ic->handler_args[i] = NULL;
}
return ic;
}
// 注册中断处理程序
void register_interrupt_handler(InterruptController* ic, int irq, void (*handler)(void*), void* arg) {
if (irq >= 0 && irq < 8) {
ic->handlers[irq] = handler;
ic->handler_args[irq] = arg;
printf("注册IRQ%d的中断处理程序
", irq);
} else {
printf("错误: 无效的IRQ号: %d
", irq);
}
}
// 设置中断屏蔽
void set_interrupt_mask(InterruptController* ic, uint8_t mask) {
ic->imr = mask;
printf("设置中断屏蔽: 0x%02X
", mask);
}
// 使能特定中断
void enable_interrupt(InterruptController* ic, int irq) {
if (irq >= 0 && irq < 8) {
ic->imr &= ~(1 << irq); // 清除对应位
printf("使能IRQ%d
", irq);
}
}
// 禁用特定中断
void disable_interrupt(InterruptController* ic, int irq) {
if (irq >= 0 && irq < 8) {
ic->imr |= (1 << irq); // 设置对应位
printf("禁用IRQ%d
", irq);
}
}
// 触发中断请求
void trigger_interrupt(InterruptController* ic, int irq) {
if (irq >= 0 && irq < 8) {
// 设置中断请求标志
ic->irr |= (1 << irq);
printf("触发IRQ%d中断请求
", irq);
}
}
// 处理挂起的中断
bool process_interrupts(InterruptController* ic) {
// 检查是否有未屏蔽的中断请求
uint8_t pending = ic->irr & (~ic->imr);
if (!pending) {
return false; // 无中断需要处理
}
// 找到最高优先级的中断(简单起见,使用最低位优先)
int irq = 0;
for (int i = 0; i < 8; i++) {
if (pending & (1 << i)) {
irq = i;
break;
}
}
// 更新中断控制器状态
ic->isr |= (1 << irq); // 设置正在服务标志
ic->irr &= ~(1 << irq); // 清除请求标志
ic->current_int = irq; // 记录当前中断
printf("开始处理IRQ%d中断
", irq);
// 调用中断处理程序
if (ic->handlers[irq]) {
ic->handlers[irq](ic->handler_args[irq]);
} else {
printf("警告: IRQ%d没有注册处理程序
", irq);
}
// 完成中断处理
ic->isr &= ~(1 << irq); // 清除服务标志
ic->current_int = -1; // 清除当前中断
printf("IRQ%d中断处理完成
", irq);
return true; // 已处理中断
}
// 模拟串口设备
typedef struct {
uint8_t data_register; // 数据寄存器
uint8_t status_register; // 状态寄存器
uint8_t control_register; // 控制寄存器
uint8_t interrupt_enable; // 中断使能寄存器
uint8_t interrupt_id; // 中断标识寄存器
uint8_t rx_buffer[16]; // 接收缓冲区
int rx_head; // 接收缓冲区头索引
int rx_tail; // 接收缓冲区尾索引
InterruptController* ic; // 关联的中断控制器
int irq; // 中断请求号
bool transmitting; // 发送中标志
clock_t tx_complete_time; // 发送完成时间
} SerialPort;
// 串口状态位定义
#define SERIAL_STATUS_DATA_READY 0x01
#define SERIAL_STATUS_THR_EMPTY 0x02
#define SERIAL_STATUS_LINE_STAT 0x04
#define SERIAL_STATUS_MODEM_STAT 0x08
// 串口中断使能位定义
#define SERIAL_INT_DATA_AVAIL 0x01
#define SERIAL_INT_THR_EMPTY 0x02
#define SERIAL_INT_LINE_STATUS 0x04
#define SERIAL_INT_MODEM_STATUS 0x08
// 中断标识值
#define SERIAL_II_MODEM_STATUS 0x00
#define SERIAL_II_THR_EMPTY 0x02
#define SERIAL_II_DATA_AVAIL 0x04
#define SERIAL_II_LINE_STATUS 0x06
#define SERIAL_II_NONE 0x01
// 初始化串口设备
SerialPort* init_serial_port(InterruptController* ic, int irq) {
SerialPort* port = (SerialPort*)malloc(sizeof(SerialPort));
port->data_register = 0;
port->status_register = SERIAL_STATUS_THR_EMPTY; // 发送保持寄存器为空
port->control_register = 0;
port->interrupt_enable = 0; // 默认禁用所有中断
port->interrupt_id = SERIAL_II_NONE; // 无中断挂起
memset(port->rx_buffer, 0, sizeof(port->rx_buffer));
port->rx_head = 0;
port->rx_tail = 0;
port->ic = ic;
port->irq = irq;
port->transmitting = false;
port->tx_complete_time = 0;
return port;
}
// 设置串口中断使能
void set_serial_interrupt_enable(SerialPort* port, uint8_t enable) {
port->interrupt_enable = enable;
printf("设置串口中断使能: 0x%02X
", enable);
// 如果禁用所有中断,清除所有挂起中断
if (enable == 0) {
port->interrupt_id = SERIAL_II_NONE;
}
}
// 读取串口状态
uint8_t read_serial_status(SerialPort* port) {
// 检查发送是否完成
if (port->transmitting) {
clock_t current_time = clock();
if (current_time >= port->tx_complete_time) {
// 发送完成
port->transmitting = false;
port->status_register |= SERIAL_STATUS_THR_EMPTY;
// 如果发送器空中断使能,触发中断
if (port->interrupt_enable & SERIAL_INT_THR_EMPTY) {
port->interrupt_id = SERIAL_II_THR_EMPTY;
trigger_interrupt(port->ic, port->irq);
}
}
}
printf("读取串口状态: 0x%02X
", port->status_register);
return port->status_register;
}
// 读取串口中断标识
uint8_t read_serial_interrupt_id(SerialPort* port) {
printf("读取串口中断标识: 0x%02X
", port->interrupt_id);
return port->interrupt_id;
}
// 向串口写入数据
void write_serial_data(SerialPort* port, uint8_t data) {
// 存储数据
port->data_register = data;
// 更新状态(发送器不再为空)
port->status_register &= ~SERIAL_STATUS_THR_EMPTY;
// 模拟发送过程
port->transmitting = true;
port->tx_complete_time = clock() + (100 * CLOCKS_PER_SEC / 1000); // 100毫秒后完成
printf("串口开始发送数据: 0x%02X (ASCII: '%c')
",
data, (data >= 32 && data <= 126) ? data : '.');
}
// 从串口读取数据
uint8_t read_serial_data(SerialPort* port) {
uint8_t data = port->data_register;
// 如果有更多数据在缓冲区,将下一个数据移到数据寄存器
if (port->rx_head != port->rx_tail) {
port->data_register = port->rx_buffer[port->rx_tail];
port->rx_tail = (port->rx_tail + 1) % sizeof(port->rx_buffer);
} else {
// 缓冲区已空,清除数据就绪标志
port->status_register &= ~SERIAL_STATUS_DATA_READY;
// 清除中断标识(如果是接收数据中断)
if (port->interrupt_id == SERIAL_II_DATA_AVAIL) {
port->interrupt_id = SERIAL_II_NONE;
}
}
printf("从串口读取数据: 0x%02X (ASCII: '%c')
",
data, (data >= 32 && data <= 126) ? data : '.');
return data;
}
// 模拟接收数据
void simulate_serial_reception(SerialPort* port, uint8_t data) {
// 计算下一个位置
int next_head = (port->rx_head + 1) % sizeof(port->rx_buffer);
// 检查缓冲区是否已满
if (next_head == port->rx_tail) {
printf("串口接收缓冲区溢出
");
return;
}
// 存储到缓冲区
port->rx_buffer[port->rx_head] = data;
port->rx_head = next_head;
// 如果数据寄存器未就绪,将数据也放入数据寄存器
if (!(port->status_register & SERIAL_STATUS_DATA_READY)) {
port->data_register = port->rx_buffer[port->rx_tail];
port->rx_tail = (port->rx_tail + 1) % sizeof(port->rx_buffer);
}
// 设置数据就绪标志
port->status_register |= SERIAL_STATUS_DATA_READY;
// 如果接收数据中断使能,触发中断
if (port->interrupt_enable & SERIAL_INT_DATA_AVAIL) {
port->interrupt_id = SERIAL_II_DATA_AVAIL;
trigger_interrupt(port->ic, port->irq);
}
printf("串口接收到数据: 0x%02X (ASCII: '%c')
",
data, (data >= 32 && data <= 126) ? data : '.');
}
// 串口接收中断处理程序
void serial_rx_interrupt_handler(void* arg) {
SerialPort* port = (SerialPort*)arg;
// 读取中断标识以确定中断原因
uint8_t iid = read_serial_interrupt_id(port);
if (iid == SERIAL_II_DATA_AVAIL) {
printf("处理串口接收数据中断
");
// 读取数据(在实际系统中,会将数据存储到缓冲区)
uint8_t data = read_serial_data(port);
printf("中断处理程序接收到数据: 0x%02X (ASCII: '%c')
",
data, (data >= 32 && data <= 126) ? data : '.');
}
else if (iid == SERIAL_II_THR_EMPTY) {
printf("处理串口发送器空中断
");
// 在实际系统中,可能会发送更多数据
}
else {
printf("处理其他串口中断: 0x%02X
", iid);
}
}
// 使用中断方式发送数据
void send_data_with_interrupt(SerialPort* port, uint8_t data) {
// 等待发送器就绪
while (!(port->status_register & SERIAL_STATUS_THR_EMPTY)) {
// 在实际系统中,可能会进入睡眠状态等待中断
// 这里简单地轮询
read_serial_status(port);
}
// 发送器就绪,写入数据
write_serial_data(port, data);
}
// 键盘控制器
typedef struct {
uint8_t data_register; // 数据寄存器
uint8_t status_register; // 状态寄存器
uint8_t command_register; // 命令寄存器
uint8_t buffer[16]; // 按键缓冲区
int buffer_head; // 缓冲区头索引
int buffer_tail; // 缓冲区尾索引
InterruptController* ic; // 关联的中断控制器
int irq; // 中断请求号
} KeyboardController;
// 键盘状态位定义
#define KB_STATUS_OUTPUT_BUFFER_FULL 0x01
#define KB_STATUS_INPUT_BUFFER_FULL 0x02
#define KB_STATUS_SYSTEM_FLAG 0x04
#define KB_STATUS_COMMAND_DATA 0x08
#define KB_STATUS_KEYBOARD_INHIBIT 0x10
#define KB_STATUS_TRANSMIT_TIMEOUT 0x20
#define KB_STATUS_RECEIVE_TIMEOUT 0x40
#define KB_STATUS_PARITY_ERROR 0x80
// 初始化键盘控制器
KeyboardController* init_keyboard_controller(InterruptController* ic, int irq) {
KeyboardController* kb = (KeyboardController*)malloc(sizeof(KeyboardController));
kb->data_register = 0;
kb->status_register = 0;
kb->command_register = 0;
memset(kb->buffer, 0, sizeof(kb->buffer));
kb->buffer_head = 0;
kb->buffer_tail = 0;
kb->ic = ic;
kb->irq = irq;
return kb;
}
// 读取键盘状态
uint8_t read_keyboard_status(KeyboardController* kb) {
printf("读取键盘状态: 0x%02X
", kb->status_register);
return kb->status_register;
}
// 从键盘读取数据
uint8_t read_keyboard_data(KeyboardController* kb) {
uint8_t data = kb->data_register;
// 缓冲区中是否有更多数据
if (kb->buffer_head != kb->buffer_tail) {
kb->data_register = kb->buffer[kb->buffer_tail];
kb->buffer_tail = (kb->buffer_tail + 1) % sizeof(kb->buffer);
} else {
// 缓冲区为空,清除输出缓冲区满标志
kb->status_register &= ~KB_STATUS_OUTPUT_BUFFER_FULL;
}
printf("从键盘读取数据: 0x%02X
", data);
return data;
}
// 向键盘发送命令
void write_keyboard_command(KeyboardController* kb, uint8_t cmd) {
kb->command_register = cmd;
kb->status_register |= KB_STATUS_INPUT_BUFFER_FULL;
// 实际处理命令(简化处理)
printf("执行键盘命令: 0x%02X
", cmd);
// 命令处理完成,清除输入缓冲区满标志
kb->status_register &= ~KB_STATUS_INPUT_BUFFER_FULL;
}
// 模拟按键按下
void simulate_keypress(KeyboardController* kb, uint8_t scancode) {
// 计算下一个位置
int next_head = (kb->buffer_head + 1) % sizeof(kb->buffer);
// 检查缓冲区是否已满
if (next_head == kb->buffer_tail) {
printf("键盘缓冲区溢出
");
return;
}
// 存储到缓冲区
kb->buffer[kb->buffer_head] = scancode;
kb->buffer_head = next_head;
// 如果数据寄存器未就绪,将数据也放入数据寄存器
if (!(kb->status_register & KB_STATUS_OUTPUT_BUFFER_FULL)) {
kb->data_register = kb->buffer[kb->buffer_tail];
kb->buffer_tail = (kb->buffer_tail + 1) % sizeof(kb->buffer);
kb->status_register |= KB_STATUS_OUTPUT_BUFFER_FULL;
}
// 触发中断
trigger_interrupt(kb->ic, kb->irq);
printf("模拟按键: 扫描码 0x%02X
", scancode);
}
// 键盘中断处理程序
void keyboard_interrupt_handler(void* arg) {
KeyboardController* kb = (KeyboardController*)arg;
printf("处理键盘中断
");
// 读取键盘数据
if (kb->status_register & KB_STATUS_OUTPUT_BUFFER_FULL) {
uint8_t scancode = read_keyboard_data(kb);
printf("中断处理程序接收到扫描码: 0x%02X
", scancode);
// 实际系统中,会将扫描码转换为ASCII码并存储到系统缓冲区
} else {
printf("键盘中断但无数据可读
");
}
}
int main() {
printf("中断传送方式示例
");
printf("=============
");
// 初始化中断控制器
InterruptController* ic = init_interrupt_controller();
// 1. 串口中断演示
printf("1. 串口中断演示:
");
printf("--------------
");
// 初始化串口(IRQ4)
SerialPort* serial = init_serial_port(ic, 4);
// 注册串口中断处理程序
register_interrupt_handler(ic, 4, serial_rx_interrupt_handler, serial);
// 使能串口接收数据中断和发送器空中断
set_serial_interrupt_enable(serial, SERIAL_INT_DATA_AVAIL | SERIAL_INT_THR_EMPTY);
// 使能IRQ4
enable_interrupt(ic, 4);
// 模拟接收数据
printf("
模拟串口接收数据:
");
simulate_serial_reception(serial, 'X');
// 处理中断
process_interrupts(ic);
// 发送数据
printf("
使用中断方式发送数据:
");
send_data_with_interrupt(serial, 'Y');
// 处理发送完成中断
printf("
模拟CPU执行其他任务...
");
usleep(110000); // 等待110毫秒(比发送时间长)
// 检查并处理发送完成中断
read_serial_status(serial); // 更新状态
process_interrupts(ic);
printf("
2. 键盘中断演示:
");
printf("--------------
");
// 初始化键盘控制器(IRQ1)
KeyboardController* kb = init_keyboard_controller(ic, 1);
// 注册键盘中断处理程序
register_interrupt_handler(ic, 1, keyboard_interrupt_handler, kb);
// 使能IRQ1
enable_interrupt(ic, 1);
// 模拟按键
printf("
模拟按键:
");
simulate_keypress(kb, 0x1E); // 'A'的扫描码
// 处理中断
process_interrupts(ic);
// 模拟连续按键
printf("
模拟连续按键:
");
simulate_keypress(kb, 0x30); // 'B'的扫描码
simulate_keypress(kb, 0x2E); // 'C'的扫描码
// 处理多个中断
while(process_interrupts(ic)) {
// 继续处理直到没有更多中断
}
// 清理资源
free(serial);
free(kb);
free(ic);
return 0;
}
7.4.2 中断工作过程详解
中断工作过程是计算机系统中重要的控制流转换机制。当I/O设备需要CPU服务时,它会触发中断,CPU暂停当前程序执行,转而处理中断请求。
中断处理的基本过程:
中断请求:设备通过中断线向CPU发送中断请求信号中断响应:CPU完成当前指令后,保存当前程序状态(程序计数器、标志寄存器等)中断识别:CPU识别中断源,确定中断向量中断处理:CPU跳转到中断服务程序执行设备服务中断返回:服务完成后,恢复之前保存的程序状态,继续执行被中断的程序
下面是一个模拟中断工作过程的C语言示例:
c
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
// 模拟CPU状态
typedef struct {
uint16_t pc; // 程序计数器
uint16_t sp; // 栈指针
uint8_t flags; // 标志寄存器
uint8_t a, b, c, d; // 通用寄存器
bool interrupt_enabled; // 中断使能标志
bool handling_interrupt; // 正在处理中断标志
} CPUState;
// 模拟内存系统
typedef struct {
uint8_t* ram; // 内存
uint32_t size; // 内存大小
} Memory;
// 模拟中断向量表
typedef struct {
uint16_t vectors[16]; // 中断向量(每个指向处理程序的地址)
} InterruptVectorTable;
// 中断控制器
typedef struct {
uint16_t imr; // 中断掩码寄存器
uint16_t irr; // 中断请求寄存器
uint16_t isr; // 中断服务寄存器
CPUState* cpu; // 关联的CPU
Memory* memory; // 关联的内存
InterruptVectorTable* ivt; // 关联的中断向量表
} InterruptController;
// 模拟I/O设备
typedef struct {
uint8_t data_register; // 数据寄存器
uint8_t status_register; // 状态寄存器
uint8_t control_register; // 控制寄存器
int irq; // 中断请求号
InterruptController* ic; // 关联的中断控制器
} IODevice;
// 初始化CPU
CPUState* init_cpu() {
CPUState* cpu = (CPUState*)malloc(sizeof(CPUState));
cpu->pc = 0x1000; // 程序从0x1000开始
cpu->sp = 0xFFF0; // 栈从高地址向低地址增长
cpu->flags = 0;
cpu->a = cpu->b = cpu->c = cpu->d = 0;
cpu->interrupt_enabled = true;
cpu->handling_interrupt = false;
return cpu;
}
// 初始化内存
Memory* init_memory(uint32_t size) {
Memory* mem = (Memory*)malloc(sizeof(Memory));
mem->ram = (uint8_t*)malloc(size);
mem->size = size;
memset(mem->ram, 0, size);
return mem;
}
// 初始化中断向量表
InterruptVectorTable* init_ivt() {
InterruptVectorTable* ivt = (InterruptVectorTable*)malloc(sizeof(InterruptVectorTable));
// 默认所有向量指向0(无效地址)
for (int i = 0; i < 16; i++) {
ivt->vectors[i] = 0;
}
return ivt;
}
// 设置中断向量
void set_interrupt_vector(InterruptVectorTable* ivt, int vector, uint16_t handler_address) {
if (vector >= 0 && vector < 16) {
ivt->vectors[vector] = handler_address;
printf("设置中断向量%d指向地址0x%04X
", vector, handler_address);
}
}
// 初始化中断控制器
InterruptController* init_interrupt_controller(CPUState* cpu, Memory* memory, InterruptVectorTable* ivt) {
InterruptController* ic = (InterruptController*)malloc(sizeof(InterruptController));
ic->imr = 0xFFFF; // 所有中断默认被屏蔽
ic->irr = 0; // 无中断请求
ic->isr = 0; // 无中断服务
ic->cpu = cpu;
ic->memory = memory;
ic->ivt = ivt;
return ic;
}
// 初始化I/O设备
IODevice* init_io_device(int irq, InterruptController* ic) {
IODevice* dev = (IODevice*)malloc(sizeof(IODevice));
dev->data_register = 0;
dev->status_register = 0;
dev->control_register = 0;
dev->irq = irq;
dev->ic = ic;
return dev;
}
// 使能中断
void enable_interrupt_line(InterruptController* ic, int irq) {
if (irq >= 0 && irq < 16) {
ic->imr &= ~(1 << irq); // 清除对应位
printf("使能IRQ%d
", irq);
}
}
// 禁用中断
void disable_interrupt_line(InterruptController* ic, int irq) {
if (irq >= 0 && irq < 16) {
ic->imr |= (1 << irq); // 设置对应位
printf("禁用IRQ%d
", irq);
}
}
// 设备请求中断
void device_request_interrupt(IODevice* dev) {
int irq = dev->irq;
InterruptController* ic = dev->ic;
// 设置中断请求标志
ic->irr |= (1 << irq);
printf("设备请求IRQ%d中断
", irq);
}
// 推入CPU状态到栈
void push_cpu_state(CPUState* cpu, Memory* memory) {
// 保存程序计数器
cpu->sp -= 2;
memory->ram[cpu->sp] = cpu->pc & 0xFF;
memory->ram[cpu->sp + 1] = (cpu->pc >> 8) & 0xFF;
// 保存标志寄存器
cpu->sp -= 1;
memory->ram[cpu->sp] = cpu->flags;
// 保存寄存器
cpu->sp -= 4;
memory->ram[cpu->sp] = cpu->a;
memory->ram[cpu->sp + 1] = cpu->b;
memory->ram[cpu->sp + 2] = cpu->c;
memory->ram[cpu->sp + 3] = cpu->d;
printf("保存CPU状态到栈: SP=0x%04X, PC=0x%04X, Flags=0x%02X
",
cpu->sp, cpu->pc, cpu->flags);
}
// 从栈恢复CPU状态
void pop_cpu_state(CPUState* cpu, Memory* memory) {
// 恢复寄存器
cpu->a = memory->ram[cpu->sp];
cpu->b = memory->ram[cpu->sp + 1];
cpu->c = memory->ram[cpu->sp + 2];
cpu->d = memory->ram[cpu->sp + 3];
cpu->sp += 4;
// 恢复标志寄存器
cpu->flags = memory->ram[cpu->sp];
cpu->sp += 1;
// 恢复程序计数器
cpu->pc = memory->ram[cpu->sp] | (memory->ram[cpu->sp + 1] << 8);
cpu->sp += 2;
printf("从栈恢复CPU状态: SP=0x%04X, PC=0x%04X, Flags=0x%02X
",
cpu->sp, cpu->pc, cpu->flags);
}
// 中断确认
int interrupt_acknowledge(InterruptController* ic) {
uint16_t pending = ic->irr & (~ic->imr);
if (!pending) {
return -1; // 无中断
}
// 找到最高优先级的中断(简单起见,使用最低位优先)
int irq = 0;
while ((pending & (1 << irq)) == 0 && irq < 16) {
irq++;
}
if (irq == 16) {
return -1; // 没有找到可服务的中断
}
// 更新中断控制器状态
ic->isr |= (1 << irq); // 设置正在服务标志
ic->irr &= ~(1 << irq); // 清除请求标志
return irq;
}
// 中断结束
void end_of_interrupt(InterruptController* ic, int irq) {
if (irq >= 0 && irq < 16) {
ic->isr &= ~(1 << irq); // 清除服务标志
printf("中断IRQ%d服务完成
", irq);
}
}
// 处理中断
void handle_interrupt(InterruptController* ic) {
CPUState* cpu = ic->cpu;
Memory* memory = ic->memory;
InterruptVectorTable* ivt = ic->ivt;
// 如果CPU禁止中断或正在处理中断,不处理新中断
if (!cpu->interrupt_enabled || cpu->handling_interrupt) {
return;
}
// 中断确认,获取IRQ号
int irq = interrupt_acknowledge(ic);
if (irq == -1) {
return; // 无中断需要处理
}
printf("开始处理IRQ%d中断
", irq);
// 设置正在处理中断标志
cpu->handling_interrupt = true;
// 保存当前CPU状态
push_cpu_state(cpu, memory);
// 获取中断处理程序地址
uint16_t handler_address = ivt->vectors[irq];
if (handler_address == 0) {
printf("警告: IRQ%d没有处理程序
", irq);
cpu->handling_interrupt = false;
return;
}
// 跳转到中断处理程序
cpu->pc = handler_address;
printf("跳转到中断处理程序地址0x%04X
", cpu->pc);
// 模拟执行中断处理程序
printf("执行中断处理程序...
");
// 在实际系统中,这里会执行中断处理程序的指令
// 这里我们只是模拟
// 中断处理完成
end_of_interrupt(ic, irq);
// 恢复保存的CPU状态
pop_cpu_state(cpu, memory);
// 清除正在处理中断标志
cpu->handling_interrupt = false;
printf("中断处理完成,恢复到PC=0x%04X
", cpu->pc);
}
// 模拟键盘设备
typedef struct {
IODevice base; // 继承基本I/O设备结构
uint8_t buffer[16]; // 按键缓冲区
int buffer_head; // 缓冲区头索引
int buffer_tail; // 缓冲区尾索引
} KeyboardDevice;
// 初始化键盘设备
KeyboardDevice* init_keyboard(int irq, InterruptController* ic) {
KeyboardDevice* kbd = (KeyboardDevice*)malloc(sizeof(KeyboardDevice));
// 初始化基本I/O设备部分
kbd->base.data_register = 0;
kbd->base.status_register = 0;
kbd->base.control_register = 0;
kbd->base.irq = irq;
kbd->base.ic = ic;
// 初始化键盘特有部分
memset(kbd->buffer, 0, sizeof(kbd->buffer));
kbd->buffer_head = 0;
kbd->buffer_tail = 0;
return kbd;
}
// 模拟按键
void simulate_keypress(KeyboardDevice* kbd, uint8_t scancode) {
// 计算下一个缓冲区位置
int next_head = (kbd->buffer_head + 1) % sizeof(kbd->buffer);
if (next_head == kbd->buffer_tail) {
printf("键盘缓冲区已满
");
return;
}
// 存储扫描码到缓冲区
kbd->buffer[kbd->buffer_head] = scancode;
kbd->buffer_head = next_head;
// 如果数据寄存器未就绪,更新数据寄存器
if ((kbd->base.status_register & 0x01) == 0) {
kbd->base.data_register = kbd->buffer[kbd->buffer_tail];
kbd->buffer_tail = (kbd->buffer_tail + 1) % sizeof(kbd->buffer);
// 设置数据就绪标志
kbd->base.status_register |= 0x01;
// 请求中断
device_request_interrupt(&kbd->base);
}
printf("模拟按键: 扫描码0x%02X
", scancode);
}
// 模拟串口设备
typedef struct {
IODevice base; // 继承基本I/O设备结构
uint8_t rx_buffer[16]; // 接收缓冲区
int rx_head; // 接收缓冲区头索引
int rx_tail; // 接收缓冲区尾索引
uint8_t tx_buffer[16]; // 发送缓冲区
int tx_head; // 发送缓冲区头索引
int tx_tail; // 发送缓冲区尾索引
bool tx_empty; // 发送缓冲区空标志
} SerialDevice;
// 初始化串口设备
SerialDevice* init_serial(int irq, InterruptController* ic) {
SerialDevice* serial = (SerialDevice*)malloc(sizeof(SerialDevice));
// 初始化基本I/O设备部分
serial->base.data_register = 0;
serial->base.status_register = 0x20; // 发送缓冲区初始为空
serial->base.control_register = 0;
serial->base.irq = irq;
serial->base.ic = ic;
// 初始化串口特有部分
memset(serial->rx_buffer, 0, sizeof(serial->rx_buffer));
serial->rx_head = 0;
serial->rx_tail = 0;
memset(serial->tx_buffer, 0, sizeof(serial->tx_buffer));
serial->tx_head = 0;
serial->tx_tail = 0;
serial->tx_empty = true;
return serial;
}
// 模拟串口接收数据
void simulate_serial_reception(SerialDevice* serial, uint8_t data) {
// 计算下一个缓冲区位置
int next_head = (serial->rx_head + 1) % sizeof(serial->rx_buffer);
if (next_head == serial->rx_tail) {
printf("串口接收缓冲区已满
");
return;
}
// 存储数据到缓冲区
serial->rx_buffer[serial->rx_head] = data;
serial->rx_head = next_head;
// 如果数据寄存器未就绪,更新数据寄存器
if ((serial->base.status_register & 0x01) == 0) {
serial->base.data_register = serial->rx_buffer[serial->rx_tail];
serial->rx_tail = (serial->rx_tail + 1) % sizeof(serial->rx_buffer);
// 设置数据就绪标志
serial->base.status_register |= 0x01;
// 请求中断
device_request_interrupt(&serial->base);
}
printf("串口接收数据: 0x%02X (ASCII: '%c')
",
data, (data >= 32 && data <= 126) ? data : '.');
}
// 向串口发送数据
void serial_send_data(SerialDevice* serial, uint8_t data) {
// 检查发送缓冲区是否已满
int next_head = (serial->tx_head + 1) % sizeof(serial->tx_buffer);
if (next_head == serial->tx_tail && !serial->tx_empty) {
printf("串口发送缓冲区已满
");
return;
}
// 存储数据到发送缓冲区
serial->tx_buffer[serial->tx_head] = data;
serial->tx_head = next_head;
serial->tx_empty = false;
// 清除发送缓冲区空标志
serial->base.status_register &= ~0x20;
printf("向串口发送数据: 0x%02X (ASCII: '%c')
",
data, (data >= 32 && data <= 126) ? data : '.');
// 模拟发送完成
printf("模拟串口发送完成
");
// 设置发送缓冲区空标志
serial->base.status_register |= 0x20;
// 如果允许发送完成中断,触发中断
if (serial->base.control_register & 0x02) {
device_request_interrupt(&serial->base);
}
}
// 模拟键盘中断处理程序
void keyboard_isr(CPUState* cpu, Memory* memory, KeyboardDevice* kbd) {
printf("执行键盘中断服务程序:
");
// 读取键盘数据寄存器
uint8_t scancode = kbd->base.data_register;
printf(" 读取扫描码: 0x%02X
", scancode);
// 清除数据就绪标志
kbd->base.status_register &= ~0x01;
// 如果缓冲区中有更多数据,更新数据寄存器并再次触发中断
if (kbd->buffer_head != kbd->buffer_tail) {
kbd->base.data_register = kbd->buffer[kbd->buffer_tail];
kbd->buffer_tail = (kbd->buffer_tail + 1) % sizeof(kbd->buffer);
kbd->base.status_register |= 0x01;
printf(" 缓冲区中有更多数据,再次触发中断
");
device_request_interrupt(&kbd->base);
}
}
// 模拟串口中断处理程序
void serial_isr(CPUState* cpu, Memory* memory, SerialDevice* serial) {
printf("执行串口中断服务程序:
");
// 检查中断原因
if (serial->base.status_register & 0x01) {
// 接收数据中断
uint8_t data = serial->base.data_register;
printf(" 接收数据中断,读取数据: 0x%02X (ASCII: '%c')
",
data, (data >= 32 && data <= 126) ? data : '.');
// 清除数据就绪标志
serial->base.status_register &= ~0x01;
// 如果缓冲区中有更多数据,更新数据寄存器并再次触发中断
if (serial->rx_head != serial->rx_tail) {
serial->base.data_register = serial->rx_buffer[serial->rx_tail];
serial->rx_tail = (serial->rx_tail + 1) % sizeof(serial->rx_buffer);
serial->base.status_register |= 0x01;
printf(" 接收缓冲区中有更多数据,再次触发中断
");
device_request_interrupt(&serial->base);
}
} else if (serial->base.status_register & 0x20) {
// 发送缓冲区空中断
printf(" 发送缓冲区空中断
");
// 如果发送缓冲区中还有数据,继续发送
if (serial->tx_head != serial->tx_tail) {
uint8_t data = serial->tx_buffer[serial->tx_tail];
serial->tx_tail = (serial->tx_tail + 1) % sizeof(serial->tx_buffer);
// 清除发送缓冲区空标志
serial->base.status_register &= ~0x20;
printf(" 从发送缓冲区取出下一个字节: 0x%02X (ASCII: '%c')
",
data, (data >= 32 && data <= 126) ? data : '.');
// 模拟发送完成
printf(" 模拟发送完成
");
// 设置发送缓冲区空标志
serial->base.status_register |= 0x20;
// 如果发送缓冲区非空,再次触发中断
if (serial->tx_head != serial->tx_tail) {
printf(" 发送缓冲区中有更多数据,再次触发中断
");
device_request_interrupt(&serial->base);
}
} else {
// 发送缓冲区已空
serial->tx_empty = true;
}
}
}
int main() {
printf("中断工作过程详细演示
");
printf("================
");
// 初始化系统组件
CPUState* cpu = init_cpu();
Memory* memory = init_memory(65536); // 64KB内存
InterruptVectorTable* ivt = init_ivt();
InterruptController* ic = init_interrupt_controller(cpu, memory, ivt);
// 初始化I/O设备
KeyboardDevice* kbd = init_keyboard(1, ic);
SerialDevice* serial = init_serial(4, ic);
// 设置中断向量(模拟中断处理程序地址)
set_interrupt_vector(ivt, 1, 0x2000); // 键盘中断处理程序
set_interrupt_vector(ivt, 4, 0x3000); // 串口中断处理程序
// 使能中断线
enable_interrupt_line(ic, 1); // 键盘中断
enable_interrupt_line(ic, 4); // 串口中断
// 启用串口接收和发送中断
serial->base.control_register = 0x03;
printf("系统初始化完成,开始演示中断过程
");
// 1. 模拟键盘中断过程
printf("1. 键盘中断过程:
");
printf("---------------
");
// 模拟按键
printf("模拟按下'A'键...
");
simulate_keypress(kbd, 0x1E); // 'A'的扫描码
// 处理中断
handle_interrupt(ic);
// 在实际系统中,这里会执行键盘中断处理程序
keyboard_isr(cpu, memory, kbd);
printf("
");
// 2. 模拟串口中断过程
printf("2. 串口中断过程:
");
printf("---------------
");
// 模拟接收数据
printf("模拟串口接收字符'X'...
");
simulate_serial_reception(serial, 'X');
// 处理中断
handle_interrupt(ic);
// 在实际系统中,这里会执行串口中断处理程序
serial_isr(cpu, memory, serial);
// 模拟串口发送
printf("
模拟向串口发送字符'Y'...
");
serial_send_data(serial, 'Y');
// 发送完成会触发中断
handle_interrupt(ic);
// 执行串口中断处理程序
serial_isr(cpu, memory, serial);
printf("
3. 多级中断嵌套示例:
");
printf("------------------
");
// 模拟CPU在执行任务时接收到多个中断
printf("CPU正在执行任务,程序计数器PC=0x%04X
", cpu->pc);
// 同时接收到串口和键盘中断
printf("
同时模拟串口接收字符'Z'和按下'B'键...
");
simulate_serial_reception(serial, 'Z');
simulate_keypress(kbd, 0x30); // 'B'的扫描码
// 处理第一个中断(假设串口优先级更高)
printf("
处理第一个中断(串口):
");
handle_interrupt(ic);
// 执行串口中断处理程序
serial_isr(cpu, memory, serial);
// 处理第二个中断(键盘)
printf("
处理第二个中断(键盘):
");
handle_interrupt(ic);
// 执行键盘中断处理程序
keyboard_isr(cpu, memory, kbd);
printf("
CPU恢复执行原任务,程序计数器PC=0x%04X
", cpu->pc);
// 释放资源
free(kbd);
free(serial);
free(ic);
free(ivt);
free(memory->ram);
free(memory);
free(cpu);
return 0;
}
7.4.3 中断优先权管理
在计算机系统中,可能会同时有多个中断请求。中断优先权管理用于确定哪个中断应该先被处理,以确保最关键的任务能够得到及时响应。
中断优先权管理的主要方法:
固定优先权:每个中断源有固定的优先级,高优先级中断总是先于低优先级中断处理循环优先权:在固定时间段内,各中断源轮流获得最高优先权动态优先权:根据系统状态和任务需求动态调整中断优先级
中断嵌套:高优先级中断可以打断正在处理的低优先级中断
以下是一个演示中断优先权管理的C语言示例:
c
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
// 中断优先级定义
#define PRIORITY_HIGHEST 0
#define PRIORITY_HIGH 1
#define PRIORITY_MEDIUM 2
#define PRIORITY_LOW 3
#define PRIORITY_LOWEST 4
// 中断类型
typedef struct {
int irq; // 中断请求号
int priority; // 优先级(0最高)
const char* name; // 中断名称
} InterruptType;
// 定义系统中的中断类型
InterruptType interrupt_types[] = {
{0, PRIORITY_HIGHEST, "系统计时器中断"},
{1, PRIORITY_HIGH, "键盘中断"},
{2, PRIORITY_HIGHEST, "级联中断"},
{3, PRIORITY_MEDIUM, "串口COM2中断"},
{4, PRIORITY_MEDIUM, "串口COM1中断"},
{5, PRIORITY_LOW, "声卡/并口中断"},
{6, PRIORITY_MEDIUM, "软盘中断"},
{7, PRIORITY_LOW, "并口中断"}
};
// 中断控制器
typedef struct {
uint8_t imr; // 中断屏蔽寄存器
uint8_t irr; // 中断请求寄存器
uint8_t isr; // 中断服务寄存器
int current_int; // 当前正在处理的中断
int current_int_priority; // 当前中断优先级
bool nested_mode; // 嵌套模式使能
bool handling_interrupt; // 正在处理中断标志
void (*handlers[8])(void*); // 中断处理程序
void* handler_args[8]; // 中断处理程序参数
} InterruptController;
// 初始化中断控制器
InterruptController* init_interrupt_controller(bool nested_mode) {
InterruptController* ic = (InterruptController*)malloc(sizeof(InterruptController));
ic->imr = 0xFF; // 所有中断默认屏蔽
ic->irr = 0; // 无中断请求
ic->isr = 0; // 无中断服务
ic->current_int = -1; // 无当前中断
ic->current_int_priority = PRIORITY_LOWEST + 1; // 初始设置为最低优先级以下
ic->nested_mode = nested_mode;
ic->handling_interrupt = false;
// 初始化处理程序
for (int i = 0; i < 8; i++) {
ic->handlers[i] = NULL;
ic->handler_args[i] = NULL;
}
return ic;
}
// 注册中断处理程序
void register_interrupt_handler(InterruptController* ic, int irq, void (*handler)(void*), void* arg) {
if (irq >= 0 && irq < 8) {
ic->handlers[irq] = handler;
ic->handler_args[irq] = arg;
printf("注册IRQ%d (%s)的中断处理程序
", irq, interrupt_types[irq].name);
}
}
// 设置中断屏蔽
void set_interrupt_mask(InterruptController* ic, uint8_t mask) {
ic->imr = mask;
printf("设置中断屏蔽寄存器: 0x%02X
", mask);
}
// 使能特定中断
void enable_interrupt(InterruptController* ic, int irq) {
if (irq >= 0 && irq < 8) {
ic->imr &= ~(1 << irq); // 清除对应位
printf("使能IRQ%d (%s)
", irq, interrupt_types[irq].name);
}
}
// 禁用特定中断
void disable_interrupt(InterruptController* ic, int irq) {
if (irq >= 0 && irq < 8) {
ic->imr |= (1 << irq); // 设置对应位
printf("禁用IRQ%d (%s)
", irq, interrupt_types[irq].name);
}
}
// 触发中断请求
void trigger_interrupt(InterruptController* ic, int irq) {
if (irq >= 0 && irq < 8) {
ic->irr |= (1 << irq); // 设置中断请求位
printf("触发IRQ%d (%s)中断请求
", irq, interrupt_types[irq].name);
}
}
// 找到最高优先级的未处理中断
int find_highest_priority_interrupt(InterruptController* ic) {
// 检查是否有未屏蔽的中断请求
uint8_t pending = ic->irr & (~ic->imr);
if (!pending) {
return -1; // 无中断需要处理
}
// 找到优先级最高的中断
int highest_priority = PRIORITY_LOWEST + 1;
int selected_irq = -1;
for (int i = 0; i < 8; i++) {
if (pending & (1 << i)) {
if (interrupt_types[i].priority < highest_priority) {
highest_priority = interrupt_types[i].priority;
selected_irq = i;
}
}
}
return selected_irq;
}
// 中断确认
int interrupt_acknowledge(InterruptController* ic) {
// 找到优先级最高的中断
int irq = find_highest_priority_interrupt(ic);
if (irq == -1) {
return -1; // 无中断需要处理
}
// 检查中断嵌套规则
if (ic->handling_interrupt && !ic->nested_mode) {
printf("中断控制器不支持嵌套,IRQ%d (%s)中断被延迟
",
irq, interrupt_types[irq].name);
return -1;
}
// 如果支持嵌套,检查优先级
if (ic->handling_interrupt && ic->nested_mode) {
if (interrupt_types[irq].priority >= ic->current_int_priority) {
printf("IRQ%d (%s)优先级不足以打断当前中断(IRQ%d),被延迟
",
irq, interrupt_types[irq].name, ic->current_int);
return -1;
}
}
// 更新中断控制器状态
ic->isr |= (1 << irq); // 设置正在服务标志
ic->irr &= ~(1 << irq); // 清除请求标志
return irq;
}
// 开始中断处理
bool begin_interrupt_processing(InterruptController* ic) {
// 如果已经在处理中断且不支持嵌套,直接返回
if (ic->handling_interrupt && !ic->nested_mode) {
return false;
}
// 中断确认,获取IRQ号
int irq = interrupt_acknowledge(ic);
if (irq == -1) {
return false; // 无中断需要处理
}
// 保存当前中断信息(用于嵌套)
int prev_irq = ic->current_int;
int prev_priority = ic->current_int_priority;
// 更新当前中断信息
ic->current_int = irq;
ic->current_int_priority = interrupt_types[irq].priority;
ic->handling_interrupt = true;
printf("开始处理IRQ%d (%s)中断", irq, interrupt_types[irq].name);
if (prev_irq != -1) {
printf(" (嵌套在IRQ%d内部)", prev_irq);
}
printf("
");
// 调用中断处理程序
if (ic->handlers[irq]) {
ic->handlers[irq](ic->handler_args[irq]);
} else {
printf("警告: IRQ%d没有注册处理程序
", irq);
}
// 结束当前中断处理
ic->isr &= ~(1 << irq); // 清除服务标志
// 恢复之前的中断状态(用于嵌套)
if (prev_irq != -1) {
printf("恢复到IRQ%d (%s)中断处理
", prev_irq, interrupt_types[prev_irq].name);
ic->current_int = prev_irq;
ic->current_int_priority = prev_priority;
} else {
ic->current_int = -1;
ic->current_int_priority = PRIORITY_LOWEST + 1;
ic->handling_interrupt = false;
}
printf("IRQ%d (%s)中断处理完成
", irq, interrupt_types[irq].name);
return true;
}
// 结束所有中断处理
void end_all_interrupt_processing(InterruptController* ic) {
ic->isr = 0;
ic->current_int = -1;
ic->current_int_priority = PRIORITY_LOWEST + 1;
ic->handling_interrupt = false;
printf("所有中断处理完成
");
}
// 简化的中断处理程序
void simple_interrupt_handler(void* arg) {
int irq = *((int*)arg);
printf("执行IRQ%d (%s)的中断处理程序
", irq, interrupt_types[irq].name);
printf(" 模拟处理时间...
");
usleep(100000); // 模拟100毫秒处理时间
printf(" 处理完成
");
}
// 长时间中断处理程序
void long_interrupt_handler(void* arg) {
int irq = *((int*)arg);
printf("执行IRQ%d (%s)的长时间中断处理程序
", irq, interrupt_types[irq].name);
for (int i = 0; i < 5; i++) {
printf(" 长时间处理中... %d/5
", i + 1);
usleep(200000); // 模拟200毫秒处理时间
}
printf(" 长时间处理完成
");
}
// 中断处理程序参数(IRQ号)
int irq_args[8];
int main() {
printf("中断优先权管理演示
");
printf("==============
");
// 初始化参数数组
for (int i = 0; i < 8; i++) {
irq_args[i] = i;
}
printf("1. 固定优先级模型(不支持嵌套):
");
printf("----------------------------
");
// 初始化不支持嵌套的中断控制器
InterruptController* ic1 = init_interrupt_controller(false);
// 注册中断处理程序
for (int i = 0; i < 8; i++) {
register_interrupt_handler(ic1, i, simple_interrupt_handler, &irq_args[i]);
}
// 使能所有中断
set_interrupt_mask(ic1, 0x00);
printf("
同时触发多个中断:
");
// 触发多个不同优先级的中断
trigger_interrupt(ic1, 4); // COM1 (中等优先级)
trigger_interrupt(ic1, 1); // 键盘 (高优先级)
trigger_interrupt(ic1, 7); // 并口 (低优先级)
// 处理中断(应该按优先级顺序处理)
printf("
开始处理中断(按优先级顺序):
");
while (begin_interrupt_processing(ic1)) {
// 继续处理直到没有更多中断
}
printf("
2. 固定优先级模型(支持嵌套):
");
printf("--------------------------
");
// 初始化支持嵌套的中断控制器
InterruptController* ic2 = init_interrupt_controller(true);
// 注册中断处理程序(使用长时间处理程序用于演示嵌套)
register_interrupt_handler(ic2, 4, long_interrupt_handler, &irq_args[4]); // COM1
register_interrupt_handler(ic2, 1, simple_interrupt_handler, &irq_args[1]); // 键盘
register_interrupt_handler(ic2, 0, simple_interrupt_handler, &irq_args[0]); // 计时器
// 使能所有中断
set_interrupt_mask(ic2, 0x00);
printf("
触发中优先级中断,然后触发高优先级中断(演示嵌套):
");
// 先触发中优先级中断
trigger_interrupt(ic2, 4); // COM1 (中等优先级)
// 开始处理第一个中断
begin_interrupt_processing(ic2);
// 在处理过程中,触发更高优先级中断
printf("
当正在处理COM1中断时,触发键盘中断(高优先级):
");
trigger_interrupt(ic2, 1); // 键盘 (高优先级)
// 嵌套处理第二个中断
begin_interrupt_processing(ic2);
// 在嵌套处理过程中,触发最高优先级中断
printf("
当正在处理键盘中断时,触发计时器中断(最高优先级):
");
trigger_interrupt(ic2, 0); // 计时器 (最高优先级)
// 再次嵌套处理
begin_interrupt_processing(ic2);
// 触发低优先级中断(应该被延迟)
printf("
触发低优先级中断(应该被延迟直到高优先级中断完成):
");
trigger_interrupt(ic2, 7); // 并口 (低优先级)
// 尝试处理低优先级中断(应该失败)
begin_interrupt_processing(ic2);
// 结束所有中断处理
end_all_interrupt_processing(ic2);
// 现在处理被延迟的低优先级中断
printf("
处理之前被延迟的低优先级中断:
");
begin_interrupt_processing(ic2);
printf("
3. 循环优先级模型演示:
");
printf("--------------------
");
printf("在循环优先级模型中,各中断源轮流获得最高优先权。
");
printf("这种模型通常用于确保每个中断源都能得到处理,避免低优先级中断被饿死。
");
printf("实现方式可以是维护一个当前最高优先权的位置指针,每处理一个中断后移动指针。
");
printf("
4. 动态优先级模型演示:
");
printf("--------------------
");
printf("在动态优先级模型中,中断优先级可以根据系统状态动态调整。
");
printf("例如,可以根据以下因素调整优先级:
");
printf(" - 中断已等待时间(等待越久,优先级越高)
");
printf(" - 系统当前负载(高负载时提高I/O中断优先级)
");
printf(" - 任务截止期限(接近截止期限的任务相关中断优先级提高)
");
// 清理资源
free(ic1);
free(ic2);
return 0;
}
7.5 直接内存存取(DMA)传送方式
直接内存存取(DMA)是一种高效的数据传输方式,允许外部设备直接与内存进行数据交换,无需CPU参与每次数据传输。DMA特别适合大量数据传输,如磁盘读写和网络通信。
DMA传输方式的核心原理和实现包括:
DMA传送机制与接口设计
DMA传输的关键在于DMA控制器,它负责管理整个数据传输过程。DMA控制器从CPU接收传输参数,然后独立完成数据传输,并在传输结束时通知CPU。
DMA控制器的主要组件:
地址寄存器:存储当前传输的内存地址计数器寄存器:存储剩余的传输字节数控制寄存器:设置传输方向、传输模式等状态寄存器:反映当前传输状态
DMA传输方式:
单字节传输:每次传输一个字节突发传输:连续传输多个字节块传输:传输一个完整的数据块需求传输:仅当设备需要时才传输数据
下面是一个模拟DMA传输机制的C语言示例:
c
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
// DMA控制器的传输模式
typedef enum {
DMA_MODE_SINGLE, // 单字节传输
DMA_MODE_BLOCK, // 块传输
DMA_MODE_DEMAND, // 需求传输
DMA_MODE_CASCADE // 级联模式(用于DMA控制器之间)
} DmaMode;
// DMA传输方向
typedef enum {
DMA_DIR_MEM_TO_DEV, // 内存到设备
DMA_DIR_DEV_TO_MEM // 设备到内存
} DmaDirection;
// DMA通道状态
typedef enum {
DMA_STATUS_IDLE, // 空闲
DMA_STATUS_ACTIVE, // 活动
DMA_STATUS_FINISHED, // 传输完成
DMA_STATUS_ERROR // 传输错误
} DmaStatus;
// DMA控制器的通道
typedef struct {
uint32_t base_address; // 基地址寄存器
uint32_t current_address; // 当前地址寄存器
uint32_t base_count; // 基计数器寄存器
uint32_t current_count; // 当前计数器寄存器
uint8_t mode; // 模式寄存器(包含模式、方向等)
DmaStatus status; // 状态
bool mask; // 通道屏蔽位
uint8_t channel; // 通道号
} DmaChannel;
// DMA控制器
typedef struct {
DmaChannel channels[4]; // 4个DMA通道
uint8_t command; // 命令寄存器
uint8_t status; // 状态寄存器
uint8_t request; // 请求寄存器
uint8_t single_mask; // 单通道屏蔽寄存器
uint8_t mode; // 模式寄存器
bool enabled; // 控制器使能标志
void (*callback)(int, void*); // 传输完成回调函数
void* callback_arg; // 回调函数参数
} DmaController;
// 模拟内存系统
typedef struct {
uint8_t* data; // 内存数据
uint32_t size; // 内存大小
} Memory;
// 模拟磁盘设备
typedef struct {
uint8_t* data; // 磁盘数据
uint32_t size; // 磁盘大小
uint32_t current_sector; // 当前扇区
uint32_t sector_size; // 扇区大小
int dma_channel; // 使用的DMA通道
DmaController* dma; // 关联的DMA控制器
} DiskDevice;
// 初始化DMA控制器
DmaController* init_dma_controller() {
DmaController* dma = (DmaController*)malloc(sizeof(DmaController));
// 初始化控制器寄存器
dma->command = 0;
dma->status = 0;
dma->request = 0;
dma->single_mask = 0;
dma->mode = 0;
dma->enabled = true;
dma->callback = NULL;
dma->callback_arg = NULL;
// 初始化所有通道
for (int i = 0; i < 4; i++) {
dma->channels[i].base_address = 0;
dma->channels[i].current_address = 0;
dma->channels[i].base_count = 0;
dma->channels[i].current_count = 0;
dma->channels[i].mode = 0;
dma->channels[i].status = DMA_STATUS_IDLE;
dma->channels[i].mask = true; // 默认屏蔽(禁用)
dma->channels[i].channel = i;
}
printf("DMA控制器初始化完成
");
return dma;
}
// 设置DMA传输完成回调函数
void set_dma_callback(DmaController* dma, void (*callback)(int, void*), void* arg) {
dma->callback = callback;
dma->callback_arg = arg;
printf("设置DMA传输完成回调函数
");
}
// 初始化内存
Memory* init_memory(uint32_t size) {
Memory* mem = (Memory*)malloc(sizeof(Memory));
mem->data = (uint8_t*)malloc(size);
mem->size = size;
// 初始化内存内容(用随机值填充)
srand(time(NULL));
for (uint32_t i = 0; i < size; i++) {
mem->data[i] = rand() % 256;
}
printf("内存初始化完成: %u字节
", size);
return mem;
}
// 初始化磁盘设备
DiskDevice* init_disk_device(uint32_t size, uint32_t sector_size, int dma_channel, DmaController* dma) {
DiskDevice* disk = (DiskDevice*)malloc(sizeof(DiskDevice));
disk->data = (uint8_t*)malloc(size);
disk->size = size;
disk->current_sector = 0;
disk->sector_size = sector_size;
disk->dma_channel = dma_channel;
disk->dma = dma;
// 初始化磁盘内容(用递增值填充)
for (uint32_t i = 0; i < size; i++) {
disk->data[i] = i % 256;
}
printf("磁盘初始化完成: %u字节, 扇区大小: %u字节
", size, sector_size);
return disk;
}
// 配置DMA通道
void configure_dma_channel(DmaController* dma, int channel, uint32_t address,
uint32_t count, DmaMode mode, DmaDirection direction) {
if (channel < 0 || channel >= 4) {
printf("错误: 无效的DMA通道号: %d
", channel);
return;
}
DmaChannel* ch = &dma->channels[channel];
// 设置地址寄存器
ch->base_address = address;
ch->current_address = address;
// 设置计数器寄存器
ch->base_count = count;
ch->current_count = count;
// 设置模式(包含模式和方向)
ch->mode = (mode << 6) | (direction << 5);
// 重置通道状态
ch->status = DMA_STATUS_IDLE;
printf("配置DMA通道%d: 地址=0x%08X, 计数=%u, 模式=%d, 方向=%d
",
channel, address, count, mode, direction);
}
// 启用DMA通道
void enable_dma_channel(DmaController* dma, int channel) {
if (channel < 0 || channel >= 4) {
printf("错误: 无效的DMA通道号: %d
", channel);
return;
}
dma->channels[channel].mask = false; // 取消屏蔽(启用)
dma->status &= ~(1 << channel); // 清除通道完成状态
printf("启用DMA通道%d
", channel);
}
// 禁用DMA通道
void disable_dma_channel(DmaController* dma, int channel) {
if (channel < 0 || channel >= 4) {
printf("错误: 无效的DMA通道号: %d
", channel);
return;
}
dma->channels[channel].mask = true; // 设置屏蔽(禁用)
printf("禁用DMA通道%d
", channel);
}
// 模拟DMA传输过程
bool simulate_dma_transfer(DmaController* dma, int channel, Memory* memory, uint8_t* device_memory) {
if (channel < 0 || channel >= 4) {
printf("错误: 无效的DMA通道号: %d
", channel);
return false;
}
if (!dma->enabled) {
printf("错误: DMA控制器已禁用
");
return false;
}
DmaChannel* ch = &dma->channels[channel];
if (ch->mask) {
printf("错误: DMA通道%d已屏蔽
", channel);
return false;
}
if (ch->status == DMA_STATUS_ACTIVE) {
printf("错误: DMA通道%d正在传输中
", channel);
return false;
}
// 检查地址和计数是否有效
if (ch->current_address >= memory->size ||
ch->current_address + ch->current_count > memory->size) {
printf("错误: DMA地址范围超出内存范围
");
ch->status = DMA_STATUS_ERROR;
return false;
}
// 标记通道为活动状态
ch->status = DMA_STATUS_ACTIVE;
// 获取传输模式和方向
DmaMode mode = (ch->mode >> 6) & 0x03;
DmaDirection direction = (ch->mode >> 5) & 0x01;
printf("开始DMA传输: 通道=%d, 地址=0x%08X, 计数=%u, 模式=%d, 方向=%d
",
channel, ch->current_address, ch->current_count, mode, direction);
// 根据传输模式和方向进行传输
switch (mode) {
case DMA_MODE_SINGLE:
printf("单字节模式: 每次传输一个字节
");
for (uint32_t i = 0; i < ch->current_count; i++) {
if (direction == DMA_DIR_MEM_TO_DEV) {
// 内存到设备
device_memory[i] = memory->data[ch->current_address + i];
} else {
// 设备到内存
memory->data[ch->current_address + i] = device_memory[i];
}
// 模拟单字节传输的延迟
usleep(1000); // 1毫秒
// 更新当前地址和计数
ch->current_address++;
ch->current_count--;
printf("已传输%u/%u字节
", i + 1, ch->base_count);
fflush(stdout);
}
break;
case DMA_MODE_BLOCK:
printf("块模式: 一次传输整个数据块
");
// 一次性传输整个块
if (direction == DMA_DIR_MEM_TO_DEV) {
// 内存到设备
memcpy(device_memory, memory->data + ch->current_address, ch->current_count);
} else {
// 设备到内存
memcpy(memory->data + ch->current_address, device_memory, ch->current_count);
}
// 模拟块传输的延迟
printf("传输中");
for (int i = 0; i < 3; i++) {
printf(".");
fflush(stdout);
usleep(10000); // 10毫秒
}
printf("
");
// 更新当前地址和计数
ch->current_address += ch->current_count;
ch->current_count = 0;
break;
case DMA_MODE_DEMAND:
printf("需求模式: 设备按需请求传输
");
// 模拟需求模式传输(分多次小批量传输)
uint32_t transferred = 0;
uint32_t batch_size = ch->base_count / 10; // 每批次传输总量的1/10
if (batch_size == 0) batch_size = 1;
while (transferred < ch->base_count) {
// 确定本批次传输量
uint32_t current_batch = batch_size;
if (transferred + current_batch > ch->base_count) {
current_batch = ch->base_count - transferred;
}
// 传输数据
if (direction == DMA_DIR_MEM_TO_DEV) {
// 内存到设备
memcpy(device_memory + transferred,
memory->data + ch->current_address,
current_batch);
} else {
// 设备到内存
memcpy(memory->data + ch->current_address,
device_memory + transferred,
current_batch);
}
// 更新计数和地址
ch->current_address += current_batch;
ch->current_count -= current_batch;
transferred += current_batch;
printf("已传输%u/%u字节
", transferred, ch->base_count);
fflush(stdout);
// 模拟设备处理延迟
usleep(5000); // 5毫秒
}
printf("
");
break;
default:
printf("错误: 不支持的DMA模式: %d
", mode);
ch->status = DMA_STATUS_ERROR;
return false;
}
// 标记传输完成
ch->status = DMA_STATUS_FINISHED;
dma->status |= (1 << channel); // 设置通道完成标志
printf("DMA传输完成: 通道=%d
", channel);
// 调用回调函数(如果已设置)
if (dma->callback) {
dma->callback(channel, dma->callback_arg);
}
return true;
}
// DMA传输完成回调函数
void dma_transfer_complete(int channel, void* arg) {
printf("DMA传输完成回调: 通道%d
", channel);
// 在实际系统中,可能会在这里生成中断通知CPU
// 或者进行下一步操作
}
// 使用DMA读取磁盘扇区
bool read_disk_sector_dma(DiskDevice* disk, uint32_t sector, uint32_t memory_address, Memory* memory) {
if (sector >= disk->size / disk->sector_size) {
printf("错误: 无效的扇区号: %u
", sector);
return false;
}
// 计算扇区在磁盘上的偏移
uint32_t sector_offset = sector * disk->sector_size;
// 配置DMA通道(设备到内存)
configure_dma_channel(disk->dma, disk->dma_channel, memory_address,
disk->sector_size, DMA_MODE_BLOCK, DMA_DIR_DEV_TO_MEM);
// 启用DMA通道
enable_dma_channel(disk->dma, disk->dma_channel);
printf("开始DMA从磁盘读取: 扇区=%u, 内存地址=0x%08X
", sector, memory_address);
// 模拟DMA传输
bool result = simulate_dma_transfer(disk->dma, disk->dma_channel, memory, disk->data + sector_offset);
// 禁用DMA通道
disable_dma_channel(disk->dma, disk->dma_channel);
return result;
}
// 使用DMA写入磁盘扇区
bool write_disk_sector_dma(DiskDevice* disk, uint32_t sector, uint32_t memory_address, Memory* memory) {
if (sector >= disk->size / disk->sector_size) {
printf("错误: 无效的扇区号: %u
", sector);
return false;
}
// 计算扇区在磁盘上的偏移
uint32_t sector_offset = sector * disk->sector_size;
// 配置DMA通道(内存到设备)
configure_dma_channel(disk->dma, disk->dma_channel, memory_address,
disk->sector_size, DMA_MODE_BLOCK, DMA_DIR_MEM_TO_DEV);
// 启用DMA通道
enable_dma_channel(disk->dma, disk->dma_channel);
printf("开始DMA向磁盘写入: 扇区=%u, 内存地址=0x%08X
", sector, memory_address);
// 模拟DMA传输
bool result = simulate_dma_transfer(disk->dma, disk->dma_channel, memory, disk->data + sector_offset);
// 禁用DMA通道
disable_dma_channel(disk->dma, disk->dma_channel);
return result;
}
// 打印内存内容
void print_memory_content(Memory* memory, uint32_t address, uint32_t length) {
if (address >= memory->size || address + length > memory->size) {
printf("错误: 内存地址范围无效
");
return;
}
printf("内存内容 (0x%08X - 0x%08X):
", address, address + length - 1);
printf("地址 | 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F | ASCII
");
printf("--------------------------------------------------------------------------
");
for (uint32_t base = address; base < address + length; base += 16) {
printf("0x%08X | ", base);
// 显示十六进制值
for (int i = 0; i < 16; i++) {
uint32_t addr = base + i;
if (addr < address + length) {
printf("%02X ", memory->data[addr]);
} else {
printf(" ");
}
}
printf("| ");
// 显示ASCII值
for (int i = 0; i < 16; i++) {
uint32_t addr = base + i;
if (addr < address + length) {
char c = memory->data[addr];
if (c >= 32 && c <= 126) {
printf("%c", c);
} else {
printf(".");
}
} else {
printf(" ");
}
}
printf("
");
}
}
// 打印磁盘内容
void print_disk_content(DiskDevice* disk, uint32_t sector) {
if (sector >= disk->size / disk->sector_size) {
printf("错误: 无效的扇区号: %u
", sector);
return;
}
uint32_t sector_offset = sector * disk->sector_size;
printf("磁盘扇区%u内容:
", sector);
printf("偏移 | 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F | ASCII
");
printf("--------------------------------------------------------------------------
");
for (uint32_t base = 0; base < disk->sector_size; base += 16) {
printf("0x%08X | ", base);
// 显示十六进制值
for (int i = 0; i < 16; i++) {
uint32_t offset = sector_offset + base + i;
if (base + i < disk->sector_size) {
printf("%02X ", disk->data[offset]);
} else {
printf(" ");
}
}
printf("| ");
// 显示ASCII值
for (int i = 0; i < 16; i++) {
uint32_t offset = sector_offset + base + i;
if (base + i < disk->sector_size) {
char c = disk->data[offset];
if (c >= 32 && c <= 126) {
printf("%c", c);
} else {
printf(".");
}
} else {
printf(" ");
}
}
printf("
");
}
}
int main() {
printf("DMA传送方式演示
");
printf("=============
");
// 初始化DMA控制器
DmaController* dma = init_dma_controller();
// 设置DMA传输完成回调
set_dma_callback(dma, dma_transfer_complete, NULL);
// 初始化内存(1MB)
Memory* memory = init_memory(1024 * 1024);
// 初始化磁盘(8MB,扇区大小512字节)
DiskDevice* disk = init_disk_device(8 * 1024 * 1024, 512, 0, dma);
printf("
1. 使用DMA从磁盘读取数据:
");
printf("-------------------------
");
// 为读取操作准备特定内存区域(清零)
uint32_t read_buffer_address = 0x1000;
memset(memory->data + read_buffer_address, 0, disk->sector_size);
// 显示读取前的内存内容
printf("
读取前的内存内容:
");
print_memory_content(memory, read_buffer_address, 64);
// 显示磁盘扇区内容
printf("
磁盘扇区内容:
");
print_disk_content(disk, 10);
// 使用DMA从磁盘读取一个扇区
read_disk_sector_dma(disk, 10, read_buffer_address, memory);
// 显示读取后的内存内容
printf("
读取后的内存内容:
");
print_memory_content(memory, read_buffer_address, 64);
printf("
2. 使用DMA向磁盘写入数据:
");
printf("-------------------------
");
// 为写入操作准备特定内存区域(填充测试数据)
uint32_t write_buffer_address = 0x2000;
for (uint32_t i = 0; i < disk->sector_size; i++) {
memory->data[write_buffer_address + i] = 'A' + (i % 26);
}
// 显示写入前的内存内容
printf("
写入前的内存内容:
");
print_memory_content(memory, write_buffer_address, 64);
// 显示写入前的磁盘扇区内容
printf("
写入前的磁盘扇区内容:
");
print_disk_content(disk, 20);
// 使用DMA向磁盘写入一个扇区
write_disk_sector_dma(disk, 20, write_buffer_address, memory);
// 显示写入后的磁盘扇区内容
printf("
写入后的磁盘扇区内容:
");
print_disk_content(disk, 20);
printf("
3. 不同DMA传输模式对比:
");
printf("-----------------------
");
// 准备测试数据
uint32_t test_buffer_address = 0x3000;
for (uint32_t i = 0; i < 1024; i++) {
memory->data[test_buffer_address + i] = i % 256;
}
// 模拟不同模式的传输
printf("
a) 单字节模式传输:
");
uint8_t* test_device_buffer1 = (uint8_t*)malloc(1024);
configure_dma_channel(dma, 1, test_buffer_address, 1024, DMA_MODE_SINGLE, DMA_DIR_MEM_TO_DEV);
enable_dma_channel(dma, 1);
simulate_dma_transfer(dma, 1, memory, test_device_buffer1);
disable_dma_channel(dma, 1);
free(test_device_buffer1);
printf("
b) 块模式传输:
");
uint8_t* test_device_buffer2 = (uint8_t*)malloc(1024);
configure_dma_channel(dma, 2, test_buffer_address, 1024, DMA_MODE_BLOCK, DMA_DIR_MEM_TO_DEV);
enable_dma_channel(dma, 2);
simulate_dma_transfer(dma, 2, memory, test_device_buffer2);
disable_dma_channel(dma, 2);
free(test_device_buffer2);
printf("
c) 需求模式传输:
");
uint8_t* test_device_buffer3 = (uint8_t*)malloc(1024);
configure_dma_channel(dma, 3, test_buffer_address, 1024, DMA_MODE_DEMAND, DMA_DIR_MEM_TO_DEV);
enable_dma_channel(dma, 3);
simulate_dma_transfer(dma, 3, memory, test_device_buffer3);
disable_dma_channel(dma, 3);
free(test_device_buffer3);
// 释放资源
free(disk->data);
free(disk);
free(memory->data);
free(memory);
free(dma);
return 0;
}
习题7
问题:请解释程序控制I/O、中断I/O和DMA这三种I/O传输方式的异同点。
答案:
程序控制I/O:CPU主动查询设备状态并控制数据传输。特点是实现简单,但效率低,占用大量CPU时间。中断I/O:设备就绪时通过中断通知CPU,CPU暂停当前程序执行中断服务程序。特点是提高了CPU利用率,但每次传输一个数据单元仍需CPU参与。DMA:由DMA控制器直接管理设备与内存间的数据传输,无需CPU参与每次数据移动。特点是效率最高,CPU参与最少。
相同点:三种方式都实现了数据从I/O设备与内存之间的传输。
不同点:CPU参与程度不同,效率不同,硬件复杂度不同。
问题:一个磁盘控制器使用DMA方式传输数据,磁盘每个扇区512字节,磁盘控制器的数据寄存器与DMA控制器之间的数据传输率为5MB/s,DMA控制器与内存之间的数据传输率为20MB/s。请计算传输一个扇区数据所需的时间。
答案:
在这个系统中,有两个数据传输环节:
磁盘控制器到DMA控制器:512B ÷ 5MB/s = 512B ÷ (5 × 10^6 B/s) = 102.4μsDMA控制器到内存:512B ÷ 20MB/s = 512B ÷ (20 × 10^6 B/s) = 25.6μs
由于这两个环节可能是串行执行的,总时间是:102.4μs + 25.6μs = 128μs
如果DMA控制器能够同时从磁盘读取并写入内存(流水线方式),总时间将取决于较慢的环节,即102.4μs。
问题:设计一个使用查询方式读取串口数据的C语言函数,要求在一定时间内等待数据,超时则返回错误。
答案:
c
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <time.h>
// 串口状态寄存器位定义
#define SERIAL_STATUS_DATA_READY 0x01
// 模拟读取串口状态寄存器
uint8_t read_serial_status(void) {
// 实际中这里应该读取硬件寄存器
// 这里仅作为示例
static int count = 0;
count++;
// 模拟每隔5次查询才有数据
if (count % 5 == 0) {
return SERIAL_STATUS_DATA_READY;
}
return 0;
}
// 模拟读取串口数据寄存器
uint8_t read_serial_data(void) {
// 实际中这里应该读取硬件寄存器
// 这里返回固定值作为示例
return 'A';
}
// 使用查询方式带超时读取串口数据
bool read_serial_with_timeout(uint8_t* data, int timeout_ms) {
clock_t start_time = clock();
int elapsed_ms;
while (1) {
// 读取状态寄存器
uint8_t status = read_serial_status();
// 检查数据是否就绪
if (status & SERIAL_STATUS_DATA_READY) {
*data = read_serial_data();
return true;
}
// 检查是否超时
elapsed_ms = (clock() - start_time) * 1000 / CLOCKS_PER_SEC;
if (elapsed_ms >= timeout_ms) {
return false; // 超时
}
// 延迟一小段时间(可选)
// usleep(1000); // 1毫秒
}
}
int main() {
uint8_t data;
bool result = read_serial_with_timeout(&data, 1000); // 1秒超时
if (result) {
printf("读取成功: 0x%02X
", data);
} else {
printf("读取超时
");
}
return 0;
}
问题:在中断系统中,中断处理程序通常需要保存哪些CPU寄存器?为什么需要保存这些寄存器?
答案:
中断处理程序通常需要保存以下CPU寄存器:
程序计数器(PC/IP):保存被中断程序的下一条指令地址标志寄存器(FLAGS):保存处理器状态标志通用寄存器(AX、BX、CX、DX等):保存被中断程序的中间计算结果段寄存器(在分段式内存架构中)栈指针(SP)和栈基指针(BP)
需要保存这些寄存器是因为:
中断打断了正在执行的程序,必须保证中断处理完成后能正确返回原程序继续执行中断处理程序可能会修改这些寄存器,导致原程序数据丢失保存和恢复这些寄存器确保中断处理对原程序透明,不会影响原程序的执行结果
问题:请设计一个使用DMA控制器从磁盘读取多个连续扇区到内存的C语言函数,要求具备错误处理能力。
答案:
c
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
// 错误码定义
#define DMA_SUCCESS 0
#define DMA_ERROR_INVALID_PARAMS 1
#define DMA_ERROR_CONTROLLER 2
#define DMA_ERROR_DISK 3
#define DMA_ERROR_TIMEOUT 4
// DMA控制器接口函数(实际实现会直接操作硬件寄存器)
void dma_set_address(uint32_t address) {
printf("设置DMA地址: 0x%08X
", address);
}
void dma_set_count(uint32_t count) {
printf("设置DMA传输计数: %u
", count);
}
void dma_set_direction(int direction) {
printf("设置DMA传输方向: %d
", direction);
}
void dma_start_transfer() {
printf("启动DMA传输
");
}
bool dma_is_transfer_complete() {
// 实际中会检查DMA状态寄存器
static int counter = 0;
counter++;
return counter > 5; // 模拟5次查询后完成
}
// 磁盘控制器接口函数
bool disk_select_sectors(uint32_t start_sector, uint32_t sector_count) {
printf("选择磁盘扇区: 起始扇区=%u, 扇区数=%u
", start_sector, sector_count);
// 模拟扇区验证
if (start_sector > 1000000) {
printf("错误: 扇区号超出范围
");
return false;
}
return true;
}
bool disk_prepare_dma_read() {
printf("准备磁盘DMA读取
");
return true;
}
bool disk_start_dma_read() {
printf("开始磁盘DMA读取
");
return true;
}
bool disk_check_error() {
// 实际中会检查磁盘控制器状态寄存器
return false; // 模拟无错误
}
/**
* 使用DMA从磁盘读取多个连续扇区到内存
* @param start_sector 起始扇区号
* @param sector_count 要读取的扇区数
* @param memory_address 内存目标地址
* @param bytes_read 实际读取的字节数(输出参数)
* @param timeout_ms 超时时间(毫秒)
* @return 错误码(0表示成功)
*/
int read_disk_sectors_dma(
uint32_t start_sector,
uint32_t sector_count,
uint32_t memory_address,
uint32_t* bytes_read,
uint32_t timeout_ms
) {
// 参数验证
if (sector_count == 0 || sector_count > 256) {
printf("错误: 无效的扇区数
");
return DMA_ERROR_INVALID_PARAMS;
}
if (memory_address % 4 != 0) {
printf("错误: 内存地址必须4字节对齐
");
return DMA_ERROR_INVALID_PARAMS;
}
// 初始化输出参数
if (bytes_read != NULL) {
*bytes_read = 0;
}
// 选择扇区
if (!disk_select_sectors(start_sector, sector_count)) {
printf("错误: 选择扇区失败
");
return DMA_ERROR_DISK;
}
// 计算传输大小(假设每扇区512字节)
const uint32_t SECTOR_SIZE = 512;
uint32_t transfer_size = sector_count * SECTOR_SIZE;
// 配置DMA控制器
dma_set_address(memory_address);
dma_set_count(transfer_size);
dma_set_direction(0); // 0表示从设备到内存
// 准备磁盘进行DMA读取
if (!disk_prepare_dma_read()) {
printf("错误: 准备磁盘DMA读取失败
");
return DMA_ERROR_DISK;
}
// 启动DMA传输
dma_start_transfer();
// 启动磁盘读取
if (!disk_start_dma_read()) {
printf("错误: 启动磁盘DMA读取失败
");
return DMA_ERROR_DISK;
}
// 等待传输完成
uint32_t elapsed_ms = 0;
const uint32_t CHECK_INTERVAL_MS = 10;
while (!dma_is_transfer_complete()) {
// 检查超时
elapsed_ms += CHECK_INTERVAL_MS;
if (elapsed_ms >= timeout_ms) {
printf("错误: DMA传输超时
");
return DMA_ERROR_TIMEOUT;
}
// 检查磁盘错误
if (disk_check_error()) {
printf("错误: 磁盘读取错误
");
return DMA_ERROR_DISK;
}
// 延迟一小段时间(模拟)
printf("等待DMA传输完成...
");
// sleep(CHECK_INTERVAL_MS / 1000); // 实际代码中使用的延迟函数
}
// 传输成功
if (bytes_read != NULL) {
*bytes_read = transfer_size;
}
printf("DMA读取成功: %u扇区, %u字节
", sector_count, transfer_size);
return DMA_SUCCESS;
}
int main() {
uint32_t bytes_read = 0;
int result = read_disk_sectors_dma(10, 8, 0x10000, &bytes_read, 5000);
printf("
结果: %d, 读取字节数: %u
", result, bytes_read);
// 测试错误情况
printf("
测试错误参数:
");
result = read_disk_sectors_dma(2000000, 8, 0x10000, &bytes_read, 5000);
printf("结果: %d, 读取字节数: %u
", result, bytes_read);
return 0;
}


