一文细说引导内存分配器

news2024/12/25 9:21:53

一、引导内存分配器

1.引导内存分配器的作用 因为内核里面有很多内存结构体,不可能在静态编译阶段就静态初始化所有的这些内存结构体。另外,在系统启动过程中,系统启动后的物理内存分配器本身也需要初始化,如伙伴分配器,那么伙伴分配器如何获取内存来初始化自己呢 ?为了达到这个目标,我们先实现一个满足要求的但是可能效率不高的笨家伙,引导内存分配器。用它来负责系统初始化初期的内存管理, 最重要的, 用它来初始化我们内存的数据结构, 直到我们真正的内存管理器被初始化完成并能投入使用, 我们将旧的内存管理器丢掉。

2.引导内存分配器的原理 在Linux内核中使用struct bootmem_data来描述一个引导内存分配,其节点结构下的一个成员,也就是说每一个节点都有一个引导内存分配。 引导内存分配使用struct bootmem_data结构中的node_bootmem_map这个bitmap来呈现memory的状态,一个bit代表一个物理页框,也就是用struct page,如果一个bit为1,表示该page已经被分配了,如果bit是0,则表示该page未被分配。为了能够满足比一个page还小的内存块的分配,引导内存分配器会使用last_pos来记住上次分配所使用的PFN以及上次分配所使用的page内的偏移:last_offset,下次分配的时候结合last_pos和last_offset将细小的内存块分配尽量集中在相同的page中。

3引导内存分配器的缺点 尽管引导内存分配器不会造成严重的内存碎片,但是每次分配过程需要线性扫描搜索内存来满足当前的分配。因为是检查bitmap,所以代价比较昂贵,尤其是最先适配(first fit)算法倾向将小块内存放置在物理内存开头,但是这些内存区域在分配大块内存时,也需要扫描,所以该过程十分浪费。所以早期内存分配器在系统启动后就被弃用的原因。

4.bootmem和memblock的比较 但是bootmem也有很多问题. 最明显的就是外碎片的问题, 因此内核维护了memblock内存分配器, 同时用memblock实现了一份bootmem相同的兼容API, 即nobootmem, Memblock以前被定义为Logical Memory Block( 逻辑内存块),但根据Yinghai Lu的补丁, 它被重命名为memblock. 并最终替代bootmem成为初始化阶段的内存管理器。 bootmem是通过位图来管理,位图存在地地址段, 而memblock是在高地址管理内存, 维护两个链表, 即memory和reserved。 memory链表维护系统的内存信息(在初始化阶段通过bios获取的), 对于任何内存分配, 先去查找memory链表, 然后在reserve链表上记录(新增一个节点,或者合并) bootmem和memblock都是就近查找可用的内存, bootmem是从低到高找, memblock是从高往低找。 在boot传递给kernel memory bank相关信息后,kernel这边会以memblcok的方式保存这些信息,当伙伴系统没有起来之前,在内核中也是要有一套机制来管理memory的申请和释放。linux内核可以通过宏定义选择nobootmem 或者bootmem 来在伙伴起来之前管理内存。这两种机制对提供的API是一致的,因此对用户是透明的

资料直通车:Linux内核源码技术学习路线+视频教程内核源码

学习直通车:Linux内核源码内存调优文件系统进程管理设备驱动/网络协议栈

5.bootmem小分析 bootmem结构体位于文件include/linux/bootmem.h:

typedef struct bootmem_data {
 unsigned long node_min_pfn;//节点内存的起始物理页号
 unsigned long node_low_pfn;//节点内存的结束物理页号
 void *node_bootmem_map;//位图指针,每个物理页对应一位,如果物理页被分配则对应位置一。
 unsigned long last_end_off;//最后一次分配的页面内的偏移量(字节);如果为0,则使用的页面已满
 unsigned long hint_idx;//最后一次分配的物理页,下次优先考虑从这个物理页分配
 struct list_head list;//按内存地址排序链表头
} bootmem_data_t;

bootmem接口函数: 1)bootmem分配内存函数:alloc_bootmem 2)bootmem释放内存函数:free_bootmem

#define alloc_bootmem(x) \
 __alloc_bootmem(x, SMP_CACHE_BYTES, BOOTMEM_LOW_LIMIT)

void __init free_bootmem(unsigned long physaddr, unsigned long size)
{
 unsigned long start, end;

 kmemleak_free_part_phys(physaddr, size);//释放映射的内存

 start = PFN_UP(physaddr);//查找到起始位置的物理页
 end = PFN_DOWN(physaddr + size);//查找到结束为止的物理页

 mark_bootmem(start, end, 0, 0);//把释放的物理页对应的位清零
}

