C++模拟进程的三状态模型和进程调度(FCFS)性能计算

C++模拟进程的三状态模型和进程调度(FCFS)性能计算

不多BB 直接上代码

main.cpp文件

//main.cpp
#include "Basic_function.h"
#include "State_transition.h"
//关键词:进程三模型状态模拟 进程调度(FCFS)性能计算
using namespace std;
typedef unsigned int uint;

uint cur_time; //时间线
uint CPU_size; //几核CPU
uint IO1_equipment_size; //几个IO1设备
uint IO2_equipment_size; //几个IO2设备
double turnover_time_sum; //所有进程周转时间之和
double weighted_turnover_time_sum; //所有进程带权周转时间之和

vector<PCB> CPU; //处理机
vector<PCB> IO1_equipment; //IO1设备
vector<PCB> IO2_equipment; //IO2设备
vector<PCB> destroy; //待销毁
queue<PCB> ready_que; //就绪队列
queue<PCB> block_IO1_que; //请求IO1设备阻塞队列
queue<PCB> block_IO2_que; //请求IO2设备阻塞队列


bool cmp(PCB &a,PCB &b){ //先来先进内存(就绪队列)
    return a.get_arrive_time()<b.get_arrive_time();
}

void READY_TO_CPU(){
    while(ready_to_CPU());//ready就绪队列里面的进程上处理机运行
    while(scan_CPU()) while(ready_to_CPU()); //扫描CPU 看是否需要有变动(有进程下CPU) 如果有的话继续让ready就绪队列上CPU
    //至于这里为什么在 while()whiel(); 语句前面还需要一个while语句 是因为可能会出现这种情况:
    //“当前的还有空闲的CPU,而ready就绪队列里面又刚好到了新的就绪进程,但是不巧的是没有需要下CPU的进程”
    //BLOCK_QUE_TO_IO函数同理
}

void BLOCK_QUE_TO_IO(){
    while(block_que_to_IO(1)); //block就绪队列里面的进程上IO1设备进行IO操作
    while(scan_IO_equipment(1)) while(block_que_to_IO(1)); //扫描IO1设备 看是否需要有变动(有进程下IO1设备) 如果有的话继续让block就绪队列上IO1设备

    while(block_que_to_IO(2));//block就绪队列里面的进程上IO2设备进行IO操作
    while(scan_IO_equipment(2)) while(block_que_to_IO(2)); //扫描IO2设备 看是否需要有变动(有进程下IO2设备) 如果有的话继续让block就绪队列上IO2设备
}

int main() { //模拟开始

    printf("请输入CPU个数(同时可以跑几个进程):");
    cin>>CPU_size;

    printf("请输入分别有几台IO1设备、IO2设备:");
    cin>>IO1_equipment_size>>IO2_equipment_size;

    printf("请输入本次模拟的总进程数目:");
    uint num;cin>>num;

    vector<PCB> tmp;
    for(uint i=0;i<num;i++) tmp.push_back(createProgress(i));

    sort(tmp.begin(),tmp.end(),cmp); //按照到达时间排序
    cur_time=0; //初始化时间线
    turnover_time_sum=weighted_turnover_time_sum=0.0; //初始化 总的周转时间和带权周转时间
    while(tmp.size() || CPU.size() || ready_que.size() || IO1_equipment.size() || IO2_equipment.size() ||block_IO1_que.size() || block_IO2_que.size()){

        while(tmp.size() && tmp[0].get_arrive_time()==cur_time){ //有进程到了就从后备队列调入内存(就绪队列)
            PCB &t=tmp[0];
            ready_que.push(t);
            tmp.erase(tmp.begin());
        }

        READY_TO_CPU(); //保证:运行完毕的进程下CPU 就绪队列进程上CPU **************1
        BLOCK_QUE_TO_IO(); //保证:IO完毕的进程下IO设备 等待IO的阻塞队列进程上IO ****2

        printProgresses(); //打印进程信息
        destroyProgresses(); //销毁进程

        cur_time++; //全程序的"key"--时间线推移,只有它才能推动这次模拟在逻辑上的运行!!!

        CPU_runProgresses(); //模拟CPU跑运行进程
        ready_wait(); //模拟就绪队列的等待

        use_IO_equipment(); //模拟阻塞进程进行IO
        block_wait(); //模拟阻塞队列的等待

        READY_TO_CPU(); //保证:运行完毕的进程下CPU 就绪队列进程上CPU **************3
        BLOCK_QUE_TO_IO(); //保证:IO完毕的进程下IO设备 等待IO的阻塞队列进程上IO ****4

        //只有保证 1 2 cur_time++ 3 4 这样的顺序才可以做到在“离散的时间点”上实现肉眼所见的“时间连续”
    }
    printProgresses();
    destroyProgresses();
    printf("\n该次模拟的进程平均周转时间 %.2f, 平均带权周转时间 %.2f\n",turnover_time_sum/num,weighted_turnover_time_sum/num);
    //模拟结束
    return 0;
}

