mtdoops 和 kdump 原理

嵌入式Linux内核崩溃日志保存实战:ramoops vs mtdoops配置详解(附避坑指南) 本文深入探讨嵌入式Linux系统中ramoops和mtdoops两种内核崩溃日志持久化方案的配置与实践。通过对比分析存储介质、内核版本要求等关键特性,提供详细的设备树配置、日志提取方法及常见问题解决方案,帮助开发者根据项目需求选择最适合的崩溃日志保存机制。 阅读详情

kdump 可以记录 kernel panic 等log信息,他的原理就是建立一个工作队列,把kmsg log 备份到一个mtd分区上。

是在drivers/mtd/mtdoops.c 中实现的。

1. module_init/ module_exit

module_init(mtdoops_init);
module_exit(mtdoops_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Richard Purdie <rpurdie@openedhand.com>");
MODULE_DESCRIPTION("MTD Oops/Panic console logger/driver");

static int __init mtdoops_init(void)
{
        struct mtdoops_context *cxt = &oops_cxt;
        int mtd_index;
        char *endp;

        if (strlen(mtddev) == 0) {
                printk(KERN_ERR "mtdoops: mtd device (mtddev=name/number) must be supplied\n");
                return -EINVAL;
        }
        if ((record_size & 4095) != 0) {
                printk(KERN_ERR "mtdoops: record_size must be a multiple of 4096\n");
                return -EINVAL;
        }
        if (record_size < 4096) {
                printk(KERN_ERR "mtdoops: record_size must be over 4096 bytes\n");
                return -EINVAL;
        }

        /* Setup the MTD device to use */
        cxt->mtd_index = -1;
        mtd_index = simple_strtoul(mtddev, &endp, 0);
        if (*endp == '\0')
                cxt->mtd_index = mtd_index;
        if (cxt->mtd_index > MAX_MTD_DEVICES) {
                printk(KERN_ERR "mtdoops: invalid mtd device number (%u) given\n",
                                mtd_index);
                return -EINVAL;
        }

        cxt->oops_buf = vmalloc(record_size);
        if (!cxt->oops_buf) {
                printk(KERN_ERR "mtdoops: failed to allocate buffer workspace\n");
                return -ENOMEM;
        }
        memset(cxt->oops_buf, 0xff, record_size);

        //建了2个工作队列,一个擦除,一个写入
        INIT_WORK(&cxt->work_erase, mtdoops_workfunc_erase);
        INIT_WORK(&cxt->work_write, mtdoops_workfunc_write);

        //注册mtd user, 用来在用户空间操作MTD。
        register_mtd_user(&mtdoops_notifier);
        return 0;
}

static void __exit mtdoops_exit(void)
{
        struct mtdoops_context *cxt = &oops_cxt;

        unregister_mtd_user(&mtdoops_notifier);
        vfree(cxt->oops_buf);
        vfree(cxt->oops_page_used);
}

2. mtdoops_context 结构体:

static struct mtdoops_context {
        struct kmsg_dumper dump;
        
        int mtd_index;
        struct work_struct work_erase;
        struct work_struct work_write;
        struct mtd_info *mtd;
        int oops_pages;
        int nextpage;           
        int nextcount; 
        unsigned long *oops_page_used;

        void *oops_buf; 
} oops_cxt;
kmsg_dumper

/**     
 * struct kmsg_dumper - kernel crash message dumper structure
 * @dump:       The callback which gets called on crashes. The buffer is passed
 *              as two sections, where s1 (length l1) contains the older
 *              messages and s2 (length l2) contains the newer.
 * @list:       Entry in the dumper list (private)
 * @registered: Flag that specifies if this is already registered
 */     
struct kmsg_dumper {
        void (*dump)(struct kmsg_dumper *dumper, enum kmsg_dump_reason reason,
                        const char *s1, unsigned long l1,
                        const char *s2, unsigned long l2);
        struct list_head list;
        int registered;
};
enum kmsg_dump_reason {
        KMSG_DUMP_OOPS, 
        KMSG_DUMP_PANIC,
        KMSG_DUMP_KEXEC,
}; 
3. mtdoops_workfunc_erase --- 擦除

/* Scheduled work - when we can't proceed without erasing a block */
static void mtdoops_workfunc_erase(struct work_struct *work)
{               
        struct mtdoops_context *cxt =
                        container_of(work, struct mtdoops_context, work_erase);
        struct mtd_info *mtd = cxt->mtd;
        int i = 0, j, ret, mod;
        
        /* We were unregistered */
        if (!mtd)
                return;
                
        mod = (cxt->nextpage * record_size) % mtd->erasesize;
        if (mod != 0) {
                cxt->nextpage = cxt->nextpage + ((mtd->erasesize - mod) / record_size);
                if (cxt->nextpage >= cxt->oops_pages)
                        cxt->nextpage = 0;
        }

        while (mtd->block_isbad) {
                ret = mtd->block_isbad(mtd, cxt->nextpage * record_size);
                if (!ret)
                        break;
                if (ret < 0) {
                        printk(KERN_ERR "mtdoops: block_isbad failed, aborting\n");
                        return;
                }
badblock:
                printk(KERN_WARNING "mtdoops: bad block at %08lx\n",
                       cxt->nextpage * record_size);
                i++;
                cxt->nextpage = cxt->nextpage + (mtd->erasesize / record_size);
                if (cxt->nextpage >= cxt->oops_pages)
                        cxt->nextpage = 0;
                if (i == cxt->oops_pages / (mtd->erasesize / record_size)) {
                        printk(KERN_ERR "mtdoops: all blocks bad!\n");
                        return;
                }
        }

        for (j = 0, ret = -1; (j < 3) && (ret < 0); j++)
                ret = mtdoops_erase_block(cxt, cxt->nextpage * record_size);
        if (ret >= 0) {
                printk(KERN_DEBUG "mtdoops: ready %d, %d\n",
                       cxt->nextpage, cxt->nextcount);
                return;
        }

        if (mtd->block_markbad && ret == -EIO) {
                ret = mtd->block_markbad(mtd, cxt->nextpage * record_size);
                if (ret < 0) {
                        printk(KERN_ERR "mtdoops: block_markbad failed, aborting\n");
                        return;
                }
        }
        goto badblock;
}

4. mtdoops_workfunc_write(struct work_struct *work)

static void mtdoops_workfunc_write(struct work_struct *work)
{       
        struct mtdoops_context *cxt =
                        container_of(work, struct mtdoops_context, work_write);
        
        mtdoops_write(cxt, 0);
}
static void mtdoops_write(struct mtdoops_context *cxt, int panic)

static void mtdoops_write(struct mtdoops_context *cxt, int panic)
{
        struct mtd_info *mtd = cxt->mtd;
        size_t retlen;
        u32 *hdr;
        int ret;

        /* Add mtdoops header to the buffer */
        hdr = cxt->oops_buf;
        hdr[0] = cxt->nextcount;
        hdr[1] = MTDOOPS_KERNMSG_MAGIC;

        if (panic)
                ret = mtd->panic_write(mtd, cxt->nextpage * record_size,
                                        record_size, &retlen, cxt->oops_buf);
        else
                ret = mtd->write(mtd, cxt->nextpage * record_size,
                                        record_size, &retlen, cxt->oops_buf);

        if (retlen != record_size || ret < 0)
                printk(KERN_ERR "mtdoops: write failure at %ld (%td of %ld written), error %d\n",
                       cxt->nextpage * record_size, retlen, record_size, ret);
        mark_page_used(cxt, cxt->nextpage);
        memset(cxt->oops_buf, 0xff, record_size);

        mtdoops_inc_counter(cxt);
}
static void mark_page_used(struct mtdoops_context *cxt, int page)

static void mark_page_used(struct mtdoops_context *cxt, int page)
{       
        set_bit(page, cxt->oops_page_used);
}      
static void mtdoops_inc_counter(struct mtdoops_context *cxt)

static void mtdoops_inc_counter(struct mtdoops_context *cxt)
{       
        cxt->nextpage++;
        if (cxt->nextpage >= cxt->oops_pages)
                cxt->nextpage = 0;
        cxt->nextcount++;
        if (cxt->nextcount == 0xffffffff) 
                cxt->nextcount = 0;
        
        if (page_is_used(cxt, cxt->nextpage)) {
                schedule_work(&cxt->work_erase);
                return;
        }

        printk(KERN_DEBUG "mtdoops: ready %d, %d (no erase)\n",
               cxt->nextpage, cxt->nextcount);
}       
5. mtdoops_notifier

static struct mtd_notifier mtdoops_notifier = {
        .add    = mtdoops_notify_add,
        .remove = mtdoops_notify_remove,
};     
static void mtdoops_notify_add(struct mtd_info *mtd)

static void mtdoops_notify_add(struct mtd_info *mtd)
{
        struct mtdoops_context *cxt = &oops_cxt;
        u64 mtdoops_pages = div_u64(mtd->size, record_size);
        int err;

        if (!strcmp(mtd->name, mtddev))
                cxt->mtd_index = mtd->index;

        if (mtd->index != cxt->mtd_index || cxt->mtd_index < 0)
                return;

        if (mtd->size < mtd->erasesize * 2) {
                printk(KERN_ERR "mtdoops: MTD partition %d not big enough for mtdoops\n",
                       mtd->index);
                return;
        }
        if (mtd->erasesize < record_size) {
                printk(KERN_ERR "mtdoops: eraseblock size of MTD partition %d too small\n",
                       mtd->index);
                return;
        }
        if (mtd->size > MTDOOPS_MAX_MTD_SIZE) {
                printk(KERN_ERR "mtdoops: mtd%d is too large (limit is %d MiB)\n",
                       mtd->index, MTDOOPS_MAX_MTD_SIZE / 1024 / 1024);
                return;
        }

        /* oops_page_used is a bit field */
        cxt->oops_page_used = vmalloc(DIV_ROUND_UP(mtdoops_pages,
                        BITS_PER_LONG));
        if (!cxt->oops_page_used) {
                printk(KERN_ERR "mtdoops: could not allocate page array\n");
                return;
        }

        cxt->dump.dump = mtdoops_do_dump;
        err = kmsg_dump_register(&cxt->dump);
        if (err) {
                printk(KERN_ERR "mtdoops: registering kmsg dumper failed, error %d\n", err);
                vfree(cxt->oops_page_used);
                cxt->oops_page_used = NULL;
                return;
        }

        cxt->mtd = mtd;
        cxt->oops_pages = (int)mtd->size / record_size;
        find_next_position(cxt);
        printk(KERN_INFO "mtdoops: Attached to MTD device %d\n", mtd->index);
}
static void mtdoops_notify_remove(struct mtd_info *mtd)

static void mtdoops_notify_remove(struct mtd_info *mtd)
{
        struct mtdoops_context *cxt = &oops_cxt;

        if (mtd->index != cxt->mtd_index || cxt->mtd_index < 0)
                return;

        if (kmsg_dump_unregister(&cxt->dump) < 0)
                printk(KERN_WARNING "mtdoops: could not unregister kmsg_dumper\n");

        cxt->mtd = NULL;
        flush_scheduled_work();
}













kmsg_dump kmsg_dump提供一种机制在系统挂掉的时候将log转存到其他介质如ramoos或者mtd等.我们以mtd中的使用为例来分析kmsg_dump机制. 使用kmsg_dump之前要先使用kmsg_dump_register来注册一个dumper,用于实际的log写函数. 我们先来看看kmsg_dump_register的定义,只定义了一个dump_list,将系统中所有注册的dump都挂在同一 阅读详情

相关推荐

Android系统错误分析工具包:2022年1月10日bug报告详解

mtdoops.md文件是Android系统中记录内核错误报告的关键组件,通常涉及硬件相关的底层问题。在分析这一层次的错误报告时,我们通常关注的是关键字段,如日志级别、错误消息及其对应模块名称等。mtdoops.md文件遵循特定的格式规范,使其内容结构化且易于解析。下面是关于mtdoops.md文件中常见字段的表格总结:字段描述示例日志级别错误的严重性<4>表示错误模块名称错误发生的内核模块mtdoops错误消息具体错误描述时间戳。

weixin_35749786的博客 419

简单记录下电脑Ubuntu系统卡死后无法正常开机的解救方式

再次强制关机后重新启动,此时选择 advanced options for ubuntu,进去之后再选择第三个选项ubuntu,with Linux5.15.0-53-generic,成功启动。再次按照常规流程开机发生mtd device must be supplied (device name is empty),无法开 机。不要随便强制关机,可以按住ctrl+alt之后依次点击prtsc、r、e、i、s、u、b安全重启。pycharm中运行python程序中途死机,之后直接强制关机。

weixin_45055622的博客 6222

UE5实战技巧:TextRender高效集成中文字体与材质优化

本文针对UE5中TextRender组件显示中文的常见问题,提供了一套完整的实战解决方案。详细讲解了如何通过手动创建字体资产、精准控制字符集来优化性能,并重点介绍了通过调整材质中的Lerp节点参数(如设为-85, 92)来解决中文字体显示残缺和边缘模糊等核心难题,帮助开发者实现高效且美观的中文文本渲染。

weixin_28040125的博客 381

(half OK) 在VirtualBox中运行 cm-13-kiwi (华为 荣耀 5X) - 移植cm-13-kiwi

(half OK) 在VirtualBox中运行 cm-13-kiwi (华为 荣耀 5X) - 移植cm-13-kiwi

张同光 (Tongguang Zhang):Hello everyone ! 5168

[模型部署]-[LLM]昇腾部署Qwen3-Coder-30B-A3B-Instruct

摘要:本文介绍了在Ascend NPU上部署Qwen3-Coder-30B-A3B-Instruct大模型的实践过程。通过vLLM-Ascend框架,详细说明了环境准备(CANN 8.1、PyTorch-NPU 2.5.1等)、模型下载、容器启动及服务部署步骤,包括vLLM服务的启动参数配置和接口测试方法。测试结果显示在当前2卡910B3配置下性能表现仅为8token/s(1并发),并指出流式调用时存在无法输出usage信息的问题。该方案为Ascend平台私有化部署大模型推理服务提供了参考实现。

1671

从Blender舞步到夜空画卷:基于Pix飞控的无人机编队灯光秀全链路开发实践

本文详细介绍了基于Pixhawk飞控的无人机编队灯光秀全链路开发实践。从Blender三维动画设计、QGroundControl地面站二次开发与MAVLink通信桥接,到PX4飞控的OFFBOARD模式路径控制与LED灯光驱动实现,最后通过Gazebo仿真验证并完成真机组装与首飞。文章为无人机爱好者提供了一套完整的、从创意设计到夜空执行的开源解决方案,重点阐述了多机同步与灯光控制的核心技术挑战与应对策略。

zeta9的博客 585

解决 System can‘t start, “mtd device must be supplied“

ubuntu磁盘扩容后无法正常开机。

m0_53155317的博客 5482

无人机中继传输数据链技术,(无人机+自组网)远距离传输技术实现详解

无人机中继传输数据链技术是指利用无人机作为空中移动中继节点,在需要通信的区域上空悬停或移动,以建立和维护通信链路,从而扩展通信范围、提高通信质量和灵活性。1. 通信链路:是无人机中继通信的重要组成部分,包括源节点到中继节点的链路和中继节点到目标节点的链路。同时,它还支持空中到地面的通信,距离大于100km。总的来说,无人机中继传输数据链技术和(无人机+自组网)远距离传输技术都是现代无人机通信领域的重要技术,它们的应用极大地扩展了无人机的通信范围和灵活性,为各种应用场景提供了强有力的支持。

专注多旋翼、复合翼无人机技术研究,从事低空经济行业。 2079

嵌入式系统linux 记录内存panic

简介 在内存发生panic时,需要把panic的日志保存下来。以方便日后进行分析。 目前有三种记录的方式: kdump; mtdoops; crashlog(这是openwrt特别的功能,正式linux内核中没有) 大家对kdump比较了解。它主要使用于x86系统。因为它使用占用大量内存和硬盘。 mtdoops和crashlog主要用于嵌入式的环境。也只是记录文本日志。 mtdo

刘末的专栏 6117

Qoder CN 完全指南:阿里云AI智能体的全景拆解

阿里云旗下AI编程助手"通义灵码"于2026年5月20日升级为QoderCN,转型为覆盖软件开发与办公场景的AI智能体产品矩阵。该平台具备四大核心能力:代码补全、智能问答、文件编辑和智能体模式,支持GLM、DeepSeek等多模型切换。特色功能包括Quest2.0任务委派、专家团协作和记忆感知,满足金融、政务等行业的合规需求。产品形态涵盖IDE插件、CLI工具、办公助手和数字员工,形成"全家桶"式解决方案。相比Cursor等海外产品,QoderCN更注重全链路覆盖,个

几十年资深高级架构师,技术专家的技术笔记。专注C、Java、Python、微服务、云原生、AI工程化及高并发系统分布式架构实战,分享源码剖析、性能调优与架构治理经验。 942

Android应用之【巧妙隐藏桌面图标的进阶方案】

本文详细探讨了Android应用中隐藏桌面图标的进阶方案,解决了传统方法在Android 10及以上版本中出现的黑色图标残留问题。通过Intent-Filter配置和厂商适配技巧,实现子应用真正隐身,同时确保主应用正常启动。适用于企业工具套件、模块化系统等场景,提升用户体验和安全性。

weixin_28363123的博客 508

Could not load library libcudnn_cnn_infer.so.8.错误解决

深度学习解决Could not load library libcudnn_cnn_infer.so.8.错误

weixin_42236469的博客 1万+

【目标检测】全局上下文感知的详解

目标检测中的全局上下文感知(Global Context Awareness)​是指模型在定位和识别目标时,能够有效利用图像中全局或大范围的上下文信息(如场景语义、目标间关系、空间布局等),而不仅仅依赖局部特征。这种能力显著提升了检测的鲁棒性,尤其是在复杂场景(如遮挡、小目标、密集目标)中表现突出。

浩瀚之水的专栏 645

panic[转]

简介 在内存发生panic时,需要把panic的日志保存下来。以方便日后进行分析。 目前有三种记录的方式: kdump; mtdoops; crashlog 大家对kdump比较了解。它主要使用于x86系统。因为它使用占用大量内存和硬盘。 mtdoops和crashlog主要用于嵌入式的环境。也只是记录文本日志。 mtdoop功能在发生oops时,把msg区写入特定的m...

awks4002的博客 260

<PLC><工控>汇川Easy521PLC与压力传感器485通讯实例

有些PLC在进行串口通讯时,配置完串口参数后,还需要自己编程序来读写,但本例中汇川Easy521配置完成后,想要读取从站什么数据,只需要在“COM0 Modbus配置窗口”配置即可。可以同时配置多个从站。在配置窗口的右侧点击新增按钮,就会自动生成一条从站的配置,默认从站站号为“1”,触发方式是“循环”,功能码是03(读寄存器),从站寄存器地址为0,长度为“1”,映射地址需要自己添加。因为我们是将PLC设置为Modbus主站,所以压力传感器就是从站,我们的需求时PLC从这些从站传感器中读取数据,比如压力值。

用沸腾的热血,支付我们的人生吧! 6461

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

嵌入式Linux系统如何检测内存,嵌入式系统linux 记录内存panic

简介在内存发生panic时,需要把panic的日志保存下来。以方便日后进行分析。目前有三种记录的方式: kdump; mtdoops; crashlog(这是openwrt特别的功能,正式linux内核中没有)大家对kdump比较了解。它主要使用于x86系统。因为它使用占用大量内存和硬盘。mtdoops和crashlog主要用于嵌入式的环境。也只是记录文本日志。mtdoop功能在发生oops时,把...

weixin_29305337的博客 599

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

系统的PANIC

panic并没有什么大不了的,就是禁抢占,善后, 禁止了抢占实际上就禁止了调度,因为禁止抢占的情况下除非自己放弃cpu才能调度, 但是我们看看那个死循环根本没有放弃cpu的意思。 发生panic无非就两种情形,一个是在中断上下文,另一个是在非中断上下文。 在中断上下文中 panic的话,此时的in_interrupt()一定返回真,如果此硬件中断触发了一个软中断,那么这个软中断是永远不会被执行的, 因为导致 panic的中断永远不会调用irq_exit来使得in_interrupt()返回假。 这就解释了为

秦涧泉的博客 2232

怀旧版电脑GBC模拟器

再现原版GBC掌机的界面,电脑运行连游戏界面都一样大小,很怀旧,很真实,(英文版)强烈推荐,很小的文件,很难才找到的。

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

kdump实现原理

前言 做软件开发的同学会花费大约1/3的时间来进行调试,一个软件开发人员的技能包括:coding,调试和设计.一个友好的开发环境中必然提供了相当多的调试工具给开发人员,例如linux环境下C/C++有基于信号的core dump文件,java、python等都有自己的dump机制,而kdump就是为内核开发而生的dump机制,是kernel dump的缩写。而kdump比较重,对于内存较大的服务器...

faxiang1230的专栏 2252

1-Linux 保存kernel panic信息到flash

Linux 保存kernel panic信息到flash

Creator_Ly的博客 3357

Ubuntu开机出现的一些错误及解决办法

1、开机时出现错误:i2c_piix4: Host SMbus controller not enabled,解决方法为编辑blacklist禁止i2c_piix4驱动的加载。具体操作步骤如下: 1)在启动Ubuntu的时候按住左Shift键; 2)Ubuntu开机时将显示GRUB启动选项; 3)接下来选择Ubuntu的高级选项(Advanced options for Ubuntu); 4)然...

