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

相关推荐
脱了格子衬衫41 分钟前
linux安装ansible
linux·运维·ansible
小丑西瓜6661 小时前
MySQL库操作
linux·服务器·数据库·mysql
小珑也要变强3 小时前
shell脚本基本概念讲解
linux·运维
爱吃喵的鲤鱼4 小时前
linux 用C语言编写自己的myshell
linux·运维·服务器·c语言·算法
矛取矛求8 小时前
Linux如何更优质调节系统性能
linux
内核程序员kevin9 小时前
在Linux环境下使用Docker打包和发布.NET程序并配合MySQL部署
linux·mysql·docker·.net
kayotin9 小时前
Wordpress博客配置2024
linux·mysql·docker
Ztiddler10 小时前
【Linux Shell命令-不定期更新】
linux·运维·服务器·ssh
小小不董10 小时前
Oracle OCP认证考试考点详解082系列16
linux·运维·服务器·数据库·oracle·dba
a1denzzz10 小时前
Linux系统的网络设置
linux·服务器·网络