Linux:内核解压缩过程简析

文章目录

  • 1. 前言
  • 2. 背景
  • 3. zImage 的构建过程
  • 4. 内核引导过程
  • 5. 内核解压缩过程
  • 6. 内核加压缩过程小结
  • 7. 参考资料

1. 前言

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

2. 背景

本文基于 ARM32架构 + Linux 4.14内核 进行分析,且仅讨论 zImage 格式的解压缩过程。

3. zImage 的构建过程

要理解内核镜像的解压缩过程,首先要了解内核镜像的建立过程。下面来看内核镜像 zImage 的构建过程。

# arch/arm/boot/Makefile# arch/arm/boot/Image 由 内核 ELF 文件 vmlinux 通过 objcopy 生成:
#         objcopy
# vmlinux =======> arch/arm/boot/Image
$(obj)/Image: vmlinux FORCE$(call if_changed,objcopy)# arch/arm/boot/compressed/vmlinux 依赖于 arch/arm/boot/Image:
# 修改了 arch/arm/boot/Image 必须更新 arch/arm/boot/compressed/vmlinux
$(obj)/compressed/vmlinux: $(obj)/Image FORCE$(Q)$(MAKE) $(build)=$(obj)/compressed $@# arch/arm/boot/zImage 由 arch/arm/boot/compressed/vmlinux 通过 objcopy 生成:
#                                  objcopy
# arch/arm/boot/compressed/vmlinux =======> arch/arm/boot/zImage
$(obj)/zImage: $(obj)/compressed/vmlinux FORCE$(call if_changed,objcopy)
# arch/arm/boot/compressed/Makfile# 内核解压缩程序主干代码
AFLAGS_head.o += -DTEXT_OFFSET=$(TEXT_OFFSET)
HEAD = head.o
OBJS += misc.o decompress.o...# 不同的内核配置,会使用不同的压缩算法对内核进行压缩
compress-$(CONFIG_KERNEL_GZIP) = gzip
compress-$(CONFIG_KERNEL_LZO)  = lzo
compress-$(CONFIG_KERNEL_LZMA) = lzma
compress-$(CONFIG_KERNEL_XZ)   = xzkern
compress-$(CONFIG_KERNEL_LZ4)  = lz4...# 生成 解压缩程序 arch/arm/boot/compressed/vmlinux:
# {head.o,piggy.o,misc.o,decompress.o,...} ==> arch/arm/boot/compressed/vmlinux
$(obj)/vmlinux: $(obj)/vmlinux.lds $(obj)/$(HEAD) $(obj)/piggy.o \$(addprefix $(obj)/, $(OBJS)) $(lib1funcs) $(ashldi3) \$(bswapsdi2) $(efi-obj-y) FORCE@$(check_for_multiple_zreladdr)$(call if_changed,ld)@$(check_for_bad_syms)# 将内核 Image 文件压缩成 piggy_data 文件:
# arch/arm/boot/Image ==> arch/arm/boot/compressed/piggy_data
$(obj)/piggy_data: $(obj)/../Image FORCE$(call if_changed,$(compress-y))# piggy.S ==> piggy.o
# arch/arm/boot/compressed/piggy.S 包含了 piggy_data (压缩的内核镜像 Image)
$(obj)/piggy.o: $(obj)/piggy_data...
/* arch/arm/boot/compressed/piggy.S */.section .piggydata,#alloc.globl input_data
input_data:.incbin "arch/arm/boot/compressed/piggy_data" /* 被压缩后的内核 Image */.globl input_data_end
input_data_end:

$(call if_changed,objcopy) 用来调用 objcopy,简单的看下它是怎么工作的:

# scripts/Kbuild.includearg-check = $(if $(strip $(cmd_$@)),,1)make-cmd = $(call escsq,$(subst $(pound),$$(pound),$(subst $$,$$$$,$(cmd_$(1)))))any-prereq = $(filter-out $(PHONY),$?) $(filter-out $(PHONY) $(wildcard $^),$^)# 在合适的条件下,调用命令 cmd_XXX (如 cmd_objcopy)
# Execute command if command has changed or prerequisite(s) are updated.
if_changed = $(if $(strip $(any-prereq) $(arg-check)),                       \@set -e;                                                             \$(echo-cmd) $(cmd_$(1));                                             \printf '%s\n' 'cmd_$@ := $(make-cmd)' > $(dot-target).cmd, @:)
# scripts/Makefile.lib# Objcopy
# ---------------------------------------------------------------------------quiet_cmd_objcopy = OBJCOPY $@
cmd_objcopy = $(OBJCOPY) $(OBJCOPYFLAGS) $(OBJCOPYFLAGS_$(@F)) $< $@

内核代码根目录下 Makefile ,定义了 OBJCOPY

# 内核代码根目录下 MakefileOBJCOPY  = $(CROSS_COMPILE)objcopy
...

具体架构目录的 Makefile ,定义了 OBJCOPYFLAGS

# arch/arm/boot/Makefile# -O binary  : 输出文件(Image,zImage) 的 BFD 格式为 binary
# -R .comment:移除输入文件(vmlinux) 中 的 注释段
# -S         : 移除输入文件(vmlinux) 中 的 符号信息、重定义信息、调试信息
OBJCOPYFLAGS :=-O binary -R .comment -S
...

通过上面的简单分析,可以将 zImage 的构建过程总结如下图:

               编译+链接                   objcopy                      压缩(gzip,lzo,lzma,...)
1. linux源代码 ---------> vmlinux(elf文件) -------> arch/arm/boot/Image -----------------------> piggy_data编译
2. piggy.S(包含 piggy_data 压缩内核) ------> piggy.o链接                                   objcopy
3. (head.o,misc.o,decompress.o,...) + piggy.o ----> arch/arm/boot/compressed/vmlinux ------> arch/arm/boot/zImage

4. 内核引导过程

BootLoader 开始,内核的引导过程,可简单概括如下:

BootLoader -> 内核解压程序 -> 内核

在本文限定的上下文中,BootLoader 可以是 U-BOOT 等其它引导程序,内核解压程序arch/arm/boot/zImage(开头一部分),内核为 arch/arm/boot/Image

5. 内核解压缩过程

内核解压程序 的链接脚本 arch/arm/boot/compressed/vmlinux.ld.S 片段