PCB.h文件

// Created by BingWeiHuang on 2021/4/6.
// 这里是PCB类的声明
// PCB.h
#ifndef SIMPLE_THREE_STATE_SIMULATION_OF_THE_PROCESS_PCB_H
#define SIMPLE_THREE_STATE_SIMULATION_OF_THE_PROCESS_PCB_H
#include <iostream>
#include <queue>
#include <algorithm>
#include <string>
#include <cstdio>

using namespace std;
typedef unsigned int uint;
extern uint cur_time;
extern uint CPU_size;
extern double turnover_time_sum;
extern double weighted_turnover_time_sum;

class PCB{
private:
    uint index; //进程序号
    string name; //进程名
    string state; //进程状态
    uint need_running_time; //预计运行时间
    uint arrive_time; //程序到达时间
    uint running_time; //实际已经运行的时间
    uint end_time; //结束时间
    uint waiting_time; //等待时间
public:
    bool need_IO1,need_IO2; //是否需要IO1 IO2设备
    uint when_need_IO1,when_need_IO2; //在CPU上运行多少时间之后需要请求IO设备
    uint IO1_time,IO2_time; //最开始设置的需要IO设备多长时间
    uint need_IO1_time,need_IO2_time; //实际上还需要占用IO设备多长时间
    bool have_IO1,have_IO2; //是否正在占用IO设备

    uint turnover_time()const;//周转时间
    double weighted_turnover_time()const;//带权周转时间
    void Run();//运行
    void Wait();//等待
    void End();//结束
    void set_state(string new_state);
    uint get_arrive_time()const;
    uint get_running_time()const;
    uint get_need_running_time()const;
    void print()const;
    PCB(uint inde,string nam,string sta,uint need_running_t,uint arrive_t,bool IO1,bool IO2);
};
#endif //SIMPLE_THREE_STATE_SIMULATION_OF_THE_PROCESS_PCB_H

PCB.cpp文件

// 这里是PCB类的实现
// PCB.cpp
#include "PCB.h"
extern uint cur_time;
extern uint CPU_size;
extern double turnover_time_sum;
extern double weighted_turnover_time_sum;

extern vector<PCB> CPU; //处理机
extern queue<PCB> ready_que; //就绪队列
extern queue<PCB> block_IO1_que; //请求IO1设备阻塞队列
extern queue<PCB> block_IO2_que; //请求IO2设备阻塞队列
extern vector<PCB> destroy; //待销毁
using namespace std;

