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 设置线程优先级。它启动一个循环来等待中断并处理。

相关推荐
木子Linux22 分钟前
【Linux打怪升级记 | 问题01】安装Linux系统忘记设置时区怎么办?3个方法教你回到东八区
linux·运维·服务器·centos·云计算
mit6.82428 分钟前
Ubuntu 系统下性能剖析工具: perf
linux·运维·ubuntu
鹏大师运维30 分钟前
聊聊开源的虚拟化平台--PVE
linux·开源·虚拟化·虚拟机·pve·存储·nfs
watermelonoops37 分钟前
Windows安装Ubuntu,Deepin三系统启动问题(XXX has invalid signature 您需要先加载内核)
linux·运维·ubuntu·deepin
滴水之功1 小时前
VMware OpenWrt怎么桥接模式联网
linux·openwrt
ldinvicible2 小时前
How to run Flutter on an Embedded Device
linux
YRr YRr3 小时前
解决Ubuntu 20.04上编译OpenCV 3.2时遇到的stdlib.h缺失错误
linux·opencv·ubuntu
认真学习的小雅兰.3 小时前
如何在Ubuntu上利用Docker和Cpolar实现Excalidraw公网访问高效绘图——“cpolar内网穿透”
linux·ubuntu·docker
zhou周大哥3 小时前
linux 安装 ffmpeg 视频转换
linux·运维·服务器
不想起昵称9293 小时前
Linux SHELL脚本中的变量与运算
linux