三、memblock 内存分配器

两个问题:

1、系统是怎么知道物理内存的?linux内存管理学习(1):物理内存探测

2、在内存管理真正初始化之前,内核的代码执行需要分配内存该怎么处理?

在Linux内核启动初期,完整的内存管理系统如Buddy System和Slab分配器尚未初始化完成。

此时,内核通过memblock机制临时管理物理内存空间。memblock作为早期内存管理器,负责记录所有可用的DRAM区域,并处理启动阶段的内存分配与保留请求,例如为内核代码、设备树或初始化数据分配内存。

当内核继续初始化并建立起Buddy System和Slab分配器等核心内存管理组件后,会在mem_init()​函数中完成内存管理权的移交。

此时,memblock分配器会将剩余的内存释放给Buddy System统一管理,后续所有动态内存分配均由Buddy System和Slab分配器等高级机制接管,而memblock仅保留调试或特殊场景下的辅助功能。

分析memblock算法,可以从几点入手:

1、 memblock算法初始化;

2、 memblock算法管理内存的申请和释放;

在分析 memblock 之前,我们需要先理清系统内存的总体使用情况。内存按照用途和生命周期可以分为三类:

(1)静态内存:永久分配给内核的固定内存区域,不可被动态分配或回收。包含内容如:

  • 内核代码段(_text​ ~ _etext​):存放内核的可执行代码。
  • 内核数据段(_data​ ~ _edata​):存放全局变量、静态变量等。
  • BSS段(__bss_start​ ~ __bss_stop​):存放未初始化的静态变量。
  • 设备树(FDT, Flattened Device Tree):描述硬件信息,由 Bootloader 传递。
  • initramfs/initrd:临时根文件系统,用于早期用户空间初始化。

这些区域在系统启动时就被占用,不会被释放或重新分配。必须通过 memblock_reserve() 进行保护,防止被错误分配。

(2)预留内存:系统预先保留的专用内存,通常用于硬件加速或特殊设备。

  • GPU/Camera/VPU:多媒体处理需要大块连续物理内存(如 64MB~1GB)
  • DMA 缓冲区:某些外设要求物理连续内存(如 DMA Engine)
  • 安全相关区域:如 TEE(Trusted Execution Environment)占用的内存

这些区域不参与常规内存分配,但可能由驱动按需启用或释放。通过 memblock_reserve()​ 或设备树 reserved-memory​ 节点预留

(3)动态内存:内核可自由分配和管理的物理内存,是系统最宝贵的资源

  • 启动初期:由 memblock​ 进行简单分配
  • Buddy System 就绪后:由页分配器(alloc_pages()​)管理,支持按页分配
  • SLAB/SLUB 就绪后:提供小块内存分配(kmalloc()​)

可被用户空间(通过 mmap)或内核(vmalloc、kmalloc)动态使用,可能因内存碎片或外设占用而面临大块连续内存不足的问题

其中memblock_reserved_init_regions主要包括上述静态内存和预留内存空间


一、MEMBLOCK 内存分配器进行初始化

初始化入口:

start_kernel--> setup_arch--> e820__memblock_setup

内存memblock基本初始化包括两个部分:

  • 根据硬件实际的物理内存初始化内核内存可见区域
  • 标记内核已经使用的内存 (设备树、内核镜像等)
static struct memblock_region memblock_memory_init_regions[INIT_MEMBLOCK_REGIONS] __initdata_memblock;
static struct memblock_region memblock_reserved_init_regions[INIT_MEMBLOCK_RESERVED_REGIONS] __initdata_memblock;
#ifdef CONFIG_HAVE_MEMBLOCK_PHYS_MAP
static struct memblock_region memblock_physmem_init_regions[INIT_PHYSMEM_REGIONS];
#endif/** memblock 内存分配器的全局实例初始化* 使用 __initdata_memblock 标记表示该数据结构仅在内核初始化阶段使用* 初始化完成后这部分内存可以被释放*/
struct memblock memblock __initdata_memblock = {/* 可用内存区域(memory)初始化 */.memory.regions = memblock_memory_init_regions,  /* 初始静态分配的内存区域数组 */.memory.cnt = 1,        /* 初始设为1表示有一个空条目(dummy entry) */.memory.max = INIT_MEMBLOCK_REGIONS,  /* 初始最大区域数,通常为128 */.memory.name = "memory", /* 用于调试识别的名称 *//* 保留内存区域(reserved)初始化 */ .reserved.regions = memblock_reserved_init_regions, /* 初始静态分配的保留区域数组 */.reserved.cnt = 1,      /* 初始设为1表示有一个空条目 */.reserved.max = INIT_MEMBLOCK_RESERVED_REGIONS, /* 初始最大保留区域数,通常为128 */.reserved.name = "reserved", /* 用于调试识别的名称 *//* 全局控制参数初始化 */.bottom_up = false,      /* 默认从高地址向低地址分配策略 */.current_limit = MEMBLOCK_ALLOC_ANYWHERE, /* 初始无分配地址限制 */
};

