jemalloc 5.3.0的base模块的源码及调用链使用场景的详细分析
一、背景
这篇博客,我们继续之前的 由jemalloc 5.3.0初始化时的内存分配的分析引入jemalloc的三个关键概念及可借鉴的高性能编码技巧-CSDN博客 博客里对初始化分配逻辑进行分析,已经涉及到了jemalloc 5.3.0里的非常重要的base模块的一部分逻辑,在这篇博客里,我们进一步展开分析base模块,针对的场景也依然是初始化分配逻辑这块来作为切入口。
在第二章里,我们先顺着之前的博客 跟踪jemalloc 5.3.0的第一次malloc的源头原因及jemalloc相关初始化细节拓展-CSDN博客 里 3.2.4 里重点提到的malloc_init_hard函数,继续展开分析,在 跟踪jemalloc 5.3.0的第一次malloc的源头原因及jemalloc相关初始化细节拓展-CSDN博客 博客里,我们分析了初始化逻辑里的tsd模块的初始化逻辑,在 由jemalloc 5.3.0初始化时的内存分配的分析引入jemalloc的三个关键概念及可借鉴的高性能编码技巧-CSDN博客 博客里,我们讲到了几个关键概念group、delta、class还有sz.h里几个常用的psz,size,index之间的转换函数及相关含义,有了这两篇的基础,base模块的详细分析相对容易一些。
我们在第二章,我们依然以malloc_init_hard里内存分配逻辑作为切入口,来展开详细分析base模块,对于相关联的概念也会一一进行介绍,在第三章里,我们会用思维导图来汇总一下并附上总结说明。
二、依然以malloc_init_hard里的内存分配逻辑作为切入口,展开详细分析base模块
2.1 base_boot里的2M的分配用的是base_block_alloc函数,是base模块的分配函数
jemalloc 5.3.0的第一次内存分配的地方就是这个malloc_init_hard_a0_locked里的base_boot函数最终调用的base_map进行的2M的分配。
这块我们在之前的博客 由jemalloc 5.3.0初始化时的内存分配的分析引入jemalloc的三个关键概念及可借鉴的高性能编码技巧-CSDN博客 里已经做了一些介绍,这篇博客里需要再展开分析一下。
base_boot有关的调用链是:
malloc_init_hard->malloc_init_hard_a0_locked->base_boot->base_new->base_block_alloc->base_map
我们先说一下base模块是什么?用来干什么?
2.2 base模块是一个metadata数据的一个分配器
base模块的文件就两个base.h和base.c。
base模块是一个metadata数据的一个分配器。那么什么是metadata数据呢?metadata就是元数据,也就是数据的数据。
base模块只分配元数据的内存,而并不分配malloc的客户所要的内存,也就是说,malloc的使用者所要的内存是由jemalloc里的base分配器以外的其他的底层内存分配器来分配的,这一点我们下面会用堆栈截图来证明。
2.2.1 三个用于分配的base接口都可能会调用到base_block_alloc函数
base模块用于分配的主要接口是三个,base_new函数和base_alloc和base_alloc_edata,其中base_alloc和base_alloc_edata函数,其中base_new用于base的初始化(base本身也是一个元数据),另外两个,则是在指定的base里分配元数据,当然,虽然说在指定的base里分配,但是还是可能按需扩容的,并不是说base_new分配出内存了以后,base_alloc和base_alloc_edata就不会分配新的内存了(关于非base分配器来分配出来的调用链例子在下面 2.4 一节里会讲到)。这三个函数在需要分配新的内存时都会调用base_block_alloc函数,这个函数我们会在后面详细展开描述。
下图是base_new函数调用base_block_alloc函数的截图:

base_alloc和base_alloc_edata,这两个函数都会调用base_alloc_impl:

而base_alloc_impl会在判断出当前空间不够时调用base_extent_alloc进行更多内存的分配:

而base_extent_alloc就会调用base_block_alloc进行内存分配:

2.2.2 分配元数据的base_alloc_edata接口相比base_alloc接口需要在base_alloc_impl时取回sn号,设到base_alloc_edata接口返回的是edata_t指针里去
base_alloc和base_alloc_edata两个函数的主要区别在于base_alloc_edata需要在base_alloc_impl时取回sn号,也就是序列号,并设到新创建的edata_t这个元数据里。
这个sn号是base实例管理的,在base结构初始化时设置成0:

在base_new里创建完元数据后,把上图里的extent_sn_next记录到了base实例里:
![]()
base_new里调用base_block_alloc创建元数据时会把传入的表示sn号的指针指向的值加1:

base_block_alloc里调用了base_edata_init函数:

在base_edata_init函数里进行了+1:

你可能会问,这个多出来的sn号有什么用,在下面的 2.7.1 里会讲到。
2.3 base模块分配了哪些元数据?
我们列一下base模块分配的元数据的种类,列出的都是相对重要的元数据,用一次调用栈例子来说明(当然分配的同一种元数据对应的调用栈也可能是不一样的,我们只是举其中的一次调用栈来说明)。
2.3.1 base模块首先会分配base_t实例,也就是base自己这个元数据

上图的堆栈是main之前的第一次malloc的调用,这次malloc的调用我们在之前的博客 跟踪jemalloc 5.3.0的第一次malloc的源头原因及jemalloc相关初始化细节拓展-CSDN博客 里也说明了是因为preload的jemalloc库时由于jemalloc的实现里包含了C++的内容,所以需要在main之前的初始化流程里做相关的C++的异常处理用的pool的分配。而上图中的堆栈,是由这一次分配触发,判断出整个jemalloc还未进行初始化,所以调用了malloc_init_hard接口(关于malloc_init_hard接口我们之前的博客里 跟踪jemalloc 5.3.0的第一次malloc的源头原因及jemalloc相关初始化细节拓展-CSDN博客 的 3.2.4 分析过一部分),这个函数会间接调用base_boot来初始化第一个base实例,base_boot继而调用了base_new,base_new继而调用了base_block_alloc进行了分配,base_block_alloc使用base_map,base_map调用pages_map,注意,pages_map是base分配器以外的其他jemalloc内存分配器都会用到的一个接口,定义在src/pages.c里,它并不属于base模块。
看base_boot的返回类型就可以体现它分配的就是base_t这个元数据实例:


0是第一个base,第一个base由一个src/base.c里的上截图的static base_t *b0变量来保存的。
2.3.2 base模块会分配提供静态的tcache的tbins信息的cache_bin_info_t数组
cache_bin_info_t数组的首地址定义如下:

它需要动态分配因为其大小不可静态确定。
相关的核心调用代码截图:

可以看到上图里的这次分配大小不大,就82字节,n_reserved_bins是41,41来自于nhbins,因为nhbins比SC_NBINS的值大(SC_NBINS是36):
![]()
相关的上下文调用链如下:

2.3.3 base模块会分配管理arena的arena_t的内存
arena是一个内存分配区,不同的线程一般属于不同的arena,默认情况下arena的个数是cpu核心数*4。
base模块分配arena实例的代码逻辑如下:

如上图可以看到一个关于arena实例分配的细节:
arena_t的大小是一个动态值,因为arena_t用了柔性数组:

相关base分配arena的调用链截图:

要注意,这次main之前的这个arena的分配由于分配的空间不大,用之前base分配出来的内存池子里的剩余空间就足够了,所以这次分配并没有触发base_map及底下分配接口。关于从base里挑选可用的空间来进行分配的逻辑,见 2.7 一节。
2.3.4 base模块会分配用来管理实际使用数据内存块的edata_t管理结构
先说明一下edata,它是管理的实际使用的数据内存块,这么说,edata还是一个元数据的数据结构,它管理对应的内存分为两种,一种是用户使用malloc接口触发进行分配的内存,另一种是jemalloc实现的内部逻辑调用泛iallocztm或泛ipallocztm的接口进行内部使用的内存分配。
jemalloc对于不同大小的内存会有不同的策略,对于小size的class,jemalloc会预分配更多块这样的小块,从而组成一个大块的内存块,这个大块的内存块是page size的整数倍。当然对于大size的class,jemalloc就不会预分配这样的内存块了。而edata_t就是管理这样的整块内存块的数据结构。
下图的场景仍然是main之前的在做初始化时的调用栈,下图是在初始化tsd的tcache data的过程中分配各个size class的tcache_bin用的stack_head指针数组的内存时,需要在具体分配该size的内存对应的内存块的分配前(2.4.1 一节的截图流程),先分配该size class的内存块对应的元数据edata数据结构。

