C++编程:嵌入式Linux-ARM与外设中断交互的程序设计

文章目录

    • [0. 引言](#0. 引言)
    • [1. 设备与处理器中断交互机制](#1. 设备与处理器中断交互机制)
      • [1.1 交互时序图](#1.1 交互时序图)
      • [1.2 时序图说明](#1.2 时序图说明)
      • [1.3 用户空间中断处理方法](#1.3 用户空间中断处理方法)
    • [2. 中断模块设计要点](#2. 中断模块设计要点)
    • [3. 代码说明](#3. 代码说明)
      • [3.1 `Interrupts` 类](#3.1 Interrupts 类)
      • [3.2 中断处理](#3.2 中断处理)
      • [3.3 `start` 方法](#3.3 start 方法)

0. 引言

本文介绍在 Linux-ARM 系统中利用中断与外设(如 DSP、DAC、扫描仪等)交互的模块,实现低延迟的中断响应服务。外设通过 UIO 驱动暴露 /dev/uio 设备节点,用户空间程序可以通过这些节点来处理中断。

本方案将使用到select,select的高效使用请阅读更多:Linux编程:使用 select高效的 UART 通信

1. 设备与处理器中断交互机制

  • 设备侧(裸机程序/外设)

    • 当设备完成任务或发生事件时,触发硬件中断信号。
    • 中断信号通过硬件线路发送至中断控制器。
  • 处理器侧(运行操作系统)

    • 中断控制器接收信号,判断中断类型和优先级。
    • 操作系统内核处理并调度相应的中断服务程序(ISR)。
    • ISR 可能位于内核空间,也可能通过 /dev/uio 让用户空间程序处理。

1.1 交互时序图

设备 (裸机程序/外设) 中断控制器 处理器 (操作系统) 中断服务程序 设备运行裸机程序/外设 传递中断信号 发送中断请求 (IRQ) 中断处理入口 调用中断服务程序 (ISR) 处理中断事件 中断处理完成 通知中断处理完成 中断响应完成 设备继续执行 设备 (裸机程序/外设) 中断控制器 处理器 (操作系统) 中断服务程序

1.2 时序图说明

  • 设备触发中断:设备触发硬件中断信号,中断信号通过中断线传递给控制器
  • 中断控制器处理:控制器生成中断请求并发送给处理器。
  • 处理器接收中断:操作系统接收到中断请求并暂停当前任务。
  • 调用 ISR:操作系统调用对应的中断服务程序(ISR)。
  • 处理中断:ISR 执行预定义的处理逻辑。
  • 中断处理完成:ISR 处理完毕后,返回内核。
  • 通知控制器:处理器通知中断控制器处理完成。
  • 设备继续执行:设备得到响应后继续运行。

1.3 用户空间中断处理方法

在用户空间处理中断时,通常使用 UIO(Userspace I/O)机制。设备驱动将中断映射到 /dev/uioX 设备文件,用户空间程序通过 select 等系统调用来等待并处理中断。

cpp 复制代码
// 用户空间中断处理示例
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. 中断模块设计要点

中断管理模块的设计包括以下要点:

  1. 线程优先级设置 :使用 sched_setscheduler 设置线程的优先级和调度策略。
  2. 实时线程 :通过 SCHED_FIFOSCHED_RR 确保线程能够及时获得 CPU 时间片。
  3. 中断信号处理:确保实时线程能够正确处理中断信号,避免任务被不必要地打断。

以下展示了如何通过 /dev/uio 设备文件接收中断,并利用 select 等待中断。

cpp 复制代码
#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, &param) == -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_setschedulerSCHED_FIFO 设置线程优先级。它启动一个循环来等待中断并处理。

相关推荐
wellnw1 分钟前
[ubuntu]编译共享内存读取出现read.c:(.text+0x1a): undefined reference to `shm_open‘问题解决方案
linux·ubuntu
不爱学习的YY酱3 分钟前
【操作系统不挂科】<CPU调度(13)>选择题(带答案与解析)
java·linux·前端·算法·操作系统
DC_BLOG4 分钟前
Linux-Nginx虚拟主机
linux·运维·nginx
XY.散人38 分钟前
初识Linux · 信号处理 · 续
linux·信号处理
19004344 分钟前
linux复习5:C prog
linux·运维·服务器
猫猫的小茶馆1 小时前
【C语言】指针常量和常量指针
linux·c语言·开发语言·嵌入式软件
朝九晚五ฺ1 小时前
【Linux探索学习】第十五弹——环境变量:深入解析操作系统中的进程环境变量
linux·运维·学习
ernesto_ji2 小时前
Jenkins下载安装、构建部署到linux远程启动运行
linux·servlet·jenkins
李迟2 小时前
某Linux发行版本无法使用nodejs程序重命名文件问题的研究
java·linux·服务器