C++编程:嵌入式Linux-ARM与外设中断交互的程序设计
文章目录
- 0. 引言
- 1. 设备与处理器中断交互机制
- 1.1 交互时序图
- 1.2 时序图说明
- 1.3 用户空间中断处理方法
- 2. 中断模块设计要点
- 3. 代码说明
- 3.1 `Interrupts` 类
- 3.2 中断处理
- 3.3 `start` 方法
0. 引言
本文介绍在 Linux-ARM 系统中利用中断与外设(如 DSP、DAC、扫描仪等)交互的模块,实现低延迟的中断响应服务。外设通过 UIO 驱动暴露 /dev/uio
设备节点,用户空间程序可以通过这些节点来处理中断。
本方案将使用到select,select的高效使用请:Linux编程:使用 select高效的 UART 通信
1. 设备与处理器中断交互机制
-
设备侧(裸机程序/外设):
- 当设备完成任务或发生事件时,触发硬件中断信号。
- 中断信号通过硬件线路发送至中断控制器。
-
处理器侧(运行操作系统):
- 中断控制器接收信号,判断中断类型和优先级。
- 操作系统内核处理并调度相应的中断服务程序(ISR)。
- ISR 可能位于内核空间,也可能通过
/dev/uio
让用户空间程序处理。
1.1 交互时序图
1.2 时序图说明
- 设备触发中断:设备触发硬件中断信号,中断信号通过中断线传递给控制器
- 中断控制器处理:控制器生成中断请求并发送给处理器。
- 处理器接收中断:操作系统接收到中断请求并暂停当前任务。
- 调用 ISR:操作系统调用对应的中断服务程序(ISR)。
- 处理中断:ISR 执行预定义的处理逻辑。
- 中断处理完成:ISR 处理完毕后,返回内核。
- 通知控制器:处理器通知中断控制器处理完成。
- 设备继续执行:设备得到响应后继续运行。
1.3 用户空间中断处理方法
在用户空间处理中断时,通常使用 UIO(Userspace I/O)机制。设备驱动将中断映射到 /dev/uioX
设备文件,用户空间程序通过 select
等系统调用来等待并处理中断。
// 用户空间中断处理示例
int fd = open("/dev/uio0", O_RDWR);
struct pollfd fds;
fds.fd = fd;
fds.events = POLLIN;
while (true) {
int ret = poll(&fds, 1, -1);
if (ret > 0) {
uint32_t info;
read(fd, &info, sizeof(info)); // 读取中断信息
// 处理中断事件
// ...
// 重新使能中断
write(fd, &info, sizeof(info));
}
}
2. 中断模块设计要点
中断管理模块的设计包括以下要点:
- 线程优先级设置:使用
sched_setscheduler
设置线程的优先级和调度策略。 - 实时线程:通过
SCHED_FIFO
或SCHED_RR
确保线程能够及时获得 CPU 时间片。 - 中断信号处理:确保实时线程能够正确处理中断信号,避免任务被不必要地打断。
以下展示了如何通过 /dev/uio
设备文件接收中断,并利用 select
等待中断。
#include <fcntl.h>
#include <sys/select.h>
#include <unistd.h>
#include <functional>
#include <unordered_map>
#include <string>
#include <iostream>
#include <thread>
#include <sched.h>
#include <sys/types.h>
#include <pthread.h>
class Interrupts {
public:
// 构造函数,初始化成员变量
Interrupts();
// 中断初始化
int init();
// 启动中断处理线程
void start();
// 注册中断并连接到回调函数
int registerInterrupt(const std::string& interrupt_name,
std::function<void(void)> interrupt_handler);
// 等待中断事件
int waitForInterrupt();
// 处理中断事件
int processInterrupts();
private:
// 保存文件描述符与回调函数的映射
std::unordered_map<int, std::function<void(void)>> interrupt_handlers_;
// 最大文件描述符
int max_interrupt_fd_;
// 文件描述符集合
fd_set master_set_;
fd_set backup_set_;
// 实时线程优先级
static constexpr int kInterruptThreadPriority = 50; // 设置线程优先级,假设为50(越高越优先)
};
// 构造函数,初始化成员变量
Interrupts::Interrupts() : max_interrupt_fd_(-1) {
FD_ZERO(&master_set_);
FD_ZERO(&backup_set_);
}
// 初始化中断源
int Interrupts::init() {
std::function<void(void)> dma_handler = []() {
fprintf(stdout, "DMA interrupt triggered!\n");
};
if (registerInterrupt("dma_irq", dma_handler) < 0) {
fprintf(stderr, "Failed to register DMA interrupt!\n");
return -1;
}
return 0;
}
// 注册中断
int Interrupts::registerInterrupt(const std::string& interrupt_name,
std::function<void(void)> interrupt_handler) {
std::string interrupt_path = "/dev/" + interrupt_name;
int fd = open(interrupt_path.c_str(), O_RDWR);
if (fd < 0) {
fprintf(stderr, "Failed to open interrupt device: %s\n", interrupt_path.c_str());
return -1; // 打开文件失败
}
uint32_t info = 1; // 解锁中断
if (write(fd, &info, sizeof(info)) != sizeof(info)) {
fprintf(stderr, "Failed to unlock interrupt: %s\n", interrupt_path.c_str());
close(fd);
return -1; // 写入失败
}
FD_SET(fd, &master_set_);
if (fd > max_interrupt_fd_) {
max_interrupt_fd_ = fd;
}
interrupt_handlers_[fd] = interrupt_handler;
return 0;
}
// 设置线程的调度策略和优先级
void Interrupts::setThreadPriority() {
struct sched_param param;
param.sched_priority = kInterruptThreadPriority;
// 设置调度策略为 FIFO
if (sched_setscheduler(0, SCHED_FIFO, ¶m) == -1) {
fprintf(stderr, "Failed to set thread priority!\n");
exit(1); // 设置失败,退出程序
}
}
// 等待中断事件
int Interrupts::waitForInterrupt() {
backup_set_ = master_set_;
if (select(max_interrupt_fd_ + 1, &backup_set_, nullptr, nullptr, nullptr) < 0) {
if (errno != EINTR) {
fprintf(stderr, "Error in select() while waiting for interrupt\n");
return -1; // select 出错
}
return 0; // 被信号中断,继续等待
}
return 0;
}
// 处理中断事件
int Interrupts::processInterrupts() {
// 遍历所有已注册的中断处理器
for (auto& entry : interrupt_handlers_) {
int fd = entry.first;
if (FD_ISSET(fd, &backup_set_)) {
uint32_t info;
if (read(fd, &info, sizeof(info)) != sizeof(info)) {
fprintf(stderr, "Failed to read interrupt data from fd: %d\n", fd);
continue; // 读取失败,跳过此中断
}
entry.second(); // 调用中断处理器
info = 1; // 解除中断屏蔽
if (write(fd, &info, sizeof(info)) != sizeof(info)) {
fprintf(stderr, "Failed to write interrupt data back to fd: %d\n", fd);
}
}
}
return 0;
}
// 启动中断处理线程
void Interrupts::start() {
// 设置当前线程的优先级
setThreadPriority();
// 启动一个循环处理
while (true) {
if (waitForInterrupt() == 0) {
if (processInterrupts() != 0) {
fprintf(stderr, "Error processing interrupts\n");
}
}
}
}
3. 代码说明
3.1 Interrupts
类
- 构造函数 (
Interrupts
):初始化文件描述符集合和最大文件描述符。 init
:初始化中断源,例如为 DMA 注册中断。registerInterrupt
:通过/dev/uioX
设备文件注册外设中断,解除中断屏蔽并存储回调函数。waitForInterrupt
:通过select
等待中断事件发生。processInterrupts
:处理触发的中断事件,调用对应的回调函数。
3.2 中断处理
当 DMA 中断触发时,会调用 dma_handler
来处理。例如,打印 "DMA interrupt triggered!"
。
3.3 start
方法
start
方法通过 sched_setscheduler
和 SCHED_FIFO
设置线程优先级。它启动一个循环来等待中断并处理。