上图里的base_alloc_edata所调用base_alloc_impl分配的大小是很小的,如下图:

就128字节:

和 2.3.2 同样的,由于分配的大小很小,所以直接从base里的当前的block块里剩下的内存里扣出一块出来就可以了,它并不会触发base_map及其他底层分配动作。
在启动过程中,关于edata_t管理结构分配的另外一个场景是在tsd_tcache_data_init时在进行分配各个size class的tcache_bin用的stack_head指针数组的内存对应的内存块时,发现通过ehooks_alloc分配出来的一大块extent内存块后有不用的多出来的部分,这部分剩下的内存也需要创建对应的edata管理结构,在创建该剩下的内存的edata管理结构时走到的edata_cache_get再到base_alloc_edata再到base_alloc_impl里,而这个最后三级函数的调用是和刚才说的场景是一致的,在extent_grow_retained函数内到最后三级函数edata_cache_get->base_alloc_edata->base_alloc_impl中间还有两级调用,为extent_split_interior->extent_split_impl,有关完整调用链可见第三章的调用链图。
2.3.5 base模块会分配用来管理关联每个edata信息所需要的radix tree所用到的叶子结构的内存
jemalloc里有个全局唯一的管理关联每个edata信息的radix tree,即jemalloc里的rtree,这个rtree需要用到rtree_leaf_elm_t结构,所需要的该结构体的数量即radix tree的一层一层的数量,这个数量是按照索引项的bit来决定的,一层是18bit,如下图:

所以一层用到了1<<18=26144个,如下图看到rtree_levels里就两层:

这种情况所触发的函数,如下图,是在rtree_leaf_init函数里:

调用链(也是在初始化时场景):

由于这次分配的大小较大:

所以需要通过pages_map来向os要内存:

上图里的pages_map实际做分配的size是在这次分配的调用链里是由base模块里的pind_last变量来管理维护的,每次分配都要+1,因为+1后,得是HUGEPAGE_CEILING进行2M的对齐,所以就变成了分配4M了,有关pind_last的逻辑具体细节可参考之前的博客 由jemalloc 5.3.0初始化时的内存分配的分析引入jemalloc的三个关键概念及可借鉴的高性能编码技巧-CSDN博客 里 2.2.5 一节。
2.3.6 base模块会分配background线程所用到的background_thread_info_t数据结构
相关的调用截图和调用链截图如下:


2.4 非base分配器分配出内存的调用栈例子
这一节并不是本文的重点,我们并不过多展开,只是把相关调用栈贴出并描述相关调用场景。
这里说的其他分配器,是指离调用os的mmap接口(jemalloc默认只用mmap进行内存分配)进行的内存分配非常近的调用层次的分配函数。jemalloc里pages_map接口封装了这样的os的mmap接口的内存分配,作为一个接口给jemalloc里较底层的分配器所使用。所以,无论是base分配器还是其他的下面会讲到的分配器,最终还是会调用到pages_map接口。
2.4.1 使用extent_alloc_core分配函数
下图调用链是最终用到了extent_alloc_core函数,而extent_alloc_core函数和base_map一样也是调用的extent_alloc_mmap函数,下图调用链所在的场景是在初始化tsd的tcache data的过程中分配各个size class的tcache_bin用的stack_head指针数组的内存对应的内存块,大小在对齐PAGE_SIZE后是32768字节:

上截图也是在初始化流程中,是在 2.3.4 截图流程之后,在 2.3.5 截图的流程之前。
2.4.2 其他调用pages_map的分配器
调用extent_alloc_mmap进行分配的函数就base_map和 2.4.1 说的extent_alloc_core两个。
但是使用pages_map进行分配的函数还有一些,如hpa模块:

hpa_hooks模块:

这里先不展开了。
2.5 base_block_alloc函数有点像分配一个内存池子,每个base都至少有一个内存池子
我们回头来说base_block_alloc函数,上面讲到,base模块里的最终向操作系统去做分配的最终都汇总到了调用这个base_block_alloc函数里。
这个base_block_alloc函数有点像是预分配一个内存池。后面的大大小小的分配都需要在这个池子里进行再分配(只是说这个内存池的管理是按照之前博客说的group delta这样管理的),当然这个池子是可以变大的,但是每一次变大的最小颗粒度很大,之前的博客也说了是至少分配2M,且一次分配比一次分配大,参考之前的 由jemalloc 5.3.0初始化时的内存分配的分析引入jemalloc的三个关键概念及可借鉴的高性能编码技巧-CSDN博客 博客里的 2.2.5 一节。
刚才说的这个内存池子的分配就是下图里的通过base_block_alloc函数得到的base_block_t:

可以从下图中看到,分配出来的block塞到了base_t结构体里的block链表里(下图是因为是第一个block,所以直接赋值就行了):
![]()
如果是新增block,则是下图里的红色框逻辑的逻辑(目前新增block只会被base_extent_alloc函数用到):

因为同一个base实例而言,可以新关联base_block_t,所以就有这一节标题里说的每个base都至少有一个内存池子,意思就是可以有大于一个内存池子。
2.6 base_block_alloc函数分配出来的池子是对应base编号的,且base编号是和arena的编号是一一对应的
1)所谓的base_block_alloc函数分配出来的池子是对应base编号的,意思就是base0至少有一个内存池子,如果有base1的话,那么base1也至少有一个内存池子,如果一个base里发现内存不够,可以新申请一个内存池子再与这个base关联。每笔使用base_block_alloc函数进行的内存分配,都需要明确一个所属的base。
如下图,传入给base_block_alloc函数的unsigned ind就是这个base编号:

0是第一个base,第一个base由一个base.c里的static base_t变量来保存是:


2)刚才说的base编号是和arena的编号一一对应的。下图是代码里能体现逻辑上arena的index对应于base的index的代码细节:


3)顺便提及一个arena的细节,对于大size的内存分配,jemalloc把它归到了最后一个index的arena里,即,如果cpu的数量是20,大内存分配用的是最后一个arena即arena80(假设arena0是第一个)。
这块细节我们在以后的博客里会讲到。
2.7 base里挑选可用的空间来进行分配的逻辑
base里的这个挑选的逻辑就一处,在base_alloc_impl里,这个base_alloc_impl就是上面讲过的两个分配元数据的接口base_alloc和base_alloc_edata必经调用到的。
2.7.1 “挑选”逻辑用到了配对堆 Pairing Heap
在base_alloc_impl里,有下面这段逻辑做所谓的“挑选”:

上图里的红色框出的逻辑里,核心函数edata_heap_remove_first是使用了配对堆的数据结构,如下图看到base的avail数组就是配对堆的数据结构:

base_alloc_impl的实现就是从base里的最多SC_NSIZES个配对堆里:
![]()
从最小的可能满足size大小的配对堆往size更大的堆去找,直到遇到非空的配对堆就返回堆里最小的元素。至于如何判断堆里元素谁大谁小,见 2.7.3 一节。
我们先看一下配对堆的宏定义和使用。
2.7.2 配对堆的宏定义和使用
jemalloc里除了base模块里的内存管理以外,数据内存块的管理者edata_cache模块和huge page的管理模块hpdata也使用到了配对堆,所以,配对堆的声明总共有3处,两处在edata.h里,另外一处在hpdata.h里:


其中,base模块用的是edata_heap。
配对堆的定义在各自的.c里,如base模块用到的edata_heap是定义在edata.c里,如下图显示了base模块用到的配对堆关联的配对函数edata_snad_comp:

配对堆的一系列函数的函数名是由大量的宏拼接形成的,如下:

2.7.3 base用到的配对堆如何比较谁大谁小?
我们看一下决定base模块用的配对堆里的元素,决定谁大谁小的逻辑,上一节也说到了,该函数是edata_snad_comp:


