Linux: ARM GIC只中断CPU 0问题分析

news2024/9/24 7:26:02

文章目录

  • 1. 前言
  • 2. 分析背景
  • 3. 问题
  • 4. 分析
    • 4.1 ARM GIC 中断芯片简介
      • 4.1.1 中断类型和分布
      • 4.1.2 拓扑结构
    • 4.2 问题根因
      • 4.2.1 设置GIC `SPI` 中断CPU亲和性
      • 4.2.2 GIC初始化:缺省的CPU亲和性
        • 4.2.2.1 boot CPU亲和性初始化流程
        • 4.2.2.1 其它非 boot CPU亲和性初始化流程
  • 5. GIC 的救赎?
    • 5.1 默认配置成转发给所有CPU
    • 5.2 用当前 CPU ID 作为 gic_cpu_map[] 索引
  • 6. 一个解决方案:irqbalance
  • 7. 参考资料

1. 前言

限于作者能力水平,本文可能存在谬误,因此而给读者带来的损失,作者不做任何承诺。

2. 分析背景

本文分析基于 linux-4.14.132 内核代码分析,运行环境 Ubuntu 16.04.4 LTS + QEMU + ARM vexpress-a9rootfs 基于 ubuntu-base-16.04-core-armhf.tar.gz 制作。

3. 问题

在使用全志H3机器时,观察到一个现象,外设中断总是集中在 CPU0 处理:

# cat /proc/interrupts
           CPU0       CPU1       CPU2       CPU3
 16:          0          0          0          0     GICv2  25 Level     vgic
 17:          0          0          0          0     GICv2  50 Level     /soc/timer@01c20c00
 18:          0          0          0          0     GICv2  29 Level     arch_timer
 19:       6119       3595       3947       3046     GICv2  30 Level     arch_timer
 20:          0          0          0          0     GICv2  27 Level     kvm guest timer
 22:          0          0          0          0     GICv2 120 Level     1ee0000.hdmi, dw-hdmi-cec
 24:          0          0          0          0     GICv2 118 Level     1c0c000.lcd-controller
 25:          0          0          0          0     GICv2  82 Level     1c02000.dma-controller
 26:         24          0          0          0     GICv2  92 Level     sunxi-mmc
 27:         25          0          0          0     GICv2  93 Level     sunxi-mmc
 28:       2697          0          0          0     GICv2  94 Level     sunxi-mmc
 29:          0          0          0          0     GICv2 103 Level     musb-hdrc.4.auto
 30:          0          0          0          0     GICv2 104 Level     ehci_hcd:usb1
 31:          0          0          0          0     GICv2 105 Level     ohci_hcd:usb2
 32:          0          0          0          0     GICv2 106 Level     ehci_hcd:usb3
 33:          0          0          0          0     GICv2 107 Level     ohci_hcd:usb6
 34:          0          0          0          0     GICv2 108 Level     ehci_hcd:usb4
 35:          0          0          0          0     GICv2 109 Level     ohci_hcd:usb7
 36:          0          0          0          0     GICv2 110 Level     ehci_hcd:usb5
 37:          0          0          0          0     GICv2 111 Level     ohci_hcd:usb8
 40:        205          0          0          0     GICv2  63 Level     1c25000.ths
 42:       1802          0          0          0     GICv2 114 Level     eth0
 43:          0        

我们观察到,除了第6行

 19:       6119       3595       3947       3046     GICv2  30 Level     arch_timer

之外,其它所有的行,CPU1~CPU3 的中断计数都为0。另外,用 QEMU 模拟的 vexpress-a9 板上,也观察到类似的现象:

$ cat /proc/interrupts 
           CPU0       CPU1       CPU2       CPU3       
 16:      69412      69401      69376      69332     GIC-0  29 Level     twd
 17:          6          0          0          0     GIC-0  34 Level     timer
 27:          0          0          0          0     GIC-0  92 Level     arm-pmu
 28:          0          0          0          0     GIC-0  93 Level     arm-pmu
 29:          0          0          0          0     GIC-0  94 Level     arm-pmu
 30:          0          0          0          0     GIC-0  95 Level     arm-pmu
 34:       3521          0          0          0     GIC-0  41 Level     mmci-pl18x (cmd)
 35:     197190          0          0          0     GIC-0  42 Level     mmci-pl18x (pio)
 36:          8          0          0          0     GIC-0  44 Level     kmi-pl050
 37:        100          0          0          0     GIC-0  45 Level     kmi-pl050
 38:         23          0          0          0     GIC-0  37 Level     uart-pl011
 44:          0          0          0          0     GIC-0  36 Level     rtc-pl031
