minos 2.4 中断虚拟化——中断子系统

首发公号:Rand_cs
该项目来自乐敏大佬:https://github.com/minosproject/minos

前面讲述了 minos 对 GICv2 的一些配置和管理,这一节再往上走一走,看看 minos 的中断子系统

中断

中断描述符

/** if a irq is handled by minos, then need to register* the irq handler otherwise it will return the vnum* to the handler and pass the virq to the vm*/
struct irq_desc {irq_handle_t handler;   // 中断 handler 函数uint16_t hno;           // 物理中断号uint16_t affinity;      // cpu 亲和性unsigned long flags;    spinlock_t lock;unsigned long irq_count;void *pdata;        void *owner;struct kobject *kobj;struct poll_event_kernel *poll_event;
};

由 minos(hypervisor) 处理的每一个中断,都有一个 irq_desc 描述符,其中主要记录了该中断对应的物理中断号 hno,以及对应的 handler

// SGI(Software Generated Interrupts)软件中断
// PPI(Private Peripheral Interrupts)私有外设中断
// SPI(Shared Peripheral Interrupts)共享外设中断
static struct irq_desc percpu_irq_descs[PERCPU_IRQ_DESC_SIZE] = {[0 ... (PERCPU_IRQ_DESC_SIZE - 1)] = {default_irq_handler,},
};static struct irq_desc spi_irq_descs[SPI_IRQ_DESC_SIZE] = {[0 ... (SPI_IRQ_DESC_SIZE - 1)] = {default_irq_handler,},
};static int default_irq_handler(uint32_t irq, void *data)
{pr_warn("irq %d is not register\n", irq);return 0;
}

全局定义了两个 irq_desc 数组,percpu_irq_descs 表示 per cpu 中断,SGI 是发送给特定 CPU(组) 的中断,PPI 是每个 CPU 私有中断,它们都可以看作为 percpu 中断,而 SPI 是所有 CPU 共享(GICD_ITARGETSR设置亲和性)的外部中断。

这里再具体说一下我理解的 percpu 中断,对于 PPI 来说比较好理解,比如说时钟中断,本身就有 NCPU 个的时钟中断源,每个 CPU 私人具有一个中断源,所以我们定义 NCPU 个的 irq_desc 来分别描述这 NCPU 个时钟中断。没什么问题,但是 SGI 呢,我们这样想,对于 CPU0 来说,其他 CPU 包括自己都有可能向 CPU0 发送 SGI,同理对于其他 CPU 也是这样,那么每一种 SGI,我们也定义 NCPU 个 irq_desc 来描述,很合理。

spi_irq_descs 的下标我们可以当做虚拟中断号 virq,一个设备的硬件中断号记录在设备树文件里面,比如说串口:

        pl011@9000000 {clock-names = "uartclk\0apb_pclk";clocks = < 0x8000 0x8000 >;interrupts = < 0x00 0x01 0x04 >;reg = < 0x00 0x9000000 0x00 0x1000 >;compatible = "arm,pl011\0arm,primecell";};

interrupts = < 0x00 0x01 0x04 >;对于设备树的 interrupts 语句,后面一般跟 3 个数或者 2 个数,倒数第二个表示硬件中断号,倒数第一个表示触发方式,倒数第三个表示中断域,比如说是 SPI?PPI?

从这里可以看出串口 pl011 的中断号为 0x01,但似乎这个数不太对,怎么会在 32 以内?那是因为获取了这个数之后还要进行转换,在设备树分析的时候,从 interrupts 获取到中断信息后,马上会调用 irq_xlate 转换中断号