可以看到它是先比较edata的sn号,关于edata的sn号,我们在上面的 2.2.2 一节里说到了是如何生成的。
比较完sn号,如果是一样的话,再去比较地址大小。
这么比较是为了让早分配的内存更早被释放,可以减少内存碎片。
2.7.4 base分配时选到的配对堆元素可能大于实际需要的大小,相关的塞回配对堆的逻辑介绍
从上面的 2.7.1 一节里就可以看到,base分配时选到的配对堆元素可能大于实际需要的大小。
我们看一下相关的塞回配对堆的逻辑在哪里,base模块用的是base_extent_bump_alloc_post进行的塞回动作。
下图是base_extent_bump_alloc_post的选择塞哪里的逻辑:

可以看到是用的floor,也就是在塞的时候,会塞到它实际所属的index的下一个,这样在分配时拿到的元素肯定都能满足当前这个delta组里的所有size的情况。关于delta以及group等概念见之前的博客 由jemalloc 5.3.0初始化时的内存分配的分析引入jemalloc的三个关键概念及可借鉴的高性能编码技巧-CSDN博客 里的 2.2.4 一节。
三、malloc_init_hard时的所有内存分配动作的调用流程图
细心的同学会发现,上面第二章里的所有截图出来的堆栈调用链都是在malloc_init_hard下的函数调用的。
我们把这个期间的所有的内存分配的逻辑调用都在上面一一列出来了,进行了分析。
下面是一张整图,如果需要再看得清楚一些可以参考我发布的资源里的图,资源链接 https://download.csdn.net/download/weixin_42766184/90385037。