IPI0:          0          1          1          1  CPU wakeup interrupts
IPI1:          0          0          0          0  Timer broadcast interrupts
IPI2:        750       1231       1441        947  Rescheduling interrupts
IPI3:          1          2          3          3  Function call interrupts
IPI4:          0          0          0          0  CPU stop interrupts
IPI5:          0          0          0          0  IRQ work interrupts
IPI6:          0          0          0          0  completion interrupts
Err:          0

4. 分析

4.1 ARM GIC 中断芯片简介

4.1.1 中断类型和分布

我们这里讨论的是 ARM GICv1 芯片,该芯片的中断分为3类:

SGI(Software-generated interrupt):用于处理期之间的通信,编号为 0~15 ;
PPI(Private peripheral interrupt):发送到特定处理器的中断,编号为 16~31 ;
SPI(Shared peripheral interrupt):可以发送给所有处理器的中断,编号为 32~1019 。

注意,每个处理器都有自己的 SGI 和 PPI 中断,它们使用相同的编号。假设系统有4个处理器,则这4个处理器都有自己的16个 SGI 中断和16个 PPI 中断。SPI 中断是全局的、所有处理器共享的。

4.1.2 拓扑结构

ARM GIC 芯片的内部结构如下图:
在这里插入图片描述
GIC 芯片内部包含两大功能模块:DistributorCPU interfaceDistributor 用于将外设投递的中断信号转发给 CPU interface,具体转发给哪些 CPU interface ,可通过寄存器 ICDIPTRn 进行配置,当然,Distributor 也可以禁止外设中断信号的传入。而 CPU interface 用于将接收自 Distributor 的中断信号传递给连接的 CPU, CPU interface 也可以禁止将信号传递给 CPU 。

4.2 问题根因

4.2.1 设置GIC SPI 中断CPU亲和性

从上一小节了解到,具体将中断转发给哪个 CPU 核处理,取决于寄存器 ICDIPTRn 的配置。看一下 GIC 手册对该寄存器的描述:

4.3.11 Interrupt Processor Targets Registers (ICDIPTRn)
       The ICDIPTR characteristics are:
Purpose                     The ICDIPTRs provide an 8-bit CPU targets field for each interrupt supported by
                            the GIC. This field stores the list of processors that the interrupt is sent to if it is
                            asserted.

在这里插入图片描述
准确来讲,这是一个寄存器集,可以按字节访问,每个字节对应一个中断号的 Distributor 转发配置,每个bit对应一个CPU核的配置,最多支持8核。对于 SGIPPI 中断号区间对应的寄存器,它们是每个CPU一份的(上面的引用没有描述),而对于 SPI 中断号区间对应的寄存器,它们的所有CPU共享一份的。GIC 芯片驱动提供接口 gic_set_affinity() 来配置 Distributor ICDIPTRn 寄存器,来决定 Distributor 将中断信号转发给哪个CPU核来处理,来看它的逻辑:

int cpumask_next_and(int n, const struct cpumask *src1p,
		     const struct cpumask *src2p)
{
	while ((n = cpumask_next(n, src1p)) < nr_cpu_ids)
		if (cpumask_test_cpu(n, src2p))
			break;
	return n;
}

/**
 * cpumask_next_and - get the next cpu in *src1p & *src2p
 * @n: the cpu prior to the place to search (ie. return will be > @n)
 * @src1p: the first cpumask pointer
 * @src2p: the second cpumask pointer
 *
 * Returns >= nr_cpu_ids if no further cpus set in both.
 */
int cpumask_next_and(int n, const struct cpumask *src1p,
		     const struct cpumask *src2p)
{
	while ((n = cpumask_next(n, src1p)) < nr_cpu_ids)
		if (cpumask_test_cpu(n, src2p))
			break;
	return n;
}

/**
 * cpumask_first_and - return the first cpu from *srcp1 & *srcp2
 * @src1p: the first input
 * @src2p: the second input
 *
 * Returns >= nr_cpu_ids if no cpus set in both.  See also cpumask_next_and().
 */
