FreeRTOS互斥信号量解决优先级翻转实战教程

FreeRtos开发之互斥信号量(有效防止优先级翻转) 前面学过,取值只有0与1两种状态的信号量称之为二值信号量。而互斥信号量是一种特殊的二值信号量,具有防止优先级翻转的特性。二值信号量主要用于同步:任务与任务间同步,中断与任务间同步,也就是说在中断中完成了某个操作以后,将二值信号量释放,然后在任务中获取到了信号量,这个时候就相当于知道了某个操作完成了,类似一种完成的信号或者说通知,这相当于我们在裸机编程时常定义的Flag,或者说是一种bool类型,只有True或者false,但是二值信号量不适合做资源管理。 阅读详情

FreeRTOS互斥信号量解决优先级翻转实战教程

大家好!今天我们来深入探讨FreeRTOS中的优先级翻转问题,并通过互斥信号量来解决这个问题。上一篇文章我们已经了解了优先级翻转的现象,今天我们将动手实践,通过代码对比来直观感受互斥信号量的作用。

🚀 本文是FreeRTOS学习系列的一部分。完整教程请访问我的GitHub仓库:https://github.com/Despacito0o/FreeRTOS,从入门到精通的全面指南!

一、互斥信号量解决优先级翻转

了解完互斥信号量可以解决优先级翻转的问题,我们来实际体验一下。我们将使用前面优先级翻转的案例,把它改造成使用互斥信号量的版本,然后观察效果。

首先,我们直接拷贝之前的012工程,重命名为013:

工程复制

二、从二值信号量切换到互斥信号量

我们有三个任务,三个任务的逻辑不需要修改,只需要将创建的信号量类型从二值信号量换成互斥信号量。

要使用互斥信号量,必须先在FreeRTOSConfig.h中开启相关配置:

#define configUSE_MUTEXES                       1  // 是否启用互斥信号量

启用互斥信号量

三、创建互斥信号量

接下来,我们将二值信号量的创建函数替换为互斥信号量的创建函数:

xSemaphoreCreateMutex();

创建互斥信号量

让我们深入看看这个函数的实现:

#if ( ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) && ( configUSE_MUTEXES == 1 ) )
    #define xSemaphoreCreateMutex()    xQueueCreateMutex( queueQUEUE_TYPE_MUTEX )
#endif

进一步查看xQueueCreateMutex函数的实现:

#if ( ( configUSE_MUTEXES == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) )

    QueueHandle_t xQueueCreateMutex( const uint8_t ucQueueType )
    {
        QueueHandle_t xNewQueue;
        const UBaseType_t uxMutexLength = ( UBaseType_t ) 1, uxMutexSize = ( UBaseType_t ) 0;

        xNewQueue = xQueueGenericCreate( uxMutexLength, uxMutexSize, ucQueueType );
        prvInitialiseMutex( ( Queue_t * ) xNewQueue );

        return xNewQueue;
    }

#endif /* configUSE_MUTEXES */

最后看看prvInitialiseMutex函数:

static void prvInitialiseMutex( Queue_t * pxNewQueue )
{
    if( pxNewQueue != NULL )
    {
        /* 队列创建函数会为通用队列正确设置所有队列结构成员,
         * 但这个函数是创建互斥量。覆盖那些需要不同设置的成员 - 
         * 特别是优先级继承所需的信息。 */
        pxNewQueue->u.xSemaphore.xMutexHolder = NULL;
        pxNewQueue->uxQueueType = queueQUEUE_IS_MUTEX;

        /* 为递归互斥量做准备 */
        pxNewQueue->u.xSemaphore.uxRecursiveCallCount = 0;

        traceCREATE_MUTEX( pxNewQueue );

        /* 以预期状态开始信号量 */
        ( void ) xQueueGenericSend( pxNewQueue, NULL, ( TickType_t ) 0U, queueSEND_TO_BACK );
    }
    else
    {
        traceCREATE_MUTEX_FAILED();
    }
}

可以看到,创建完互斥信号量后,会自动调用一次xQueueGenericSend,相当于主动释放了一次资源,这与我们之前介绍的"互斥信号量默认创建完会自动释放一次资源"一致。