int get_device_irq_index(struct device_node *node, uint32_t *irq,unsigned long *flags, int index)
{int irq_cells, len, i;of32_t *value;uint32_t irqv[4];if (!node)return -EINVAL;value = (of32_t *)of_getprop(node, "interrupts", &len);if (!value || (len < sizeof(of32_t)))return -ENOENT;irq_cells = of_n_interrupt_cells(node);if (irq_cells == 0) {pr_err("bad irqcells - %s\n", node->name);return -ENOENT;}pr_debug("interrupt-cells %d\n", irq_cells);len = len / sizeof(of32_t);if (index >= len)return -ENOENT;value += (index * irq_cells);for (i = 0; i < irq_cells; i++)irqv[i] = of32_to_cpu(*value++);return irq_xlate(node, irqv, irq_cells, irq, flags);
}irq_xlate -> irq_chip->irq_xlate -> gic_xlate_irqint gic_xlate_irq(struct device_node *node,uint32_t *intspec, unsigned int intsize,uint32_t *hwirq, unsigned long *type)
{if (intsize != 3)return -EINVAL;// SPI 中断if (intspec[0] == 0)*hwirq = intspec[1] + 32;// PPI 中断else if (intspec[0] == 1) {if (intspec[1] >= 16)return -EINVAL;*hwirq = intspec[1] + 16;} elsereturn -EINVAL;*type = intspec[2];return 0;
}

通过上述代码我们可以知道,pl101 的中断实际上是 1 + 32 = 33,这是一个物理中断号,在 minos 中物理中断号与虚拟中断号是一样的,没有做什么复杂的映射。在 Linux 系统,因为要考虑各个平台,各个平台使用的中断控制器,向后兼容一系列复杂的原因,做不到物理中断号与虚拟中断号直接映射。但目前 minos 没有太多平台特性,只支持 ARM,所以将物理中断号和虚拟中断号直接映射来简化实现。

注册中断

// 注册 percpu 类型的 irq
int request_irq_percpu(uint32_t irq, irq_handle_t handler,unsigned long flags, char *name, void *data)
{int i;struct irq_desc *irq_desc;unsigned long flag;unused(name);if ((irq >= NR_PERCPU_IRQS) || !handler)return -EINVAL;// 遍历每个CPU,注册对应的 irqfor (i = 0; i < NR_CPUS; i++) {// 获取 per cpu 类型中断对应的 irq_descirq_desc = get_irq_desc_cpu(i, irq);if (!irq_desc)continue;// 初始化 irq_desc 结构体spin_lock_irqsave(&irq_desc->lock, flag);irq_desc->handler = handler;irq_desc->pdata = data;irq_desc->flags |= flags;irq_desc->affinity = i;irq_desc->hno = irq;/* enable the irq here */// 使能该中断irq_chip->irq_unmask_cpu(irq, i);// irq_desc 中也取消 masked 标志irq_desc->flags &= ~IRQ_FLAGS_MASKED;spin_unlock_irqrestore(&irq_desc->lock, flag);}return 0;
}// 注册普通的 SPI 共享外设
int request_irq(uint32_t irq, irq_handle_t handler,unsigned long flags, char *name, void *data)
{int type;struct irq_desc *irq_desc;unsigned long flag;unused(name);if (!handler)return -EINVAL;// 获取该 irq 对应的 irq_desc// irq < 32 返回 percpu_irq_descs// irq >= 32 返回 spi_descirq_desc = get_irq_desc(irq);if (!irq_desc)return -ENOENT;type = flags & IRQ_FLAGS_TYPE_MASK;flags &= ~IRQ_FLAGS_TYPE_MASK;// 设置 irq_desc 各个字段spin_lock_irqsave(&irq_desc->lock, flag);irq_desc->handler = handler;irq_desc->pdata = data;irq_desc->flags |= flags;irq_desc->hno = irq;/* enable the hw irq and set the mask bit */// 使能该中断irq_chip->irq_unmask(irq);// 在 irq_desc 层级也取消屏蔽irq_desc->flags &= ~IRQ_FLAGS_MASKED;// 如果 irq < SPI_IRQ_BASE,要么是 SGI 软件中断,要么是 PPI 私有中断// 都属于 percpu 中断,设置该 irq 的亲和性为当前 cpuif (irq < SPI_IRQ_BASE)irq_desc->affinity = smp_processor_id();spin_unlock_irqrestore(&irq_desc->lock, flag);// 设置触发类型if (type)irq_set_type(irq, type);return 0;
}

minos 中有上述两个注册中断函数,看函数名称一个是注册 percpu 类型的中断,一个是注册其他(SPI) 类型的中断,但其实 request_irq 什么类型的中断都会注册,从代码 if (irq < SPI_IRQ_BASE)就可以看出来

注册中断就是在中断号对应的 irq_desc 填写好 handler 等信息,然后 irq_chip->irq_unmask(irq);使能该中断,中断的注册主要就是做这两件事

