当前位置: 首页 > news >正文

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

文章目录

  • 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/interruptsCPU0       CPU1       CPU2       CPU316:          0          0          0          0     GICv2  25 Level     vgic17:          0          0          0          0     GICv2  50 Level     /soc/timer@01c20c0018:          0          0          0          0     GICv2  29 Level     arch_timer19:       6119       3595       3947       3046     GICv2  30 Level     arch_timer20:          0          0          0          0     GICv2  27 Level     kvm guest timer22:          0          0          0          0     GICv2 120 Level     1ee0000.hdmi, dw-hdmi-cec24:          0          0          0          0     GICv2 118 Level     1c0c000.lcd-controller25:          0          0          0          0     GICv2  82 Level     1c02000.dma-controller26:         24          0          0          0     GICv2  92 Level     sunxi-mmc27:         25          0          0          0     GICv2  93 Level     sunxi-mmc28:       2697          0          0          0     GICv2  94 Level     sunxi-mmc29:          0          0          0          0     GICv2 103 Level     musb-hdrc.4.auto30:          0          0          0          0     GICv2 104 Level     ehci_hcd:usb131:          0          0          0          0     GICv2 105 Level     ohci_hcd:usb232:          0          0          0          0     GICv2 106 Level     ehci_hcd:usb333:          0          0          0          0     GICv2 107 Level     ohci_hcd:usb634:          0          0          0          0     GICv2 108 Level     ehci_hcd:usb435:          0          0          0          0     GICv2 109 Level     ohci_hcd:usb736:          0          0          0          0     GICv2 110 Level     ehci_hcd:usb537:          0          0          0          0     GICv2 111 Level     ohci_hcd:usb840:        205          0          0          0     GICv2  63 Level     1c25000.ths42:       1802          0          0          0     GICv2 114 Level     eth043:          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     twd17:          6          0          0          0     GIC-0  34 Level     timer27:          0          0          0          0     GIC-0  92 Level     arm-pmu28:          0          0          0          0     GIC-0  93 Level     arm-pmu29:          0          0          0          0     GIC-0  94 Level     arm-pmu30:          0          0          0          0     GIC-0  95 Level     arm-pmu34:       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-pl05037:        100          0          0          0     GIC-0  45 Level     kmi-pl05038:         23          0          0          0     GIC-0  37 Level     uart-pl01144:          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 bythe GIC. This field stores the list of processors that the interrupt is sent to if it isasserted.

在这里插入图片描述
准确来讲,这是一个寄存器集,可以按字节访问,每个字节对应一个中断号的 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);elsecpu = 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     twd17:          6          0          0          0     GIC-0  34 Level     timer27:          0          0          0          0     GIC-0  92 Level     arm-pmu28:          0          0          0          0     GIC-0  93 Level     arm-pmu29:          0          0          0          0     GIC-0  94 Level     arm-pmu30:          0          0          0          0     GIC-0  95 Level     arm-pmu34:       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-pl05037:          0          0         99          1     GIC-0  45 Level     kmi-pl05038:         18          3          0          2     GIC-0  37 Level     uart-pl01144:          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     twd17:          6          0          0          0     GIC-0  34 Level     timer27:          0          0          0          0     GIC-0  92 Level     arm-pmu28:          0          0          0          0     GIC-0  93 Level     arm-pmu29:          0          0          0          0     GIC-0  94 Level     arm-pmu30:          0          0          0          0     GIC-0  95 Level     arm-pmu34:          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-pl05037:          0          0          0        100     GIC-0  45 Level     kmi-pl05038:         23          0          0          0     GIC-0  37 Level     uart-pl01144:          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》

相关文章:

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

文章目录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初始化&#xff1a;缺省的CPU亲和性4.2.2.1 boot CPU亲和性初始化流程4.2.2.1 其它非 boot CPU亲和性初始化流程5.…...

测试软件5

一 css基础 css定义&#xff1a;可以设置网页中的样式&#xff0c;外观&#xff0c;美化 css中文名字&#xff1a;级联样式表&#xff0c;层叠样式表&#xff0c;样式表 二 css基础语法 1.style标签写在title标签后面 2.选择器{属性名1&#xff1a;属性值1&#xff1b;属性名…...

前端JS内存管理

JS内存管理 内存原理&#xff1a; 任何变成语言在执行的时候都需要操作系统来分配内存&#xff0c;只是有些语言需要手动管理分配的内存有些语言有专门来管理内存的方式 如 JVM 了解以上的概念之后&#xff0c;我们再来了解一下大致的内存周期 分配需要的内存使用内存在不使用…...