uint PCB::turnover_time()const{ //周转时间
    return cur_time-arrive_time;
}
double PCB::weighted_turnover_time()const{ //带权周转时间
    return turnover_time()*1.0/running_time;
}
void PCB::Run(){ //运行
    running_time++;
}
void PCB::Wait(){ //等待
    waiting_time++;
}
void PCB::End(){ //结束
    end_time=cur_time;
    have_IO1=have_IO2=0;
}
void PCB::set_state(string new_state){
    state=new_state;
}
uint PCB::get_arrive_time()const{
    return arrive_time;
}
uint PCB::get_running_time()const{
    return running_time;
}
uint PCB::get_need_running_time()const{
    return need_running_time;
}
void PCB::print()const{
    printf("进程%d:\t",index);
    cout<<"|名称:"<<name<<"\t";
    cout<<"|状态:"<<state<<"\t";
    printf("|到达时刻:%4d",arrive_time);
    printf("|预计运行时长:%4d",need_running_time);
    printf("|已运行总时长:%4d",running_time);
    printf("|已等待总时长:%4d|",waiting_time);
    if(state=="block"){
        if(need_IO1 && have_IO1){
            printf("\n\t|已经使用IO1时长:%4d",IO1_time-need_IO1_time);
            printf("|还需要使用时长:%4d|",need_IO1_time);
        }
        else if(need_IO2 && have_IO2){
            printf("\n\t|已经使用IO2时长:%4d",IO2_time-need_IO2_time);
            printf("|还需要使用时长:%4d|",need_IO2_time);
        }
    }
    else if(state=="destroy"){
        printf("\n\t|执行IO的总时长:%4d",turnover_time()-waiting_time-running_time);
        printf("|结束时刻:%4d",end_time);
        printf("|周转时间:%4d",turnover_time());
        printf("|带权周转:%6.2lf|",weighted_turnover_time());
    }
    cout<<endl;
}
PCB::PCB(uint inde,string nam,string sta,uint need_running_t,uint arrive_t,bool IO1,bool IO2):
        index(inde),name(nam),state(sta),need_running_time(need_running_t),arrive_time(arrive_t),need_IO1(IO1),need_IO2(IO2){
    running_time=waiting_time=0;
    have_IO1=have_IO2=0;
}

State_transition.h文件

// Created by BingWeiHuang on 2021/4/6.
// 这里是关于状态转换的函数的声明
// State_transition.h
#ifndef SIMPLE_THREE_STATE_SIMULATION_OF_THE_PROCESS_STATE_TRANSITION_H
#define SIMPLE_THREE_STATE_SIMULATION_OF_THE_PROCESS_STATE_TRANSITION_H
#include "PCB.h"
using namespace std;
typedef unsigned int uint;
extern uint cur_time;
extern uint CPU_size;
extern uint IO1_equipment_size;
extern uint IO2_equipment_size;
extern double turnover_time_sum;
extern double weighted_turnover_time_sum;

extern vector<PCB> CPU; //处理机
extern vector<PCB> IO1_equipment; //IO1设备
extern vector<PCB> IO2_equipment; //IO2设备
extern queue<PCB> ready_que; //就绪队列
extern queue<PCB> block_IO1_que; //请求IO1设备阻塞队列
extern queue<PCB> block_IO2_que; //请求IO2设备阻塞队列
extern vector<PCB> destroy; //待销毁

void CPU_to_destroy(int i);//从cpu里面终止进程


void CPU_to_block(uint i,uint num);//等待IO 阻塞

void block_to_ready(uint i,uint num);//已经具备所有资源 唤醒

bool ready_to_CPU();//就绪队列上处理机(FCFS)


void block_to_destroy(uint i,uint num);//有可能出现IO请求被完成之后已经不需要再运行了,为了方便起见我们直接再写一个函数直接终止。


#endif //SIMPLE_THREE_STATE_SIMULATION_OF_THE_PROCESS_STATE_TRANSITION_H

State_transition.cpp文件

// 这里是这里是关于状态转换的函数的实现
// State_transition.cpp
#include "State_transition.h"

void CPU_to_destroy(int i){ //从cpu里面终止进程
    PCB &t=CPU[i];
    t.End();
    t.set_state("destroy");
    destroy.push_back(t);
    CPU.erase(CPU.begin()+i);
}

void CPU_to_block(uint i,uint num){ //等待IO 阻塞
    PCB t=CPU[i];
    t.set_state("block");
    if(num==1){
        block_IO1_que.push(t);
    }
    else if(num==2){
        block_IO2_que.push(t);
    }
    CPU.erase(CPU.begin()+i);
}

void block_to_ready(uint i,uint num){ //已经具备所有资源 唤醒
    if(num==1){
        PCB &t=IO1_equipment[i];
        t.have_IO1=0;
        t.need_IO1=0;
        t.set_state("ready");
        ready_que.push(t);
        IO1_equipment.erase(IO1_equipment.begin()+i);
    }
    else if(num==2){
        PCB &t=IO2_equipment[i];
        t.have_IO2=0;
        t.need_IO2=0;
        t.set_state("ready");
        ready_que.push(t);
        IO2_equipment.erase(IO2_equipment.begin()+i);
    }

}

