【基于链表的软件定时器实现】

基于链表的软件定时器实现


1. 应用需求
1.1 背景
  • 主要是针对单片机的开发,一般单片机的硬件定时器是有限的,不太可能有一个定时任务就用硬件定时器实现。
1.2 前提
  • 有一个已经实现的硬件定时器在进行计数。(本文的实现是基于time.h的库进行获取的时间)
1.3 功能
  • 支持根据参数设置的优先级进行顺序插入,优先级的值越小,则轮询定时器的时候会更快执行。
  • 支持配置软件定时器的定时时间,可以有两种模式配置,一种是持续性的自动装载,另外一种是设置重复运行的次数。
2. 功能实现
2.1 头文件定义
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
#include "time.h"

//所用的参数类型定义
typedef unsigned char       uint8_t;
typedef unsigned short      uint16_t;
typedef unsigned long       uint32_t;

//超时回调函数,无参数无返回值
typedef void (*TimeoutCb)(void);

//软件定时器属性
typedef struct
{
    uint8_t id;                 //软件定时器ID
    uint8_t priority;           //软件定时器优先级
    uint16_t mode;              //BIT15:1表示无限制连续重载,0表示启用重载次数,此时BIT[14:0]表示重载的次数
    uint16_t timeout;           //定时时间
    uint32_t startTimer;        //设置软件定时器的起始时间
    TimeoutCb timeoutFun;       //软件定时的超时回调
} SoftTimer;

//软件定时器节点
typedef struct _SoftTimerNode
{
    SoftTimer property;
    struct _SoftTimerNode *next;
} SoftTimerNode;
2.2 源文件功能
2.2.1 链表头的定义
//初始化一个软件定时器链表默认指向空
static SoftTimerNode *s_softTimerHead = NULL;
2.2.2 基准定时器的参考
  • 在单片机的应用中,单片机中应该有一个基准定时器进行计数,可供外部文件获取计数值。本文中直接从系统中获取一个utc时间,模拟计数值的产生。
  • 检测软件定时器是否超时,这里要特别注意溢出的情况,本文中实现没有考虑这个问题,实际应用中根据情况而定。
//获取系统当前UTC时间
uint32_t GetTimerCount(void)
{
    time_t utcTimer = 0;

    time(&utcTimer);

    return utcTimer;
}

//软件定时器是否超时
uint8_t IsSoftTimerTimeout(SoftTimer *softTimer)
{
    return ((softTimer->startTimer + softTimer->timeout) <= GetTimerCount());
}
2.2.3 获取软件定时器的个数
  • 如果链表是空的话,直接返回0,表示没有定时器。
  • 如果链表存在,则返回实际的软件定时器个数。
//获取软件定时器链表的长度
uint32_t GetSoftTimerCount(void)
{
    SoftTimerNode *cur = NULL;
    uint32_t cnt = 0;

    if (s_softTimerHead == NULL)
    {
        return 0;
    }

    cur = s_softTimerHead;
    //下一个数据不为空
    while (cur->next != NULL)
    {
        cnt++;
        cur = cur->next;
    }

    return 1 + cnt;
}
2.2.4 查找软件定时器的位置
  • 主要用于查找固定的定时器ID是否存在于当前链表的位置,如果不存在返回一个-1。
  • 如果链表中存在这个名称为ID的软件定时器,则返回当前存在的位置。这个位置从0开始表示,即链表头指向的是第一个数据,此时位置为0。
//查找定时器ID的位置
int SearchSoftTimerById(uint8_t id)
{
    SoftTimerNode *cur = NULL;
    int pos = 0;

    if (s_softTimerHead == NULL)
    {
        return -1;
    }

    cur = s_softTimerHead;
    for (;;)
    {
        if (cur->property.id == id)
        {
            break;
        }

        if (cur->next == NULL)
        {
            pos = -1;            //找不到位置
            break;
        }

        cur = cur->next;
        pos++;
    }

    return pos;
}
2.2.5 根据优先级判断应该插入的位置
  • 主要用于判断当前优先级应该位于链表的哪个位置,这个位置从0开始表示,即链表头指向的是第一个数据,此时位置为0。
  • 链表的排放顺序是按照优先级进行顺序排放,优先级的参数值越小,则插入的位置应该越靠前。
  • 如果优先级是相同的,则放在相同优先级结点的最后一个位置。