#define cpumask_first_and(src1p, src2p) cpumask_next_and(-1, (src1p), (src2p))

/**
 * cpumask_any_and - pick a "random" cpu from *mask1 & *mask2
 * @mask1: the first input cpumask
 * @mask2: the second input cpumask
 *
 * Returns >= nr_cpu_ids if no cpus set.
 */
/* 这个注释里的 "random" 真是个误导,从来也存在什么随机可言 */ 
#define cpumask_first_and(src1p, src2p) cpumask_next_and(-1, (src1p), (src2p))

/**
 * cpumask_first - get the first cpu in a cpumask
 * @srcp: the cpumask pointer
 *
 * Returns >= nr_cpu_ids if no cpus set.
 */
static inline unsigned int cpumask_first(const struct cpumask *srcp)
{
	return find_first_bit(cpumask_bits(srcp), nr_cpumask_bits);
}

static int gic_set_affinity(struct irq_data *d, const struct cpumask *mask_val,
			    bool force)
{
	/* 计算中断 @d->hwirq 的 ICDIPTRn 寄存器地址 */
	void __iomem *reg = gic_dist_base(d) + GIC_DIST_TARGET + (gic_irq(d) & ~3);
	/* ICDIPTRn 是 32-bit 寄存器,包含4个中断的设置,要计算中断 @d->hwirq 配置域的位偏移 */
	unsigned int cpu, shift = (gic_irq(d) % 4) * 8;
	u32 val, mask, bit;
	unsigned long flags;

	/*
	 * 从 @mask_val 选一个 CPU,然后用它的转发配置来配置对中断 @d->hwirq 的转发。
	 * 这里就是导致 SPI 中断都转发给 CPU0 的根因了。force 可能是 true ,也可能是 
	 * false ,看起来似乎得到的 @cpu 的值会是变化的。
	 * SPI 中断初始化从 request_irq*() 系列调用发起,在过程中会设置 SPI 中断的CPU
	 * 亲和性,传进来的 @mask_val 掩码包含系统所有的 CPU,这样在这里得到的 @cpu 均
	 * 为 0 (假定 CPU0 是 boot CPU,这是通常的情形) !!!
	 * 当然,从 request_percpu_irq() 注册的每 CPU 中断是例外。
	 */
	if (!force)
		cpu = cpumask_any_and(mask_val, cpu_online_mask);
	else
		cpu = cpumask_first(mask_val);

	...

	mask = 0xff << shift;
	/* 
	 * 这个逻辑,说实话,很是违反人类的直觉(至少是我的)。 譬如,用户空间通过指令
	 * echo f > /proc/irq/38/smp_affinity 
	 * 来配置38号中断的 CPU affinity (即 Distributor 转发38号中断给哪些CPU核),
	 * 我理解的是可以将38号中断发送给 CPU0~3 中的任一个来处理。
	 * 但最终发生了什么呢?GIC 驱动代码仅仅是从掩码计算出一个编号 @cpu ,然后以 
	 * @cpu 为索引,取值 @gic_cpu_map[@cpu] 来配置中断 @d->hwirq 的 Distributor 
	 * 的转发配置:发送中断 @d->hwirq 给哪些 CPU !!! 呵呵,完全出乎意料。
	 * 从前面的代码片段了解到,除非特殊配置,索引值 @cpu 总是返回 0,这意味着,驱动
	 * 总是将 SPI 中断的转发配置为固定值 @gic_cpu_map[@cpu] ,也就是 SPI 中断总是
	 * 被某几个或某个CPU核处理,最终结果是 SPI 中断总是被 CPU0 处理。
	 * 从哪里知道固定值 @gic_cpu_map[@cpu] 是固定值,而且是 0x01 这个固定值(假设
	 * boot CPU 是 CPU0)?简略的看后面中断初始化的流程,可以了解这些。
	 */
	bit = gic_cpu_map[cpu] << shift;
	val = readl_relaxed(reg) & ~mask;
	writel_relaxed(val | bit, reg);
	...

	irq_data_update_effective_affinity(d, cpumask_of(cpu));

	return IRQ_SET_MASK_OK_DONE;
}

4.2.2 GIC初始化:缺省的CPU亲和性

4.2.2.1 boot CPU亲和性初始化流程