bool ready_to_CPU(){ //就绪队列上处理机(FCFS)
    if(CPU.size()==CPU_size || ready_que.empty()) return 0;
    PCB &t=ready_que.front();
    t.set_state("running");
    CPU.push_back(t);
    ready_que.pop();
    return 1;
}

void block_to_destroy(uint i,uint num){ //有可能出现IO请求被完成之后已经不需要再运行了,直接终止。
    if(num==1){          //因为如果按步骤 阻塞队列->就绪队列->CPU->终止 可能会因为所有CPU都在被占用而无法及时执行完此步骤终止该进程。
        PCB &t=IO1_equipment[i];
        t.End();
        t.set_state("destroy");
        destroy.push_back(t);
        IO1_equipment.erase(IO1_equipment.begin()+i);
    }
    else if(num==2){
        PCB &t=IO2_equipment[i];
        t.End();
        t.set_state("destroy");
        destroy.push_back(t);
        IO2_equipment.erase(IO2_equipment.begin()+i);
    }
}

Basic_function.h文件

// Created by BingWeiHuang on 2021/4/6.
// 这里是基本函数的声明
// Basic_function.h
#ifndef SIMPLE_THREE_STATE_SIMULATION_OF_THE_PROCESS_BASIC_FUNCTION_H
#define SIMPLE_THREE_STATE_SIMULATION_OF_THE_PROCESS_BASIC_FUNCTION_H
#include "PCB.h"
using namespace std;
typedef unsigned int uint;
extern uint cur_time;
extern uint CPU_size;
extern uint IO1_equipment_size;
extern uint IO2_equipment_size;
extern double turnover_time_sum;
extern double weighted_turnover_time_sum;

extern vector<PCB> CPU; //处理机
extern vector<PCB> IO1_equipment; //IO1设备
extern vector<PCB> IO2_equipment; //IO2设备
extern queue<PCB> ready_que; //就绪队列
extern queue<PCB> block_IO1_que; //请求IO1设备阻塞队列
extern queue<PCB> block_IO2_que; //请求IO2设备阻塞队列
extern vector<PCB> destroy; //待销毁

bool input_valid(uint need_running_t,uint when,uint time);//检测 ”运行多长时间之后需要IO1设备以及需要多长时间的输入“ 是否合理

PCB createProgress(uint index);//创建进程

void printProgresses();//输出各个进程的情况

bool scan_CPU();//扫描CPU 将该下的进程下CPU

void CPU_runProgresses();//模拟CPU的运行

void ready_wait();//就绪队列每个进程的等待模拟 其实就是waiting_time++

bool scan_IO_equipment(uint num);//扫描IO设备 看看是否有进程使用完毕

void use_IO_equipment();//模拟IO设备的使用 其实就是need_IO1(2)_time--;

void block_wait();//阻塞队列每个进程的等待模拟 其实就是waiting_time++

bool block_que_to_IO(uint num);//给阻塞队列的进程安排IO设备 其实就和就绪队列上CPU类似 只不过这里是没有状态的转换 都是阻塞态

void destroyProgresses();//销毁已经终止的进程
#endif //SIMPLE_THREE_STATE_SIMULATION_OF_THE_PROCESS_BASIC_FUNCTION_H

Basic_function.cpp文件

// 这里是基本函数的实现
// Basic_function.cpp
#include "Basic_function.h"
#include "State_transition.h"

bool input_valid(uint need_running_t,uint when,uint time){ //检测 ”运行多长时间之后需要IO1设备以及需要多长时间的输入“ 是否合理
    return (need_running_t>=when)&&(time);
}