//获取插入的位置
uint32_t GetInsertPosByPriority(uint16_t priority)
{
    SoftTimerNode *cur = NULL;
    uint32_t pos = 0;

    if (s_softTimerHead == NULL)
    {
        return pos;           //插入在头部
    }

    cur = s_softTimerHead;
    for (;;)
    {
        //优先级比当前的结点要高
        if (priority < cur->property.priority)
        {
            break;
        }

        pos++;
        if (cur->next == NULL)
        {
            break;
        }

        cur = cur->next;
    }

    return pos;
}
2.2.6 删除一个软件定时器
  • 软件ID应该是唯一的,类似于软件定时的名称
//删除软件ID为id的软件定时器
void DeleteSoftTimer(uint8_t id)
{
    SoftTimerNode *del = NULL;
    SoftTimerNode *cur = NULL;
    int pos = 0;
    int i = 0;

    pos = SearchSoftTimerById(id);
    if (pos < 0)
    {
        //不存在当前ID
        return;
    }

    if (pos == 0)
    {
        //删除头结点
        del = s_softTimerHead;
        s_softTimerHead = s_softTimerHead->next;
        free(del);
        return;
    }

    cur = s_softTimerHead;
    //删除非头部结点
    for (i = 0; i < pos - 1; i++)
    {
        cur = cur->next;
    }

    del = cur->next;
    cur->next = del->next;
    free(del);

    return;
}
2.2.7 创建一个软件定时器
  • 如果当前软件ID是重名的,会将重名的软件定时器删掉并用新创建的替代。
//创建软件定时器
void CreatSoftTimer(SoftTimer *softTimer)
{
    SoftTimerNode *prev = NULL;
    SoftTimerNode *cur = NULL;
    SoftTimerNode *add = NULL;
    uint32_t pos = 0;
    uint32_t i = 0;

    //动态分配空间
    add = (SoftTimerNode*)malloc(sizeof(SoftTimerNode));
    if (add == NULL)
    {
        return;
    }

    //生成结点
    memcpy(&add->property, softTimer, sizeof(SoftTimerNode));
    add->next = NULL;

    //如果当前软件定时器ID存在则会直接删除
    DeleteSoftTimer(softTimer->id);

    //获取插入的位置
    pos = GetInsertPosByPriority(softTimer->priority);
    if (pos == 0)
    {
        add->next = s_softTimerHead;
        s_softTimerHead = add;
        return;
    }

    //插入的位置不是头部
    cur = s_softTimerHead;
    for (i = 0; i < pos - 1; i++)
    {
        cur = cur->next;
    }

    add->next = cur->next;
    cur->next = add;
    return;
}
2.2.8 轮询软件定时器
  • 这个接口一般放在主函数中轮询调用,如果检测到计数时间到了,则运行软件定时器的回调函数。
  • 如果软件定时器是连续自动重载的,在每次执行回调之后会在下一个超时继续执行。
  • 如果软件定时器是设置的重载次数,则在重载次数清0之后,会自动删除该软件定时器。实际运行次数是重载次数+1。即设置的重载次数为0会执行1次。
