Linux中断管理:(一)中断号的映射

文章说明:

  • Linux内核版本:5.0

  • 架构:ARM64

  • 参考资料及图片来源:《奔跑吧Linux内核》

  • Linux 5.0内核源码注释仓库地址:

    zhangzihengya/LinuxSourceCode_v5.0_study (github.com)

1. 中断控制器

Linux 内核支持众多的处理器架构,因此从系统角度来看,Linux内核的中断管理可以分成如下4层:

  • 硬件层,如CPU和中断控制器的连接
  • 处理器架构管理层,如CPU中断异常处理
  • 中断控制器管理层,如IRQ号的映射
  • Linux 内核通用中断处理器层,如中断注册和中断处理

不同的架构对中断控制器有着不同的设计理念:

  • ARM架构采用通用中断控制器 (Generic Interrupt Controller, GIC)
  • x86架构采用高级可编程中断控制器 (Advanced Programmable Interrupt Controller, APIC)

本文以 ARM Vexpress V2P-CAIS-CA7 平台为例来介绍中断管理的实现,它支持 Cortex-A15 和 Cortex-A7 两个CPU簇,中断控制器采用GIC-400,支持GIC Version 2 (GIC-V2),如下图所示:

在这里插入图片描述

对于一个中断来说,支持多个中断状态

  • 不活跃(inactive)状态:中断处于无效状态
  • 等待(pending)状态:中断处于有效状态,但是等待CPU响应该中断
  • 活跃(active)状态:GPU已经响应中断
  • 活跃并等待(active and pending)状态:CPU正在响应中断,但是该中断源又发送中断过来

对于GIC来说,为每一个硬件中断源分配一个中断号,这就是硬件中断号。GIC会为支持的中断类型分配中断号范围,如下表所示:

在这里插入图片描述

  • SGI通常用于多核之间的通信。GIC-V2最多支持16个SGI,硬件中断号范围为0~15。SGI通常在Linux内核中被用作处理器之间的中断 (Inter-Processor Interrupt, IPI),并会送达到系统指定的CPU上
  • PPI是每个处理器内核私有的中断。GIC-V2 最多支持16个PPI中断,硬件中断号范围为16~31。PPI通常会送达到指定的CPU 上,应用场景有CPU本地定时器(local timer)。
  • SPI是公用的外设中断。GIC-V2最多可以支持988个外设中断,硬件中断号范围为32~1019。
  • SGI和PPI是每个CPU私有的中断,而SPI是所有CPU内核共享的。

外设中断可以支持两种中断触发方式

  • 边沿触发(edge-triggered):当中断源产生一个上升沿或者下降沿时,触发一个中断
  • 电平触发(level-sensitive):当中断信号线产生一个高电平或者低电平时,触发一个 中断

GIC主要由两部分组成,分别是仲裁单元(distributor)和CPU接口模块。仲裁单元为每一个中断源维护一个状态机,支持的状态有 inactive pending、active和active and pending。GIC检测中断的流程如下:

  1. 当GIC检测到一个中断发生时,会将该中断标记为pending状态

  2. 对于处于pending状态的中断,仲裁单元会确定目标CPU,将中断请求发送到这个CPU

  3. 对于每个CPU,仲裁单元会从众多处于pending状态的中断中选择一个优先级最高的中断,发送到目标CPU的CPU接口模块上

  4. CPU接口模块会决定这个中断是否可以发送给CPU。如果该中断的优先级满足要求,GIC会发送一个中断请求信号给该CPU

  5. 当一个CPU进入中断异常后,会读取GICC_IAR来响应该中断(一般由Linux内核的中断处理程序来读寄存器)。寄存器会返回硬件中断号(hardware interrupt ID),对于SGI来说,返回源CPU的ID(source processor ID)。当GIC感知到软件读取了该寄存器后,又分为如下情况:

    • 如果该中断处于pending状态,那么状态将变成active
    • 如果该中断又重新产生,那么pending将状态变成active and pending状态
    • 如果该中断处于active状态,将变成active and pending状态
  6. 当处理器完成中断服务,必须发送一个完成信号结束中断(End Of Interrupt, EOI)给GIC

2. 硬件中断号和 Linux 中断号的映射