因此,我们只需要简单地替换创建函数即可:

myPrintfQueueHandler = xSemaphoreCreateMutex(); // 创建互斥信号量,创建成功会自动释放一次信号量

替换信号量创建函数

四、编译调试,观察现象

我们编译、调试、运行代码:

编译运行

现在,我们通过串口输出分析互斥信号量的效果。

在没有使用互斥信号量的情况下,当高优先级任务3尝试获取已被低优先级任务1占用的信号量时,任务3会阻塞;此时中优先级任务2可以抢占任务1执行,导致任务1无法快速释放信号量,任务3长时间等待,出现优先级翻转。

而使用互斥信号量后,我们可以看到:当任务3(高优先级)尝试获取被任务1(低优先级)持有的互斥信号量时,任务1的优先级会被临时提升到与任务3相同的级别,这样任务2(中优先级)就无法抢断任务1的执行了。

通过串口输出,我们可以发现:

  1. 任务1获取互斥信号量后开始执行
  2. 当任务3尝试获取同一个互斥信号量时,任务1的优先级被提升
  3. 此时任务2无法抢占任务1,任务1可以继续执行并最终释放互斥信号量
  4. 任务1释放互斥信号量后,其优先级被恢复为原来的值
  5. 任务3获取互斥信号量并执行

这就验证了互斥信号量通过优先级继承机制解决优先级翻转的工作原理。

五、代码分析

让我们详细分析一下任务执行的完整过程:

  1. 首先,高优先级的任务3先完整执行一轮,然后进入延时状态(vTaskDelay(1000))
  2. 接着,中优先级的任务2开始执行,它需要执行1.5秒
  3. 但在任务2执行1秒后,任务3的延时结束,抢占执行权,获取互斥信号量,执行自己的逻辑,然后释放互斥信号量并再次进入延时状态
  4. 任务2继续执行剩余的0.5秒,然后也进入延时状态
  5. 这时轮到低优先级的任务1执行,它获取互斥信号量并开始执行
  6. 0.5秒后,任务3的延时结束,尝试获取互斥信号量,但发现信号量被任务1占用
  7. 关键点:此时任务1的优先级被临时提升到与任务3相同的级别
  8. 由于任务1现在具有与任务3相同的高优先级,任务2无法抢断其执行
  9. 任务1可以继续执行直到释放互斥信号量
  10. 任务1释放互斥信号量后,其优先级恢复原值,任务3获取信号量并开始执行
  11. 任务3执行完毕后,再次轮到任务2执行,而不是任务1,这证明任务1的优先级已被恢复

这整个过程清晰地展示了互斥信号量如何通过优先级继承机制解决优先级翻转问题。

六、完整代码

下面是完整的示例代码,使用互斥信号量解决优先级翻转:

#include "stm32f10x.h"                  // Device header
#include "FreeRTOS.h"
#include "task.h"
#include "usart.h"
#include "semphr.h"
#include "delay.h"

TaskHandle_t myTaskHandler1;
TaskHandle_t myTaskHandler2;
TaskHandle_t myTaskHandler3;
TaskHandle_t startTaskHandler;

QueueHandle_t myPrintfQueueHandler;

void myTask1(void *arg)
{
    BaseType_t res = 0;
    while (1)
    {
        taskENTER_CRITICAL();
        /* 获取信号量 */
        printf("The low-priority Task1 gets the semaphore\r\n");
        res = xSemaphoreTake(myPrintfQueueHandler, portMAX_DELAY);
        if (res != pdPASS)
        {
            printf("Task1 failed to get semaphore\r\n");
        }

        /* 执行其他逻辑 */
        printf("A low-priority Task1 is being executed\r\n");
        
        Delay_ms(3000);

        /* 释放信号量 */
        printf("Low-priority Task1 releases semaphores\r\n");
        res = xSemaphoreGive(myPrintfQueueHandler);
        if (res != pdPASS)
        {
            printf("Task1 failed to release semaphore\r\n");
        }

        taskEXIT_CRITICAL();
        
        vTaskDelay(1000);
    }
}