//运行软件定时器
void RunSoftTimer(void)
{
    SoftTimerNode *cur = NULL;
    uint32_t reload = 0;

    if (s_softTimerHead == NULL)
    {
        return;
    }

    cur = s_softTimerHead;
    for (;;)
    {
        if (IsSoftTimerTimeout(&cur->property))
        {
            if (cur->property.timeoutFun != NULL)
            {
                cur->property.timeoutFun();
            }

            if (cur->property.mode & (1 << 15))     //自动重载
            {
                reload = GetTimerCount();
                printf("[%d]+++ soft timer auto reload %d - %d = %d\r\n", cur->property.id,
                    reload, cur->property.startTimer, reload - cur->property.startTimer);
                cur->property.startTimer = reload;
            }
            else if (cur->property.mode > 0)
            {
                cur->property.mode--;               //限制重载次数
                reload = GetTimerCount();
                printf("[%d]+++ soft timer remain reload %d, %d - %d = %d\r\n", cur->property.id, cur->property.mode,
                    reload, cur->property.startTimer, reload - cur->property.startTimer);
                cur->property.startTimer = reload;
            }
            else
            {
                printf("[%d]--- soft timer auto delete\r\n", cur->property.id);
                DeleteSoftTimer(cur->property.id);     //删除该结点
            }
        }

        if (cur->next == NULL)
        {
            break;
        }

        cur = cur->next;
    }

    return;
}
3. 功能测试
3.1 打印软件定时器链表信息
//显示软件定时器列表
void ShowSoftTimerList(void)
{
    SoftTimerNode *cur = NULL;
    uint32_t i = 0;
    uint32_t count = 0;

    cur = s_softTimerHead;
    count = GetSoftTimerCount();
    printf("soft timer totol count: %d\r\n", count);
    printf("------------------------------------------------------------------------\r\n");
    for (i = 0; i < GetSoftTimerCount(); i++)
    {

        printf("[%d]: addr = %08X, id = %d; priority = %d; mode = %d, timeout = %d, startTimer = %d, next = %08X\r\n", cur->property.id, cur,
                cur->property.id, cur->property.priority, cur->property.mode, cur->property.timeout, cur->property.startTimer, cur->next);
        cur = cur->next;
    }
    printf("------------------------------------------------------------------------\r\n");

    return;
}
3.2 测试函数
int main()
{
    uint8_t i = 0;
    uint8_t optMode = 0;
    SoftTimer softTimer = {0};
    SoftTimer softTimerList[] = {
        {0, 4, 4, 4, 0, NULL},      //测试插入一个结点
        {0, 7, 7, 7, 0, NULL},      //测试插入同名结点
        {2, 3, 3, 3, 0, NULL},      //测试头部插入
        {3, 8, 8, 8, 0, NULL},      //测试尾部插入
        {4, 7, 7, 7, 0, NULL},      //测试中间插入且为同优先级
        {5, 1, 1, 1, 0, NULL},      //优先级最高测试删除头
        {6, 9, 9, 9, 0, NULL},      //优先级最低测试删除头
        {7, 5, 5, 5, 0, NULL},      //在中间删除一个优先级
    };

    for (i = 0; i < sizeof(softTimerList) / sizeof(SoftTimer); i++)
    {
        printf("[%d]+++ Creat soft timer. ", softTimerList[i].id);
        softTimerList[i].startTimer = GetTimerCount();
        CreatSoftTimer(&softTimerList[i]);
        ShowSoftTimerList();
    }

    for (i = 0; i < 3; i++)
    {
        printf("[%d]--- Delete soft timer. ", softTimerList[sizeof(softTimerList) / sizeof(SoftTimer) -1 - i].id);
        softTimerList[sizeof(softTimerList) / sizeof(SoftTimer) -1 - i].startTimer = GetTimerCount();
        DeleteSoftTimer(softTimerList[sizeof(softTimerList) / sizeof(SoftTimer) -1 - i].id);
        ShowSoftTimerList();
    }

    do
    {
        RunSoftTimer();
    } while (GetSoftTimerCount());

    printf("All software timers are automatically released!\r\n");

    return 0;
}
3.3 测试结果
[0]+++ Creat soft timer. soft timer totol count: 1
------------------------------------------------------------------------
[0]: addr = 001713E0, id = 0; priority = 4; mode = 4, timeout = 4, startTimer = 1653389188, next = 00000000
------------------------------------------------------------------------
[0]+++ Creat soft timer. soft timer totol count: 1
------------------------------------------------------------------------
[0]: addr = 00171410, id = 0; priority = 7; mode = 7, timeout = 7, startTimer = 1653389188, next = 00000000
------------------------------------------------------------------------
[2]+++ Creat soft timer. soft timer totol count: 2
------------------------------------------------------------------------
[2]: addr = 001713E0, id = 2; priority = 3; mode = 3, timeout = 3, startTimer = 1653389188, next = 00171410
[0]: addr = 00171410, id = 0; priority = 7; mode = 7, timeout = 7, startTimer = 1653389188, next = 00000000
------------------------------------------------------------------------
[3]+++ Creat soft timer. soft timer totol count: 3
------------------------------------------------------------------------
[2]: addr = 001713E0, id = 2; priority = 3; mode = 3, timeout = 3, startTimer = 1653389188, next = 00171410
[0]: addr = 00171410, id = 0; priority = 7; mode = 7, timeout = 7, startTimer = 1653389188, next = 00171440
[3]: addr = 00171440, id = 3; priority = 8; mode = 8, timeout = 8, startTimer = 1653389188, next = 00000000
------------------------------------------------------------------------
[4]+++ Creat soft timer. soft timer totol count: 4
------------------------------------------------------------------------
[2]: addr = 001713E0, id = 2; priority = 3; mode = 3, timeout = 3, startTimer = 1653389188, next = 00171410
[0]: addr = 00171410, id = 0; priority = 7; mode = 7, timeout = 7, startTimer = 1653389188, next = 00171470
[4]: addr = 00171470, id = 4; priority = 7; mode = 7, timeout = 7, startTimer = 1653389188, next = 00171440
[3]: addr = 00171440, id = 3; priority = 8; mode = 8, timeout = 8, startTimer = 1653389188, next = 00000000
------------------------------------------------------------------------
[5]+++ Creat soft timer. soft timer totol count: 5
------------------------------------------------------------------------
[5]: addr = 001714A0, id = 5; priority = 1; mode = 1, timeout = 1, startTimer = 1653389188, next = 001713E0
[2]: addr = 001713E0, id = 2; priority = 3; mode = 3, timeout = 3, startTimer = 1653389188, next = 00171410
[0]: addr = 00171410, id = 0; priority = 7; mode = 7, timeout = 7, startTimer = 1653389188, next = 00171470
[4]: addr = 00171470, id = 4; priority = 7; mode = 7, timeout = 7, startTimer = 1653389188, next = 00171440
[3]: addr = 00171440, id = 3; priority = 8; mode = 8, timeout = 8, startTimer = 1653389188, next = 00000000
------------------------------------------------------------------------
[6]+++ Creat soft timer. soft timer totol count: 6
------------------------------------------------------------------------
[5]: addr = 001714A0, id = 5; priority = 1; mode = 1, timeout = 1, startTimer = 1653389188, next = 001713E0
[2]: addr = 001713E0, id = 2; priority = 3; mode = 3, timeout = 3, startTimer = 1653389188, next = 00171410
[0]: addr = 00171410, id = 0; priority = 7; mode = 7, timeout = 7, startTimer = 1653389188, next = 00171470
[4]: addr = 00171470, id = 4; priority = 7; mode = 7, timeout = 7, startTimer = 1653389188, next = 00171440
[3]: addr = 00171440, id = 3; priority = 8; mode = 8, timeout = 8, startTimer = 1653389188, next = 001714D0
[6]: addr = 001714D0, id = 6; priority = 9; mode = 9, timeout = 9, startTimer = 1653389188, next = 00000000
------------------------------------------------------------------------
[7]+++ Creat soft timer. soft timer totol count: 7
------------------------------------------------------------------------
[5]: addr = 001714A0, id = 5; priority = 1; mode = 1, timeout = 1, startTimer = 1653389188, next = 001713E0
[2]: addr = 001713E0, id = 2; priority = 3; mode = 3, timeout = 3, startTimer = 1653389188, next = 00171500
[7]: addr = 00171500, id = 7; priority = 5; mode = 5, timeout = 5, startTimer = 1653389188, next = 00171410
[0]: addr = 00171410, id = 0; priority = 7; mode = 7, timeout = 7, startTimer = 1653389188, next = 00171470
[4]: addr = 00171470, id = 4; priority = 7; mode = 7, timeout = 7, startTimer = 1653389188, next = 00171440
[3]: addr = 00171440, id = 3; priority = 8; mode = 8, timeout = 8, startTimer = 1653389188, next = 001714D0
[6]: addr = 001714D0, id = 6; priority = 9; mode = 9, timeout = 9, startTimer = 1653389188, next = 00000000
------------------------------------------------------------------------
[7]--- Delete soft timer. soft timer totol count: 6
------------------------------------------------------------------------
[5]: addr = 001714A0, id = 5; priority = 1; mode = 1, timeout = 1, startTimer = 1653389188, next = 001713E0
[2]: addr = 001713E0, id = 2; priority = 3; mode = 3, timeout = 3, startTimer = 1653389188, next = 00171410
[0]: addr = 00171410, id = 0; priority = 7; mode = 7, timeout = 7, startTimer = 1653389188, next = 00171470
[4]: addr = 00171470, id = 4; priority = 7; mode = 7, timeout = 7, startTimer = 1653389188, next = 00171440
[3]: addr = 00171440, id = 3; priority = 8; mode = 8, timeout = 8, startTimer = 1653389188, next = 001714D0
[6]: addr = 001714D0, id = 6; priority = 9; mode = 9, timeout = 9, startTimer = 1653389188, next = 00000000
------------------------------------------------------------------------
[6]--- Delete soft timer. soft timer totol count: 5
------------------------------------------------------------------------
[5]: addr = 001714A0, id = 5; priority = 1; mode = 1, timeout = 1, startTimer = 1653389188, next = 001713E0
[2]: addr = 001713E0, id = 2; priority = 3; mode = 3, timeout = 3, startTimer = 1653389188, next = 00171410
[0]: addr = 00171410, id = 0; priority = 7; mode = 7, timeout = 7, startTimer = 1653389188, next = 00171470
[4]: addr = 00171470, id = 4; priority = 7; mode = 7, timeout = 7, startTimer = 1653389188, next = 00171440
[3]: addr = 00171440, id = 3; priority = 8; mode = 8, timeout = 8, startTimer = 1653389188, next = 00000000
------------------------------------------------------------------------
[5]--- Delete soft timer. soft timer totol count: 4
------------------------------------------------------------------------
[2]: addr = 001713E0, id = 2; priority = 3; mode = 3, timeout = 3, startTimer = 1653389188, next = 00171410
[0]: addr = 00171410, id = 0; priority = 7; mode = 7, timeout = 7, startTimer = 1653389188, next = 00171470
[4]: addr = 00171470, id = 4; priority = 7; mode = 7, timeout = 7, startTimer = 1653389188, next = 00171440
[3]: addr = 00171440, id = 3; priority = 8; mode = 8, timeout = 8, startTimer = 1653389188, next = 00000000
------------------------------------------------------------------------
[2]+++ soft timer remain reload 2, 1653389191 - 1653389188 = 3
[2]+++ soft timer remain reload 1, 1653389194 - 1653389191 = 3
[4]+++ soft timer remain reload 6, 1653389195 - 1653389188 = 7
[0]+++ soft timer remain reload 6, 1653389195 - 1653389188 = 7
[3]+++ soft timer remain reload 7, 1653389196 - 1653389188 = 8
[2]+++ soft timer remain reload 0, 1653389197 - 1653389194 = 3
[2]--- soft timer auto delete
[0]+++ soft timer remain reload 5, 1653389202 - 1653389195 = 7
[4]+++ soft timer remain reload 5, 1653389202 - 1653389195 = 7
[3]+++ soft timer remain reload 6, 1653389204 - 1653389196 = 8
[0]+++ soft timer remain reload 4, 1653389209 - 1653389202 = 7
[4]+++ soft timer remain reload 4, 1653389209 - 1653389202 = 7
[3]+++ soft timer remain reload 5, 1653389212 - 1653389204 = 8
[4]+++ soft timer remain reload 3, 1653389216 - 1653389209 = 7
[0]+++ soft timer remain reload 3, 1653389216 - 1653389209 = 7
[3]+++ soft timer remain reload 4, 1653389220 - 1653389212 = 8
[0]+++ soft timer remain reload 2, 1653389223 - 1653389216 = 7
[4]+++ soft timer remain reload 2, 1653389223 - 1653389216 = 7
[3]+++ soft timer remain reload 3, 1653389228 - 1653389220 = 8
[0]+++ soft timer remain reload 1, 1653389230 - 1653389223 = 7
[4]+++ soft timer remain reload 1, 1653389230 - 1653389223 = 7
[3]+++ soft timer remain reload 2, 1653389236 - 1653389228 = 8
[0]+++ soft timer remain reload 0, 1653389237 - 1653389230 = 7
[4]+++ soft timer remain reload 0, 1653389237 - 1653389230 = 7
[4]--- soft timer auto delete
[3]+++ soft timer remain reload 1, 1653389244 - 1653389236 = 8
[0]--- soft timer auto delete
[3]+++ soft timer remain reload 0, 1653389252 - 1653389244 = 8
[3]--- soft timer auto delete
All software timers are automatically released!

