Linux: 中断只被GIC转发到CPU0问题分析
文章目录
- 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-a9
,rootfs
基于 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 芯片内部包含两大功能模块:Distributor
和 CPU interface
。Distributor
用于将外设投递的中断信号转发给 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核。对于 SGI
和 PPI
中断号区间对应的寄存器,它们是每个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 处理。SGI
和 PPI
中断不在我们考虑范围之内,因为它们本来就是特定于 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: 中断只被GIC转发到CPU0问题分析
文章目录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.…...
模电学习10. MOS管简单应用电路
模电学习10. MOS管简单使应用电路一、开关和放大器1. 开关电路2. 放大电路二、时序电路中作为反相器使用三、双向电平转换电路1. 原理图2. 工作状态分析(1)分析SDA,信号从左向右(2)分析SDA,信号从右向左四、…...
轻松搞懂Linux中的用户管理
文章目录概念用户账户用户组用户权限用户管理工具概念 用户管理是Linux系统管理员必须掌握的重要技能之一。Linux系统是一个多用户操作系统,可以支持多个用户同时使用,每个用户拥有自己的账户和权限,因此管理员需要了解如何创建、管理和删除…...
力扣-丢失信息的雇员
大家好,我是空空star,本篇带大家了解一道简单的力扣sql练习题。 文章目录前言一、题目:1965. 丢失信息的雇员二、解题1.正确示范①提交SQL运行结果2.正确示范②提交SQL运行结果3.正确示范③提交SQL运行结果4.正确示范④提交SQL运行结果5.其他…...
FPGA采集AD7606全网最细讲解 提供串行和并行2套工程源码和技术支持
目录1、前言2、AD7606数据手册解读输入信号采集范围输出模式选择过采样率设置3、AD7606串行输出采集4、AD7606并行输出采集5、vivado仿真6、上板调试验证7、福利:工程代码的获取1、前言 AD7606是一款非常受欢迎的AD芯片,因为他支持8通道同时采集数据&am…...
CSS介绍
文章目录一. CSS介绍二. CSS的引入方式三. CSS选择器一. CSS介绍 定义: 层叠样式表作用: 美化界面: 设置标签文字大小,颜色,字体加粗等样式控制页面布局: 设置浮动,定位等样式 基本语法: 选择器{样式规则 } 样式规则: 属性名1: 属性值1 属性名2: 属性值2 属性名3: 属性值3 ..…...
Auto-encoder 系列
Auto-Encoder (AE)Auto-encoder概念自编码器要做的事:将高维的信息通过encoder压缩到一个低维的code内,然后再使用decoder对其进行重建。“自”不是自动,而是自己训练[1]。PCA要做的事其实与AE一样,只是没有神经网络。对于一个输入…...
【蓝桥杯入门不入土】变幻莫测的链表
文章目录一:链表的类型单链表双链表循环链表二:链表的存储方式三:链表的定义删除节点添加节点四:实战练习1.设计链表2. 移除链表元素最后说一句一:链表的类型 单链表 什么是链表,链表是一种通过指针串联在…...
axios的二次封装
方式一:将axios单独分装到某个配置文件中import axios from axios; const axiosApi axios.create({baseURL:http://127.0.0.1:3000,timeout:3000 }) export default axiosApi在组件中使用:import $http from axios配置文件的地址 $http.get(/student/test).then(re…...
GET与POST区别(最详细)
相同点:本质上都是TCP连接。 不同点:由于HTTP规定和服务器/浏览器限制,在应用过程中区别如下: 1.get产生一个TCP数据包,post 产生两个TCP数据包 get请求,浏览器会把http header和data一起发送,…...
精选博客系列|将基于决策树的Ensemble方法用于边缘计算
在即将到来的边缘计算时代,越来越需要边缘设备执行本地快速训练和分类的能力。事实上,无论是手机上的健康应用程序、冰箱上的传感器还是扫地机器人上的摄像头,由于许多原因,例如需要快速响应时间、增强安全性、数据隐私࿰…...
JS混淆加密:Eval的未公开用法
JavaScript奇技淫巧:Eval的未公开用法 作者:http://JShaman.com w2sft,转载请保留此信息很多人都知道,Eval是用来执行JS代码的,可以执行运算、可以输出结果。 但它还有一种未公开的用途,想必很少有人用过。…...
π型滤波器 计算_π型滤波电路
滤波器在功率和音频电子中常用于滤除不必要的频率。而电路设计中,基于不同应用有着许多不同种类的滤波器,但它们的基本理念都是一致的,那就是移除不必要的信号。所有滤波器都可以被分为两类,有源滤波器和无源滤波器。有源滤波器用…...
大数据常见术语
大数据常见术语一览 主要内容包含以下(收藏,转发给你身边的朋友) 雪花模型、星型模型和星座模型 事实表 维度表 上钻与下钻 维度退化 数据湖 UV与PV 画像 ETL 机器学习 大数据杀熟 SKU与SPU 即席查询 数据湖 数据中台 ODS,DWD&…...
带你了解“函数递归”
目录 1. 什么是递归? 2. 函数递归的必要条件 2.1 接收一个整型值(无符号),按照顺序打印它的每一位。 代码如下: 2.2 编写一个函数,不用临时变量求字符串长度 代码如下: 2.3 递归与迭代 …...
网络资源面经2
文章目录Kafka 原理,数据怎么平分到消费者生产者分区消费者分区Flume HDFS Sink 小文件处理Flink 与 Spark Streaming 的差异,具体效果Spark 背压机制具体实现原理Yarn 调度策略Spark Streaming消费方式及区别Zookeeper 怎么避免脑裂,什么是脑…...
4年经验来面试20K的测试岗,一问三不知,我还真不如去招应届生。
公司前段缺人,也面了不少测试,结果竟然没有一个合适的。一开始瞄准的就是中级的水准,也没指望来大牛,提供的薪资在10-20k,面试的人很多,但平均水平很让人失望。看简历很多都是4年工作经验,但面试…...
K8S搭建NACOS集群踩坑问题
一、NACOS容器启动成功无法访问现象描述:通过K8S的statefulset启动,通过NodePort暴露不能在外网访问,只能在MASTER主节点访问。yaml配置:apiVersion: apps/v1 kind: StatefulSet metadata:name: nacos-${parameters.nameSpace}-dm…...
怎么避免计算机SCI论文的重复率过高? - 易智编译EaseEditing
论文成稿前 在撰写阶段就避免重复:在撰写阶段就避免文章中的重复内容,可以减少后期修改的工作量。 在写作前,可以制定良好的计划和大纲,规划好文章的结构和内容,从而减少重复内容。 加强对相关文献的阅读 为了避免自己…...
uni-app路由拦截
新建一个auth.js /** * description 权限存储函数 */ const authorizationKey Authorization export function getAuthorization() { return uni.getStorageSync(authorizationKey) } export function setAuthorization(authorization) { return uni.setStorageSync(aut…...
如何使用固态继电器实现更高可靠性的隔离和更小的解决方案尺寸
自晶体管发明之前,继电器就已被用作开关。从低压信号安全控制高压系统的能力,如隔离电阻监控,对于许多汽车系统的开发是必要的。虽然机电继电器和接触器的技术多年来有所改进,但设计人员要实现其终身可靠性和快速开关速度以及低噪…...
【YOLOv8/YOLOv7/YOLOv5系列算法改进NO.56】引入Contextual Transformer模块(sci期刊创新点之一)
文章目录前言一、解决问题二、基本原理三、添加方法四、总结前言 作为当前先进的深度学习目标检测算法YOLOv8,已经集合了大量的trick,但是还是有提高和改进的空间,针对具体应用场景下的检测难点,可以不同的改进方法。此后的系列…...
深圳大学计软《面向对象的程序设计》实验3 指针2
A. 月份查询(指针数组) 题目描述 已知每个月份的英文单词如下,要求创建一个指针数组,数组中的每个指针指向一个月份的英文字符串,要求根据输入的月份数字输出相应的英文单词 1月 January 2月 February 3月 March …...
【基于机器学习的推荐系统项目实战-2】项目介绍与技术选型
本节目录一、项目介绍1.1 采用的数据源1.2 Concrec架构技术选型1.3 Sprak介绍1.4 Flink1.5 TensorFlow一、项目介绍 1.1 采用的数据源 Kaggle Anime Recommendations Dataset。 其中的动漫数据源自myanimelist.net。 1.2 Concrec架构技术选型 数据预处理模块:汇总…...
对称锥规划:锥与对称锥
文章目录对称锥规划:锥与对称锥锥的几何形状常用的指向锥Nonnegative Orthant二阶锥半定锥对称锥对称锥的平方操作对称锥的谱分解对称锥的自身对偶性二阶锥规划SOCP参考文献对称锥规划:锥与对称锥 本文主要讲锥与对称锥的一些基本概念。 基础预备&…...
4.基于Label studio的训练数据标注指南:情感分析任务观点词抽取、属性抽取
情感分析任务Label Studio使用指南 1.基于Label studio的训练数据标注指南:信息抽取(实体关系抽取)、文本分类等 2.基于Label studio的训练数据标注指南:(智能文档)文档抽取任务、PDF、表格、图片抽取标注等…...
算法拾遗二十五之暴力递归到动态规划五
算法拾遗二十七之暴力递归到动态规划七题目一【数组累加和最小的】题目二什么暴力递归可以继续优化暴力递归和动态规划的关系面试题和动态规划的关系如何找到某个问题的动态规划方式面试中设计暴力递归的原则知道了暴力递归的原则 然后设计常见的四种尝试模型如何分析有没有重复…...
Linux进程的创建结束类系统调用总结
tags: Linux OS Syscall C 写在前面 总结一下Linux系统的进程创建/终止/等待等系统调用, 参考: Linux/Unix系统编程手册. 下面主要给出例子, 关于函数原型可以参考书中或者man 2 syscall(例如man 2 fork). 测试环境: Ubuntu 20.04 x86_64 gcc-9 进程创建: fork() 用于创建…...
Git分支的合并策略有哪些?Merge和Rebase有什么区别?关于Merge和Rebase的使用建议
Git分支的合并策略有哪些?Merge和Rebase有什么区别?关于Merge和Rebase的使用建议1. 关于Git的一些基本原理1.1 Git的工作流程原理2. Git的分支合并方式浅析2.1 分支是什么2.2 分支的合并策略2.2.1 Three-way-merge(三向合并原理)2…...
2022-2-23作业
一、通过操作Cortex-A7核,串口输入相应的命令,控制LED灯进行工作 1.例如在串口输入led1on,开饭led1灯点亮 2.例如在串口输入led1off,开饭led1灯熄灭 3.例如在串口输入led2on,开饭led2灯点亮 4.例如在串口输入led2off,开饭led2灯熄灭 5.例如在串口输…...
wordpress框架文件上传/台州百度快照优化公司
1 介绍 sentinal,中文名是哨兵 哨兵是redis集群架构中非常重要的一个组件,主要功能如下: (1)集群监控,负责监控redis master和slave进程是否正常工作 (2)消息通知,如果…...
设计软件免费下载网站/网红推广
简单版:7-9 特殊a串数列求和 与该题差别:该题非负整数N,简单版是正整数,该题是0-100000,简单版是0-9 知识点 N(0≤N≤100000) 结果是会大于int类型的 因为要防止数据溢出,所以都要要用数组来保存数字的位…...
重庆 机械有限公司 江北网站建设/企业网站的主要类型有
对于淘宝店铺来说,淘宝客服是非常重要的岗位,他们每天都需要处理很多的问题,接待很多的消费者,对于商家来说,你知道自己的店铺如何去添加客服吗?接下来的内容进行相关介绍。 一、淘宝店如何添加客服? 商家…...
wordpress宠物模板/百度引流推广哪家好
如果不换罗米,如果门将没受伤,如果梅西能够上场,如果索林守住位置,如果....没有那么多如果....足球就是足球,Argentina,dont cry for me....走好...我的世界杯结束了 转载于:https://www.cnblogs.com/maxwolf/archive/2006/07/02/440646.html...
Wordpress 学校网站/个人优秀网页设计
液晶电视大家都是熟悉的,对于液晶电视闪屏这个故障,相信大家或多或少,听说过,甚至见过。那么,液晶电视闪屏是什么原因呢?该如何检修呢?一起来了解一下。一、液晶电视闪屏的原因闪屏是指显示器在…...
网站服务器维护内容/如何做企业产品推广
Android群英传笔记——第四章:ListView使用技巧 最近也是比较迷茫,但是有一点点还是要坚持的,就是学习了,最近离职了,今天也是继续温习第四章ListView,也拖了其实也挺久的了,listview可谓是老牌…...