另外,对于某个状态的状态标志,虽然寄存器里面存有相关信息,但是我们一般在系统软件层面上也设置相关标志,那么每次获取状态信息直接读取变量就行了,不用再去从设备寄存器里面获取

中断处理

int do_irq_handler(void)
{uint32_t irq;struct irq_desc *irq_desc;int cpuid = smp_processor_id();while (1) {// 循环调用 get_pending_irq 读取 IAR 寄存器来获取中断号irq = irq_chip->get_pending_irq();if (irq >= BAD_IRQ)return 0;// 根据中断号获取 irq_descirq_desc = get_irq_desc_cpu(cpuid, irq);// 不太可能为空,如果为空可能是发生了伪中断if (unlikely(!irq_desc)) {pr_err("irq is not actived %d\n", irq);irq_chip->irq_eoi(irq);irq_chip->irq_dir(irq);continue;}do_handle_host_irq(cpuid, irq_desc);}return 0;ec->handler
}// 执行中断对应的 handler
static int do_handle_host_irq(int cpuid, struct irq_desc *irq_desc)
{int ret;if (cpuid != irq_desc->affinity) {pr_notice("irq %d do not belong to this cpu\n", irq_desc->hno);ret =  -EINVAL;goto out;}// 执行 handlerret = irq_desc->handler(irq_desc->hno, irq_desc->pdata);// drop priorityirq_chip->irq_eoi(irq_desc->hno);
out:/** 1: if the hw irq is to vcpu do not DIR it.* 2: if the hw irq is to vcpu but failed to send then DIR it.* 3: if the hw irq is to userspace process, do not DIR it.*/// 除了上述三种情况,调用 irq_dir deactivate if (ret || !(irq_desc->flags & IRQ_FLAGS_VCPU))irq_chip->irq_dir(irq_desc->hno);return ret;
}

与前文联系起来:

__irq_exception_from_current_elirq_from_current_elirq_handlerdo_irq_handlerdo_handle_host_irqirq_desc->handler__irq_exception_from_lower_elirq_from_lower_elirq_handler......

异常

异常描述符

struct sync_desc {uint8_t aarch;     // 执行状态uint8_t irq_safe;  // 概念同 Linux,如果handler不会导致死锁竞争等,safeuint8_t ret_addr_adjust;  // 返回地址修正uint8_t resv;      // padsync_handler_t handler;  
};

对于异常的处理,也类似中断,每一个异常都定义了一个 sync_desc 来描述,里面记录了 handler 等信息

其他都比较好理解,就这个返回地址修正什么意思呢?当发生异常的时候,是将发生异常的指令的地址保存到 ELR_EL2 寄存器里面,但是返回的时候不一定返回异常指令地址。比如说 svc 系统调用指令,当 svc 执行完成后肯定是返回 svc 下一条指令,这个 ret_addr_adjust 就是做这个事情的,记录对应异常是否需要返回地址的修正

手册里有个地方记录着每种异常的伪代码,其中记录了是否修正,以及修正值:TODO 补充链接

#define DEFINE_SYNC_DESC(t, arch, h, is, raa)           \static struct sync_desc sync_desc_##t __used = {    \.aarch = arch,                  \.handler = h,                   \.irq_safe = is,                 \.ret_addr_adjust = raa,             \}DEFINE_SYNC_DESC(trap_unknown, EC_TYPE_AARCH64, unknown_trap_handler, 1, 0);
DEFINE_SYNC_DESC(trap_kernel_da, EC_TYPE_AARCH64, kernel_mem_fault, 1, 0);
DEFINE_SYNC_DESC(trap_kernel_ia, EC_TYPE_AARCH64, kernel_mem_fault, 1, 0);

目前 minos 定义了上述几个异常描述符(还有一些与虚拟化相关,暂且不谈),实际就两个,一个是指令异常,一个是数据异常,其他的都处于未定义状态(都调用到 panic)

异常处理

static void handle_sync_exception(gp_regs *regs)
{uint32_t esr_value;uint32_t ec_type;struct sync_desc *ec;// 获取异常原因,ESR[31:26]记录了异常的种类,其值当做异常号esr_value = read_esr();ec_type = ESR_ELx_EC(esr_value);if (ec_type >= ESR_ELx_EC_MAX)panic("unknown sync exception type from current EL %d\n", ec_type);/** for normal userspace process the return address shall* be adjust*/// 获取该异常对应的异常描述符ec = process_sync_descs[ec_type];// 修正返回地址regs->pc += ec->ret_addr_adjust;// 处理该异常ec->handler(regs, ec_type, esr_value);
}

再与前文联系起来:

__sync_exception_from_current_elsync_exception_from_current_elhandle_sync_exceptionec->handler__sync_exception_from_lower_elsync_exception_from_lower_elhandle_sync_exceptionec->handler

首发公号:Rand_cs

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

属性(property)

自学python如何成为大佬(目录):https://blog.csdn.net/weixin_67859959/article/details/139049996?spm1001.2014.3001.5501 1 创建用于计算的属性 在Python中&#xff0c;可以通过property&#xff08;装饰器&#xff09;将一个方法转换为属性&#xff0c;从而实现用于计算…

几种更新 npm 项目依赖的实用方法

引言 在软件开发的过程中&#xff0c;我们知道依赖管理是其中一个至关重要的环节。npm&#xff08;Node Package Manager&#xff09; 是 Node.js 的包管理器&#xff0c;它主要用于 Node.js 项目的依赖管理和包发布。随着项目的不断发展&#xff0c;依赖库的版本更新和升级成…

字符串-459重复字符串

459. 重复的子字符串 - 力扣&#xff08;LeetCode&#xff09; 给定一个非空的字符串 s &#xff0c;检查是否可以通过由它的一个子串重复多次构成。 示例 1: 输入: s "abab" 输出: true 解释: 可由子串 "ab" 重复两次构成。示例 2: 输入: s "ab…

基于Win11下的Wireshark的安装和使用

Wireshark的安装和使用 前言一、Wireshark是什么简介 二、下载Wireshark下载过程查看自己电脑配置 三、安装Wireshark安装过程安装组件创建快捷方式winPacpNpcap 打开检验 四、使用Wireshark实施抓包捕获数据包 五、基于Wireshark使用显示过滤器简介使用方法注意ICMP的请求和应…

【明道云】如何在发送邮件模块给出多条记录形成的列表

【背景】 在完成批量分别发送邮件给不同部门担当的任务后,作为主管部门的用户希望能够获得成功得到处理的所有记录信息。这些信息需要在提醒邮件中以表格形式呈现。 【分析】 获得多条记录的方法能够拿到数据,问题在于如何合适地以表格形式呈现在邮件中。 只有富文本支持表…

c++文件流

1.流的概念 在 C 中&#xff0c;流&#xff08;stream&#xff09;是一种用来处理输入输出数据的概念。 可以把流看作从源头&#xff08;如文件、键盘、网络连接等&#xff09;到终端的数据传输管道。 这些管道在程序运行时动态创建&#xff0c;并可以连接到多种不同的源头和…

目标检测算法综述

1 研究背景 1.1 概述 目标检测是计算机视觉的重要分支&#xff0c;主要任务是在给定的图片中精确找到物体所在位置&#xff0c;并标注出物体的类别&#xff0c;即包含了目标定位与目标分类两部分。在计算机视觉领域中的目标跟踪、图像分割、事件检测、场景理解等的任务都以目标…

Java线程几种常用方法详细说明

在Java编程中&#xff0c;多线程编程是一个非常重要的主题。它允许我们同时运行多个任务&#xff0c;提高程序的性能和响应速度。在这篇博客中&#xff0c;我们将介绍一些常用的Java线程方法和构造器&#xff0c;并通过示例代码展示如何使用它们。 Thread提供的常用方法 publi…

速盾:速盾高防cdn的防御原理?

速盾高防CDN是一种专业的网络安全解决方案&#xff0c;通过多种防御措施保护网站不受各种网络攻击的影响。其防御原理主要包括以下几个方面&#xff1a; 高强度DDoS防护 速盾高防CDN采用了弹性云防护集群&#xff0c;能够应对大规模的分布式拒绝服务&#xff08;DDoS&#xff0…

SpringBoot与Spring Framework提供的缓存抽象

目录 缓存 项目总结 新建一个SpringBoot项目 pom.xml application.properties CacheConfig Book BookRepository接口 BookService服务类 BookController控制器 SpringbootCacheApplication启动类 启动项目&#xff0c;使用Postman测试 参考博文&#xff1a; 1、使用…

llvm 3.5 源码分析 clang for x86 001 之搭环境

0&#xff0c;目标 编译 针对x86 的&#xff0c;debug 的 c语言的编译器 1&#xff0c;下载代码 git clone --recursive 。。。llvm-project.git $ cd llvm-project 2&#xff0c;预备代码 llvm 3.5 版本的源代码&#xff0c;早期版本&#xff0c;可能比较小比较容易debug $…

Python知识点1---变量和数据类型

提前说一点&#xff1a;如果你是专注于Python开发&#xff0c;那么本系列知识点只是带你入个门再详细的开发点就要去看其他资料了&#xff0c;而如果你和作者一样只是操作其他技术的Python API那就足够了。 在所有知识开始前&#xff0c;先给大家介绍一下Python的注释&#xf…

使用STM32和TB6600驱动器控制42BYGH步进电机

项目概述 1. 系统组成 STM32微控制器&#xff1a;作为主控制器&#xff0c;负责发出控制指令。TB6600驱动器&#xff1a;用于接收STM32的指令并驱动步进电机。42BYGH步进电机&#xff1a;作为执行元件&#xff0c;根据控制信号进行转动。电源&#xff1a;为STM32、TB6600和步…

Unity MiniCPM-V 让引擎拥有视觉

Unity MiniCPM-V 让引擎拥有视觉 前言项目Python环境布置Unity场景布置代码编写添加并设置脚本总结 鸣谢AI提示 前言 新发布的MiniCPM-V&#xff0c;忍不住玩一下&#xff0c;可以让之前制作的语音助手拥有一定的视觉能力&#xff08;不是OpenCV不行&#xff0c;而是AI更加符合…

深入理解Java关系运算符,避免常见错误!

哈喽&#xff0c;各位小伙伴们&#xff0c;你们好呀&#xff0c;我是喵手。运营社区&#xff1a;C站/掘金/腾讯云&#xff1b;欢迎大家常来逛逛 今天我要给大家分享一些自己日常学习到的一些知识点&#xff0c;并以文字的形式跟大家一起交流&#xff0c;互相学习&#xff0c;一…

Java Web是前端吗:深入解析Java Web技术的定位与边界

Java Web是前端吗&#xff1a;深入解析Java Web技术的定位与边界 在探讨Java Web是否属于前端领域时&#xff0c;我们首先需要明确Java Web技术的定位和它所涉及的范畴。本文将从四个方面、五个方面、六个方面和七个方面来深入解析这一问题&#xff0c;带您领略Java Web技术的…

复现CELL文章图片,在线绘制scRNA-seq多个cluster的差异火山图

导读 火山图表示两组间的差异&#xff0c;而在scRNA-seq中&#xff0c;同时会有多个cluster&#xff08;vs其他cluster&#xff09;的差异。如果用常规火山图展示&#xff0c;可能需要绘制十几个图。有没有更高效的展示方法呢&#xff1f;今天给大家带来CELL文章“A Spatiotem…

linux pigz 多线程压缩解压

压缩&#xff1a; tar --use-compress-programpigz -cvpf abc.tgz ./abc 解压&#xff1a; tar --use-compress-programpigz -xvf ./abc.tgz 解压到指定位置&#xff1a;tar --use-compress-programpigz -xvf ./abc.tgz -C ./hhh

原生js访问http获取数据的方法

在原生JavaScript中&#xff0c;直接通过浏览器端的JavaScript访问HTTP接口获取数据通常涉及XMLHttpRequest对象或现代的fetch API。 1. 使用XMLHttpRequest XMLHttpRequest是一个老旧的API&#xff0c;但在某些情况下仍然很有用。以下是一个简单的例子&#xff1a; javascr…

打造SimPO新算法,微调8B模型超越Claude 3 Opus

前言 大型语言模型&#xff08;LLM&#xff09;近年来取得了巨大进展&#xff0c;但要将其与人类价值观和意图相一致&#xff0c;使其变得有用、诚实和无害&#xff0c;仍然是一个挑战。强化学习从人类反馈中&#xff08;RLHF&#xff09;是一种常用的方法&#xff0c;通过微调…