6.memblock结构解析 memblock结构体位于include/linux/memblock.h文件:

struct memblock {
 bool bottom_up;//表示内存分配方式,真:从低地址向上分配,假:从高地址向下分配
 phys_addr_t current_limit;//可分配内存的最大物理地址
 struct memblock_type memory;//可用物理内存区域(包括已分配和未分配的)
 struct memblock_type reserved;//预留物理内存区域(预留起来不可用,例子:设备树)
#ifdef CONFIG_HAVE_MEMBLOCK_PHYS_MAP
 struct memblock_type physmem;//所有的物理内存区域
#endif
};

struct memblock_type {
 unsigned long cnt;//区域数量
 unsigned long max;//分配区域的大小
 phys_addr_t total_size;//所有区域的大小
 struct memblock_region *regions;//区域数组指向区域数组
 char *name;//内存类型符号名
};

struct memblock_region {
 phys_addr_t base;//起始物理地址
 phys_addr_t size;//长度
 enum memblock_flags flags;//内存区域标志属性
#ifdef CONFIG_HAVE_MEMBLOCK_NODE_MAP
 int nid;//节点编号
#endif
};
//内存区域标志属性定义
enum memblock_flags {
 MEMBLOCK_NONE  = 0x0,//表示没有特殊要求区域
 MEMBLOCK_HOTPLUG = 0x1,//表示可以热插拔的区域 
 MEMBLOCK_MIRROR  = 0x2,//表示镜像的区域,将内存数据做两份复制,分配放在主内存和镜像内存中 
 MEMBLOCK_NOMAP  = 0x4,//表示不添加到内核直接映射区域,即线性映射区
};

memblock体系的结构:

7.memblock接口函数解析 1)memblock添加内存区域函数:

int __init_memblock memblock_add(phys_addr_t base, phys_addr_t size)
{
 phys_addr_t end = base + size - 1;

 memblock_dbg("memblock_add: [%pa-%pa] %pF\n",
       &base, &end, (void *)_RET_IP_);
 //直接调用memblock_add_range将内存区块添加到memblock.memory进行管理
 return memblock_add_range(&memblock.memory, base, size, MAX_NUMNODES, 0);
}

我们继续追memblock_add_range:

int __init_memblock memblock_add_range(struct memblock_type *type,
    phys_addr_t base, phys_addr_t size,
    int nid, enum memblock_flags flags)
{
 bool insert = false;
 phys_addr_t obase = base;
 phys_addr_t end = base + memblock_cap_size(base, &size);
 int idx, nr_new;
 struct memblock_region *rgn;

 if (!size)
  return 0;

 
 if (type->regions[0].size == 0) {
  WARN_ON(type->cnt != 1 || type->total_size);
  type->regions[0].base = base;
  type->regions[0].size = size;
  type->regions[0].flags = flags;
  memblock_set_region_node(&type->regions[0], nid);
  type->total_size = size;
  return 0;
 }
repeat:
 /*
  * The following is executed twice.  Once with %false @insert and
  * then with %true.  The first counts the number of regions needed
  * to accommodate the new area.  The second actually inserts them.
  */
 base = obase;
 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;
  /*
   * @rgn overlaps.  If it separates the lower part of new
   * area, insert that portion.
   */
  //如果新加入的内存起始地址已经到了,但是还没到遍历的内存则插入
  if (rbase > base) {
#ifdef CONFIG_HAVE_MEMBLOCK_NODE_MAP
   WARN_ON(nid != memblock_get_region_node(rgn));
#endif
   WARN_ON(flags != rgn->flags);
   nr_new++;
   if (insert)
    //添加内存区域,也就是填充struct memblock_region而已
    memblock_insert_region(type, idx++, base,
             rbase - base, nid,
             flags);
  }
  /* area below @rend is dealt with, forget about it */
  base = min(rend, end);
 }

 /* insert the remaining portion */
 if (base < end) {
  nr_new++;
  if (insert)
   memblock_insert_region(type, idx, base, end - base,
            nid, flags);
 }
 
 //如果需要加入的内存块个数为0则返回,不需要第二次遍历执行加入操作
 if (!nr_new)
  return 0;

 /*
  * If this was the first round, resize array and repeat for actual
  * insertions; otherwise, merge and return.
  */
 //第一次会进入,判断内存区域块是否达到上限,是则退出,否则回到repeat
 //因为insert参数原因,第一次没有真正插入,第二次才会真正的插入
 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;
 }
}