2022-05-24 李不清的烦恼,总结篇。

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
C语言中,可以使用链表实现软件定时器,常用的方法是将定时器按照时间先后顺序加入到链表中,然后在主程序中不断遍历链表,查找是否有定时器时间已到期,如果有则执行相应的任务并将该定时器链表中删除。 以下是一个简单的使用链表增加软件定时器的示例代码: ```c #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <signal.h> struct timer_node { int time_left; // 定时器剩余时间 void (*handler)(void*); // 定时器处理函数 void *arg; // 定时器参数 struct timer_node *next; // 链表指针 }; struct timer_node *timer_list = NULL; // 定时器链表 void timer_handler(int signum) { struct timer_node *p = timer_list; while (p != NULL) { if (p->time_left > 0) { p->time_left--; if (p->time_left == 0) { p->handler(p->arg); // 执行定时器处理函数 } } p = p->next; } } void add_timer(int ms, void (*handler)(void*), void *arg) { struct timer_node *node = (struct timer_node*)malloc(sizeof(struct timer_node)); node->time_left = ms / 10; // 将ms转换为10ms的单位 node->handler = handler; node->arg = arg; node->next = NULL; struct timer_node *p = timer_list; struct timer_node *prev = NULL; while (p != NULL) { if (node->time_left <= p->time_left) { if (prev == NULL) { timer_list = node; } else { prev->next = node; } node->next = p; p->time_left -= node->time_left; return; } else { node->time_left -= p->time_left; prev = p; p = p->next; } } if (prev == NULL) { timer_list = node; } else { prev->next = node; } struct itimerval timer; timer.it_value.tv_sec = 0; timer.it_value.tv_usec = 10 * 1000; // 定时器时间间隔为10ms timer.it_interval.tv_sec = 0; timer.it_interval.tv_usec = 10 * 1000; setitimer(ITIMER_REAL, &timer, NULL); // 设置定时器 } void delete_timer(void (*handler)(void*), void *arg) { struct timer_node *p = timer_list; struct timer_node *prev = NULL; while (p != NULL) { if (p->handler == handler && p->arg == arg) { if (prev == NULL) { timer_list = p->next; } else { prev->next = p->next; } free(p); return; } else { prev = p; p = p->next; } } } void task1(void *arg) { printf("Task 1 executed.\n"); add_timer(2000, task1, NULL); // 重新添加定时器 } void task2(void *arg) { printf("Task 2 executed.\n"); } int main() { signal(SIGALRM, timer_handler); // 注册信号处理函数 add_timer(1000, task1, NULL); // 添加定时器1 add_timer(500, task2, NULL); // 添加定时器2 printf("Waiting for timer to expire...\n"); while (1) { pause(); // 等待信号 } return 0; } ``` 在上述代码中,定义了一个结构体timer_node作为定时器链表的节点,包含定时器剩余时间、定时器处理函数、定时器参数和链表指针。使用timer_list来存储定时器链表。 在定时器处理函数timer_handler中,遍历链表,查找是否有定时器时间已到期,如果有则执行相应的任务并将该定时器链表中删除。 在add_timer函数中,将新的定时器按照时间先后顺序加入到链表中,并根据定时器时间间隔设置定时器的时间和时间间隔。 在delete_timer函数中,从链表中删除指定的定时器。 在主程序中,添加了两个定时器task1和task2,分别在1000ms和500ms后执行。在task1的处理函数中,重新添加了一个2000ms的定时器。 需要注意的是,软件定时器的精度和稳定性可能受到系统负载、CPU占用率等因素的影响,因此在设计和使用时需要考虑到这些因素。此外,还需要注意定时器的时间间隔不能太小,否则会影响系统的正常运行。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值