在Linux中,注册中断接口函数request_irq()、request_threaded_irq() 使用Linux内核软件中断号(俗称软件中断号或IRQ号),而不是硬件中断号。接下来,以QEMU虚拟机的串口 0设备(它在主板上的序号为1,硬件中断号为33)为例,介绍硬件中断号是如何和Linux内核的IRQ号映射的。

  • 首先介绍下前置知识

    • ARM64平台的设备描述基本上采用设备树(Device Tree)模式来描述硬件设备。QEMU虚拟机的设备树的描述脚本并没有实现在内核代码中,而实现在QEMU代码里。因此,可以通过DTC命令反编译出设备树脚本(Device Tree Script, DTS),反编译 DTS时,与串口中断相关的描述如下:

      pl011@9000000 {
      	clock-names = "uartclk\Oapb_pclk";
      	clocks = < 0x8000 0x8000 >;
      // interrupts 域描述相关的属性:
      // 中断类型,共享外设中断(GIC_SPI)在设备树中用0来表示,私有外设中断(GIC_PPI)在设备树中用1来表示,这里是0x00
      // 中断 ID,这里是0x01
      // 触发类型,(即IRQ_TYPE_LEVEL_HIGH,高电平触发)
      interrupts = < 0x00 0x01 0x04 >;
      	 reg = < 0x00 0x9000000 0x00 0x1000 >;
      // "arm,pl011\0arm,primecell" 外设的兼容字符串,用于和驱动程序进行匹配工作
      compatible = "arm,pl011\0arm,primecell";
      };
      
    • 一个中断控制器用一个irq_domain数据结构来抽象描述,irq_domain数据结构的定义如下:

      // 一个中断控制器用一个 irq_domain 数据结构来抽象描述
      struct irq_domain {
      	// 用于将 irq_domain 连接到全局链表 irq_domain_list 中
      	struct list_head link;
      	// irq_domain 的名称
      	const char *name;
      	// irq_domain 映射操作使用的方法集合
      	const struct irq_domain_ops *ops;
      	...
      	// 该 irq_domain 支持中断数量的最大值
      	irq_hw_number_t hwirq_max;
      	unsigned int revmap_direct_max_irq;
      	// 线性映射的大小
      	unsigned int revmap_size;
      	// 基数树映射的根节点
      	struct radix_tree_root revmap_tree;
      	struct mutex revmap_tree_mutex;
      	// 线性映射用到的查找表
      	unsigned int linear_revmap[];
      };
      
  • 系统初始化时会查找DTS中定义的中断控制器,计算GIC最多支持的中断源的个数,GIC-V2规定最多支持1020个中断源,在SoC设计阶段就确定ARM SoC可以支持多少个中断源了,然后,调用irq_domain_create_linear()->__irq_domain_add()函数注册一个irq_domain数据结构:

    struct irq_domain *__irq_domain_add(struct fwnode_handle *fwnode, int size,
    				    irq_hw_number_t hwirq_max, int direct_max,
    				    const struct irq_domain_ops *ops,
    				    void *host_data)
    {
    	...
        // 注册一个 irq_domain 数据结构
    	domain = kzalloc_node(sizeof(*domain) + (sizeof(unsigned int) * size),
    			      GFP_KERNEL, of_node_to_nid(of_node));
    	// 初始化 irq_domain 数据结构
        ...
        // 把 irq_domain 数据结构加入全局的链表 irq_domain_list 中
        list_add(&domain->link, &irq_domain_list);
        ...
    }
    
  • 回到系统枚举阶段的中断号映射过程,在of_amba_device_create()函数中,irq_of_parse_and_map()函数负责把硬件中断号映射到Linux内核的IRQ号

    // 把硬件中断号映射到 Linux 内核的 IRQ 号
    unsigned int irq_of_parse_and_map(struct device_node *dev, int index)
    {
    	struct of_phandle_args oirq;
    
    	if (of_irq_parse_one(dev, index, &oirq))
    		return 0;
    
    	return irq_create_of_mapping(&oirq);
    }
    
  • irq_of_parse_and_map()->of_irq_parse_one():主要用于解析DTS文件中设备定义的属性,如reg、interrupts等, 最后把DTS中的interrupts的值存放在oirq->args[]数组中

  • irq_of_parse_and_map()->irq_create_of_mapping->irq_create_fwspec_mapping()

    unsigned int irq_create_fwspec_mapping(struct irq_fwspec *fwspec)
    {
    	...
    
    	// 查找外设所属的中断控制器的 irq_domain
    	if (fwspec->fwnode) {
    		domain = irq_find_matching_fwspec(fwspec, DOMAIN_BUS_WIRED);
    		if (!domain)
    			domain = irq_find_matching_fwspec(fwspec, DOMAIN_BUS_ANY);
    	} else {
    		domain = irq_default_domain;
    	}
    
    	if (!domain) {
    		pr_warn("no irq domain found for %s !\n",
    			of_node_full_name(to_of_node(fwspec->fwnode)));
    		return 0;
    	}
    
    	// 进行硬件中断号的转换
    	// hwirq 存储着这个硬件中断号,type存储该外设的中断类型
    	if (irq_domain_translate(domain, fwspec, &hwirq, &type))
    		return 0;
    
    	...
    	// 如果这个硬件中断号已经映射过了,那么 irq_find_mapping() 函数可以找到映射后的中断号,在此情境下,该硬件中断号还没有映射
    	virq = irq_find_mapping(domain, hwirq);
    	if (virq) {
    		...
    	}
    
    	if (irq_domain_is_hierarchy(domain)) {
    		// 映射的核心函数
    		virq = irq_domain_alloc_irqs(domain, 1, NUMA_NO_NODE, fwspec);
    		if (virq <= 0)
    			return 0;
    	} else {
    		/* Create mapping */
    		virq = irq_create_mapping(domain, hwirq);
    		if (!virq)
    			return virq;
    	}
    
    	...
    
    	return virq;
    }
    
  • irq_of_parse_and_map()->irq_create_of_mapping->irq_create_fwspec_mapping()->irq_domain_alloc_irqs()->__irq_domain_alloc_irqs->irq_domain_alloc_descs()->__irq_alloc_descs()

    int __ref
    __irq_alloc_descs(int irq, unsigned int from, unsigned int cnt, int node,
    		  struct module *owner, const struct irq_affinity_desc *affinity)
    {
    	...
    
    	// 在 allocated_irqs 位图中查找第一个包含连续 cnt 个 0 的位图区域
    	start = bitmap_find_next_zero_area(allocated_irqs, IRQ_BITMAP_BITS,
    					   from, cnt, 0);
    	...
    	// 分配 irq_desc 数据结构
    	ret = alloc_descs(start, cnt, node, affinity, owner);
    ...
    }
    

    irq_desc数据结构:

    struct irq_desc {
    	...
    	struct irq_data		irq_data;
    	...
    } ____cacheline_internodealigned_in_smp;
    

    irq_data数据结构:

    struct irq_data {
    	...
    	// 软件中断号
    	unsigned int		irq;
    	// 硬件中断号
    	unsigned long		hwirq;
    	...
    };
    
  • irq_of_parse_and_map()->irq_create_of_mapping->irq_create_fwspec_mapping()->irq_domain_alloc_irqs()->__irq_domain_alloc_irqs()->irq_domain_alloc_descs()函数返回 allocated_irqs 位图中第一个空闲位,这是软件中断号

  • irq_of_parse_and_map()->irq_create_of_mapping->irq_create_fwspec_mapping()->irq_domain_alloc_irqs()->__irq_domain_alloc_irqs()->irq_domain_alloc_irqs_hierarchy()->gic_irq_domain_alloc()

    static int gic_irq_domain_alloc(struct irq_domain *domain, unsigned int virq,
    				unsigned int nr_irqs, void *arg)
    {
    	...
        // 解析出硬件中断号并存放在 hwirq 中
    	ret = gic_irq_domain_translate(domain, fwspec, &hwirq, &type);
    	...
    
    	for (i = 0; i < nr_irqs; i++) {
    		// 映射工作
            ret = gic_irq_domain_map(domain, virq + i, hwirq + i);
    		if (ret)
    			return ret;
    	}
    
    	return 0;
    }
    
  • irq_of_parse_and_map()->irq_create_of_mapping->irq_create_fwspec_mapping()->irq_domain_alloc_irqs()->__irq_domain_alloc_irqs()->irq_domain_alloc_irqs_hierarchy()->gic_irq_domain_alloc()->gic_irq_domain_map()->irq_domain_set_info()->irq_domain_set_hwirq_and_chip()通过IRQ号获取irq_data数据结构,并把硬件中断号hwirq设置到irq_data数据结构中的hwirq成员中,就完成了硬件中断号到软件中断号的映射。

  • irq_of_parse_and_map()->irq_create_of_mapping->irq_create_fwspec_mapping()->irq_domain_alloc_irqs()->__irq_domain_alloc_irqs()->irq_domain_alloc_irqs_hierarchy()->gic_irq_domain_alloc()->gic_irq_domain_map()->irq_domain_set_info()->__irq_set_handler():设置中断描述符 desc->handle_irq 的回调函数

  • 综上所述,硬件中断号和软件中断号的映射过程如下图所示:

    在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/505322.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