2)memblock删除内存区域函数:memblock_remove

int __init_memblock memblock_remove(phys_addr_t base, phys_addr_t size)
{
 phys_addr_t end = base + size - 1;

 memblock_dbg("memblock_remove: [%pa-%pa] %pS\n",
       &base, &end, (void *)_RET_IP_);

 return memblock_remove_range(&memblock.memory, base, size);
}

memblock_remove_range:

static int __init_memblock memblock_remove_range(struct memblock_type *type,
       phys_addr_t base, phys_addr_t size)
{
 int start_rgn, end_rgn;
 int i, ret;

 //要删除的内存区域内存区内的内存块存在重叠部分,把这部分需要独立出来
 ret = memblock_isolate_range(type, base, size, &start_rgn, &end_rgn);
 if (ret)
  return ret;

 //根据要删除内存区的索引号,删除内存区块
 for (i = end_rgn - 1; i >= start_rgn; i--)
  memblock_remove_region(type, i);
 return 0;
}

3)memblock分配内存函数:memblock_alloc

phys_addr_t __init memblock_alloc(phys_addr_t size, phys_addr_t align)
{
 return memblock_alloc_base(size, align, MEMBLOCK_ALLOC_ACCESSIBLE);
}


phys_addr_t __init memblock_alloc_base(phys_addr_t size, phys_addr_t align, phys_addr_t max_addr)
{
 phys_addr_t alloc;

 alloc = __memblock_alloc_base(size, align, max_addr);

 if (alloc == 0)
  panic("ERROR: Failed to allocate %pa bytes below %pa.\n",
        &size, &max_addr);

 return alloc;
}

phys_addr_t __init __memblock_alloc_base(phys_addr_t size, phys_addr_t align, phys_addr_t max_addr)
{
 return memblock_alloc_base_nid(size, align, max_addr, NUMA_NO_NODE,
           MEMBLOCK_NONE);
}

phys_addr_t __init memblock_alloc_base_nid(phys_addr_t size,
     phys_addr_t align, phys_addr_t max_addr,
     int nid, enum memblock_flags flags)
{
 return memblock_alloc_range_nid(size, align, 0, max_addr, nid, flags);
}

static 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,
     enum memblock_flags flags)
{
 phys_addr_t found;

 if (!align)
  align = SMP_CACHE_BYTES;

 //在给定范围和节点内找一块空区域
 found = memblock_find_in_range_node(size, align, start, end, nid,
         flags);
 //memblock_reserve是把找到的空区域添加到memblock.reserved中,表示已经用了
 if (found && !memblock_reserve(found, size)) {
  /*
   * The min_count is set to 0 so that memblock allocations are
   * never reported as leaks.
   */
   //一个内存块分配物理内存的通知
  kmemleak_alloc_phys(found, size, 0, 0);
  return found;
 }
 return 0;
}

4)memblock释放内存函数:memblock_free

int __init_memblock memblock_free(phys_addr_t base, phys_addr_t size)
{
 phys_addr_t end = base + size - 1;

 memblock_dbg("   memblock_free: [%pa-%pa] %pF\n",
       &base, &end, (void *)_RET_IP_);

 //通知释放部分内存块
 kmemleak_free_part_phys(base, size);
 return memblock_remove_range(&memblock.reserved, base, size);
}

static int __init_memblock memblock_remove_range(struct memblock_type *type,
       phys_addr_t base, phys_addr_t size)
{
 int start_rgn, end_rgn;
 int i, ret;

 //要删除的内存区域内存区内的内存块存在重叠部分,把这部分需要独立出来
 ret = memblock_isolate_range(type, base, size, &start_rgn, &end_rgn);
 if (ret)
  return ret;

 //根据要删除内存区的索引号,删除内存区块
 for (i = end_rgn - 1; i >= start_rgn; i--)
  memblock_remove_region(type, i);
 return 0;
}

7.memblock启动流程 1)解析设备树中的/memory,把所有物理内存添加到memblock 2)在memblock_init中初始化memblock linux启动从init/main.c文件的start_kernel函数开始,然后从文件setup_arch(arch/arm64/kernel/setup.c文件中)函数检测处理器类型,初始化处理器和内存,其中的arm64_memblock_init(arch/arm64/mm/init.c文件中)函数就是arm64架构的memblock初始化流程。