int __init
gic_of_init(struct device_node *node, struct device_node *parent)
{
	...

	ret = __gic_init_bases(gic, -1, &node->fwnode);

	...
}

static int __init __gic_init_bases(struct gic_chip_data *gic,
				   int irq_start,
				   struct fwnode_handle *handle)
{
	if (gic == &gic_data[0]) { /* ROOT 中断控制器 */
		for (i = 0; i < NR_GIC_CPU_IF; i++)
			gic_cpu_map[i] = 0xff; /* 初始将中断转发给所有 CPU interface 上的 CPU */
		...
		/* 设置非 boot CPU 中断初始化入口 gic_starting_cpu() */
		cpuhp_setup_state_nocalls(CPUHP_AP_IRQ_GIC_STARTING,
					  "irqchip/arm/gic:starting",
					  gic_starting_cpu, NULL);
		set_handle_irq(gic_handle_irq); /* 设置中断 GIC 中断处理入口 */
		...
	}

	...

	ret = gic_init_bases(gic, irq_start, handle);
	ret = gic_cpu_init(gic);
	
	...
}

static int gic_init_bases(struct gic_chip_data *gic, int irq_start,
			  struct fwnode_handle *handle)
{
	...

	cpumask = gic_get_cpumask(gic); /* 读取当前 CPU 的默认中断转发配置 */
	/* 将 gic_get_cpumask() 读回的 8-bit 值复制到 32-bit @cpumask 
	 * 的所有 4个 8-bit 域 */
	cpumask |= cpumask << 8;
	cpumask |= cpumask << 16;
	/* 
	 * 默认的 Distributor 转发配置:只将中断转发给 boot CPU。 
	 * 这是合理的,因为当前处于 boot 阶段,除 boot CPU 外的其它CPU还没有运行起来。
	 */
	for (i = 32; i < gic_irqs; i += 4)
		writel_relaxed(cpumask, base + GIC_DIST_TARGET + i * 4 / 4);
	
	...
}

/* 读取当前 CPU 的默认中断转发配置 */
static u8 gic_get_cpumask(struct gic_chip_data *gic)
{
	/* 
	 * 这里的代码,如果没读过 GIC 手册,是比较难理解的。 
	 * 这里读的是 CPU 私有中断 SGI,PPI 的转发配置值,它们的理所当然的是只会
	 * 转发给对应的 CPU 核,譬如编号为 29 的 PPI 中断:
	 * . CPU0 读到的值就应该是 0xXX_XX_01_XX
	 * . CPU1 读到的值就应该是 0xXX_XX_02_XX
	 * . CPU2 读到的值就应该是 0xXX_XX_04_XX
	 * . CPU3 读到的值就应该是 0xXX_XX_08_XX
	 * 对于 SGI,PPI 这些私有中断,寄存器是多份的(banked register),所以不同的CPU
	 * 在这里读的是自己私有的寄存器;而 SPI 中断的寄存器是全局独一份的。
	 */
	for (i = mask = 0; i < 32; i += 4) {
		/* 
		 * 对于不同的 SPI 中断号,读到值的不为 0 的 8-bit 的位偏移位置不同, 
		 * 后面的移位操作是为了保证将不为 0 的 8-bit 移动到最低 8-bit 。
		 * 为什么?因为返回值类型是 u8 。
		 */
		mask = readl_relaxed(base + GIC_DIST_TARGET + i);
		mask |= mask >> 16;
		mask |= mask >> 8;
		/*
		 * 为什么 @mask 不为 0 就返回了?
		 * 因为只要是同一 CPU 核,只要读到某个 SGI 或 PPI 中断的转发配置值不为0,剩余
		 * 其它 SGI 或 PPI 中断配置值一定是相同的。这里只是要取得当前 CPU 的某个 SGI 
		 * 或 PPI 中断转发配置值就够了。
		 * 那又为什么不直接根据 CPU ID 返回 0x01, 0x02, 0x04, 0x08, ... 这样的值呢?
		 * 因为 SGI 或 PPI 中断转发配置寄存器的值它们是【只读的】,具体的值是由硬件设
		 * 计决定的,有可能 CPU0 读到的值是 0x02 ,因为 GIC 的 CPU interface 0 有可
		 * 能连接到 CPU1 ,这在理论上是可能出现的。
		 */
		if (mask) /* 硬件实现了某 SGI, PPI 中断的映射,即中断转发配置不为0值 */
			break;
	}

	return mask;
}

