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

三、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.dtcms.com/a/335245.html

相关文章:

  • 深入理解文件硬链接、软链接与引用计数的那些事
  • 机器学习相关算法:回溯算法 贪心算法 回归算法(线性回归) 算法超参数 多项式时间 朴素贝叶斯分类算法
  • 超详细yolo8/11-pose人体姿态全流程概述:配置环境、数据标注、训练、验证/预测、onnx部署(c++/python)详解
  • 8.16、8.17 JavaWeb(MyBatis P116-P134)
  • 【网络与爬虫 00】试读
  • lcx、netcat、powercat--安装、使用
  • 【RH134知识点问答题】第 10 章:控制启动过程
  • 深入浅出OpenGL的glDrawArray函数
  • 设计索引的原则有哪些?
  • 数据结构初阶(16)排序算法——归并排序
  • w嵌入式分享合集66
  • 开发一款多商户电商APP要多久?功能拆解与源码技术落地方案
  • vulhub-driftingblues9缓冲区溢出攻击提权
  • 写一个linux脚本,要求实现查找9010端口,如果端口存在则kill,否则不处理,返回对应的提示
  • LE AUDIO----COMMAND AND EVENT
  • ArrayList的扩容源码分析
  • colmap
  • ABB焊接机器人弧焊省气
  • windows扩展(外接)显示器位置调节
  • 狗品种识别数据集:1k+图像,6个类别,yolo标注完整
  • 利用Qwen大模型进行c++11并发库的学习,与时俱进!!!!
  • File 类的用法和 InputStream, OutputStream 的用法
  • C#高级用法:元组
  • pidgen!DecodeProdKey函数分析之iDecodedBytesMax
  • docker安装mongodb及java连接实战
  • 视频理解综述
  • 【异步】js中异步的实现方式 async await /Promise / Generator
  • 码上爬第十一题【协程+wasm】
  • 博弈论07——Lemke-Howson 算法
  • STM32-GPIO实践部分1-跑马灯实验