PCB createProgress(uint index){ //创建进程
    string name,state="ready";
    uint need_running_t,arrive_t;
    bool IO1,IO2;
    printf("\n进程序号: %d\n",index);

    printf("请输入进程名称:");
    cin>>name;

    printf("请输入预计运行时间:");
    cin>>need_running_t;

    printf("请输入到达时间:");
    cin>>arrive_t;

    printf("进程是否需要IO1或IO2设备(1表示需要 0表示不需要):");
    cin>>IO1>>IO2;

    PCB res(index,name,state,need_running_t,arrive_t,IO1,IO2);

    uint when1,time1,when2,time2;
    if(IO1){
        printf("请输入运行多长时间后需要IO1设备 需要多长时间:");
        cin>>when1>>time1;
        while(!input_valid(need_running_t,when1,time1)){
            cout<<endl<<"输入数据不合理 请重新输入:";
            cin>>when1>>time1;
        }
        res.when_need_IO1=when1;res.IO1_time=res.need_IO1_time=time1;
    }
    if(IO2){
        printf("请输入运行多长时间之后需要IO2设备 需要多长时间:");
        cin>>when2>>time2;
        while(!input_valid(need_running_t,when2,time2) || when1==when2){
            cout<<endl<<"输入数据不合理 请重新输入:";
            cin>>when2>>time2;
        }
        res.when_need_IO2=when2;res.IO2_time=res.need_IO2_time=time2;
    }
    return res;
}

void printProgresses(){ //输出各个进程的情况
    printf("\n******************************************************************************************\n");
    printf("目前时间: %d\n",cur_time);

    printf("\n处理机上有 %d 个进程正在运行:\n",CPU.size());
    for(auto progress:CPU) progress.print();

    queue<PCB> tmp=ready_que; //拷贝一个副本进行操作 以免影响到原始的进程队列
    printf("\n就绪队列中存在 %d 个进程:\n",tmp.size());
    while(tmp.size()){
        tmp.front().print();
        tmp.pop();
    }

    printf("\nIO1设备上有 %d 个进程正在IO:\n",IO1_equipment.size());
    for(auto progress:IO1_equipment) progress.print();

    tmp=block_IO1_que;
    printf("\nIO1阻塞队列中存在 %d 个进程:\n",tmp.size());
    while(tmp.size()){
        tmp.front().print();
        tmp.pop();
    }


    printf("\nIO2设备上有 %d 个进程正在IO:\n",IO2_equipment.size());
    for(auto progress:IO2_equipment) progress.print();
    tmp=block_IO2_que;

    printf("\nIO2阻塞队列中存在 %d 个进程:\n",tmp.size());
    while(tmp.size()){
        tmp.front().print();
        tmp.pop();
    }

    printf("\n待销毁 %d 个进程:\n",destroy.size());
    for(auto progress:destroy) progress.print();

    cout<<endl;
}

bool scan_CPU(){ //扫描CPU
    bool f=0;
    for(uint i=0;i<CPU.size();i++){
        PCB &t=CPU[i];
        if(t.need_IO1&&t.when_need_IO1==t.get_running_time()){ //进程到了该IO1的时刻
            CPU_to_block(i,1);
            i--;f=1;
        }
        else if(t.need_IO2&&t.when_need_IO2==t.get_running_time()){ //进程到了该IO2的时刻
            CPU_to_block(i,2);
            i--;f=1;
        }
        else if(t.get_running_time()==t.get_need_running_time()){ //进程达到预计运行时间运行该终止了 不能将该条放在最前面if里面 因为会出现预计运行时间运行完后再需要IO的情况
            CPU_to_destroy(i);
            i--;f=1;
        }
    }
    return f; //是否有进程下CPU了
}

void CPU_runProgresses(){ //模拟CPU运行
    for(uint i=0;i<CPU.size();i++) CPU[i].Run(); //就是每个进程running_time++
}

void ready_wait(){ //就绪队列每个进程的等待模拟 其实就是waiting_time++
    if(ready_que.empty()) return ;
    queue<PCB> tmp=ready_que; //因为queue不提供迭代器遍历 所以借助一个tmp副本进行逐个操作
    while(ready_que.size()) ready_que.pop();
    while(tmp.size()){
        tmp.front().Wait();
        ready_que.push(tmp.front());
        tmp.pop();
    }
}