static int gic_cpu_init(struct gic_chip_data *gic)
{
	if (gic == &gic_data[0]) {
		cpu_mask = gic_get_cpumask(gic);
		gic_cpu_map[cpu] = cpu_mask; /* 当前 CPU @cpu 中断的默认转发配置:只转发给自身 */

		/*
		 * Clear our mask from the other map entries in case they're
		 * still undefined.
		 */
		for (i = 0; i < NR_GIC_CPU_IF; i++)
			if (i != cpu)
				gic_cpu_map[i] &= ~cpu_mask;
	}
	...
}

4.2.2.1 其它非 boot CPU亲和性初始化流程

secondary_start_kernel()
	notify_cpu_starting(cpu)
		struct cpuhp_cpu_state *st = per_cpu_ptr(&cpuhp_state, cpu);
		enum cpuhp_state target = min((int)st->target, CPUHP_AP_ONLINE);

		...
		while (st->state < target) {
			st->state++;
			ret = cpuhp_invoke_callback(cpu, st->state, true, NULL, NULL);
				...
				gic_starting_cpu(cpu)
		}
		...
static int gic_starting_cpu(unsigned int cpu)
{
	gic_cpu_init(&gic_data[0]); /* 见 4.2.2.1 章节分析 */
	return 0;
}

总结起来就是,在 boot CPU 为 CPU0 的情形下,所有 SPI 中断的 ICDIPTRn 寄存器 都配置为 gic_cpu_map[0] == 0x01 ,即将所有 SPI 中断都转发给 CPU0 处理。SGIPPI 中断不在我们考虑范围之内,因为它们本来就是特定于 CPU 的。

5. GIC 的救赎?

以下测试均基于 QEMU 模拟的 vexpress-a9 开发板 。

5.1 默认配置成转发给所有CPU

这看起来是比较合理的解决方案,对 GIC 驱动的 gic_set_affinity() 接口做如下修改:

- bit = gic_cpu_map[cpu] << shift;
+ bit = 0xff << shift; /* 默认转发给所有的 CPU */

我们来看一下实际效果:

$ cat /proc/interrupts 
           CPU0       CPU1       CPU2       CPU3       
 16:       4558       4548       4578       4505     GIC-0  29 Level     twd
 17:          6          0          0          0     GIC-0  34 Level     timer
 27:          0          0          0          0     GIC-0  92 Level     arm-pmu
 28:          0          0          0          0     GIC-0  93 Level     arm-pmu
 29:          0          0          0          0     GIC-0  94 Level     arm-pmu
 30:          0          0          0          0     GIC-0  95 Level     arm-pmu
 34:       1426       1146       1275       1308     GIC-0  41 Level     mmci-pl18x (cmd)
 35:      81272      78233      77890      78991     GIC-0  42 Level     mmci-pl18x (pio)
 36:          0          0          8          0     GIC-0  44 Level     kmi-pl050
 37:          0          0         99          1     GIC-0  45 Level     kmi-pl050
 38:         18          3          0          2     GIC-0  37 Level     uart-pl011
 44:          0          0          0          0     GIC-0  36 Level     rtc-pl031
IPI0:          0          1          1          1  CPU wakeup interrupts
IPI1:          0          0          0          0  Timer broadcast interrupts
IPI2:        750        501        548        864  Rescheduling interrupts
IPI3:          0          2          2          1  Function call interrupts
IPI4:          0          0          0          0  CPU stop interrupts
IPI5:          0          0          0          0  IRQ work interrupts
IPI6:          0          0          0          0  completion interrupts
Err:          0

看起来不错,SPI 中断的处理,已经散落到各个 CPU 核上了。如果…,但世界上没有如果,由于 GIC 芯片设计的缺陷,当配置多个 CPU 接收中断的时候,所有这些 CPU 核在中断发生时,都来争抢处理进入的中断,当然最终只会有一个CPU获得胜利,其它的核白白的在那里浪费时间。如果某核当前正在睡眠,如执行了 WFI,WFE 指令,就会造成能耗,这在很多设备是电池供电的嵌入式环境下,也是不合适的。看看 Russell King 的解释:

The behaviour of the GIC is as follows. If you set two CPUs in
GICD_ITARGETSRn, then the interrupt will be delivered to _both_ of
those CPUs. Not just one selected at random or determined by some
algorithm, but both CPUs.