memblock把物理内存划分为若干内存区,按使用类型分别放在memory和reserved两个集合(数组)中,memory即动态内存的集合,reserved集合包括静态内存和预留内存

每个数组包含了 128 个内存区域。我们可以在 INIT_MEMBLOCK_REGIONS 宏定义中看到它:

#define INIT_MEMBLOCK_REGIONS			128
#define INIT_PHYSMEM_REGIONS			4#ifndef INIT_MEMBLOCK_RESERVED_REGIONS
# define INIT_MEMBLOCK_RESERVED_REGIONS		INIT_MEMBLOCK_REGIONS
#endif

内核和memblock相关的数据结构:

// 1、struct memblock 结构体描述分配器整体特性
struct memblock {/* 内存分配方向控制 */bool bottom_up;  /* true: 从低地址向高地址分配内存;false: 从高地址向低地址分配(默认) *//* 内存分配的安全边界 */phys_addr_t current_limit; /* memblock_alloc()能分配的最高物理地址,用于防止分配到未映射或不安全的区域 */struct memblock_type memory;   /* 所有可用RAM区域的连续列表,从固件获取(如e820/EFI内存映射) */struct memblock_type reserved; /* 所有保留/已分配的区域,包括:* - 内核静态区域(代码段、数据段、BSS段)* - 设备保留内存(GPU、DMA缓冲区)* - 早期动态分配的内存 */
};// 2、struct memblock_type 结构体用于维护特定内存类型集合
struct memblock_type {unsigned long cnt;        /* 当前实际存储的内存区域数量。例如:系统中有3块可用的物理内存区域 */unsigned long max;        /* regions数组的当前最大容量,当cnt == max时需要动态扩容 */phys_addr_t total_size;   /* 该类型所有内存区域的总大小。例如:所有保留区域加起来共256MB */struct memblock_region *regions; /* 动态分配的内存区域数组。每个元素描述一个连续内存区域 */char *name;              /* 该内存类型的名称字符串。例如:"memory"、"reserved"等,主要用于调试输出 */
};// 3、struct memblock_region 结构体代表被管理的内存区块
struct memblock_region {phys_addr_t base;    /* 内存区域的起始物理地址。例如:0x80000000(2GB处开始)*/phys_addr_t size;    /* 内存区域的长度(字节数)。例如:0x20000000(512MB大小)*/enum memblock_flags flags; /* 区域属性标志位,可能取值:* MEMBLOCK_NONE       - 默认无特殊属性* MEMBLOCK_HOTPLUG    - 支持热插拔的内存* MEMBLOCK_MIRROR     - 镜像内存区域* MEMBLOCK_NOMAP      - 不映射到内核地址空间 */#ifdef CONFIG_NUMA       /* 仅在NUMA(非统一内存访问)系统生效 */int nid;            /* NUMA节点ID,表示该内存属于哪个物理节点。例如:0表示第一个NUMA节点 */
#endif
};
这三个结构体: memblock, memblock_type 和 memblock_region 是 Memblock 的主要组成部分。现在我们可以进一步了解 Memblock 和 它的初始化过程了。

1、e820__memblock_setup

这段代码是 Linux 内核中用于初始化内存块(memblock)分配器的函数,主要作用是根据 BIOS 提供的 E820 内存映射表来设置系统的物理内存布局。