void __init arm64_memblock_init(void)
{
 const s64 linear_region_size = -(s64)PAGE_OFFSET;

 /* Handle linux,usable-memory-range property */
 //解析设备树文件的内存节点
 fdt_enforce_memory_region();

 /* Remove memory above our supported physical address size */
 //删除超出我们支持的物理地址大小的内存
 memblock_remove(1ULL << PHYS_MASK_SHIFT, ULLONG_MAX);

 /*
  * Ensure that the linear region takes up exactly half of the kernel
  * virtual address space. This way, we can distinguish a linear address
  * from a kernel/module/vmalloc address by testing a single bit.
  */
 BUILD_BUG_ON(linear_region_size != BIT(VA_BITS - 1));

 /*
  * Select a suitable value for the base of physical memory.
  */
 //全局变量memstart_addr记录了内存的起始物理地址
 memstart_addr = round_down(memblock_start_of_DRAM(),
       ARM64_MEMSTART_ALIGN);

 /*
  * Remove the memory that we will not be able to cover with the
  * linear mapping. Take care not to clip the kernel which may be
  * high in memory.
  */
 //把线性映射区无法覆盖的物理内存范围从memblock中删除
 memblock_remove(max_t(u64, memstart_addr + linear_region_size,
   __pa_symbol(_end)), ULLONG_MAX);
 if (memstart_addr + linear_region_size < memblock_end_of_DRAM()) {
  /* ensure that memstart_addr remains sufficiently aligned */
  memstart_addr = round_up(memblock_end_of_DRAM() - linear_region_size,
      ARM64_MEMSTART_ALIGN);
  memblock_remove(0, memstart_addr);
 }

 /*
  * Apply the memory limit if it was set. Since the kernel may be loaded
  * high up in memory, add back the kernel region that must be accessible
  * via the linear mapping.
  */
 //如果设置了内存限制,要根据限制使用内存
 if (memory_limit != PHYS_ADDR_MAX) {
  memblock_mem_limit_remove_map(memory_limit);//把超出限制的内存移除
  memblock_add(__pa_symbol(_text), (u64)(_end - _text));//添加可以使用的内存
 }

 if (IS_ENABLED(CONFIG_BLK_DEV_INITRD) && initrd_start) {
  /*
   * Add back the memory we just removed if it results in the
   * initrd to become inaccessible via the linear mapping.
   * Otherwise, this is a no-op
   */
  u64 base = initrd_start & PAGE_MASK;
  u64 size = PAGE_ALIGN(initrd_end) - base;

  /*
   * We can only add back the initrd memory if we don't end up
   * with more memory than we can address via the linear mapping.
   * It is up to the bootloader to position the kernel and the
   * initrd reasonably close to each other (i.e., within 32 GB of
   * each other) so that all granule/#levels combinations can
   * always access both.
   */
  if (WARN(base < memblock_start_of_DRAM() ||
    base + size > memblock_start_of_DRAM() +
           linear_region_size,
   "initrd not fully accessible via the linear mapping -- please check your bootloader ...\n")) {
   initrd_start = 0;
  } else {
   memblock_remove(base, size); /* clear MEMBLOCK_ flags */
   memblock_add(base, size);
   memblock_reserve(base, size);
  }
 }

 if (IS_ENABLED(CONFIG_RANDOMIZE_BASE)) {
  extern u16 memstart_offset_seed;
  u64 range = linear_region_size -
       (memblock_end_of_DRAM() - memblock_start_of_DRAM());

  /*
   * If the size of the linear region exceeds, by a sufficient
   * margin, the size of the region that the available physical
   * memory spans, randomize the linear region as well.
   */
  if (memstart_offset_seed > 0 && range >= ARM64_MEMSTART_ALIGN) {
   range /= ARM64_MEMSTART_ALIGN;
   memstart_addr -= ARM64_MEMSTART_ALIGN *
      ((range * memstart_offset_seed) >> 16);
  }
 }

 /*
  * Register the kernel text, kernel data, initrd, and initial
  * pagetables with memblock.
  */
 //把内核镜像占用的内存添加到memblock的预留区中,表示预留了不再分配出去
 memblock_reserve(__pa_symbol(_text), _end - _text);
#ifdef CONFIG_BLK_DEV_INITRD
 if (initrd_start) {
  memblock_reserve(initrd_start, initrd_end - initrd_start);

  /* the generic initrd code expects virtual addresses */
  initrd_start = __phys_to_virt(initrd_start);
  initrd_end = __phys_to_virt(initrd_end);
 }
#endif

 //扫描设备树中的保留内存区域并添加到memblock的预留区域中
 early_init_fdt_scan_reserved_mem();

 /* 4GB maximum for 32-bit only capable devices */
 if (IS_ENABLED(CONFIG_ZONE_DMA32))
  arm64_dma_phys_limit = max_zone_dma_phys();
 else
  arm64_dma_phys_limit = PHYS_MASK + 1;

 reserve_crashkernel();

 reserve_elfcorehdr();

 high_memory = __va(memblock_end_of_DRAM() - 1) + 1;

 dma_contiguous_reserve(arm64_dma_phys_limit);

 memblock_allow_resize();
}