J_H_C的博客 8042

第 5 章 Kernel modules

add and remove modules from the Linux Kernel 5.1.modprobe - program to add and remove modules from the Linux Kernel $ modprobe -l kernel/arch/x86/kernel/cpu/mcheck/mce-i...

weixin_34037977的博客 1045

Netty-2零拷贝

零拷贝基本介绍 零拷贝是网络编程的关键,很多性能优化都离不开。在Java程序中,常用的零拷贝有mmap(内存映射)和 sendfile。 DMA copy :直接内存拷贝 一次传统的IO操作和网络编程需要经历多少次用户态与内核态的转换,以及多少次数据拷贝? File file = new File("D://test.txt"); RandomAccessFile raf = new RandomAccessFile(file,"rw"); byte[] bytes =

phn555的博客 509

linux pstore 存储内核崩溃日志

在系统运行过程中,如果内核发生了panic,那么开发人员需要通过内核报错日志来进行定位问题。但是很多时候出现问题的时候没有接调试串口,而报错日志是在内存里面的,重启后就丢失了。所以需要一种方法,可以在系统发生crash时,将crash info保存于非易失存储器中。这对分析那种小概率且没办法抓到现场的问题非常实用,尤其是现在智能互联网的设备逐渐普及的时候,远端的设备可以自己捕抓崩溃日志再通过网络传输到服务器,维护人员就可以根据收集来的日志定位和解决问题,然后通过OTA让设备升级迭代。内核使用。

枫潇潇 3104

mtdoops

module_init(mtdoops_init); static int __init mtdoops_init(void) 向workstruct &cxt->work_erase 注册延期执行函数     INIT_WORK(&cxt->work_erase, mtdoops_workfunc_erase);     INIT_WORK(&cxt->work_write, mtd

3508

龙芯1C ls1c300b的openwrt以及安装过程

最近折腾完了龙芯1C300B的4.14内核,正好openwrt19.07版本使用的内核为4.14,就突然想尝试下openwrt,折腾完了分享给大家。由于智龙V3.0的默认内存为32M,这里提供了两个版本的openwrt文件系统32M基本系统以及64M完整系统一、默认智龙32M内存的版本(ls1c-openwrt-32M-base-bin),内核、文件系统以及ipk安装包,在ls1c-openwrt-32M-base-bin文件夹下,tftpd和httpd工具请定位至此目录下操作1.刷内核。...

qq_21850249的博客 3675
上一篇: CFE 和BMC
下一篇: add timer
lamdoc
博客等级 码龄18年 155粉丝 655原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值