[TOC]
❓ 问题描述:
描述问题现象




日志
添加打印日志信息
分析步骤
第1步:
第2步:
...
代码片段
- alloc_pages分配内存示例,从buddy系统中获取内存,属于物理内存管理范畴
 
//第1种方式:从buddy系统中分配内存和释放内存,配对使用
struct page *alloc_pages(gfp_t gfp_mask, unsigned int order)
void __free_pages(struct page *page, unsigned int order);
//第2种方式:从buddy系统中分配内存和释放内存,配对使用
unsigned long __get_free_pages(gfp_t gfp_mask, unsigned int order);
void free_pages(unsigned long addr, unsigned int order);
struct page *alloc_pages(gfp_t gfp_mask, unsigned int order)
gfp_mask(内存分配标志)
    用于指定内存分配的行为和约束,是一组标志的组合(通过位或 | 操作)。常用标志包括:
    分配优先级与上下文:
    GFP_KERNEL:最常用,允许睡眠等待内存(适用于进程上下文)。
    GFP_ATOMIC:不允许睡眠(适用于中断、软中断等原子上下文)。
    GFP_USER:为用户空间分配内存(如页缓存)。
    GFP_HIGHUSER:从高端内存(high memory)分配,供用户空间使用。
    内存区域限制:
    __GFP_DMA:仅从 DMA 区域分配内存(适用于需要 DMA 传输的设备)。
    __GFP_HIGHMEM:允许从高端内存分配(可能需要 kmap 映射访问)。
    其他行为控制:
    __GFP_ZERO:分配后将内存清零。
    __GFP_RETRY_MAYFAIL:允许分配失败前多次重试(提高成功率)。
    
2. order(分配阶数)
    指定分配的连续物理页数量,单位是 “2 的阶数次方”:
    order = 0:分配 2^0 = 1 个页(单个页)。
    order = 1:分配 2^1 = 2 个连续页。
    order = n:分配 2^n 个连续页(总大小为 2^n * PAGE_SIZE)。
    最大阶数由内核宏 MAX_ORDER 定义(一般为 11 或 12),对应最大可分配 2^11 = 2048 个页(若页大小为 4KB,则约 8MB)