最后,引导内存分配器退休,会将物理内存填充到伙伴分配器中,移交给伙伴分配器进行管理。

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

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

相关文章

OD笔试题-空汽水瓶可以换汽水

/*** 某商店规定&#xff1a;三个空汽水瓶可以换一瓶汽水&#xff0c;允许向老板借空汽水瓶&#xff08;但是必须要归还&#xff09;。* 小张手上有n个空汽水瓶&#xff0c;她想知道自己最多可以喝到多少瓶汽水。* 数据范围&#xff1a;输入的正整数满足 1≤n≤100* <p>*…

springboot 多环境配置yml

创建多个配置文件 创建文件时注意&#xff0c;一定是 application-文件名称.yml 这种格式 application.yml #主配置文件 application-dev.yml #开发环境的配置 application-prod.yml #生产环境的配置application-prod.yml # 生产环境端口为90 server:port: 90applica…

Python实现将一段话txt生成字幕srt文件

Python实现将一段话txt生成字幕srt文件 作者&#xff1a;虚坏叔叔 博客&#xff1a;https://xuhss.com 早餐店不会开到晚上&#xff0c;想吃的人早就来了&#xff01;&#x1f604; 一、为什么要将txt转换成字幕 1.1方便到剪辑软件剪辑 有时获取到一段文本&#xff0c;想要直…

点分治学习笔记

有时候我们会碰到一些树上的路径问题&#xff0c;如果需要处理的规模很大的话&#xff0c;这时候点分治是一个很好的工具&#xff0c;往往可以在O(nlogn)的复杂度内完成操作&#xff0c;一般用于离线处理问题 前置芝士 树的重心&#xff1a;最大子树的值最小的点叫做重心。 …

【手撕面试题】HTML+CSS(高频知识点二)

目录 面试官&#xff1a;页面导入样式时&#xff0c;使用 link 和 import 有什么区别&#xff1f; 面试官&#xff1a;简要说说 title与h1的区别、b与strong的区别、i与em的区别&#xff1f; 面试官&#xff1a;img标签的title和alt有什么区别&#xff1f; 面试官&#xff…

给特别规则FeignClient增加统一的RequestInterceptor

需求背景&#xff1a; 在微服务横行天下的今天&#xff0c;Spring Cloud OpenFeign 早就成了我们服务间调度的主要远程调用手段。 在Feign进行远程调度的时候&#xff0c;难免会做一些心跳&#xff0c;权限的校验&#xff0c;来保证调度过程的相对安全。 但对于外部系统来说…

Unity 之 Addressable可寻址系统 -- 资源远程加载 | 资源预下载 -- 进阶(三)

可寻址系统远程加载 -- 资源预下载 -- 进阶&#xff08;三&#xff09;一&#xff0c;Unity 云资源分发 -- 使用介绍1.1 CCD 的介绍1.2 后台准备工作二&#xff0c;CDD的使用2.1 CCD可视化界面的使用2.2 CDD命令行界面使用2.2.1 准备工作2.2.2 CLI 用法三&#xff0c;AA CCD资…

Java中的快速排序

快速排序递归版本挖坑法Hoare法优化非递归相信即使大家并不知道快速排序究竟是个啥,但也一定听说过快排,今天我来给兄弟们讲讲快速排序!递归版本 快速排序的思想就是找基准,就比如我们以数组中的第一个数字12为基准,我们从最后往前面找,如果找到一个比12小的数字就用它覆盖12,但…

Linux—InstallOS-RedHat9.1

下载https://developers.redhat.com/products/rhel/download 需注册账号。安装正常安装就行。安装注意事项&#xff1a;(1)Software SelectionCentOS的摘录过来&#xff0c;通用。最小安装&#xff08;Minimal Install&#xff09;这个选项只提供运行CentOS 的基本软件包。最小…

Python学习-----起步4(列表元素的添加,删除,修改,查询,获取长度)

目录 前言&#xff1a; 列表元素的添加&#xff08;或者叫写入&#xff09; 1.append&#xff08;&#xff09;函数 2.extend&#xff08;&#xff09;函数 3.insert()函数 列表元素的删除 1.remove() 函数 2. pop() 函数 3.clear&#xff08;&#xff09;函数 4.del …

公司40岁的程序員到底在写什么代码

去年在前公司玩了一年&#xff08;基本兩三個月一個需求&#xff09;&#xff0c;除了日常維護就一些特別簡單的功能開發&#xff0c;到年底也沒見到公司黃&#xff08;國企背景&#xff09;&#xff0c;沒辦法只好裸辭&#xff0c;現在這個公司各方面还不错&#xff0c;但是令…

Cookie、Session、Token、JWT只看这一篇文章就够了

什么是认证&#xff08;Authentication&#xff09; 通俗地讲就是验证当前用户的身份&#xff0c;证明“你是你自己”&#xff08;比如&#xff1a;你每天上下班打卡&#xff0c;都需要通过指纹打卡&#xff0c;当你的指纹和系统里录入的指纹相匹配时&#xff0c;就打卡成功&a…

MongoDB Map Reduce

在用 MongoDB 查询时&#xff0c;若返回的数据量很大&#xff0c;或者做一些比较复杂的统计和聚合操作做花费的时间很长时&#xff0c;可以使用 MongoDB 中的 mapReduce 进行实现。mapReduce 是个灵活且强大的数据聚合工具&#xff0c;它的好处是可以把一个聚合任务分解为多个小…

设计模式(三)----创建型模式之单例模式(一)

一、创建型模式 创建型模式的主要关注点是“怎样创建对象&#xff1f;”&#xff0c;它的主要特点是“将对象的创建与使用分离”。 这样可以降低系统的耦合度&#xff0c;使用者不需要关注对象的创建细节。 创建型模式分为&#xff1a; 单例模式 工厂方法模式 抽象工厂模式…

英语学习 3

1 词汇积累 1、ships 船 2、class 级 3、marvels 奇迹 4、marvelous 非凡的、了不起的、极好的 5、cursed 诅咒、被诅咒的 6、the most luxurious ships 最豪华的船 7、luxury 奢侈、奢华的 8、luxurious 心满意足的、舒适的 9、utmost 极度的、最大的 10、kind 种类 11、voya…

Kali Linux神秘工具教程(详细版)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录前言一、Kali Linux - 安装和配置信息收集工具二、NMAP隐形扫描搜索Searchsploit域名系统工具dnsenum.plDNSMAPdnstracerLBDHping3漏洞分析工具Cisco-torch工具Cisco…

回溯算法(基础)

目录 一、基本概念 二、以简单全排列认识回溯 &#xff08;一&#xff09;决策树 &#xff08;二&#xff09;回溯示意图 &#xff08;三&#xff09;核心代码 &#xff08;四&#xff09;完整代码 三、组合问题 &#xff08;一&#xff09;问题 &#xff08…

如何通过groovy扩展方法

最近一直使用jmeter做接口测试&#xff0c;虽然好用&#xff0c;但是每次解析结果都要写大量重复代码。然后想到groovy是可以在运行时动态增强jvm字节码的&#xff0c;比如Date中就有大量增强的方法&#xff0c;比如format,upto,downto......&#xff0c;既然groovy可以&#x…

用 NFTScan 的角度解析 Yuga labs NFT 项目系列

如果要说 NFT 影响力最大的公司是哪个&#xff1f;如果说是 Yuga Labs 应该我想大家应该都不会否认。一个创立一年多的 NFT 营销和开发公司&#xff0c;多次的并购以及行销操作都立下 NFT 界的标竿典范&#xff0c;尤其 BAYC NFT 系列取得巨大成功之后&#xff0c;该团队已成为…

DSP_定义一个大的全局数组_探索之路

前言 最近在做基于dsp平台的无通信接口系统辨识&#xff0c;辨识的时候会有很大的数据需要存到一个数组当中&#xff0c;而dsp如果定义一个很大的全局数组&#xff0c;编译会报错。 本文将探索如何解决这个报错以及全局数组的大小极限。 正文 首先&#xff0c;我们定义了一个…