bool scan_IO_equipment(uint num){
    bool f=0;
    if(num==1){
        for(uint i=0;i<IO1_equipment.size();i++){
            PCB &t=IO1_equipment[i];
            if(t.need_IO1_time==0){
                if(t.get_running_time()==t.get_need_running_time()){ //出现了IO请求被完成之后已经不需要再运行了,直接终止
                    block_to_destroy(i,1);
                }
                else
                    block_to_ready(i,1);
                f=1;i--;
            }
        }
    }
    else if(num==2){
        for(uint i=0;i<IO2_equipment.size();i++){
            PCB &t=IO2_equipment[i];
            if(t.need_IO2_time==0){
                if(t.get_running_time()==t.get_need_running_time()){ //出现了IO请求被完成之后已经不需要再运行了,直接终止
                    block_to_destroy(i,2);
                }
                else
                    block_to_ready(i,2);
                f=1;i--;
            }
        }
    }
    return f;
}

void use_IO_equipment(){
    for(uint i=0;i<IO1_equipment.size();i++){
        IO1_equipment[i].need_IO1_time--;
    }
    for(uint i=0;i<IO2_equipment.size();i++){
        IO2_equipment[i].need_IO2_time--;
    }
}

void block_wait(){
    queue<PCB> tmp=block_IO1_que;//因为queue不提供迭代器遍历 所以借助一个tmp副本进行逐个操作
    while(block_IO1_que.size()) block_IO1_que.pop();
    while(tmp.size()){ //模拟等待
        tmp.front().Wait();
        block_IO1_que.push(tmp.front());
        tmp.pop();
    }

    tmp=block_IO2_que;
    while(block_IO2_que.size()) block_IO2_que.pop();
    while(tmp.size()){ //模拟等待
        tmp.front().Wait();
        block_IO2_que.push(tmp.front());
        tmp.pop();
    }
}

bool block_que_to_IO(uint num){
    if(num==1){
        if(IO1_equipment.size()==IO1_equipment_size || block_IO1_que.empty()) return 0;
        PCB &t=block_IO1_que.front();
        t.have_IO1=1;
        IO1_equipment.push_back(t);
        block_IO1_que.pop();
        return 1;
    }
    else if(num==2){
        if(IO2_equipment.size()==IO2_equipment_size || block_IO2_que.empty()) return 0;
        PCB &t=block_IO2_que.front();
        t.have_IO2=1;
        IO2_equipment.push_back(t);
        block_IO2_que.pop();
        return 1;
    }
    return 0;
}


void destroyProgresses(){ //销毁已经终止的进程
    for(auto i:destroy){
        turnover_time_sum+=i.turnover_time();
        weighted_turnover_time_sum+=i.weighted_turnover_time();
    }
    destroy.clear();
}

验证:

在这里插入图片描述
在这里插入图片描述

总结:

这算是实现脑子里面奇奇怪怪的想法?
反正就瞎写,也不知道思路正不正,但从我个人的验证来看程序应该是没什么大毛病(可能只是没有测试到,就算有也不承认 ,你们也可以试试,试到了悄咪咪告诉我就行,哈哈哈)。
可能有人就会问了,为什么不用java写?为什么不用py?
但是我老师曰过:“其实那些个什么java啊、py啊。。。go语言啊什么‘乱七八糟’的语言底层都是C”,所以我选择用C++,嘿嘿嘿。

(好吧,不装了,摊牌了,其实我就只会写一点点C,其他的语言都不会哇呜呜呜呜呜~)
其实代码里面的细节还是做的不到位很多地方要整改,而且还有很多需要完善和补充的地方,比如:
1.进程调度的其他算法(害,我只会FCFS)
2.优先级,当两个处于相同状态进程同时需要争抢最后一份资源(IO设备、CPU)的时候该怎么处理(此时FCFS不奏效,因为“同时”,这就要涉及到优先级了,太麻烦了太难了 暂时不写)
3.高级调度(后备队列进入内存)需要考虑(昨天听操作系统老师讲题目才意识到,从后备队列高级调度到就绪队列也是需要进行写算法限制一下的,毕竟实际上内存大小有限。所以上课直接全程神游于我自己写的代码中,到底怎么样新增高级调度的算法可以实现内存大小限制,怎么样又可以解决进程在内存中不能移动的情况的限制,啊…太复杂了,水瓶有限写不来写不来…)
4.五状态模型
5.写一个能解放纸笔的程序来解那些个我不想不会 写的题??

下次接着完善吧,这次对于我来说已经是一场小型的头脑风暴了。(打码两分钟 debug一小时是真的痛苦,太多了,眼睛都花了,第一次独立写这么多的代码。)

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值