void myTask2(void *arg)
{
    while (1)
    {
        taskENTER_CRITICAL();
        printf("Task 2 of medium priority is being executed\r\n");
        Delay_ms(1500);
        printf("task2 is executed once\r\n");
        taskEXIT_CRITICAL();
        vTaskDelay(1000);
    }
}

void myTask3(void *arg)
{
    BaseType_t res = 0;
    while (1)
    {
        taskENTER_CRITICAL();
        
        /* 获取信号量 */
        printf("The high priority Task3 gets the semaphore\r\n");
        res = xSemaphoreTake(myPrintfQueueHandler, portMAX_DELAY);
        if (res != pdPASS)
        {
            printf("Task3 failed to get semaphore\r\n");
        }

        /* 执行其他逻辑 */
        printf("High Priority Task3 is executing...\r\n");
        Delay_ms(1000);

        /* 释放信号量 */
        printf("A high-priority Task3 releases a semaphore\r\n");
        res = xSemaphoreGive(myPrintfQueueHandler);
        if (res != pdPASS)
        {
            printf("Failed to release semaphore in Task3\r\n");
        }

        taskEXIT_CRITICAL();
        
        vTaskDelay(1000);
    }
}

void startTask(void *arg)
{
    taskENTER_CRITICAL();
    printf("startTask runnig\n");
    taskEXIT_CRITICAL();
    xTaskCreate(myTask1,"myTask1",128,NULL,2,&myTaskHandler1);
    xTaskCreate(myTask2,"myTask2",128,NULL,3,&myTaskHandler2);  // 注意这里的优先级设置
    xTaskCreate(myTask3,"myTask3",128,NULL,4,&myTaskHandler3);  // 任务名应为myTask3
    vTaskDelete(NULL);
}

int main(void)
{
    USART_Config();
    
    myPrintfQueueHandler = xSemaphoreCreateMutex(); // 创建互斥信号量
    
    if(myPrintfQueueHandler == NULL)
    {
        printf("The semaphore is created failed\r\n");
    }
    else 
    {
        printf("The semaphore is created successfully\r\n");
    }
    
    xTaskCreate(startTask,"startTask",128,NULL,2,&startTaskHandler);
    
    vTaskStartScheduler();
    
    while (1)
    {
        
    }
}

StaticTask_t        IdleTaskTCB;
StackType_t         IdleTaskStack[configMINIMAL_STACK_SIZE];
void vApplicationGetIdleTaskMemory( StaticTask_t ** ppxIdleTaskTCBBuffer,
                                   StackType_t ** ppxIdleTaskStackBuffer,
                                   uint32_t * pulIdleTaskStackSize )
{
    * ppxIdleTaskTCBBuffer = &IdleTaskTCB;
    * ppxIdleTaskStackBuffer = IdleTaskStack;
    * pulIdleTaskStackSize = configMINIMAL_STACK_SIZE;
}

注意:在startTask函数中,我修正了三个任务的优先级设置,分别为2、3、4,确保优先级递增,且修正了第三个任务的名称应为"myTask3"。

七、总结与拓展

通过本文的实践,我们清晰地看到了互斥信号量是如何通过优先级继承机制解决优先级翻转问题的。当高优先级任务等待低优先级任务持有的资源时,低优先级任务的优先级被临时提升,以防止中优先级任务抢占执行权。

互斥信号量与二值信号量的区别

  1. 优先级继承:互斥信号量支持优先级继承,二值信号量不支持
  2. 用途:互斥信号量专门用于互斥访问共享资源,二值信号量更适合任务同步
  3. 所有权:互斥信号量有所有权概念,只有获取者才能释放;二值信号量没有所有权概念
  4. 递归性:FreeRTOS还提供递归互斥信号量,允许同一任务多次获取同一互斥量

优先级继承的局限性

优先级继承机制虽然能解决优先级翻转问题,但仍有一些局限性:

  1. 只能解决单个互斥量的情况,对于多个互斥量形成的死锁,无能为力
  2. 增加了系统开销,尤其是频繁的优先级调整
  3. 在某些极端情况下可能导致系统行为不可预测

优先级天花板协议