OUTPUT_ARCH(arm)
ENTRY(_start) // 指定内核解压程序入口
SECTIONS
{.... = TEXT_START;_text = .;.text : {_start = .;*(.start) // 解压程序入口位置*(.text)*(.text.*)*(.fixup)*(.gnu.warning)*(.glue_7t)*(.glue_7)}...
}

了解到解压程序的入口位置在 .start 代码段,我们从这里开始分析内核解压过程。一开始,会将处理器设置为 SVC 模式,并禁用 FIQ 和 IRQ 中断,以及保存一下上下文(如保存 CPU 架构 和 DTB 数据物理地址 等)

// arch/arm/boot/compressed/head.S/* 内核解压程序入口 */.section ".start", #alloc, #execinstr.alignAR_CLASS(	.arm	)
start:.type	start,#function// 重复 7 条 nop 指令.rept	7__nop.endr
#ifndef CONFIG_THUMB2_KERNEL // ARM 指令模式内核(非 Thumb 指令模式)mov	r0, r0 // 第 8 条空指令
#else...
#endifW(b)	1f// 一些 MAGIC 数字数据,// 以及 UEFI 启动的 数据(本文不讨论 UEFI,ARM32 没见过用  UEFI 模式启动的)...1:AR_CLASS(	mrs	r9, cpsr	) // 读取程序状态寄存器 cpsr 到 r9.../** BootLoader* . 从 r1 传递硬件架构 ID* . 从 r2 传递 DTB 物理地址* 后续的代码会破坏 r1, r2 的值,这里先:* 保存 硬件架构 ID 到 r7* 保存 DTB 地址到 r8*/mov	r7, r1			@ save architecture IDmov	r8, r2			@ save atags pointer#ifndef CONFIG_CPU_V7Mmrs	r2, cpsr		@ get current modetst	r2, #3			@ not user?bne	not_angel		// 如果不是 User 模式,跳转到 not_angle 标号处...
not_angel:safe_svcmode_maskall r0 /* 将处理器设置为 SVC 模式, 同时禁用 FIQ & IRQ */msr	spsr_cxsf, r9		@ Save the CPU boot mode in@ SPSR		
#endif

然后是确定内核被解压后放置地址到寄存器 r4

// arch/arm/boot/compressed/head.S.text/* 设定 内核 被解压缩后 的 加载地址 到 r4 */
#ifdef CONFIG_AUTO_ZRELADDRmov	r4, pc/** 将加载向下对齐到 128MB: * 这要求内核镜像被加载到所在物理内存 (128MB - TEXT_OFFSET) 位置开始及往上空间.*/and	r4, r4, #0xf8000000/* * TEXT_OFFSET 由两个 Makefile 一起定义: * (1) arch/arm/Makefile)*     textofs-y	:= 0x00008000*     ...*     TEXT_OFFSET := $(textofs-y)*     ...*     export	TEXT_OFFSET GZFLAGS MMUEXT* (2) arch/arm/boot/compressed/Makefile*     AFLAGS_head.o += -DTEXT_OFFSET=$(TEXT_OFFSET)*/add	r4, r4, #TEXT_OFFSET /* 设定 解压后 内核的加载地址到 r4 */
#else...
#endif

接下来,看 解压后的内核 和 内核解压程序中解压相关部分代码,是否存在空间重叠如果两者存在空间重叠将 解压缩程序中解压相关部分代码 重定位 到解压后的内核 之后的空间上去。来看细节:

// arch/arm/boot/compressed/head.S/** 比较 解压程序当前运行地址 和 解压后内核加载起始地址:* if (r0 < r4) { // 解压程序当前运行地址 < 解压后内核加载起始地址* 		r0 = 解压程序当前结束位置地址(尾部向后扩展了部分空间)* 		if (r4 < r0) // 内核加载起始地址 < 解压程序当前结束位置地址* 			r4 |= 1 // 标记解压过程未使用 cache 加速* 		else // 内核加载起始地址 >= 解压程序当前结束位置地址: 两者无空间重叠* 			blcs cache_on // 开启 cache 加速解压过程* } else { // 解压程序当前运行地址 >= 解压后内核加载起始地址: : 两者无空间重叠* 		blcs cache_on // 开启 cache 加速解压过程* }* 从上面逻辑看到,如果 解压程序 和 解压后内核 位置在空间上没有重叠,则开启 cache* 加速解压过程,这样做的原因,可能有更高的命中率 ???*/mov	r0, pc // r0: 解压程序当前运行地址 + 4cmp	r0, r4 // 比较 解压程序当前运行地址 和 解压后内核起始加载地址ldrcc	r0, LC0+32addcc	r0, r0, pccmpcc	r4, r0 // 比较 解压后内核起始加载地址 和 解压程序当前结束地址orrcc	r4, r4, #1		@ remember we skipped cache_onblcs	cache_onrestart:	adr	r0, LC0 /* r0: LC0 的 当前运行时地址 *//** r1 : LC0 的 链接地址* r2 : __bss_start 的 链接地址* r3 : _end 的 链接地址* r6 : _edata 的 链接地址* r10: input_data_end 的 链接地址, 即 紧邻 压缩内核(piggy_data) 后的*      4字节 的 链接地址,该地址开始的 4个字节存储了压缩前 内核的大小. *      详见 piggy.S* r11: _got_start 的 链接地址* r12: _got_end 的 链接地址*/ldmia	r0, {r1, r2, r3, r6, r10, r11, r12}/* sp : 指向预分配 4K 的堆栈空间 .L_user_stack 底部链接地址,*      即 .L_user_stack_end 的链接地址 (堆栈向低地址增长)*/ldr	sp, [r0, #28]// r0: LC0 当前运行时地址 - LC0 的链接地址sub	r0, r0, r1		@ calculate the delta offset// r6: _edata 运行时地址 (解压缩程序 当前运行时 结束地址)add	r6, r6, r0		@ _edata// r10: input_data_end 当前运行时地址add	r10, r10, r0		@ inflated kernel size location// 读取压缩前内核大小到 r9 (即 arch/arm/boot/Image 的大小) ldrb	r9, [r10, #0]ldrb	lr, [r10, #1]orr	r9, r9, lr, lsl #8ldrb	lr, [r10, #2]ldrb	r10, [r10, #3]orr	r9, r9, lr, lsl #16orr	r9, r9, r10, lsl #24#ifndef CONFIG_ZBOOT_ROM/* malloc space is above the relocated stack (64k max) */add	sp, sp, r0 // 修正 sp 堆栈指针:指向栈空间底部 .L_user_stack_end 的 当前运行时地址add	r10, sp, #0x10000 // 移动到距离 .L_user_stack_end 64K 的位置
#else...
#endifmov	r5, #0			@ init dtb size to 0
#ifdef CONFIG_ARM_APPENDED_DTB// 早期支持 dts 的内核,要求 DTB 紧贴在内核之后的位置,后期的内核不再有这个要求
#endif/* * 检查 解压缩程序 和 解压后内核 是否存在空间重叠的情形。* 解压缩程序 和 解压后内核 位置不重叠 有如下 两种 情形:* (1) 解压缩程序 整个在 解压后内核 之前*      -------------*     |             |*     | 解压缩程序   |*     |             | *     \-------------\*     \             \*     |-------------|*     |             |*     |  解压后内核  |*     |             |*      -------------** (2) 解压缩程序 整个在 解压后内核 之后*      -------------*     |             |*     | 解压后内核   |*     |             | *     \-------------\*     \             \*     |-------------|*     |             |*     | 解压缩程序   |*     |             |*      -------------* 我们注意到, 检查代码中,解压缩程序顶部是以 wont_overwrite * 为边界, 为什么? 因为如果存在除 (1) 或 (2) 之外的覆盖情形,如果覆盖* 的不是 wont_overwrite 之后、用来解压缩的代码部分,前面这些已经运行* 过的代码,即使覆盖了也无所谓,因为已经用不着了。*//* 检验 是否是 情形 (1) */add	r10, r10, #16384cmp	r4, r10bhs	wont_overwrite // 情形 (1): 不需要做 解压缩程序 重定位,进入解压缩过程/* 检验 是否是 情形 (2) */add	r10, r4, r9 // r10: 解压后内核 结束地址adr	r9, wont_overwritecmp	r10, r9bls	wont_overwrite // 情形 (2): 不需要做 解压缩程序 重定位,进入解压缩过程/** 解压缩程序(wont_overwrite 之后的解码代码) 和 解压后内核* 存在空间重叠,需要对 解压缩程序 进行重定位,然后重新检测,* 并最终进入解码 wont_overwrite 后的解压逻辑。* 不管是什么情形的重叠,都是将 解压缩程序(区间* [restart,reloc_code_end] 部分) 重定位到 内核 后面位置。*//** Bump to the next 256-byte boundary with the size of* the relocation code added. This avoids overwriting* ourself when the offset is small.*/add	r10, r10, #((reloc_code_end - restart + 256) & ~255)bic	r10, r10, #255/* Get start of code we want to copy and align it down. */adr	r5, restart /* r5: restart 标号 的 当前运行时地址 */bic	r5, r5, #31...// 当前的上下文:// r6: _edata 运行时地址 (解压缩程序 运行时 结束地址)// r5: restart 标号的 运行时地址// r10: 解压后内核 结束地址// r9: 解压缩程序 需重定位的  代码数据 大小 (向上、向下均对齐后的大小)sub	r9, r6, r5		@ size to copyadd	r9, r9, #31		@ rounded up to a multiplebic	r9, r9, #31		@ ... of 32 bytesadd	r6, r9, r5 // r6: 解压缩程序 需重定位的代码数据 【旧的】 结束地址add	r9, r9, r10 // r9: 解压缩程序 【新的】 重定位起始地址// 将 解压缩程序 重定位到 新的位置: 由 高地址 向 低地址 逆向拷贝
1:		ldmdb	r6!, {r0 - r3, r10 - r12, lr}cmp	r6, r5stmdb	r9!, {r0 - r3, r10 - r12, lr}bhi	1b/* Preserve offset to relocated code. */// r6: 重定位后,新、旧 解压缩程序 开始位置 之间的距离sub	r6, r9, r6#ifndef CONFIG_ZBOOT_ROM/* cache_clean_flush may use the stack, so relocate it */add	sp, sp, r6 /* 随着重定位 解压缩程序 到新位置,4K .L_user_stack 堆栈也需要重定位 */
#endifbl	cache_clean_flush // cache 清理badr	r0, restart // r0: restart 当前的 运行时地址add	r0, r0, r6 // r0: restart 重定位后、新的 运行时地址mov	pc, r0 /* 重定位 解压缩程序 后, 跳转 restart 标号处 重新执行 */

上述过程中,涉及的数据、堆栈、链接脚本如下:

// arch/arm/boot/compressed/head.S// 解压程序代码、数据的边界标记符号.align	2.type	LC0, #object
LC0:		.word	LC0			@ r1 // +0.word	__bss_start		@ r2 // +4.word	_end			@ r3 // +8.word	_edata			@ r6 // +12.word	input_data_end - 4	@ r10 (inflated size location) // + 16.word	_got_start		@ r11 // + 20.word	_got_end		@ ip // +24.word	.L_user_stack_end	@ sp // +28.word	_end - restart + 16384 + 1024*1024 // +32.size	LC0, . - LC0 // +36...// 解压过程使用的堆栈空间.align.section ".stack", "aw", %nobits
.L_user_stack:	.space	4096
.L_user_stack_end:
// arch/arm/boot/compressed/piggy.S/* SPDX-License-Identifier: GPL-2.0 */.section .piggydata,#alloc.globl	input_data
input_data:/** arch/arm/boot/compressed/Makefile:** $(obj)/piggy_data: $(obj)/../Image FORCE* 		$(call if_changed,$(compress-y))** $(obj)/piggy.o: $(obj)/piggy_data** 被压缩后的内核 Image, 压缩程序(如 gzip) 会在 piggy_data 的* 最后 4个字节 储存 压缩前内核的大小(即 arch/arm/boot/Image 的大小) 。*/.incbin	"arch/arm/boot/compressed/piggy_data".globl	input_data_end
input_data_end:
// 链接脚本:arch/arm/boot/compressed/vmlinux.ld.SOUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{/DISCARD/ : {*(.ARM.exidx*)*(.ARM.extab*)/** Discard any r/w data - this produces a link error if we have any,* which is required for PIC decompression.  Local data generates* GOTOFF relocations, which prevents it being relocated independently* of the text/got segments.*/*(.data) // 这里很重要,因为解压涉及到重定位,允许包 r/w 数据影响到程序重定位}. = TEXT_START;_text = .;.text : {_start = .;*(.start)*(.text)*(.text.*)*(.fixup)*(.gnu.warning)*(.glue_7t)*(.glue_7)}.rodata : {*(.rodata)*(.rodata.*)}.piggydata : { // 内核 Image 压缩数据*(.piggydata)}. = ALIGN(4);_etext = .; // 只读代码、数据结束位置// GOT(Global Offset Table) :PIC(位置无关代码)重定位表.got.plt		: { *(.got.plt) }_got_start = .;.got			: { *(.got) }_got_end = .;/* ensure the zImage file size is always a multiple of 64 bits *//* (without a dummy byte, ld just ignores the empty section) */.pad			: { BYTE(0); . = ALIGN(8); }..._edata = .;.image_end (NOLOAD) : {_edata_real = .;	}_magic_sig = ZIMAGE_MAGIC(0x016f2818);_magic_start = ZIMAGE_MAGIC(_start);_magic_end = ZIMAGE_MAGIC(_edata);. = BSS_START;__bss_start = .;.bss			: { *(.bss) }_end = .; // 解压程序结束位置. = ALIGN(8);		/* the stack must be 64-bit aligned *//** 解压缩程序使用的堆栈段.* arch/arm/boot/compressed/head.S:*		.align*		.section ".stack", "aw", %nobits* .L_user_stack:  .space  4096* .L_user_stack_end:* 这是 解压缩程序 中 【唯一一个】.stack 段。*/.stack		: { *(.stack) }...
}