Both CPUs get woken up if they're in sleep, and both CPUs attempt to
process the interrupt. One CPU will win the lock, while the other CPU
spins waiting for the lock to process the interrupt.

The winning CPU will process the interrupt, clear it on the device,
release the lock and acknowledge it at the GIC CPU interface.

The CPU that lost the previous race can now proceed to process the
very same interrupt, discovers that it's no longer pending on the
device, and signals IRQ_NONE as it appears to be a spurious interrupt.

The result is that the losing CPU ends up wasting CPU cycles, and
if the losing CPU was in a low power idle state, needlessly wakes up
to process this interrupt.

If you have more CPUs involved, you have more CPUs wasting CPU cycles,
being woken up wasting power - not just occasionally, but almost every
single interrupt that is raised from a device in the system.

On architectures such as x86, the PICs distribute the interrupts in
hardware amongst the CPUs. So if a single interrupt is set to be sent
to multiple CPUs, only _one_ of the CPUs is actually interrupted.
Hence, x86 can have multiple CPUs selected as a destination, and
the hardware delivers the interrupt across all CPUs.

On ARM, we don't have that. We have a thundering herd of CPUs if we
set more than one CPU to process the interrupt, which is grossly
inefficient.

原文见此处。

5.2 用当前 CPU ID 作为 gic_cpu_map[] 索引

对 GIC 驱动的 gic_set_affinity() 接口做如下修改:

- if (!force)
-	cpu = cpumask_any_and(mask_val, cpu_online_mask);
- else
-	cpu = cpumask_first(mask_val);
+ cpu = smp_processor_id();

当前 CPU ID 作为数组 gic_cpu_map[] 的索引,这样 SPI 中断不会集中到 CPU0 上。看一下实际效果:

$ cat /proc/interrupts 
           CPU0       CPU1       CPU2       CPU3       
 16:       7626       7583       7531       7495     GIC-0  29 Level     twd
 17:          6          0          0          0     GIC-0  34 Level     timer
 27:          0          0          0          0     GIC-0  92 Level     arm-pmu
 28:          0          0          0          0     GIC-0  93 Level     arm-pmu
 29:          0          0          0          0     GIC-0  94 Level     arm-pmu
 30:          0          0          0          0     GIC-0  95 Level     arm-pmu
 34:          0          0          0       2912     GIC-0  41 Level     mmci-pl18x (cmd)
 35:          0          0          0     204459     GIC-0  42 Level     mmci-pl18x (pio)
 36:          0          0          0          8     GIC-0  44 Level     kmi-pl050
 37:          0          0          0        100     GIC-0  45 Level     kmi-pl050
 38:         23          0          0          0     GIC-0  37 Level     uart-pl011
 44:          0          0          0          0     GIC-0  36 Level     rtc-pl031
IPI0:          0          1          1          1  CPU wakeup interrupts
IPI1:          0          0          0          0  Timer broadcast interrupts
IPI2:       1423       1288       1527        360  Rescheduling interrupts
IPI3:          1          2          2          1  Function call interrupts
IPI4:          0          0          0          0  CPU stop interrupts
IPI5:          0          0          0          0  IRQ work interrupts
IPI6:          0          0          0          0  completion interrupts
Err:          0

SPI 中断集中在 CPU0,CPU3 两个核上处理,但至少不会集中在同一个核上了。一个改进思路是记录每个核上当前转发的 SPI 个数,然后将它们均匀的散落到各个核上,这其实有点类似于我们后面即将提到的 irqbalance 了。但这并不值得提倡,因为这意味将机制实现在内核中,限制了用户空间的灵活性,内核应该只提供策略,而实现留给用户空间。

6. 一个解决方案:irqbalance

使用 irqbalance 是一个常见的解决方案,它的实现时监控 CPU 上的中断处理状况,通过 /proc/irq/N/smp_affinity/proc/irq/N/smp_affinity_list 对中断进行显式配置,让它们均匀的散落到各个 CPU 上去。
irqbalance 是完美的吗?由于可能会经常的改变中断转发的 CPU,所以也会影响到数据访问的本地 cache 命中率,在网络场景下,可能对性能造成损害。世界就是这样,总是这么复杂,没有什么是完美无缺的。

7. 参考资料