// 将BIOS提供的E820内存信息导入memblock分配器
void __init e820__memblock_setup(void)
{... .../** 允许memblock动态扩容:* - 初始静态分配128个区域(INIT_MEMBLOCK_REGIONS)* - 当E820条目超过128时自动扩容* - 安全原因:此时已知道保留区域,扩容不会覆盖关键内存*/memblock_allow_resize();/* 遍历所有E820条目 */for (i = 0; i < e820_table->nr_entries; i++) {struct e820_entry *entry = &e820_table->entries[i];/* 检查地址范围是否溢出 */end = entry->addr + entry->size;if (end != (resource_size_t)end)continue;  // 跳过非法地址范围/* 处理特殊保留内存(如调试保留区) */if (entry->type == E820_TYPE_SOFT_RESERVED) {memblock_reserve(entry->addr, entry->size);  // 加入reserved列表continue;}/* 仅处理可用内存类型 */if (entry->type != E820_TYPE_RAM &&       // 普通可用内存entry->type != E820_TYPE_RESERVED_KERN) // 内核保留可用内存continue;/* 将可用内存加入memblock.memory */memblock_add(entry->addr, entry->size);}/** 内存对齐修剪:* - 确保所有区域边界按PAGE_SIZE(通常4K)对齐* - 避免Buddy System出现部分页的问题*/memblock_trim_memory(PAGE_SIZE);/* 打印memblock最终状态(调试用) */memblock_dump_all();
}
  • 遍历E820内存映射表,将可用内存添加到memblock.memory
  • 将特殊保留内存标记到memblock.reserved
  • 确保所有内存区域按页对齐,打印最终内存布局信息

接着看memblock_add

1.1 memblock_add
// memblock_add - 添加新的内存区域到memblock管理
int __init_memblock memblock_add(phys_addr_t base, phys_addr_t size)
{// 计算区域结束地址(包含边界)phys_addr_t end = base + size - 1;memblock_dbg("%s: [%pa-%pa] %pS\n", __func__,&base, &end, (void *)_RET_IP_);return memblock_add_range(&memblock.memory, base, size, MAX_NUMNODES, 0);
}// memblock_add_range - 将新的内存区域添加到memblock管理系统中
static int __init_memblock memblock_add_range(...)
{... .../* 空数组特殊处理(首次添加)*/if (type->regions[0].size == 0) {type->regions[0] = (struct memblock_region){.base = base, .size = size, .flags = flags, .nid = nid};type->total_size = size;return 0;}repeat:/* 两阶段控制:重置基础参数 */base = obase;int nr_new = 0;  // 需要新增的区域计数/* 遍历现有区域处理重叠 */for_each_memblock_type(idx, type, rgn) {phys_addr_t rbase = rgn->base;phys_addr_t rend = rbase + rgn->size;/* 跳过无重叠区域 */if (rbase >= end) break;if (rend <= base) continue;/* 处理左侧非重叠部分 */if (rbase > base) {nr_new++;if (insert) memblock_insert_region(type, idx++, base, rbase-base, nid, flags);}base = min(rend, end); // 推进处理位置}/* 处理右侧剩余部分 */if (base < end) {nr_new++;if (insert)memblock_insert_region(type, idx, base, end-base, nid, flags);}/* 第一阶段:数组扩容 */if (!insert) {while (type->cnt + nr_new > type->max)if (memblock_double_array(type, obase, size) < 0)return -ENOMEM;insert = true;goto repeat;  // 跳转执行第二阶段} /* 第二阶段:合并区域 */else {memblock_merge_regions(type);return 0;}
}

memblock内存管理机制的核心工作流程可分为四个关键步骤:

  • 初始化处理:当检测到memblock管理的内存区域为空时,直接将当前待添加的内存空间作为首个管理单元插入。
  • 重叠检测与处理:在非空状态下,算法会先检查新区域与现有区域是否存在地址重叠。若发现重叠,则自动剔除重叠部分,仅将有效的非重叠内存段加入管理系统。
  • 动态扩容机制:当预设的128个region管理单元不足时,通过memblock_double_array()函数动态扩展存储空间,确保能容纳更多内存区域信息。
  • 区域合并优化:最终调用memblock_merge_regions()函数,将地址连续且属性相同的相邻内存区域合并为更大的连续区块。

这套机制的核心作用是将BIOS提供的e820内存布局信息,特别是标记为"usable"的可用内存区域,精确转换为memblock.memory中的规范化管理单元。e820探测到多少个usable内存块,就对应多少个region,这些region严格按地址从低到高排列,且保证没有重叠。

图示,详细见Linux Kernel:启动时内存管理(MemBlock 分配器)一、Bootmem 与 Memblock 系统初始化 - 掘金

memblock_trim_memory:将 memblock 中所有内存区域的起始地址(start)和结束地址(end)按照指定的对齐大小(通常为 PAGE_SIZE​)进行边界调整,确保每个区域满足以下条件:

  • 起始地址(start):向上对齐到 PAGE_SIZE​ 的倍数
  • 结束地址(end):向下对齐到 PAGE_SIZE 的倍数

memblock_dump_all:内存布局信息打印

二、memblock 内存分配与回收
2.1、memblock_alloc

使用默认参数分配内存(自动选择位置),具体调用流程与内容如下:

--> memblock_alloc--> memblock_alloc_try_nid(--> memblock_alloc_internal(size, align, min_addr, max_addr, nid, false);static void *__init memblock_alloc_internal(phys_addr_t size, phys_addr_t align,phys_addr_t min_addr, phys_addr_t max_addr,int nid, bool exact_nid)
{... ...// 安全检查:如果slab分配器已就绪(memblock不应再被使用)if (WARN_ON_ONCE(slab_is_available()))return kzalloc_node(size, GFP_NOWAIT, nid); // 降级到slab分配// 确保分配范围不超过memblock的当前限制if (max_addr > memblock.current_limit)max_addr = memblock.current_limit;// 首次尝试:在[min_addr, max_addr]范围内分配alloc = memblock_alloc_range_nid(size, align, min_addr, max_addr, nid,exact_nid);// 若失败,放宽限制:允许分配低于min_addr的内存if (!alloc && min_addr)alloc = memblock_alloc_range_nid(size, align, 0, max_addr, nid,exact_nid);// 转换物理地址为虚拟地址if (!alloc)return NULL;return phys_to_virt(alloc);
}// memblock_alloc_range_nid - 在指定范围和NUMA节点分配启动内存块
phys_addr_t __init memblock_alloc_range_nid(phys_addr_t size,phys_addr_t align, phys_addr_t start,phys_addr_t end, int nid,bool exact_nid)
{... ...// 4. 主要分配逻辑(可能多次尝试)
again:// 4.1 优先尝试在指定节点和范围内分配found = memblock_find_in_range_node(size, align, start, end, nid,flags);if (found && !memblock_reserve(found, size))  // 成功则保留该区域goto done;// 4.2 若允许回退且指定了具体节点,尝试任意节点分配if (nid != NUMA_NO_NODE && !exact_nid) {found = memblock_find_in_range_node(size, align, start,end, NUMA_NO_NODE,flags);if (found && !memblock_reserve(found, size))goto done;}// 4.3 处理内存镜像情况:首次失败后尝试非镜像区域if (flags & MEMBLOCK_MIRROR) {flags &= ~MEMBLOCK_MIRROR;  // 清除镜像标志pr_warn("Could not allocate %pap bytes of mirrored memory\n",&size);goto again;  // 重新尝试分配}// 5. 所有尝试失败后返回0return 0;// 6. 分配成功后的处理
done:/* 跳过kasan_init的高频分配检测 */if (end != MEMBLOCK_ALLOC_KASAN)/** 设置min_count=0避免kmemleak报告内存泄漏。* 因为这些内存块通常只通过物理地址引用,kmemleak无法追踪。*/kmemleak_alloc_phys(found, size, 0, 0);return found;  // 返回分配的内存物理地址
}// 在指定范围和节点内查找空闲区域
static phys_addr_t __init_memblock memblock_find_in_range_node(phys_addr_t size, phys_addr_t align,phys_addr_t start, phys_addr_t end,int nid, enum memblock_flags flags)
{/* 1. 处理特殊end标志 */if (end == MEMBLOCK_ALLOC_ACCESSIBLE ||end == MEMBLOCK_ALLOC_KASAN)end = memblock.current_limit; // 使用memblock的当前地址限制/* 2. 避免分配第一个物理页(0x0-0xFFF)*/start = max_t(phys_addr_t, start, PAGE_SIZE); // 至少从PAGE_SIZE开始end = max(start, end); // 确保end >= start/* 3. 根据分配策略选择搜索方向 */if (memblock_bottom_up())// 自底向上搜索(低地址优先)return __memblock_find_range_bottom_up(start, end, size, align,nid, flags);else// 自顶向下搜索(高地址优先,默认策略)return __memblock_find_range_top_down(start, end, size, align,nid, flags);
}

主要逻辑:从可用内存区中找一块大小为 size 的物理内存区块, 然后调用 memblock_reseve() 函数在找到的情况下,将这块物理内存区块加入到预留区内

2.2、memblock_free

释放已分配的内存区域,具体调用流程与内容如下:

/*** memblock_free - 释放由memblock_alloc_xx()分配的启动内存块* 释放先前分配的内存块,但不会将内存返还给伙伴系统(Buddy Allocator)。* 仅从memblock.reserved中移除标记。*/
int __init_memblock memblock_free(phys_addr_t base, phys_addr_t size)
{... ...// 1. 通知kmemleak停止追踪该物理内存范围kmemleak_free_part_phys(base, size);// 2. 从memblock.reserved中移除该区域return memblock_remove_range(&memblock.reserved, base, size);
}/*** kmemleak_free_part_phys - 解除对物理内存范围的泄漏追踪* 将物理地址转换为虚拟地址后调用标准释放接口。*/
void __ref kmemleak_free_part_phys(phys_addr_t phys, size_t size)
{// 仅处理低端内存(若未配置HIGHMEM或地址在lowmem范围内)if (!IS_ENABLED(CONFIG_HIGHMEM) || PHYS_PFN(phys) < max_low_pfn)kmemleak_free_part(__va(phys), size);  // __va转换为虚拟地址
}// memblock_remove_range - 从指定memblock类型中移除内存区域
static int __init_memblock memblock_remove_range(struct memblock_type *type,phys_addr_t base, phys_addr_t size)
{	... ...// 1. 定位与目标范围重叠的region区间ret = memblock_isolate_range(type, base, size, &start_rgn, &end_rgn);// 2. 反向遍历并移除region(避免索引错位)for (i = end_rgn - 1; i >= start_rgn; i--)memblock_remove_region(type, i);... ...
}// 从memblock类型中移除指定region: 该函数会更新总大小、压缩region数组,并处理空数组的特殊情况。
static void __init_memblock memblock_remove_region(struct memblock_type *type, unsigned long r)
{// 1. 从总大小中减去被移除region的大小type->total_size -= type->regions[r].size;// 2. 移动后续region填补空缺(内存拷贝)memmove(&type->regions[r], &type->regions[r + 1],(type->cnt - (r + 1)) * sizeof(type->regions[r]));type->cnt--;  // region计数减1/* 3. 处理空数组的特殊情况 */if (type->cnt == 0) {WARN_ON(type->total_size != 0);  // 验证一致性:总大小应为0// 重置为初始空状态type->cnt = 1;type->regions[0].base = 0;type->regions[0].size = 0;type->regions[0].flags = 0;memblock_set_region_node(&type->regions[0], MAX_NUMNODES);}
}

memblock_isolate_range() 将要移除的物理内存区从 reserved 内存区中分离出来,将 start_rgn 和 end_rgn(该内存区块的起始、结束索引号)返回回去

memblock_remove_region() 将这些索引对应的内存区块从内存区中移除,这里具体做法为调用 memmove 函数将 r 索引之后的内存区块全部往前挪一个位置,这样 r 索引对应的内存区块就被移除了,如果移除之后,内存区不含有任何内存区块,那么就初始化该内存区

三、memblock释放和移交管理权

当内核完成关键子系统初始化后,内存管理将进入从 memblock 到伙伴系统(Buddy System)的移交阶段。这一重要过渡由 mm_init() 函数主导,其核心是通过 memblock_free_all() 实现控制权转移,具体流程如下:

--> mm_init--> mem_init--> memblock_free_all
四、memblock内存分配API概览
4.1、初始化与设置
  • memblock_allow_resize()​: 允许动态调整memblock数组大小
  • memblock_set_bottom_up()​: 设置内存分配方向(自底向上或自顶向下)
  • memblock_set_current_limit()​: 设置当前内存分配限制地址
4.2、内存区域管理
  • memblock_add(base, size)​: 添加新的可用内存区域(参数:基址,大小)
  • ​memblock_remove(base, size)​: 移除指定的内存区域
  • memblock_reserve(base, size)​: 保留(预留)指定的内存区域
  • memblock_free(base, size)​: 释放已分配的内存区域
  • memblock_mark_hotplug()​/clear_hotplug()​: 设置/清除内存区域的热插拔属性
  • memblock_mark_nomap()​/clear_nomap()​: 设置/清除内存区域的"不映射到内核"属性
4.3、内存分配函数
  • memblock_phys_alloc(size, align)​: 分配指定大小和对齐的物理内存
  • ​memblock_alloc(size, align)​: 使用默认参数分配内存(自动选择位置)
  • memblock_alloc_node(size, align, nid)​: 在指定NUMA节点上分配内存
  • memblock_alloc_from(size, align, min_addr)​: 从指定最小地址开始分配
  • memblock_alloc_low(size, align)​: 分配低端内存(通常低于4GB)
4.4、查询函数
  • memblock_is_memory(addr)​: 检查地址是否属于可用内存区域
  • memblock_is_reserved(addr)​: 检查地址是否属于保留区域
  • ​memblock_phys_mem_size()​: 返回所有内存区域的总大小
  • ​memblock_reserved_size()​: 返回所有保留区域的总大小
  • ​memblock_start_of_DRAM()​/end_of_DRAM()​: 获取DRAM内存的起始/结束地址

五、总结:

memblock 是 Linux 内核在初始化阶段使用的临时物理内存管理器,其核心设计围绕以下几点:

(1)分区管理

  • memblock.memory​:记录所有可用物理内存(由 BIOS/e820 探测到的 usable​ 区域)
  • memblock.reserved​:记录所有已分配或保留的内存(内核代码、设备预留等)。

(2)内存管理

  • 申请内存:仅将目标区域从 memory​ 移到 reserved​,不修改原始 memory​ 布局。
  • 释放内存:极少使用(多数早期内存为永久分配),仅从 reserved​ 中移除。

(3)与后续内存管理的衔接

  • memblock 记录所有物理内存信息。
  • 内核初始化后期,Buddy System 通过 memblock_free_all()​ 接管可用内存。
  • Buddy System 直接从 memblock.memory​ 提取空闲区域,忽略 reserved​ 中的已分配部分
  • memblock 仅保留调试接口,不再参与主动管理。


参考文档:

(3 封私信 / 79 条消息) Linux Kernel:启动时内存管理(MemBlock 分配器) - 知乎

linux 内核内存机制之e820(linux启动时,利用e820读取物理内存信息) - jinzi - 博客园

Linux內存初始化过程(ZZ) | L&H SITE

Linux内存都去哪了:(1)分析memblock在启动过程中对内存的影响 - ArnoldLu - 博客园

(3 封私信 / 79 条消息) linux内存管理(一)内存初始化 - 知乎

【计算子系统】内存管理之一:地址映射

Linux内核内存管理(1):内存块 - memblock-CSDN博客

early manage:memblock - Linux Book

【原创】(二)Linux物理内存初始化 - LoyenWang - 博客园

【linux 内存管理】memblock算法简单梳理_linux memblock-CSDN博客

3.10.2.4. linux物理内存初始化(memblock) — ywg_dev_doc 0.1 文档

Linux Kernel:启动时内存管理(MemBlock 分配器)一、Bootmem 与 Memblock 系统初始化 - 掘金

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

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

相关文章

Python 桌面应用形态后台管理系统的技术选型与方案报告

下面是一份面向“Python 桌面应用形态的后台管理系统”的技术选型与方案报告。我把假设前提→总体架构→客户端技术选型→服务端与数据层→基础设施与安全→交付与运维→质量保障→里程碑计划→风险与对策→最小可行栈逐层给出。 一、前置假设 & 非功能目标 业务假设 典型…

Winsows系统去除右键文件显示的快捷列表

前言&#xff1a;今天重做了电脑系统&#xff0c;安装的是纯净版的系统。然后手动指定D盘安装了下列软件。&#xff08;QQ&#xff0c;迅雷&#xff0c;百度网盘&#xff0c;搜狗输入法&#xff0c;驱动精灵&#xff09;然后我右键点击桌面的软件快捷方式&#xff0c;出现了一排…

【Go】Gin 超时中间件的坑:fatal error: concurrent map writes

Gin 社区超时中间件的坑&#xff1a;导致线上 Pod 异常重启 在最近的项目中&#xff0c;我们遇到了因为 Gin 超时中间件&#xff08;timeout&#xff09; 引发的生产事故&#xff1a;Pod 异常退出并重启。 问题现场 pod无故重启&#xff0c;抓取标准输出日志&#xff0c;问题…

数据结构:用数组实现队列(Implementing Queue Using Array)

目录 第1步&#xff1a;设计蓝图 (The Struct) 第2步&#xff1a;队列的诞生 (创建与初始化) 第3步&#xff1a;状态检查 (判满与判空) 第4步&#xff1a;核心操作 (入队与出队) 入队 (Enqueue) 出队 (Dequeue) 第5步&#xff1a;善后工作 (销毁队列) 现在&#xff0c;我…

Boost库核心组件与应用

一、BOOST 库简介&#xff1a;C 开发者的 “扩展工具集” 在 C 编程领域&#xff0c;除了标准库&#xff08;STL&#xff09;外&#xff0c;BOOST 库是最具影响力的第三方库之一。它由全球数百位开发者共同维护&#xff0c;包含超过 160 个高质量的组件&#xff0c;覆盖从基础…

机器学习 [白板推导](十二)[卡曼滤波、粒子滤波]

15. 线性动态系统&#xff08;卡曼滤波&#xff0c;Kalman Filter&#xff09; 15.1. 概述 15.1.1. 背景介绍 变量随时间变化的系统叫做动态系统&#xff0c;其中隐变量取值离散的是隐马尔可夫模型&#xff08;HMM&#xff09;&#xff0c;而隐变量取值连续的分为线性动态系统…

RH134 访问网络附加存储知识点

1. NFS 的主要功能是什么&#xff1f;答&#xff1a;NFS是一种分布式文件系统协议&#xff0c;主要功能包括&#xff1a;允许远程计算机通过网络访问共享文件。 实现文件系统在客户端和服务器之间的透明访问。支持文件的共享、读取和写入&#xff0c;使得多个 …

组合模式及优化

组合模式是一种结构型设计模式&#xff0c;其核心思想是将对象组合成树形结构&#xff0c;以表示“部分-整体”的层次关系&#xff0c;使得用户对单个对象和组合对象的使用具有一致性。 一、介绍 核心角色 组合模式包含以下3个关键角色&#xff1a; 抽象组件&#xff08;Compon…

【wmi异常】关于taskkill命令提示“错误:找不到” 以及无法正常获取设备机器码的处理办法

记录一下我的解决方案。 我先查阅了这篇博客&#xff1a;https://blog.csdn.net/qq_45698181/article/details/138957277 发现他写的批处理不知怎么执行不了&#xff0c;后来问了ai又可以执行了&#xff0c;估计是csdn防盗版格式问题 这里写一下我跟ai的对话&#xff0c;大家可…

制造装配、仓储搬运、快递装卸皆适配!MinkTec 弯曲形变传感器助力,让人体工学改变劳动生活

【导语】Minktec 最新实验显示&#xff1a;将Minktec 柔性弯曲形变传感器FlexTail 贴于受试者背部&#xff0c;记录 1 分钟内从洗碗机取餐具的动作&#xff0c;结合配套的flexlib -专用Python库分析&#xff0c;不仅量化出 “越低越伤腰” 的结论&#xff0c;更为制造装配、物流…

Nginx蜘蛛请求智能分流:精准识别爬虫并转发SEO渲染服务

> 一招解决搜索引擎爬虫无法解析现代前端框架的痛点,提升网站收录率与SEO排名! **痛点场景**:你的网站采用Vue/React等前端框架构建,页面内容依赖JavaScript动态渲染。搜索引擎爬虫访问时,只能抓取到空HTML骨架,无法获取真实内容,导致网站收录率低、SEO效果差。 --…

链表。。。

目录 5.1 链表的结点 5.2 插入 5.3 链表长度 5.4 查找 5.5 指定位置删除 5.6 代码 5.1 链表的结点 一个结点包括&#xff1a;值和指向下一个结点的指针。 package com.qcby.链表;public class Node {int value;Node next;public Node(int val){valueval;}Overridepublic…

私人AI搜索新突破:3步本地部署Dify+Ollama+QwQ,搜索能力MAX

1.安装Docker容器 本地部署Dify要先安装Docker桌面版&#xff0c;跟Ollama一样简单&#xff0c;也是去官网下载对应版本文件&#xff0c;直接安装就OK。 2&#xff1a;安装Dify 安装 Dify 简单的方式就是git clone&#xff0c;复制其github地址github.com/langgenius/dify&am…

(2-10-1)MyBatis的基础与基本使用

目录 0.前置小节 1. MyBatis 框架介绍 1.1 软件开发中的框架 1.2 使用框架的好处 1.3 SSM 开发框架 1.4 什么是 MyBatis 1.5 MyBatis 的开发流程 2. MyBatis 的开发流程 2.0 MyBatis的工作流程 2.1 引入 MyBatis 依赖 00.base(目录、pom、单元测试、Junit4) 01.Cal…

StarRocks集群部署

Starrocks 是一款基于 MPP 架构的高性能实时分析型数据库&#xff0c;专为 OLAP&#xff08;联机分析处理&#xff09;场景 设计&#xff0c;尤其擅长处理海量数据的实时分析、复杂查询和多维统计。 硬件 CPU&#xff1a;StarRocks依靠AVX2指令集充分发挥其矢量化能力。因此&am…

【CPP】自己实现一个CPP小工具demo,可以扩展其他选项

自己写CPP脚本小工具1. 思路描述2. 代码实现2.1 代码文件CppTool.cpp2.2 CMakeLists.txt3. 工具示例3.1 帮助信息3.2 工具用法3.3 实际使用1. 思路描述 实现一个简单的命令行工具。内容包括&#xff1a; 命令帮助信息参数检查&#xff0c;参数解析等功能。执行其他命令。将指…

如何使用嵌入模型创建本地知识库Demo

为data目录下的txt文档用阿里百炼的文本嵌入模型创建一个本地知识库import os from llama_index.core import ,Settings, SimpleDirectoryReader, VectorStoreIndex from llama_index.core.node_parser import SentenceSplitter from llama_index.llms.dashscope import DashSc…

SpringBoot 整合 Langchain4j:系统提示词与用户提示词实战详解

> 掌握提示词工程的核心技巧,让你的AI应用效果提升300%! **真实痛点**:为什么同样的模型,别人的应用精准专业,而你的却答非所问?关键在于提示词工程!本文将揭秘如何通过系统提示词与用户提示词的巧妙配合,打造专业级AI应用。 --- ### 一、Langchain4j 核心概念…

Sklearn 机器学习 邮件文本分类 加载邮件数据

💖亲爱的技术爱好者们,热烈欢迎来到 Kant2048 的博客!我是 Thomas Kant,很开心能在CSDN上与你们相遇~💖 本博客的精华专栏: 【自动化测试】 【测试经验】 【人工智能】 【Python】 Sklearn 机器学习 邮件文本分类 - 加载邮件数据 在自然语言处理(NLP)中,邮件文本分…

腾讯云开发小程序工具箱使用心得

一、核心优势与使用体验 作为首批使用腾讯云开发&#xff08;CloudBase&#xff09;工具箱的开发者&#xff0c;我深刻感受到其通过CloudBase AI与MCP服务重构开发范式的创新价值。结合微信小程序开发场景&#xff0c;该平台在以下维度表现突出&#xff1a; 1. AI驱动的全栈开发…