除了优先级继承,还有一种解决优先级翻转的方法是"优先级天花板协议"(Priority Ceiling Protocol)。该协议为每个互斥信号量指定一个优先级天花板,任何获取该互斥量的任务都会临时提升到这个优先级。虽然FreeRTOS没有直接实现这一协议,但我们可以通过适当的设计来模拟它。

🔥 完整的FreeRTOS学习项目请访问:https://github.com/Despacito0o/FreeRTOS,从入门到精通的全面指南!

这个开源项目提供了从入门到精通的FreeRTOS学习资源,包括详细的移植教程、丰富的示例代码和深入浅出的文档说明,欢迎大家star和fork!


如果你对FreeRTOS有更多疑问或建议,欢迎在评论区留言,也欢迎关注我获取更多嵌入式开发内容!

FreeRTOS小项目实战------基于FreeRTOS和stm32的门禁系统 freertos小项目--基于stm32和freertos的门禁系统 阅读详情

相关推荐

FreeRTOS-互斥信号量(五)

介绍互斥信号量所用到的函数 1.互斥量创建函数 xSemaphoreCreateMutex() 功能:互斥量创建函数 参 数:void 无 返回值:返回一个互斥量句柄 2.互斥量获取函数 xSemaphoreTake( xSemaphore, xBlockTime ) 参数: xSemaphore:互斥量句柄 xBlockTime : portMAX_DELAY 没有申请到资源阻塞, 0 申请到立马返回 3.互斥量释放函数 xSemaphoreGive( xSemaphore

小阳先生 4930

【FreeRTOS优先级翻转问题及其解决方案】

1.至少3个任务(分别高中低3优先级)2.使用信号量资源。

m0_59023148的博客 1654

FreeRTOS 互斥信号量和二值信号量对比

互斥信号量 1.有优先级继承。 2.尽量不要在中断中调用 3.xSemaphoreCreateMutex创建后,可以直接take使用。 二值信号量 1.无优先级继承。 2.允许在中断中调用。 3.可以当做标志位来使用。 4.xSemaphoreCreateBinary创建后,必须得先give一下才能被take。 这么说其实很笼统,直接代码测试。 这里使用的是互斥信号量。 1.创建两个测试任务,app_mqtt_get_properties_handle_task和app_mqtt_se

qq_26226375的博客 1997

freeRTOS任务优先级反转

在嵌套中断模式下,如果一个高优先级任务被阻塞在一个低优先级任务上,那么操作系统会自动将低优先级任务的优先级提高到与高优先级任务相同,以确保高优先级任务可以及时运行。在优先级继承中,当一个高优先级的任务需要获取一个由低优先级任务持有的共享资源时,它会临时提升该低优先级任务的优先级,以便允许该低优先级任务快速释放该资源。在修改后的代码中,当任务1获取共享资源时,会使用 vTaskPrioritySet() 函数将任务2的优先级提升到与任务1相同,然后在释放共享资源后恢复任务2的原始优先级。

嵌入式之斋 2218

FreeRTOS互斥信号量与优先级翻转原理及实战

在实时操作系统中,任务优先级调度的确定性是保障系统响应及时性的基础。当多任务共享临界资源时,若仅依赖二值信号量等简单同步机制,极易引发优先级翻转——即高优先级任务因低优先级持有者被中优先级任务抢占而被迫延迟执行。该现象本质源于同步原语缺乏对持有者调度属性的动态干预能力。互斥信号量通过优先级继承机制,在等待发生时临时提升持有者优先级,从而封锁中优先级抢占路径,确保高优先级任务等待时间严格受限于临界区本身。这一技术广泛应用于STM32嵌入式开发、工业控制通信(如UART/SPI共享)、多任务日志系统等对WCRT

weixin_34471637的博客 226

freeRTOS任务优先级翻转

freeRTOS优先级反转及方案

嘉禾天成‘s Blog 822

FreeRTOS——优先级翻转

优先级翻转:高优先级的任务反而慢执行,低优先级的任务反而优先执行注意:在实时操作系统中不允许出现,在二值信号量中经常出现。

李彬的c++ 1291

TPRI-DMP平台介绍

TPRI-DMP为华能集团西安热工院自主产权的工业云PaaS平台,已经过13年的发展和迭代,其具备大规模能源电力行业生产应用软件开发和运行能力

ashou706的专栏 2264

FreeRTOS 优先级翻转以及互斥信号量

关于FreeRTOS 优先级翻转以及互斥信号量相关知识。在实时操作系统(RTOS)中,优先级翻转是一个常见的问题,特别是在多任务环境中。当一个高优先级的任务被一个低优先级的任务阻塞时,可能会导致系统性能问题和响应延迟。FreeRTOS中的互斥信号量(也称为互斥量或二值信号量)是用来解决这个问题的一种机制。,详细仔细说明优先级翻转带来的困难,以及通过互斥信号量来降低优先级翻转带来的影响!!!

上山的月的博客 1710

【实战解析】RTL8306MB交换芯片驱动开发中的RMII接口调试陷阱

本文通过一个RTL8306MB交换芯片驱动开发的实战案例,深入剖析了RMII接口调试中的经典陷阱。文章详细描述了因TX_EN与CRS_DV信号线交叉连接错误导致的“静默”故障,并分享了使用逻辑分析仪和示波器进行硬件排查的完整流程与核心技巧,为嵌入式网络驱动开发提供了宝贵的避坑指南。

weixin_29267307的博客 417

408王道计算机组成原理强化——指令系统及大题解构

1.大纲要求 2.x86

JiangNan_1002的博客 8502

FreeRTOS中的优先级翻转问题及其解决方案:互斥信号量详解

低优先级任务获取了某个共享资源高优先级任务需要访问同一资源,因资源被占用而阻塞中优先级任务此时抢占低优先级任务运行结果:高优先级任务间接被中优先级任务阻塞,实际执行顺序变成了"中→低→高"这种情况下,高优先级任务被迫等待中优先级任务完成,然后再等待低优先级任务释放资源,完全违背了优先级调度的初衷。优先级翻转是实时操作系统中的一个常见问题,可能严重影响系统的实时性能。FreeRTOS通过互斥信号量及其内建的优先级继承机制,提供了一种优雅而高效的解决方案。

Despacito0o的博客 1334

基于Stigmergy的团队LLM wiki协作机制与FastAPI工程实践

大语言模型驱动的知识管理正在改变个人笔记与团队文档的维护方式,但当多人共同维护一个维基时,如何避免版本覆盖、上下文丢失和幻觉污染成为核心挑战。Stigmergy这一源自生物学的协作机制提供了新思路:通过共享环境中的痕迹(如评论、标记、修订历史)实现异步协作,无需实时沟通或中心协调者。本文将原理落地为可工程化的系统,设计Page、Revision、Trace三大实体,并结合向量索引与LLM审查流程,最终用FastAPI与SQLite搭建一个最小可运行原型。从白蚁筑巢到团队知识库,Stigmergy让每个参与者

weixin_34174322的博客 361

FreeRTOS系列教程(五):如何使用互斥信号量

本文主要探索了互斥信号量的原理及其使用方法,包括互斥信号量运行机制,互斥信号量的应用场景,互斥信号量的优先级继承机制,互斥信号量的函数使用,以及信号量的使用实验等。通过本文,将会了解到:为什么要使用互斥信号量? 什么情况下使用互斥信号量? 为什么要有优先级继承机制? 互斥信号量怎么使用?

旭辉君的博客 2606

【DeepSeek-R1 】分词系统架构解析

DeepSeek-R1 分词系统解析 DeepSeek-R1 采用 SentencePiece Unigram 算法,通过统计语言模型实现高效子词分词。其核心优势包括: 算法对比:相比 BPE/WordPiece,Unigram 基于概率模型,支持多语言,自动回退字符级处理。 特性优化:智能空格处理、数字分桶、多语言统一支持,并采用前缀树索引和 SIMD 并行加速,编码速度达 12,458 tok/s。

赶紧的博客 426

《QGIS快速入门与应用基础》021:插件仓库 URL 获取与添加

详细介绍了在QGIS中添加第三方插件仓库的方法。首先强调必须从官方推荐、开源社区等可靠渠道获取插件仓库URL以确保安全性,并列出了4类可信获取渠道。文章分步骤讲解了URL添加流程,包括打开插件管理器设置面板、填写仓库信息、刷新缓存验证等操作。针对常见问题如URL格式错误、连接失败等提供了具体解决方案。最后总结指出,成功添加第三方仓库后即可直接搜索安装各类专业插件,从而扩展QGIS功能边界。全文注重实操指导,特别适合需要安装行业专用插件的QGIS用户参考。

LuckyHanMo的博客 453

FreeRTOS互斥信号量实战:如何用优先级继承解决任务卡死问题

本文深入探讨了FreeRTOS中互斥信号量的优先级继承机制,并通过STM32实战演示了如何有效解决多任务系统中的优先级翻转问题。文章对比了二值信号量与互斥信号量的差异,详细解析了互斥信号量的内部实现,并提供了避免任务卡死的最佳实践和性能优化技巧。

weixin_29310605的博客 532

如何使用 SAP ABAP Development Tool 连接 SAP BTP 上的免费 ABAP 编程环境

在云端环境编写 ABAP 代码会遇到一些限制,比如你无法在云端 ABAP 编程环境进行发生在传统 SAPGUI 里的 Dynpro 和 Screen 编程。部分在 SAPGUI 里使用的 List 处理相关的关键字比如 WRITE,还有 CALL TRANSACTION 等代码,也无法在云端使用。 当然如果大家觉得上面提到的这些限制不会影响自己的 ABAP 学习,就可以继续本文的阅读,使用 ABAP Development Tool 登录云端的 ABAP 编程环境。

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 1503

FreeRTOS的学习(四)—— 信号量之间的优先级翻转问题和互斥信号量

FreeRTOS的学习(三)—— 信号量之间的优先级翻转问题和互斥信号量

m0_58832575的博客 2088

Airtest进阶使用篇-提高脚本稳定性+批量运行脚本

Airtest进阶篇,主要分享如何提高脚本稳定性+如何批量运行脚本生成测试报告!

程序员小杨哥的博客 2567

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

FreeRTOS(互斥信号量)

前面学过,取值只有0与1两种状态的信号量称之为二值信号量。而互斥信号量是一种特殊的二值信号量,具有防止优先级翻转的特性。

qq_57594025的博客 2179

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

FreeRTOS 学习:(四十)信号量,优先级翻转问题,以及互斥信号量(互斥锁)

详细介绍了信号量当中的优先级翻转问题,以及互斥信号量(互斥锁)

qq_51409113的博客 1125

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

廖雪峰Python教程[完整版]

廖雪峰的python教程整理的,已经排好版了,而且还是完整的,适合小白

FreeRTOS互斥信号量实战:如何用优先级继承解决任务死锁问题

本文深入探讨了FreeRTOS互斥信号量的核心机制与应用实战,重点解析了其优先级继承特性如何有效解决多任务系统中的优先级翻转与死锁问题。通过对比二值信号量,并结合STM32嵌入式开发中的实际场景与代码示例,指导开发者正确使用互斥量保护共享资源,确保系统实时性与稳定性。

git9versioner的博客 730

16,FreeRTOS互斥信号量操作

创建四个动态任务,栈空间大小均为128字。startTask仅运行一次,负责互斥信号量的创建,task1、task2、task3任务的创建,startTask任务的删除。Task1(低优先级)负责互斥信号量的获取后绝对延时3S后释放。Task2(中优先级)负责串口打印运行状态。Task3(高优先级)负责互斥信号量的获取后绝对延时1S后释放。注:本实验基于正点原子FreeRTOS教程的学习总结。

LC_8575的博客 1003

STM32单片机FreeRTOS系统15 互斥信号量

介绍了互斥信号量以及API及相关实现。

陌夏微秋的博客 1154
上一篇: FreeRTOS中的优先级翻转问题及其解决方案:互斥信号量详解
下一篇: FreeRTOS深度解析:队列集(Queue Sets)的原理与应用
Despacito0o
博客等级 码龄4年 5171粉丝 137原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Despacito0o

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值