《IHI0048A_gic_architecture_spec_v1_0.pdf》
《DDI0471A_gic400_r0p0_trm.pdf》

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

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

相关文章

【微信小程序】-- 全局配置 -- window - 下拉刷新 上拉触底(十六)

&#x1f48c; 所属专栏&#xff1a;【微信小程序开发教程】 &#x1f600; 作  者&#xff1a;我是夜阑的狗&#x1f436; &#x1f680; 个人简介&#xff1a;一个正在努力学技术的CV工程师&#xff0c;专注基础和实战分享 &#xff0c;欢迎咨询&#xff01; &…

模型实战(6)之Alex实现图像分类:模型原理+训练+预测(详细教程!)

Alex实现图像分类:模型原理+训练+预测 图像分类或者检索任务在浏览器中的搜索操作、爬虫搜图中应用较广,本文主要通过Alex模型实现猫狗分类,并且将可以复用的开源模型在文章中给出!!!数据集可以由此下载:Data本文将从以下内容做出讲述: 1.模型简介及环境搭建2.数据集准…

微纳制造期末复习

章节分为六个模块&#xff1a; 1. 微纳制造中的材料 2.光刻技术 3.微纳制造中使用的技术 4.刻蚀 5.沉积 7.微纳刻蚀 根据导电性区分材料&#xff1a;导体&#xff0c;半导体&#xff0c;绝缘体 晶体&#xff1a;长程有序 多晶&#xff1a;短程有序 非晶&#xff1a;无序…

八 SpringMVC【拦截器】登录验证

目录&#x1f6a9;一 SpringMVC拦截器✅ 1.配置文件✅2.登录验证代码&#xff08;HandlerInterceptor&#xff09;✅3.继承HandlerInterceptorAdapter&#xff08;不建议使用&#xff09;✅4.登录页面jsp✅5.主页面&#xff08;操作页面&#xff09;✅6.crud用户在访问页面时 只…

【算法】PatchMatch立体匹配算法_原理解析

目录 前言 原理解析 1.倾斜支持窗口&#xff08;Slanted Support Windows&#xff09; 什么是视差平面&#xff1f; 为什么视差和像素坐标点之间的关系可以解释为平面方程&#xff1f; 视差平面的通用参数方程和点加法向量方程 什么是倾斜支持窗口&#xff1f; 2.基于倾…

宏基因组鉴定病毒流程中需要的生物信息工具

谷禾健康 许多流行病的爆发都是病毒引起的&#xff0c;面对新的传染性基因组出现的最佳策略是及时识别&#xff0c;以便于在感染开始时立即实施相应措施。 目前可用的诊断测试仅限于检测新的病理因子。适用于同时检测存在的任何病原体的高通量方法可能比使用基于当前方法的大量…

Mel Spectrogram

参考链接&#xff1a; Short-time Fourier transform - MATLAB stft- MathWorks 中国 https://medium.com/analytics-vidhya/understanding-the-mel-spectrogram-fca2afa2ce53 a spectrogram as a bunch of FFTs stacked on top of each other. 给出hop length后&#xff0…

TMP耗时较高的优化问题

1&#xff09;TMP耗时较高的优化问题 ​2&#xff09;Unity重载Object后&#xff0c;如何判定物体是否为空 3&#xff09;SRP Batch在添加unity_SpecCube后的问题 4&#xff09;堆内存会持续上升&#xff0c;如何用UWA报告来分析 这是第326篇UWA技术知识分享的推送&#xff0c;…

大话数据结构-栈

1 概述 栈&#xff08;Stack&#xff09;是限定仅在表尾进行插入和删除操作的线性表。 允许插入和删除的一端称为栈顶&#xff08;top&#xff09;&#xff0c;另一端称为栈底&#xff08;bottom&#xff09;&#xff0c;不含任何数据元素的栈称为空栈&#xff0c;栈又称为后进…

界面控件DevExpress WinForm——轻松构建类Visual Studio UI(三)

DevExpress WinForm拥有180组件和UI库&#xff0c;能为Windows Forms平台创建具有影响力的业务解决方案。DevExpress WinForm能完美构建流畅、美观且易于使用的应用程序&#xff0c;无论是Office风格的界面&#xff0c;还是分析处理大批量的业务数据&#xff0c;它都能轻松胜任…

MyBatisPlus Study Notes