TCP通信——端口转发(重点内容)

实现多人群聊 Client(客户端&#xff09;建立通信 package com.zz.tcp.case1;import java.io.DataOutputStream; import java.io.IOException; import java.io.OutputStream; import java.net.Socket; import java.util.Scanner;public class Client {public static void mai…

Java- maven下载jar包,提示找不到,Could not find artifact

1、执行下面命令行 mvn install:install-file -Dfile/home/quangang/桌面/isv-sdk-2.0.jar -DgroupIdcom.jd -DartifactIdisv-sdk -Dversion2.0 -Dpackangjar 2、然后这里要加上jar包

Linux(CentOS 7 )基于git、maven实现springboot自动化部署

前提 1、已安装git、maven、java环境 不清楚的可以看另一篇文章&#xff1a; https://blog.csdn.net/weixin_44646763/article/details/137041469 2、已为项目设置远程 git 仓库 origin (可以通过&#xff1a;git remote add origin https://github.com/xxx/xxx.git设置) 创…

MCRNet:用于乳腺超声成像语义分割的多级上下文细化网络

MCRNet&#xff1a;用于乳腺超声成像语义分割的多级上下文细化网络 摘要引言方法 MCRNet_ Multi-level context refinement network for semantic segmentation in breast ultrasound imaging 摘要 由于对比度差、目标边界模糊和大量阴影的不利影响&#xff0c;乳腺超声成像中…

基于ssm校园教务系统论文

摘 要 互联网发展至今&#xff0c;无论是其理论还是技术都已经成熟&#xff0c;而且它广泛参与在社会中的方方面面。它让信息都可以通过网络传播&#xff0c;搭配信息管理工具可以很好地为人们提供服务。针对校园教务信息管理混乱&#xff0c;出错率高&#xff0c;信息安全性差…

数据结构——优先级队列及多服务台模拟系统的实现

一、优先级队列的定义和存储 优先级队列定义&#xff1a;优先级高的元素在队头&#xff0c;优先级低的元素在队尾 基于普通线性表实现优先级队列&#xff0c;入队和出队中必有一个时间复杂度O(n),基于二叉树结构实现优先级队列&#xff0c;能够让入队和出队时间复杂度都为O(log…

WPF碎片

1、Style作为资源可放在控件自身资源下&#xff0c;也可以放在上级控件下如Window.Resources甚至Application.Resources下&#xff0c;但第二种方法需要加为Style添加key并通过Style"{StaticResource xxx}类似方式调用&#xff0c;而前者控件直接默认使用&#xff1b; 方法…

printf()对浮点数的四舍五入是有问题的!!!

一、问题描述 4.5四舍五入应该是5&#xff0c;8.5四舍五入应该是9 但是printf()函数以".f"和.lf打印&#xff0c;得到的却是4和8 二、问题演示 1、代码 #include<stdio.h> int main() {float f4.5;double d8.5;printf("%.f\n",f);printf("…

C语言实现猜数字游戏(有提示,限制次数版)

这次的猜数字游戏我添加了新的功能&#xff1a;为玩家添加了提示&#xff0c;以及输入数字的限制次数。 首先&#xff0c;我们的猜数字游戏需要一个菜单&#xff0c;来让玩家可以选择玩游戏还是退出游戏&#xff0c;所以我们需要开始就打印一个菜单&#xff1a; int main() {…

Linux之进程间通信

1.进程间通信的目的 数据传输&#xff1a;一个进程需要将它的数据发送给另一个进程 资源共享&#xff1a;多个进程之间共享同样的资源。 通知事件&#xff1a;一个进程需要向另一个或一组进程发送消息&#xff0c;通知它&#xff08;它们&#xff09;发生了某种事件&#xff…

谷歌商店如何绑定银行卡!通过支付宝!

完整操作视频在B站&#xff1a; https://www.bilibili.com/video/BV1zt421g7pa/?spm_id_from333.337.search-card.all.click&vd_sourceb5a2563a2e562c5165936c011dcfd0a5 谷歌商店怎么支付&#xff01; 谷歌商店主要用来购买游戏和支付app的应用&#xff0c;由于都是采…

蓝桥杯省赛刷题——题目 2656:刷题统计

刷题统计OJ链接&#xff1a;蓝桥杯2022年第十三届省赛真题-刷题统计 - C语言网 (dotcpp.com) 题目描述 小明决定从下周一开始努力刷题准备蓝桥杯竞赛。他计划周一至周五每天做 a 道题目&#xff0c;周六和周日每天做 b 道题目。请你帮小明计算&#xff0c;按照计划他将在第几…

P6学习:Oracle Primavera P6 OBS/责任人解析

前言 Primavera P6 EPPM 责任人用于管理 P6 企业项目组合管理 (EPPM) 系统中的项目所有权和权限。 Primavera P6 EPPM 中的所有项目都至少围绕三个结构进行组织&#xff1a;称为企业项目结构 (EPS) 的用于组织项目的结构、称为工作分解结构 (WBS) 的用于组织项目内活动的结构…

一篇讲明白 Hadoop 生态的三大部件

文章目录 每日一句正能量前言01 HDFS02 Yarn03 Hive04 HBase05 Spark及Spark Streaming关于作者推荐理由后记赠书活动 每日一句正能量 黎明时怀着飞扬的心醒来&#xff0c;致谢爱的又一天&#xff0c;正午时沉醉于爱的狂喜中休憩&#xff0c;黄昏时带着感恩归家&#xff0c;然后…

ALPHA开发板上PHY网络芯片LAN8720:常用的几个寄存器功能

一. 简介 正点原子的开发板 ALPHA开发板&#xff0c;有线网络硬件方案所使用的也是最常用的一种方案&#xff0c;IMX6ULL芯片内部是自带 MAC网络芯片的&#xff0c;所以&#xff0c;也就是采用 "SOC内部集成网络MAC外设 PHY网络芯片方案"。 前面一篇文章简单了解了…

【python从入门到精通】-- 第三战:输入输出 运算符

&#x1f308; 个人主页&#xff1a;白子寰 &#x1f525; 分类专栏&#xff1a;python从入门到精通&#xff0c;魔法指针&#xff0c;进阶C&#xff0c;C语言&#xff0c;C语言题集&#xff0c;C语言实现游戏&#x1f448; 希望得到您的订阅和支持~ &#x1f4a1; 坚持创作博文…

Android动画(一):视图动画

文章概览 1 Android动画概述1.1 动画的分类1.2 视图动画与属性动画的区别 2 视图动画View Animation2.1 补间动画Tween Animation2.1.1 XML中用标签实现补间动画2.1.2 代码实现补间动画 2.2 逐帧动画Frame Animation2.2.1 XML实现逐帧动画2.2.2 代码实现逐帧动画 本系列将介绍以…

MySQL 之 数据库操作 及 表操作

&#x1f389;欢迎大家观看AUGENSTERN_dc的文章(o゜▽゜)o☆✨✨ &#x1f389;感谢各位读者在百忙之中抽出时间来垂阅我的文章&#xff0c;我会尽我所能向的大家分享我的知识和经验&#x1f4d6; &#x1f389;希望我们在一篇篇的文章中能够共同进步&#xff01;&#xff01;&…

(一)kafka实战——kafka源码编译启动

前言 本节内容是关于kafka消息中间键的源码编译&#xff0c;并通过idea工具实现kafka服务器的启动&#xff0c;使用的kafka源码版本是3.6.1&#xff0c;由于kafka源码是通过gradle编译的&#xff0c;以及服务器是通过scala语言实现&#xff0c;我们要预先安装好gradle编译工具…

了解XSS和CSRF攻击与防御

什么是XSS攻击 XSS&#xff08;Cross-Site Scripting&#xff0c;跨站脚本攻击&#xff09;是一种常见的网络安全漏洞&#xff0c;它允许攻击者在受害者的浏览器上执行恶意脚本。这种攻击通常发生在 web 应用程序中&#xff0c;攻击者通过注入恶意脚本来利用用户对网站的信任&…