相关文章:
jemalloc 5.3.0的base模块的源码及调用链使用场景的详细分析
一、背景 这篇博客,我们继续之前的 由jemalloc 5.3.0初始化时的内存分配的分析引入jemalloc的三个关键概念及可借鉴的高性能编码技巧-CSDN博客 博客里对初始化分配逻辑进行分析,已经涉及到了jemalloc 5.3.0里的非常重要的base模块的一部分逻辑ÿ…...
ThreadLocal源码分析
文章目录 1.核心数据结构 ThreadLocalMap1.静态内部类 Entry2.真正存储数据的是table数组 2.ThreadLocal.set()方法源码详解1.set2.getMap3.ThreadLocalMap.set4.createMap5.rehash6.resize 3.ThreadLocalMap.get()详解1.get2.ThreadLocalMap.getEntry3.getEntryAfterMiss 4.Th…...
Python爬虫实战:获取笔趣阁图书信息,并做数据分析
注意:以下内容仅供技术研究,请遵守目标网站的robots.txt规定,控制请求频率避免对目标服务器造成过大压力! 1. 环境准备与反爬策略 python import requests from bs4 import BeautifulSoup import pandas as pd import re import time import random from fake_useragent …...
如何在Java EE中使用标签库?
在Java EE(现在称为Jakarta EE)中使用标签库(Tag Library),主要是通过JSP标准标签库(JSTL)或自定义标签库来实现的。标签库允许在JSP页面中使用自定义的标签,从而简化页面逻辑、增强…...
3天功能开发→3小时:通义灵码2.0+DEEPSEEK实测报告,单元测试生成准确率92%的秘密
前言 随着人工智能技术的迅猛发展,AI 赋能编程成为了必然趋势。通义灵码应运而生,它是阿里巴巴集团在人工智能与编程领域深度探索的结晶。通义灵码旨在借助 AI 的强大能力,为开发者提供更加智能、高效的编程辅助工具。通义灵码 2.0 作为其升…...
STM32 Flash详解教程文章
目录 Flash基本概念理解 Flash编程接口FPEC Flash擦除/写入流程图 Flash选项字节基本概念理解 Flash电子签名 函数读取地址下存放的数据 Flash的数据处理限制部分 编写不易,请勿搬运,感谢理解!!! Flash基本概念…...
ubuntu服务器部署
关闭欢迎消息 服务器安装好 ubuntu 系统后,进行终端登录,会显示出很多的欢迎消息 通过在用户的根目录下执行 touch .hushlogin 命令,再次登录终端就不会出现欢迎消息 修改hostname显示 修改 /etc/hostname 文件内容为主机名,保…...
小爱音箱控制手机和电视听歌的尝试
最近买了小爱音箱pro,老婆让我扔了,吃灰多年的旧音箱。当然舍不得,比小爱还贵,刚好还有一台红米手机,能插音箱,为了让音箱更加灵活,买了个2元的蓝牙接收模块Type-c供电3.5接口。这就是本次尝试起…...
问卷数据分析|SPSS实操之独立样本T检验
适用条件: 检验分类变量和定量变量之间的差异 分类变量只能为二分类变量,如性别 1.选择分析--比较平均值--独立样本检验 2. 在下方选择性别(分类变量) 3. 点击定义组,组1输入1,组2输入2 4.在上方填入定量…...
Linux 内核 IPoIB 驱动中 sysfs 属性冲突问题的分析与解决
一、引言 在 Linux 内核的设备驱动开发中,sysfs 文件系统是一种重要的机制,用于向用户空间暴露内核对象的属性信息。通过 sysfs,用户空间程序可以读取或修改设备的属性,从而实现对硬件设备的监控和配置。然而,在实际开发中,可能会遇到 sysfs 属性冲突的问题,特别是在复…...
双ESP8266-01S通讯UDP配置
第一台ESP8266(发送命令需要勾---发送新行) ATCWMODE3 ATCWSAP_DEF"CAR_wifi_Master","12345678",5,3 //设置本地wifi名称以及密码 ATCIPSTA_DEF"192.168.4.1" //设置本地IP ATCIFSR …...
【C】初阶数据结构5 -- 栈
前面学习了两种最基本的数据结构 -- 顺序表和链表,接下来就可以基于这两种数据结构来实现其他数据结构了。其实,其他的数据结构的物理结构要么是数组,要么就是链表,所以学好顺序表和链表是学好其他数据结构的基础。接下里…...
闭源大语言模型的怎么增强:提示工程 检索增强生成 智能体
闭源大语言模型的怎么增强 提示工程 检索增强生成 智能体 核心原理 提示工程:通过设计和优化提示词,引导大语言模型进行上下文学习和分解式思考,激发模型自身的思维和推理能力,使模型更好地理解和生成文本,增强其泛用性和解决问题的能力。检索增强生成:结合检索的准确…...
C语言-------结构体(1)
数据类型 (1)基本数据类型 整型 浮点型 字符型 (2)构造类型 数组 结构体 结构体: 用来处理,现实生活中,更复杂的数据的描述 用来 描述复杂数据的 一种用户自定义的数…...
org.apache.kafka.common.errors.TimeoutException
个人博客地址:org.apache.kafka.common.errors.TimeoutException | 一张假钞的真实世界 使用kafka-console-producer.sh向远端Kafka写入数据时遇到以下错误: $ bin/kafka-console-producer.sh --broker-list 172.16.72.202:9092 --topic test This is …...
Ceph集群搭建2025(squid版)
squid版本维护年限 apt install -y cephadmecho >> "deb http://mirrors.163.com/ceph/debian-squid/ bookworm main" echo >> "deb-src http://mirrors.163.com/ceph/debian-squid/ bookworm main"#安装源 cephadm install #开始初始化一个最…...
DeepSeek从入门到精通:提示词设计的系统化指南
目录 引言:AIGC时代的核心竞争力 第一部分 基础篇:提示词的本质与核心结构 1.1 什么是提示词? 1.2 提示词的黄金三角结构 第二部分 类型篇:提示词的六大范式 2.1 提示语的本质特征 2.2 提示语的类型 2.2.1 指令型提示词 …...
python后端调用Deep Seek API
python后端调用Deep Seek API 需要依次下载 ●Ollama ●Deepseek R1 LLM模型 ●嵌入模型nomic-embed-text / bge-m3 ●AnythingLLM 参考教程: Deepseek R1打造本地化RAG知识库:安装部署使用详细教程 手把手教你:deepseek R1基于 AnythingLLM API 调用本地…...
自有证书的rancher集群使用rke部署k8s集群异常
rancher使用自签域名,或者商业证书容易踩到的坑。 最开始的报错: docker logs kubelet‘s id E0214 13:04:14.590268 9614 pod_workers.go:1300] "Error syncing pod, skipping" err"failed to \"StartContainer\" for …...
【线性代数】1行列式
1. 行列式的概念 行列式的符号表示: 行列式的计算结果:一个数 计算模型1:二阶行列式 二阶行列式: 三阶行列式: n阶行列式: 🍎计算行列式 计算模型2:上三角形行列式 上三角形行列式特征:主对角线下皆为0。 上三角形行列式: 化上三角形通用方法:主对角线下,…...
观成科技:隐蔽隧道工具Ligolo-ng加密流量分析
1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具,该工具基于TUN接口实现其功能,利用反向TCP/TLS连接建立一条隐蔽的通信信道,支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式,适应复杂网…...
《Playwright:微软的自动化测试工具详解》
Playwright 简介:声明内容来自网络,将内容拼接整理出来的文档 Playwright 是微软开发的自动化测试工具,支持 Chrome、Firefox、Safari 等主流浏览器,提供多语言 API(Python、JavaScript、Java、.NET)。它的特点包括&a…...
postgresql|数据库|只读用户的创建和删除(备忘)
CREATE USER read_only WITH PASSWORD 密码 -- 连接到xxx数据库 \c xxx -- 授予对xxx数据库的只读权限 GRANT CONNECT ON DATABASE xxx TO read_only; GRANT USAGE ON SCHEMA public TO read_only; GRANT SELECT ON ALL TABLES IN SCHEMA public TO read_only; GRANT EXECUTE O…...
DIY|Mac 搭建 ESP-IDF 开发环境及编译小智 AI
前一阵子在百度 AI 开发者大会上,看到基于小智 AI DIY 玩具的演示,感觉有点意思,想着自己也来试试。 如果只是想烧录现成的固件,乐鑫官方除了提供了 Windows 版本的 Flash 下载工具 之外,还提供了基于网页版的 ESP LA…...
【C++从零实现Json-Rpc框架】第六弹 —— 服务端模块划分
一、项目背景回顾 前五弹完成了Json-Rpc协议解析、请求处理、客户端调用等基础模块搭建。 本弹重点聚焦于服务端的模块划分与架构设计,提升代码结构的可维护性与扩展性。 二、服务端模块设计目标 高内聚低耦合:各模块职责清晰,便于独立开发…...
Python ROS2【机器人中间件框架】 简介
销量过万TEEIS德国护膝夏天用薄款 优惠券冠生园 百花蜂蜜428g 挤压瓶纯蜂蜜巨奇严选 鞋子除臭剂360ml 多芬身体磨砂膏280g健70%-75%酒精消毒棉片湿巾1418cm 80片/袋3袋大包清洁食品用消毒 优惠券AIMORNY52朵红玫瑰永生香皂花同城配送非鲜花七夕情人节生日礼物送女友 热卖妙洁棉…...
佰力博科技与您探讨热释电测量的几种方法
热释电的测量主要涉及热释电系数的测定,这是表征热释电材料性能的重要参数。热释电系数的测量方法主要包括静态法、动态法和积分电荷法。其中,积分电荷法最为常用,其原理是通过测量在电容器上积累的热释电电荷,从而确定热释电系数…...
Golang——6、指针和结构体
指针和结构体 1、指针1.1、指针地址和指针类型1.2、指针取值1.3、new和make 2、结构体2.1、type关键字的使用2.2、结构体的定义和初始化2.3、结构体方法和接收者2.4、给任意类型添加方法2.5、结构体的匿名字段2.6、嵌套结构体2.7、嵌套匿名结构体2.8、结构体的继承 3、结构体与…...
关于easyexcel动态下拉选问题处理
前些日子突然碰到一个问题,说是客户的导入文件模版想支持部分导入内容的下拉选,于是我就找了easyexcel官网寻找解决方案,并没有找到合适的方案,没办法只能自己动手并分享出来,针对Java生成Excel下拉菜单时因选项过多导…...
stm32wle5 lpuart DMA数据不接收
配置波特率9600时,需要使用外部低速晶振...