完成了空间覆盖检测、重定位工作后,最后剩下的就是内核的解压了,看具体细节:

// arch/arm/boot/compressed/head.Swont_overwrite:
/** If delta is zero, we are running at the address we were linked at.*   r0  = delta*   r2  = BSS start*   r3  = BSS end*   r4  = kernel execution address (possibly with LSB set)*   r5  = appended dtb size (0 if not present)*   r7  = architecture ID*   r8  = atags pointer*   r11 = GOT start*   r12 = GOT end*   sp  = stack pointer*/
/** r0: 运行时地址 - 链接地址*/orrs	r1, r0, r5beq	not_relocatedadd	r11, r11, r0 // r11: _got_start (GOT 表起始位置) 当前运行时地址add	r12, r12, r0 // r12: _got_end (GOT 表结束位置) 当前运行时地址#ifndef CONFIG_ZBOOT_ROM/** If we're running fully PIC === CONFIG_ZBOOT_ROM = n,* we need to fix up pointers into the BSS region.* Note that the stack pointer has already been fixed up.*/// 修正 BSS 段的位置add	r2, r2, r0 // r2: BSS 段起始位置 (__bss_start) 当前运行时地址add	r3, r3, r0 // r3: BSS 段结束位置 当前运行时地址/** Relocate all entries in the GOT table.* Bump bss entries to _edata + dtb size*//* 遍历修正所有 GOT 表项: GOT[i] += (运行时地址 - 链接地址) */
1:		ldr	r1, [r11, #0]		@ relocate entries in the GOTadd	r1, r1, r0		@ This fixes up C referencescmp	r1, r2			@ if entry >= bss_start &&cmphs	r3, r1			@       bss_end > entryaddhi	r1, r1, r5		@    entry += dtb size // GOT[i] 再次修正: GOT[i] += DTB 大小str	r1, [r11], #4		@ next entrycmp	r11, r12blo	1b/* bump our bss pointers too */add	r2, r2, r5 // 再次 BSS 段起始位置 (__bss_start): GOT[i] += DTB 大小add	r3, r3, r5 // 再次 BSS 段结束位置: GOT[i] += DTB 大小
#else...
#endif// 清 0 整个 BSS 段
not_relocated:	mov	r0, #0
1:		str	r0, [r2], #4		@ clear bssstr	r0, [r2], #4str	r0, [r2], #4str	r0, [r2], #4cmp	r2, r3blo	1b/** Did we skip the cache setup earlier?* That is indicated by the LSB in r4.* Do it now if so.*/tst	r4, #1bic	r4, r4, #1blne	cache_on/** The C runtime environment should now be setup sufficiently.* Set up some pointers, and start decompressing.*   r4  = kernel execution address*   r7  = architecture ID*   r8  = atags pointer*/
/* 解压内核调用 C 函数 decompress_kernel() ,需设置好 C 运行时环境。 */
// 解压内核到 r4 指向的地址mov	r0, r4mov	r1, sp			@ malloc space above stackadd	r2, sp, #0x10000	@ 64k maxmov	r3, r7bl	decompress_kernel // 解压缩内核到 r4 指向的位置bl	cache_clean_flushbl	cache_off#ifdef CONFIG_ARM_VIRT_EXT...bne	__enter_kernel		@ boot kernel directly...
#elseb	__enter_kernel // 准备跳转到解压后的内核
#endif...__enter_kernel:mov	r0, #0			@ must be 0// 恢复保存的 BootLoader 传递的 CPU 架构、DTB 物理地址 到 r1, r2mov	r1, r7			@ restore architecture numbermov	r2, r8			@ restore atags pointer// 跳转到内核入口执行: arch/arm/boot/head.S 中 ENTRY(stext) 处执行ARM(		mov	pc, r4		)	@ call kernelreloc_code_end:

6. 内核加压缩过程小结

我们用一幅流程图来简单小结下内核的解压缩过程。如下:
在这里插入图片描述

7. 参考资料

https://www.man7.org/linux/man-pages/man1/objcopy.1.html

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

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

相关文章

剑指 Offer 62. 圆圈中最后剩下的数字(简单)

题目&#xff1a; class Solution { public:int lastRemaining(int n, int m) {int pos 0;for(int i2;i<n;i){pos (posm)%i;}return pos;} };作者&#xff1a;想吃火锅的木易 链接&#xff1a;详细题解 来源&#xff1a;力扣&#xff08;LeetCode&#xff09;

【微服务部署】07-调用链追踪

文章目录 集成SkyWalking实现调用链追踪1. SkyWalking架构图2. 代码集成SkyWalking 集成SkyWalking实现调用链追踪 1. SkyWalking架构图 Receiver是SkyWalking的入口&#xff0c;支持gRPC和HTTP协议。 SkyWalking内部有分析和查询两个部分 存储方面SkyWalking支持Elasticsearc…

第一个实例:QT实现汽车电子仪表盘

1.实现效果 1.1.视频演示 QT 实现汽车仪表盘 1.2.实现效果截图 2.生成的安装程序 此程序是个windows下的安装程序,可以直接安装,看到汽车仪表盘的实现效果,安装程序从下面链接下载: 【免费】使用QT实现的汽车电子仪表盘,在windows下的安装程序资源-CSDN文库 3.功能概述…

(三)行为模式:6、备忘录模式(Memento Pattern)(C++示例)

目录 1、备忘录模式&#xff08;Memento Pattern&#xff09;含义 2、备忘录模式的UML图学习 3、备忘录模式的应用场景 4、备忘录模式的优缺点 &#xff08;1&#xff09;优点&#xff1a; &#xff08;2&#xff09;缺点 5、C实现备忘录模式的实例 1、备忘录模式&#…

数学建模--整数规划匈牙利算法的Python实现

目录 1.算法流程简介 2.算法核心代码 3.算法效果展示 1.算法流程简介 #整数规划模型--匈牙利算法求解 """ 整数规划模型及概念&#xff1a;规划问题的数学模型一般由三个因素构成 决策变量 目标函数 约束条件&#xff1b;线性规划即以线性函数为目标函数&a…

密码算法、密钥体系---安全行业基础篇1

一、密码算法 密码算法是一种数学和计算方法&#xff0c;用于保护数据的机密性和安全性。不同的密码算法使用不同的数学原理和技术来加密和解密数据。以下是一些常见的密码算法类型&#xff1a; 1. **对称密码算法&#xff1a;** 特点&#xff1a;相同的密钥用于加密和解密数…

工服穿戴检测联动门禁开关算法

工服穿戴检测联动门禁开关算法通过yolov8深度学习框架模型&#xff0c;工服穿戴检测联动门禁开关算法能够准确识别和检测作业人员是否按照规定进行工服着装&#xff0c;只有当人员合规着装时&#xff0c;算法会发送开关量信号给门禁设备&#xff0c;使门禁自动打开。YOLO的结构…

Maven入门教程(一):安装Maven环境

视频教程&#xff1a;Maven保姆级教程 Maven入门教程(一)&#xff1a;安装Maven环境 Maven入门教程(二)&#xff1a;idea/Eclipse使用Maven Maven入门教程(三)&#xff1a;Maven语法 Maven入门教程(四)&#xff1a;Nexus私服 Maven入门教程(五)&#xff1a;自定义脚手架 Maven项…

近年GDC服务器分享合集(四): 《火箭联盟》:为免费游玩而进行的扩展

如今&#xff0c;网络游戏采用免费游玩&#xff08;Free to Play&#xff09;加内购的比例要远大于买断制&#xff0c;这是因为前者能带来更低的用户门槛。甚至有游戏为了获取更多的用户&#xff0c;选择把原来的买断制改为免费游玩&#xff0c;一个典型的例子就是最近的网易的…

c++图论免费ppt,简单深度理解图论

本篇博文想分享一个ppt,是帮助大家简单深度理解c图论. 作者承诺&#xff1a;分享的东西没有病毒&#xff0c;是资料。 分享的东西一个是ppt,ppt里面是150页的&#xff0c;里面将带领大家简单深度理解c图论&#xff0c;还有一个就是里面例题的数据&#xff0c;大家可以按照数据…

Qt应用开发(基础篇)——输入对话框 QInputDialog

一、前言 QInputDialog类继承于QDialog&#xff0c;是一个简单方便的对话框&#xff0c;用于从用户获取单个值。 对话框窗口 QDialog QInputDialog输入对话框带有一个文本标签、一个输入框和标准按钮。输入内容可以字符、数字和选项&#xff0c;文本标签用来告诉用户应该要输入…

MyBatis-Plus学习笔记

1.MyBatis-Plus简介&#xff1a; MyBatis-Plus是一个MyBatis的增强工具&#xff0c;在MyBatis的基础上只做增强不做改变&#xff0c;为简化开发、提高效率而生。MyBatis-Plus提供了通用的mapper和service&#xff0c;可以在不编写任何SQL语句的情况下&#xff0c;快速的实现对单…

优化爬虫请求:如何选择合适的爬虫ip轮换策略?

在进行爬虫任务时&#xff0c;使用隧道爬虫ip并采用合适的轮换策略可以提高稳定性和效率。选择合适的隧道爬虫ip轮换策略可以优化您的爬虫请求过程。 1、考量目标网站特点 不同网站对于频繁请求可能有不同限制或反爬机制。 了解目标网站是否存在IP封禁、验证码等问题&#xff…

swagger 接口测试,用 python 写自动化时该如何处理?

在使用Python进行Swagger接口测试时&#xff0c;可以使用requests库来发送HTTP请求&#xff0c;并使用json库和yaml库来处理响应数据。以下是一个简单的示例代码&#xff1a; import requests import json import yaml# Swagger API文档地址和需要测试的接口路径 swagger_url …

云原生Kubernetes:K8S概述

目录 一、理论 1.云原生 2.K8S 3.k8s集群架构与组件 二、总结 一、理论 1.云原生 &#xff08;1&#xff09;概念 云原生是一种基于容器、微服务和自动化运维的软件开发和部署方法。它可以使应用程序更加高效、可靠和可扩展&#xff0c;适用于各种不同的云平台。 如果…

2分钟搭建FastGPT训练企业知识库AI助理(Docker部署)

我们使用宝塔面板来进行搭建&#xff0c;更方便快捷灵活&#xff0c;争取操作时间只需两分钟 宝塔面板下安装Docker 在【软件商店中】安装【docker管理器】【docker模块】即可 通过Docker安装FastGPT 通过【Docker】【添加容器】【容器编排】创建里新增docker-compose.yaml以下…

oled--SSD1315驱动

OLED 接口方式&#xff08;由硬件电路确定&#xff09;&#xff1a;6800、8080、spi、i2c. 常见的驱动芯片&#xff1a;ssd1306、ssd1315。 oled屏幕的发光原理不同于lcd&#xff0c;上电后无法直接显示&#xff0c;需要初始化后才能正常显示。 SSD1315手册资料 SSD1315是一款…

2023年05月 C/C++(六级)真题解析#中国电子学会#全国青少年软件编程等级考试

C/C++编程(1~8级)全部真题・点这里 第1题:字符串插入 有两个字符串str和substr,str的字符个数不超过10,substr的字符个数为3。(字符个数不包括字符串结尾处的’\0’。)将substr插入到str中ASCII码最大的那个字符后面,若有多个最大则只考虑第一个。 时间限制:1000 内存…

怎样来实现流量削峰方案

削峰从本质上来说就是更多地延缓用户请求&#xff0c;以及层层过滤用户的访问需求&#xff0c;遵从“最后落地到数据库的请求数要尽量少”的原则。 1.消息队列解决削峰 要对流量进行削峰&#xff0c;最容易想到的解决方案就是用消息队列来缓冲瞬时流量&#xff0c;把同步的直…

C语言每日一练--Day(15)

本专栏为c语言练习专栏&#xff0c;适合刚刚学完c语言的初学者。本专栏每天会不定时更新&#xff0c;通过每天练习&#xff0c;进一步对c语言的重难点知识进行更深入的学习。 今日练习题关键字&#xff1a;珠玑妙算 两数之和 &#x1f493;博主csdn个人主页&#xff1a;小小uni…