/*
 * @Author: your name
 * @Date: 2025-09-05 15:50:58
 * @LastEditTime: 2025-09-05 16:55:54
 * @LastEditors: Please set LastEditors
 * @Description: In User Settings Edit
 * @FilePath: linux-4.14.143driversgpudrm	estmemory.c
 */
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/mm.h>
#include <linux/gfp.h>
#include <linux/delay.h>       // msleep 函数
static int __init alloc_pages_init(void) {
    struct page *pages;
    void *vaddr;
    unsigned int i;
    unsigned int order = 2;  // 分配2^2=4个连续页
    size_t size = (1 << order) * PAGE_SIZE;
    //一个page的大小是PAGE_SIZE=4096B=4K大小
    printk(KERN_INFO "PAGE_SIZE : %ld
", PAGE_SIZE);
    // 分配连续物理页,order=2,分配2^order = 4 个连续页,每个页4K大小,总大小16K,返回的是page
    pages = alloc_pages(GFP_KERNEL, order);
    if (!pages) {
        printk(KERN_ERR "alloc_pages failed
");
        return -ENOMEM;
    }
    printk(KERN_INFO "alloc_pages (order %d) 分配信息:
", order);
    printk(KERN_INFO "  总大小: %zu 字节 (%d 页)
", size, 1 << order);
    printk(KERN_INFO "  各页详细信息:
");
    /*
        涉及4个类型变量:
            pages: 分配的页结构体数组
            vaddr: 虚拟地址
            phys_addr: 物理地址
            pfn: 页帧号
    */
    // 遍历每个页,打印地址信息,其中页帧号是连续的
    for (i = 0; i < (1 << order); i++) {
        struct page *page = &pages[i];  // 连续页的page结构在数组中连续
        //根据page获取虚拟地址
        vaddr = page_address(page);
        //根据page获取物理地址
        phys_addr_t phys_addr = page_to_phys(page);
        //根据page获取页帧号
        unsigned long pfn = page_to_pfn(page);
        printk(KERN_INFO "  第 %d 页:
", i);
        printk(KERN_INFO "    虚拟地址: 0x%p,phys_to_virt=0x%p
", vaddr,phys_to_virt(phys_addr)); //从物理地址获取虚拟地址
        printk(KERN_INFO "    物理地址: 0x%pa,virt_to_phys=0x%pa,phys_addr = 0x%lx
", &phys_addr,&virt_to_phys(vaddr),phys_addr); //从虚拟地址获取物理地址
        printk(KERN_INFO "    页帧号: 0x%lx
", pfn);
    }
    // 释放内存
    __free_pages(pages, order);
    return 0;
}
static void __exit alloc_pages_exit(void) {
    printk(KERN_INFO "alloc_pages模块退出
");
}
module_init(alloc_pages_init);
module_exit(alloc_pages_exit);
MODULE_LICENSE("GPL");
运行结果如下: 可以看到分配的页帧号是连续的
PAGE_SIZE : 4096
alloc_pages (order 2) 分配信息:
  总大小: 16384 字节 (4 页)
  各页详细信息:
  第 0 页:
    虚拟地址: 0x9ddfc000,phys_to_virt=0x9ddfc000
    物理地址: 0x0x7ddfc000,virt_to_phys=0x7ddfc000,phys_addr = 0x7ddfc000
    页帧号: 0x7ddfc
  第 1 页:
    虚拟地址: 0x9ddfd000,phys_to_virt=0x9ddfd000
    物理地址: 0x0x7ddfd000,virt_to_phys=0x7ddfd000,phys_addr = 0x7ddfd000
    页帧号: 0x7ddfd
  第 2 页:
    虚拟地址: 0x9ddfe000,phys_to_virt=0x9ddfe000
    物理地址: 0x0x7ddfe000,virt_to_phys=0x7ddfe000,phys_addr = 0x7ddfe000
    页帧号: 0x7ddfe
  第 3 页:
    虚拟地址: 0x9ddff000,phys_to_virt=0x9ddff000
    物理地址: 0x0x7ddff000,virt_to_phys=0x7ddff000,phys_addr = 0x7ddff000
    页帧号: 0x7ddff
注意事项: (1)page_addresss和page_to_virt区别 page_to_virt适用的场景,page_addresss肯定适用,可以替换page_to_virt使用

(2)在驱动中打印物理地址


(3)cat /proc/buddyinfo 查看信息(可以注释上述代码free函数,对比前后buddyinfo的信息变化)
cat /proc/buddyinfo
Node 0, zone   Normal      2      0      4      3      4      2      3      5      6      2    117
cat /proc/buddyinfo 是 Linux 系统中用于查看伙伴系统(Buddy System)内存分配状态的核心命令,其输出反映了内核中不同大小的 “连续空闲内存块” 的数量,是排查内存碎片、评估连续内存可用性的关键依据
Node 0 的 Normal 内存域中:
Order 0(4KB):有 2 个空闲的连续 4KB 块;
Order 1(8KB):有 0 个空闲的连续 8KB 块(当前无此大小的连续空闲内存);
Order 2(16KB):有 4 个空闲的连续 16KB 块;
Order 3(32KB):有 3 个空闲的连续 32KB 块;
Order 4(64KB):有 4 个空闲的连续 64KB 块;
Order 5(128KB):有 2 个空闲的连续 128KB 块;
Order 6(256KB):有 3 个空闲的连续 256KB 块;
Order 7(512KB):有 5 个空闲的连续 512KB 块;
Order 8(1024KB):有 6 个空闲的连续 1MB 块;
Order 9(2048KB):有 2 个空闲的连续 2MB 块;
Order 10(4096KB):有 117 个空闲的连续 4MB 块
四、如何利用该输出排查问题?
/proc/buddyinfo 的核心用途是评估 “连续内存的可用性”,常见场景:
排查 “内存碎片” 问题:
如果低阶(如 Order 0~3)空闲块数量多,但高阶(如 Order 8~10)空闲块数量极少甚至为 0,说明系统存在严重的内存碎片—— 虽然总空闲内存足够,但缺乏连续的大内存块,会导致内核 / 驱动无法分配大尺寸连续内存(如 DMA 缓冲区),进而引发性能下降或功能失败。
评估内存分配能力:
若某驱动需要分配 2MB 连续内存(对应 Order 9),你的输出中 Order 9 有 2 个空闲块,说明当前可满足该需求;若 Order 9 为 0,则分配会失败。
验证内存回收效果:
执行 echo 3 > /proc/sys/vm/drop_caches 手动回收缓存后,若高阶空闲块数量增加,说明回收有效;若无变化,可能是内存被进程占用或碎片无法整理。
总结
/proc/buddyinfo 本质是 “伙伴系统的实时快照”,通过它可以:
了解不同大小连续空闲内存的存量;
判断系统是否存在内存碎片;
预判大尺寸连续内存分配的成功率。
你的输出中,Normal 域的高阶(Order 8~10)空闲块数量充足,说明当前系统连续内存可用性较好,无明显碎片问题。
#include <stdio.h>
int main()
{
    //定义 一个普通的变量
    int a = 1;
    //使用%p,打印物理地址,输出都是1
    printf("%p,%p
",(void *)a,a);
    return 0;
}
- __get_free_pages分配内存示例,从buddy系统中获取内存,属于物理内存管理范畴
 
unsigned long __get_free_pages(gfp_t gfp_mask, unsigned int order);
1. gfp_mask(内存分配标志)
    与 alloc_pages() 的 gfp_mask 完全一致,用于指定分配行为和约束,常用标志:
    分配上下文:
    GFP_KERNEL:进程上下文,允许睡眠等待内存(最常用)。
    GFP_ATOMIC:原子上下文(如中断),不允许睡眠。
    内存区域:
    __GFP_DMA:仅从 DMA 区域分配(适用于 DMA 设备)。
    __GFP_HIGHMEM:允许从高端内存分配(可能需要 kmap 访问)。
    其他行为:
    __GFP_ZERO:分配后将内存清零。
    示例组合:GFP_KERNEL | __GFP_ZERO(进程上下文分配并清零)。
    
2. order(分配阶数)
    指定分配的连续物理页数量,格式为 2^order 个页:
    order = 0:分配 1 个页(1 × PAGE_SIZE)。
    order = 1:分配 2 个连续页(2 × PAGE_SIZE)。
    order = n:分配 2^n 个连续页(总大小 2^n × PAGE_SIZE)。
    最大阶数由 MAX_ORDER 定义(一般为 11),即最大可分配 2048 个页(若 PAGE_SIZE=4KB,则为 8MB)
3. 返回值
    成功:返回分配内存的 内核虚拟地址(unsigned long 类型,可转换为 void * 使用)。
    失败:返回 0(NULL),表明内存分配失败。
/*
 * @Author: your name
 * @Date: 2025-09-05 17:25:35
 * @LastEditTime: 2025-09-05 17:25:36
 * @LastEditors: your name
 * @Description: In User Settings Edit
 * @FilePath: linux-4.14.143driversgpudrm	estmemory1.c
 */
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/mm.h>
#include <linux/gfp.h>
static int __init get_free_pages_init(void) {
    unsigned long vaddr;  // 虚拟地址(直接返回值)
    struct page *page;
    unsigned int i;
    unsigned int order = 2;  // 分配2^2=4个连续页
    size_t size = (1 << order) * PAGE_SIZE;
    // 分配连续物理页(返回虚拟地址)
    vaddr = __get_free_pages(GFP_KERNEL, order);
    if (!vaddr) {
        printk(KERN_ERR "_get_free_pages failed
");
        return -ENOMEM;
    }
    printk(KERN_INFO "_get_free_pages (order %d) 分配信息:
", order);
    printk(KERN_INFO "  总大小: %zu 字节 (%d 页)
", size, 1 << order);
    printk(KERN_INFO "  各页详细信息:
");
    // 遍历每个页,打印地址信息
    for (i = 0; i < (1 << order); i++) {
        // 将虚拟地址转换为page结构
        page = virt_to_page((void *)(vaddr + i * PAGE_SIZE));
        phys_addr_t phys_addr = page_to_phys(page);
        unsigned long pfn = page_to_pfn(page);
        printk(KERN_INFO "  第 %d 页:
", i);
        printk(KERN_INFO "    虚拟地址: 0x%lx
", vaddr + i * PAGE_SIZE);
        printk(KERN_INFO "    物理地址: 0x%pa
", &phys_addr);
        printk(KERN_INFO "    页帧号: 0x%lx
", pfn);
    }
    // 释放内存
    free_pages(vaddr, order);
    return 0;
}
static void __exit get_free_pages_exit(void) {
    printk(KERN_INFO "_get_free_pages模块退出
");
}
module_init(get_free_pages_init);
module_exit(get_free_pages_exit);
MODULE_LICENSE("GPL");
输出信息如下:可以看到页帧号也是连续的
_get_free_pages (order 2) 分配信息:
  总大小: 16384 字节 (4 页)
  各页详细信息:
  第 0 页:
    虚拟地址: 0x9ddc4000
    物理地址: 0x0x7ddc4000
    页帧号: 0x7ddc4
  第 1 页:
    虚拟地址: 0x9ddc5000
    物理地址: 0x0x7ddc5000
    页帧号: 0x7ddc5
  第 2 页:
    虚拟地址: 0x9ddc6000
    物理地址: 0x0x7ddc6000
    页帧号: 0x7ddc6
  第 3 页:
    虚拟地址: 0x9ddc7000
    物理地址: 0x0x7ddc7000
    页帧号: 0x7ddc7
- slab分配内存示例,从buddy系统中获取内存,属于物理内存管理范畴
 
slab 分配器是小对象高频分配的优化方案,优势在于效率和内存利用率;alloc_pages() 是底层连续页分配的基础接口,优势在于支持物理连续内存和特殊内存区域。两者互补,共同构成内核的内存分配体系(slab 基于伙伴系统,最终调用 alloc_pages() 分配底层页)
kmalloc():是内核提供的通用内存分配函数,本质是对 slab 分配器的封装 —— 内核预定义了一系列固定大小的 slab 缓存(如 16 字节、32 字节、64 字节……4096 字节),kmalloc(size) 会自动选择最匹配的 slab 缓存分配内存。自定义 slab 分配器:通过 kmem_cache_create() 创建专用的 slab 缓存,用于分配固定大小的自定义对象(如特定结构体),更灵活且针对性更强。kmalloc() 是基于 slab 分配器的通用简化接口,优势在于易用性和灵活性,适合大多数临时或不频繁的小内存分配;而自定义 slab 分配器是针对特定对象的优化方案,优势在于效率和内存利用率,适合高频、固定大小的对象分配。两者本质上是 “通用” 与 “专用” 的关系,内核根据场景选择使用
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/mm.h>
#include <linux/highmem.h>
// 定义自定义数据结构
struct demo_struct {
    int id;
    char name[32];
    unsigned long timestamp;
};
// slab缓存描述符
static struct kmem_cache *demo_cache;
#define OBJECT_COUNT 4  // 要分配的对象数量
static int __init slab_demo_init(void) {
    struct demo_struct *objs[OBJECT_COUNT];
    phys_addr_t phys_addr;
    unsigned long pfn;
    int i;
    // 创建slab缓存(指定单个对象大小)
    demo_cache = kmem_cache_create("demo_cache",
                                  sizeof(struct demo_struct),
                                  0,
                                  SLAB_HWCACHE_ALIGN,
                                  NULL);
    // 分配4个对象并循环调用4次分配函数)
    for (i = 0; i < OBJECT_COUNT; i++) {
        objs[i] = kmem_cache_alloc(demo_cache, GFP_KERNEL);
        
        // 初始化对象数据
        objs[i]->id = i + 1;
        snprintf(objs[i]->name, sizeof(objs[i]->name)-1, 
                "slab_obj_%d", i+1);
        objs[i]->timestamp = jiffies;  // 内核当前节拍数
    }
    // 打印每个对象的地址信息和成员变量
    printk(KERN_INFO "成功分配%d个slab对象:
", OBJECT_COUNT);
    for (i = 0; i < OBJECT_COUNT; i++) {
        phys_addr = virt_to_phys(objs[i]);
        pfn = virt_to_pfn(objs[i]);
        printk(KERN_INFO "===== 对象%d 信息 =====
", i+1);
        // 地址信息
        printk(KERN_INFO "  虚拟地址: %p
", objs[i]);
        printk(KERN_INFO "  物理地址: 0x%pa
", &phys_addr);
        printk(KERN_INFO "  页帧号: 0x%lx
", pfn);
        // 成员变量信息
        printk(KERN_INFO "  成员变量:
");
        printk(KERN_INFO "    id: %d
", objs[i]->id);
        printk(KERN_INFO "    name: %s
", objs[i]->name);
        printk(KERN_INFO "    timestamp: %lu
", objs[i]->timestamp);
    }
    // 释放所有对象
    for (i = 0; i < OBJECT_COUNT; i++) {
        kmem_cache_free(demo_cache, objs[i]);
    }
    return 0;
}
static void __exit slab_demo_exit(void) {
    if (demo_cache) {
        kmem_cache_destroy(demo_cache);
        printk(KERN_INFO "slab缓存已销毁
");
    }
}
module_init(slab_demo_init);
module_exit(slab_demo_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("打印slab对象成员变量的示例");
输出信息如下,可以看到都在一个page页
成功分配4个slab对象:
===== 对象1 信息 =====
  虚拟地址: 9e624a80
  物理地址: 0x0x7e624a80
  页帧号: 0x7e624
  成员变量:
    id: 1
    name: slab_obj_1
    timestamp: 4294939752
===== 对象2 信息 =====
  虚拟地址: 9e624ac0
  物理地址: 0x0x7e624ac0
  页帧号: 0x7e624
  成员变量:
    id: 2
    name: slab_obj_2
    timestamp: 4294939752
===== 对象3 信息 =====
  虚拟地址: 9e624b00
  物理地址: 0x0x7e624b00
  页帧号: 0x7e624
  成员变量:
    id: 3
    name: slab_obj_3
    timestamp: 4294939752
===== 对象4 信息 =====
  虚拟地址: 9e624b40
  物理地址: 0x0x7e624b40
  页帧号: 0x7e624
  成员变量:
    id: 4
    name: slab_obj_4
    timestamp: 4294939752
slab与alloc_pages区别

slab与kmalloc区别

- kmalloc分配内存示例,从buddy系统中获取内存,属于物理内存管理范畴
 
/*
 * @Author: your name
 * @Date: 2025-09-05 18:14:46
 * @LastEditTime: 2025-09-05 18:14:57
 * @LastEditors: Please set LastEditors
 * @Description: In User Settings Edit
 * @FilePath: linux-4.14.143driversgpudrm	estmemory3.c
 */
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <linux/page-flags.h>
static int __init kmalloc_page_init(void) {
    void *vaddr;
    struct page *page;
    unsigned int i;
    size_t size = 4 * PAGE_SIZE;  // 分配2个页大小的内存
    // 使用kmalloc分配内存,GFP_KERNEL表明可以睡眠等待内存
    vaddr = kmalloc(size, GFP_KERNEL);
    if (!vaddr) {
        printk(KERN_ERR "kmalloc failed
");
        return -ENOMEM;
    }
    printk(KERN_INFO "kmalloc 分配信息:
");
    printk(KERN_INFO "  虚拟地址范围: 0x%p 到 0x%p
", vaddr, vaddr + size - 1);
    printk(KERN_INFO "  总大小: %zu 字节 (%u 页)
", size, size / PAGE_SIZE);
    printk(KERN_INFO "  各页详细信息:
");
    // 遍历每个页,获取物理地址和页帧号
    for (i = 0; i < size; i += PAGE_SIZE) {
        // 将虚拟地址转换为对应的page结构
        page = virt_to_page(vaddr + i);
        
        // 计算物理地址和页帧号
        phys_addr_t phys_addr = page_to_phys(page);
        unsigned long pfn = page_to_pfn(page);
        printk(KERN_INFO "  第 %d 页:
", i / PAGE_SIZE);
        printk(KERN_INFO "    虚拟地址: 0x%p
", vaddr + i);
        printk(KERN_INFO "    物理地址: 0x%pa
", &phys_addr);
        printk(KERN_INFO "    页帧号: 0x%lx
", pfn);
    }
    kfree(vaddr);
    return 0;
}
static void __exit kmalloc_page_exit(void) {
    printk(KERN_INFO "模块已退出
");
}
module_init(kmalloc_page_init);
module_exit(kmalloc_page_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("kmalloc地址信息示例");
    
输出信息,可以看到kmalloc分配的内存是连续的,不仅仅是虚拟地址是连续的,页帧号也是连续的,物理地址也是连续的
kmalloc 分配信息:
  虚拟地址范围: 0x9dd4c000 到 0x9dd4ffff
  总大小: 16384 字节 (4 页)
  各页详细信息:
  第 0 页:
    虚拟地址: 0x9dd4c000
    物理地址: 0x0x7dd4c000
    页帧号: 0x7dd4c
  第 1 页:
    虚拟地址: 0x9dd4d000
    物理地址: 0x0x7dd4d000
    页帧号: 0x7dd4d
  第 2 页:
    虚拟地址: 0x9dd4e000
    物理地址: 0x0x7dd4e000
    页帧号: 0x7dd4e
  第 3 页:
    虚拟地址: 0x9dd4f000
    物理地址: 0x0x7dd4f000
    页帧号: 0x7dd4f
注意事项:

- vmalloc分配内存示例,从buddy系统中获取内存,属于物理内存管理范畴
 
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/mm.h>
#include <linux/vmalloc.h>
#include <linux/page-flags.h>
static int __init vmalloc_page_init(void) {
    void *vaddr;
    struct page *page;
    unsigned int i;
    size_t size = 4 * PAGE_SIZE;  // 分配4个页大小的内存
    // 使用vmalloc分配内存
    vaddr = vmalloc(size);
    if (!vaddr) {
        printk(KERN_ERR "vmalloc failed
");
        return -ENOMEM;
    }
    printk(KERN_INFO "vmalloc 分配信息:
");
    printk(KERN_INFO "  虚拟地址范围: 0x%p 到 0x%p
", vaddr, vaddr + size - 1);
    printk(KERN_INFO "  总大小: %zu 字节 (%u 页)
", size, size / PAGE_SIZE);
    printk(KERN_INFO "  各页详细信息:
");
    // 遍历每个页,获取物理地址和页帧号
    for (i = 0; i < size; i += PAGE_SIZE) {
        // 获取当前页的struct page指针
        page = vmalloc_to_page(vaddr + i);
        if (!page) {
            printk(KERN_ERR "  无法获取第 %d 页的page结构
", i / PAGE_SIZE);
            continue;
        }
        // 计算物理地址和页帧号
        phys_addr_t phys_addr = page_to_phys(page);
        unsigned long pfn = page_to_pfn(page);
        printk(KERN_INFO "  第 %d 页:
", i / PAGE_SIZE);
        printk(KERN_INFO "    虚拟地址: 0x%p
", vaddr + i);
        printk(KERN_INFO "    物理地址: 0x%pa
", &phys_addr);
        printk(KERN_INFO "    页帧号: 0x%lx
", pfn);
    }
    vfree(vaddr);
    return 0;
}
static void __exit vmalloc_page_exit(void) {
    printk(KERN_INFO "模块已退出
");
}
module_init(vmalloc_page_init);
module_exit(vmalloc_page_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("vmalloc地址信息示例");
    
输出信息如下:可以看到vmalloc分配的内存是不连续的,虚拟地址是连续的,但是物理地址不是连续的,页帧号也不是连续的
vmalloc 分配信息:
  虚拟地址范围: 0xa08f2000 到 0xa08f5fff
  总大小: 16384 字节 (4 页)
  各页详细信息:
  第 0 页:
    虚拟地址: 0xa08f2000
    物理地址: 0x0x7dd39000
    页帧号: 0x7dd39
  第 1 页:
    虚拟地址: 0xa08f3000
    物理地址: 0x0x7dd38000
    页帧号: 0x7dd38
  第 2 页:
    虚拟地址: 0xa08f4000
    物理地址: 0x0x7ddc3000
    页帧号: 0x7ddc3
  第 3 页:
    虚拟地址: 0xa08f5000
    物理地址: 0x0x7ddc2000
    页帧号: 0x7ddc2
注意事项:

图片
✅ 结论
输出结论
待查资料问题
- ❓ 问题 1:?
 - ❓ 问题 2:?
 
参考链接
- 官方文档
 
© 版权声明
文章版权归作者所有,未经允许请勿转载。如内容涉嫌侵权,请在本页底部进入<联系我们>进行举报投诉!
THE END
    



















暂无评论内容