第七章.集成学习(Ensemble Learning)—袋装(bagging),随机森林(Random Forest)

第七章.集成学习 (Ensemble Learning) 7.1 集成学习—袋装(bagging),随机森林(Random Forest) 集成学习就是组合多个学习器&#xff0c;最后得到一个更好的学习器。 1.常见的4种集成学习算法 个体学习器之间不存在强依赖关系&#xff0c;袋装&#xff08;bagging&#xff09;…...

Java_面向对象

Java_面向对象 1.面向对象概述 ​ 面向对象是一种符合人类思想习惯的编程思想。显示生活中存在各种形态的不同事物&#xff0c;这些食物存在着各种各样的联系。在程序中使用对象来映射现实中的事物&#xff0c;使用对象的关系来描述事物之间的关系&#xff0c;这种思想就是面…...

【IoT】智能烟雾报警器

设计简介 硬件设计由AT89C51单片机、DS18B20温度传感器、4位共阳数码管、电源模块、报警模块、按键模块、MQ-2烟雾检测模块和ADC0832模数转换模块组成。 烟雾传感器MQ-2检测空气中的烟雾气体&#xff0c;通过ADC0832进行数据转换&#xff0c;经过单片机的运算处理后在数码管上…...

Python实现定时执行脚本(5)

前言 本文是该专栏的第17篇,后面会持续分享python的各种干货知识,值得关注。 笔者在前面有详细介绍过几种使用python实现定时执行任务的方法,可以说都是简单易上手的那种。而本文,再来详细介绍另外一种定时方法,那就是利用任务框架APScheduler(advanceded python schedu…...

JavaSe第4次笔记

1.转义字符和编程语言无关。 2.斜杠(\)需要转义&#xff0c;反斜杠(/)不需要转义。 3.不能做switch的参数的数据类型&#xff1a;long float double boolean( String可以)。 4.输入的写法&#xff1a;Scanner(回车自动带头文件(import java.util.Scanner;)) Scanner scan …...

epoll机制

预备知识 文件描述符file descriptor 文件描述符是Linux系统中对文件、套接字等I/O资源的抽象&#xff0c;每个打开的文件或套接字都有一个唯一的文件描述符。应用程序可以使用文件描述符来读写文件或进行网络通信。 epoll允许程序监控多个文件描述符&#xff0c;以便在这些…...

Java使用不同方式获取两个集合List的交集、补集、并集(相加)、差集(相减)

1 明确概念首先知道几个单词的意思&#xff1a;并集 union交集 intersection补集 complement析取 disjunction减去 subtract1.1 并集对于两个给定集合A、B&#xff0c;由两个集合所有元素构成的集合&#xff0c;叫做A和B的并集。记作&#xff1a;AUB 读作“A并B”例&#…...

【Android笔记80】Android之Retrofit适配器和文件上传下载

这篇文章,主要介绍Android之Retrofit适配器和文件上传下载。 目录 一、Retrofit适配器 1.1、Retrofit适配器 (1)引入RxJava依赖 (2)定义接口...

Nodejs模块化

1.模块化 1.1.模块化的基本概念 模块化是指解决一个复杂问题时&#xff0c;自顶向下逐层把系统划分为若干模块的过程。对于整个系统而言&#xff0c;模块是可组合、分解和更换的单元。 1.2 编程中的模块化 编程领域的模块化就是把一个大文件拆成独立并相互依赖的多个小模块…...

C++STL基础

STL基础 诞生 cpp的面向对象和泛型编程的思想本质就是提高复用性诞生了STL库 基本概念 STL标准模板库STL从广义上分为容器、算法及迭代器容器和算法之间通过迭代器进行连接STL几乎所有的代码都采用了模板类或者模板函数 基本组件 容器、算法、迭代器、仿函数、适配器、空间配置…...

数学建模经验【更新中】

数学建模简单入门 一、 分工 3人&#xff0c;1人论文&#xff0c;1人代码主力&#xff0c;1人论文代码&#xff08;前一半时间主代码&#xff0c;后一半时间主论文&#xff09; Tips: 不养闲人&#xff0c;论文必须要在对代码和题目极其了解并且能跟上队友思路的情况下才能写…...

【python学习笔记】:Excel 数据的封装函数

对比其它编程语言&#xff0c;我们都知道Python最大的优势是代码简单&#xff0c;有丰富的第三方开源库供开发者使用。伴随着近几年数据分析的热度&#xff0c;Python也成为最受欢迎的编程语言之一。而对于数据的读取和存储&#xff0c;对于普通人来讲&#xff0c;除了数据库之…...

如何获取或设置CANoe以太网网卡信息(GET篇)

CAPL提供了一系列函数用来操作CANoe网卡。但是,但是,首先需要明确一点,不管是获取网卡信息,还是设置网卡信息,只能访问CAPL程序所在的节点下的网卡,而不是节点所在的以太网通道下的所有网卡 关于第一张图中,Class节点下,有三个网卡:Ethernet1、VLAN 1.100、VLAN 1.200…...

“终于我从字节离职了...“一个年薪50W的测试工程师的自白...

我递上了我的辞职信&#xff0c;不是因为公司给的不多&#xff0c;也不是因为公司待我不好&#xff0c;但是我觉得&#xff0c;我每天看中我憔悴的面容&#xff0c;每天晚上拖着疲惫的身体躺在床上&#xff0c;我都不知道人生的意义&#xff0c;是赚钱吗&#xff1f;是为了更好…...

【Spring】八种常见Bean加载方式

&#x1f6a9;本文已收录至专栏&#xff1a;Spring家族学习 一.引入 (1) 概述 ​ 关于bean的加载方式&#xff0c;spring提供了各种各样的形式。因为spring管理bean整体上来说就是由spring维护对象的生命周期&#xff0c;所以bean的加载可以从大的方面划分成2种形式&#xff…...

第五回:样式色彩秀芳华

import matplotlib as mpl import matplotlib.pyplot as plt import numpy as np第五回详细介绍matplotlib中样式和颜色的使用&#xff0c;绘图样式和颜色是丰富可视化图表的重要手段&#xff0c;因此熟练掌握本章可以让可视化图表变得更美观&#xff0c;突出重点和凸显艺术性。…...

关于@Test单元测试

1、关于doReturndoReturn(new Test()).when(testService).updateStatusByLock(any(), any());在单元测试里这个方法可以执行到这里之间跳过不去执行&#xff0c;返回你想要的返回值2、关于givengiven(user.getName(any())).willReturn("张三");在单元测试里这个方法 …...

【项目实战】WebFlux整合r2dbc-mysql实战

一、背景 Webflux虽然是响应式的,但是没办法,JDBC是基于阻塞IO实现的,所以无法真正的威力发挥不出来。 但是,Webflux一旦整合了R2DBC之后,那么它将不再受限于数据库连接了,真正打通了响应式应用的任督二脉,性能才被释放。 当然,除了Spring推出的R2DBC协议,还有Orac…...

go版本分布式锁redsync使用教程

redsync使用教程前言redsync结构Pool结构Mutex结构acquire加锁操作release解锁操作redsync包的使用前言 在编程语言中锁可以理解为一个变量&#xff0c;该变量在同一时刻只能有一个线程拥有&#xff0c;以便保护共享数据在同一时刻只有一个线程去操作。对于高可用的分布式锁应…...

大数据之Hudi数据湖_大数据治理_简介_发展历史_特性_应用场景---大数据之Hudi数据湖工作笔记0001

支持hive spark flink 美国公司开发的~ 都在使用,这些企业都在用 支持hadoop的,更新,插入,删除 和数据增量处理 支持流式数据处理. hive是离线数仓 hive不支持事物 insert overwrite 底层后来通过这种方式支持了事物 insert overwrite处理数据很低效,因为更新是基于覆盖实现…...

射频功率放大器基于纵向导波的杆状构件腐蚀诊断方法的研究

实验名称&#xff1a;基于纵向导波的杆状构件腐蚀诊断方法研究方向&#xff1a;无损探伤测试设备&#xff1a;信号号发生器、安泰ATA-8202功率放大器、数据采集卡、直流电源、超声探头、钢杆、前置放大器。实验过程&#xff1a;图&#xff1a;试验装置试验装置如图3.2所示。监测…...

Leedcode 二分查找 理解1

一个up的理解 一、二分查找基础例题 力扣https://leetcode.cn/problems/binary-search/ 二、二分查找模板问题 带搜索区间分为3个部分&#xff1a; 1、[mid]&#xff0c;直接返回 2、[left&#xff0c;mid-1]&#xff0c;设置边界right mid - 1 3、[mid1,right]&#x…...

【告别篇】大家好,再见了,我转行了,在筹备创业

前言 相信大家也一直看到我的博客没有更新过了&#xff0c;我其实很久没有打开过博客了&#xff0c;也就意味着我很长一段时间都在停滞不前&#xff0c;没有了学习的动力。 现在我上来是想跟大家告个别 &#xff1a; 很多粉丝宝宝的私信我看了&#xff0c;但是没有回&#xf…...

Java——岛屿数量

题目链接 leetcode在线oj题——岛屿数量 题目描述 给你一个由 ‘1’&#xff08;陆地&#xff09;和 ‘0’&#xff08;水&#xff09;组成的的二维网格&#xff0c;请你计算网格中岛屿的数量。 岛屿总是被水包围&#xff0c;并且每座岛屿只能由水平方向和/或竖直方向上相…...

《代码整洁之道》笔记

1章&#xff1a;专业人士要有专业人士素养&#xff0c;要有责任心&#xff0c;编写代码尽可能完善没有bug&#xff0c;有bug也要勇于承担。坚持学习&#xff0c;坚持练习&#xff0c;保证自己的专业技能。谦虚&#xff0c;相互学习&#xff0c;与顾客达成一致2章&#xff1a;说…...

个人网站如何集成QQ快捷登录功能?

目录 一、网站集成QQ快捷登录的好处 二、网站接入QQ快捷登录具体步骤 &#xff08;1&#xff09;登录到QQ互联官网 &#xff08;2&#xff09;进行个人开发者认证 &#xff08;3&#xff09;创建网站应用 &#xff08;4&#xff09;填写网站资料 三、如何在本地开发环境…...

从工厂打螺丝到月薪18k测试工程师,我该满足吗?

以前我比较喜欢小米那句“永远相信美好的事情即将发生”&#xff0c;后来发现如果不努力不可能有美好的事情发生&#xff01;01高中毕业进厂5年&#xff0c;创业经商多次战败&#xff0c;为了生计辗转奔波高中毕业后我就进了工厂&#xff0c;第一份工作是做模具加工。从500元一…...

学校怎么做网站/今日热点新闻事件2022

Happy Chrismas&#xff01; 转载于:https://www.cnblogs.com/allenblogs/archive/2010/12/22/1913568.html...

重庆公司大学派斯学院/合肥百度网站排名优化

硬盘的特点 1.硬盘存储空间大&#xff0c;内存存储空间小 2.硬盘访问速度慢&#xff0c;内存访问速度快 3.硬盘成本低&#xff0c;内存成本高 4.硬盘上的数据断电不会丢失&#xff0c;内存数据断电会丢失&#xff08;可持久化存储&#xff09; 文件 1.狭义的文件&#xf…...

建设公司网站的细节/什么样的人适合做策划

20161215请问全志的android平台的kernel启动早期的 打印信息 全关了吗开发板&#xff1a;索智SC3817。全志R16SDK&#xff1a;android4.4.2 parrotv1.1打开的方法&#xff1a;rootrootrootroot-E400:~/wyb/bt1_parrot_v1.1_20161212/lichee/linux-3.4$ cp .config bak1_orig.co…...

汕头网站建设运营团队/网站优化服务

这篇文章主要介绍了关于mac下brew安装php及扩展&#xff0c;有着一定的参考价值&#xff0c;现在分享给大家&#xff0c;有需要的朋友可以参考一下Mac HomeBrew [2018-03-31]起弃用homebrew/php&#xff0c;php版本改名(如&#xff1a;php70 > php7.0)&#xff0c;无法像以前…...

免费网站风格/外贸网站建站和推广

逆序建链表&#xff08;头插法&#xff09; //输入五个数&#xff0c;逆序建带头结点的单链表&#xff08;head&#xff09; //举个栗子&#xff1a;11 12 13 14 15 建一个如下图的带头结点的单链表&#xff08;emmmm图有点丑&#xff09; &#xff08;1&#xff09;首先定…...

wordpress商品按钮代码/广州番禺发布网

一、Lifecycle简介 Lifecycle是Android Architecture Components架构设计方案中的生命周期管理框架。也是LiveData、ViewModel搭建MVVM架构的基础&#xff0c;目前在SupportActivity&#xff08;AppCompatActivity的父类&#xff09;和Fragment中都已植入这个框架。 Lifecycle优…...