文章目录1 MyBatisPlus概述1.1 MyBatis介绍1.2 MyBatisPlus特性2 标准数据层开发2.1 MyBatisPlus的CRUD操作API2.2 分页功能接口实现2.2.1 config&#xff08;配置层&#xff09;拦截器实现2.2.2 Dao(Mapper)数据访问层&#xff08;CRUD&#xff09;操作2.2.3 Junit单元测试进行…

新版本GPU加速的tensorflow库的配置方法

本文介绍在Anaconda环境中&#xff0c;配置可以用GPU运行的Python新版tensorflow库的方法。 在上一篇文章Anaconda配置Python新版本tensorflow库&#xff08;CPU、GPU通用&#xff09;的方法&#xff08;https://blog.csdn.net/zhebushibiaoshifu/article/details/129285815&am…

【分布式】10张图带你彻底搞懂限流、熔断、服务降级

文章目录1 限流1.1 限流指标1.1.1 TPS1.1.2 HPS1.1.3 QPS1.2 限流方法1.2.1 流量计数器1.2.2 滑动时间窗口1.2.3 漏桶算法1.2.4 令牌桶算法1.2.5 分布式限流1.2.6 hystrix限流1.2.6.1 信号量限流1.2.6.2 线程池限流2 熔断2.1 断路器的状态2.2 需要考虑的问题2.3 使用场景3 服务…

游戏开发是个“坑”,而且是个“天坑”

本文首发于CSDN公众号 作者 | 开发游戏的老王 责编 | 梦依丹 出品 | CSDN&#xff08;ID&#xff1a;CSDNnews&#xff09; 各位游戏开发者大家好&#xff0c;我是开发游戏的老王&#xff0c;一名游戏开发者同时也是一名高校游戏方向的主讲教师&#xff0c;从事游戏开发及相关教…

HTTP缓存从入门到踹门

1 与缓存相关的字段Expires&#xff1a;缓存的绝对过期时间Cache-Control&#xff1a;缓存的相对过期时间Last-Modified&#xff1a;缓存上一次修改的时间&#xff08;服务端保存&#xff09;If-Modified-Since&#xff1a;缓存上一次修改的时间&#xff08;客户端保存&#xf…

第十一届蓝桥杯省赛——2解密

题目&#xff1a;【问题描述】小明设计了一种文章加密的方法&#xff1a;对于每个字母 c&#xff0c;将它变成某个另外的字符 Tc。下表给出了字符变换的规则&#xff1a;字母cTc字母cTc字母cTc字母cTcaynlAYNLbxogBXOGcmpoCMPOddquDDQUearfEARFfcssFCSSgitzGITZhkupHKUPinvwINV…

【ArcGIS Pro二次开发】(11):面要素的一键拓扑

在工作中&#xff0c;经常需要对要素进行拓扑检查。 在ArcGIS Pro中正常的工作流程是在数据库中【新建要素数据集——新建拓扑——将要素加入拓扑——添加规则——验证】&#xff0c;工作流程不算短&#xff0c;操作起来比较繁琐。 下面以一个例子演示如何在ArcGIS Pro SDK二次…

数组一次性删除多条数据

需求描述 最后提交时删除表格中的空行 实现方法 单行删除 - 并不是一次性删除 表格每行的最后设置删除按钮&#xff0c;点击时将当前行的索引传递给方法&#xff0c;splice 删除当前行。 <el-table :data"tableData" class"myTable" border>..…

爬虫实战进阶版【1】——某眼专业版实时票房接口破解

某眼专业版-实时票房接口破解 某眼票房接口:https://piaofang.maoyan.com/dashboard-ajax 前言 当我们想根据某眼的接口获取票房信息的时候,发现它的接口处的参数是加密的,如下图: 红色框框的参数都是动态变化的,且signKey明显是加密的一个参数。对于这种加密的参数,我们需要…

第14届蓝桥杯STEMA测评真题剖析-2023年2月12日Scratch编程初中级组

[导读]&#xff1a;超平老师的《Scratch蓝桥杯真题解析100讲》已经全部完成&#xff0c;后续会不定期解读蓝桥杯真题&#xff0c;这是Scratch蓝桥杯真题解析第103讲。 蓝桥杯选拔赛现已更名为STEMA&#xff0c;即STEM 能力测试&#xff0c;是蓝桥杯大赛组委会与美国普林斯顿多…