Linux 操作系统课程设计

Linux操作系统课程设计


中国地质大学(武汉)计算机学院计算机科学与技术专业本科生《操作系统概论》课程设计(作记录)。

1. 课程设计要求

  1. 按时参加上机实习,不得旷课、迟到、早退。
  2. 每个学生需在 Linux 下用 C 语言完成六道上机实习题。
  3. 每个学生需独立完成上机实习,不得相互抄袭。如发现抄袭者和被抄袭者成绩均不及格。
  4. 每个学生需在实习结束一周内将实习报告和源程序提交到班级学习委员,再由学习委员打包发到老师的邮箱,用于检查是否存在抄袭现象,邮件中请注明学生的班级和姓名。
  5. 每个学生需在实习结束一周内提交纸质实习报告一份。应包括如 下内容: 1)课程设计题目 2)程序功能及设计思路 3)数据结构及算法设计 4)程序运行情况 5)编程中遇到的困难及解决方法、实习心得或良好建议。

2. 评分

  1. 考勤占 20%,上机验收占 40%,实习报告占 40%。
  2. 抄袭程序者和被抄袭程序者,课程设计成绩不及格。
  3. 抄袭报告者和被抄袭报告者,课程设计成绩不及格。前六次上机每次完成一道实习题,最后两次上机组织验收。

3. 上机时间地点

  • 17 周 (12 月 25-29 日)周二下午、周四下午、周五上午,晚上。
  • 18 周 (元月 1-5 日)周二下午、周三下午,晚上 。
  • 前 7 次地点:12 班在 313 机房(Fedora) 34 班在 413 机房(Ubuntu)
  • 最后一次上机: 12 班:18 周周四 晚上 34 班:18 周周五 晚上

地点:313 机房(Fedora) 上午 8:30-11:30 下午 2:00-5:00 晚上 6:30-9:30

开机时进入 Linux 系统,默认使用用户名:suer,密码:123456。若默认密码登录不了,可能是被同学修改过了,换一台电脑使用。请同学们千万不要修改系统登录密码。若有同学自己的笔记本电脑已经装了 Linux,欢迎自带电脑上机实习。

4. 每次上机实习前先自学预备知识,并在作业本上编写初步的源程序。

5. 课程设计题目课程设计分六个实验,具体如下

5.1 实验 1:作业调度

5.1.1 实验目的

  1. 对作业调度的相关内容作进一步的理解。
  2. 明白作业调度的主要任务。
  3. 通过编程掌握作业调度的主要算法。

5.1.2 实验内容

1、假设系统中可同时运行两道作业,给出每道作业的到达时间和运行时间,如下表所示:

作业名ABCDEFGHIJ
到达时间0257121546810
运行时间10203040882010127

2、分别用先来先服务算法、短作业优先和响应比高者优先三种算法给出作业的调度顺序。

3、计算每一种算法的平均周转时间及平均带权周转时间并比较不同算法的优劣。

5.1.3 预备知识

  1. 响应比=等待时间/运行时间+1

  2. 周转时间=完成时间-到达时间 带权周转时间=周转时间/运行时间

5.1.4 源代码

#include <iostream>
#include <string>
#include <fstream>
#include <vector>

using namespace std;

struct Work
{
    char name;//作业名称
    double arrive;//到达时间点
    double run;//运行时间
    Work() {}
    Work(char n, double a, double t) :name(n), arrive(a), run(t) {}
    Work operator =(const Work &rhs)
    {
        this->name = rhs.name;
        this->arrive = rhs.arrive;
        this->run = rhs.run;
        return *this;
    }
};

vector<Work> gWork;//存放所有的作业信息
double sum_turn, sum_dturn;//总的周转时间和带权周转时间

void ReadFile();//从txt文本中读取作业调度信息
void Output();//输出结果
void Dispatch();//调度过程中的数据处理
void Fcfs();//先来先服务算法
void Sjf();//短作业优先算法
void Hrf();//高响应比优先算法

int main()
{
    ReadFile();
    cout << "先来先服务算法:" << endl;
    Fcfs();
    cout << "短作业优先算法:" << endl;
    Sjf();
    cout << "高响应比算法:" << endl;
    Hrf();
    return 0;
}

//从文本中读取作业调度信息
void ReadFile()
{
    ifstream ifile;
    ifile.open("/home/jason/文档/OS/Pro_1/data.txt");
    if (!ifile)
    {
        cout << "文件打开失败!" << endl;
        return;
    }
    char name;
    double arrive;
    double run;
    while (ifile >> name >> arrive >> run)
    {
        gWork.push_back(Work(name, arrive, run));
    }


    ifile.close();
}

//输出结果
void Output()
{
    cout << "作业调度顺序:";
    for (unsigned i = 0; i < gWork.size(); ++i)
        cout << gWork[i].name << " ";
    cout << endl;
    cout << "平均周转时间:" << sum_turn / gWork.size() << endl;
    cout << "平均带权周转时间:" << sum_dturn / gWork.size() << endl;
    cout << endl;
}

//调度过程中的数据处理
void Dispatch()
{
    unsigned n = static_cast<unsigned>(gWork.size());
    double t1 = 0, t2 = 0;//同时运行两个作业,设置两个时间轴以记录各个作业完成情况
    sum_turn = 0;//初始化
    sum_dturn = 0;
    for (unsigned i = 0; i < n; ++i)
    {
        if (t1 > t2)//表明t1有作业时t2空闲
        {
            if (t2 < gWork[i].arrive)
                t2 += gWork[i].arrive;
            t2 = t2 + gWork[i].run;
            sum_turn = sum_turn + t2 - gWork[i].arrive;
            sum_dturn = sum_dturn + (t2 - gWork[i].arrive) / gWork[i].run;
        }
        else
        {
            if (t1 < gWork[i].arrive)
                t1 += gWork[i].arrive;
            t1 = t1 + gWork[i].run;
            sum_turn = sum_turn + t1 - gWork[i].arrive;
            sum_dturn = sum_dturn + (t1 - gWork[i].arrive) / gWork[i].run;
        }
    }
}

//先来先服务算法
void Fcfs()
{
    unsigned n = static_cast<unsigned>(gWork.size());
    //按照来到的时间点排序,升序
    Work temp;
    for (unsigned i = 0; i < n; ++i)
    {
        for (unsigned j = 0; j < n - i - 1; ++j)
        {
            if (gWork[j].arrive > gWork[j + 1].arrive)
            {
                temp = gWork[j + 1];
                gWork[j + 1] = gWork[j];
                gWork[j] = temp;
            }
        }
    }
    Dispatch();
    Output();
}

//短作业优先算法
void Sjf()
{
    unsigned n = static_cast<unsigned>(gWork.size());
    //按照作业运行时间的长短排序,升序
    Work temp;
    for (unsigned i = 0; i < n; ++i)
    {
        for (unsigned j = 0; j < n - i - 1; ++j)
        {
            if (gWork[j].run > gWork[j + 1].run)
            {
                temp = gWork[j + 1];
                gWork[j + 1] = gWork[j];
                gWork[j] = temp;
            }
        }
    }
    Dispatch();
    Output();
}

//高响应比优先算法
void Hrf()
{
    int n = static_cast<int>(gWork.size());
    //按照来到的时间点排序,升序
    Work temp;
    for (int i = 0; i < n; ++i)
    {
        for (int j = 0; j < n - i - 1; ++j)
        {
            if (gWork[j].arrive > gWork[j + 1].arrive)
            {
                temp = gWork[j + 1];
                gWork[j + 1] = gWork[j];
                gWork[j] = temp;
            }
        }
    }

    vector<Work> temp_work;
    for (int i = 0; i < n; ++i)
        temp_work.push_back(gWork[i]);
    gWork.clear();
    sum_turn = 0;//初始化
    sum_dturn = 0;
    double t1 = 0, t2 = 0;//同时运行两个作业,设置两个时间轴以记录各个作业完成情况
    int i = 0, flag = 0;
    n = static_cast<int>(temp_work.size());
    double max_rate = 0, rate = 0;

    //运行第一个作业
    for (i = 0; i < n; ++i)
    {
        //rate = wait / run + 1
        rate = (temp_work[n - 1].arrive - temp_work[i].arrive) / temp_work[i].run + 1;
        if (max_rate < rate)
        {
            max_rate = rate;
            flag = i;
        }
    }

    t1 = temp_work[flag].arrive + temp_work[flag].run;
    sum_turn = t1 - temp_work[flag].arrive;
    sum_dturn = (t1 - temp_work[flag].arrive) / temp_work[flag].run;
    gWork.push_back(temp_work[flag]);
    temp_work.erase(temp_work.begin() + flag);//已经运行的作业要删去
    //运行剩下的作业
    while (!temp_work.empty())
    {
        n = static_cast<int>(temp_work.size());
        flag = 0;
        max_rate = 0;
        rate = 0;
        if (t1 > t2)//表明t1有作业时t2空闲
        {
            //运行第二个作业
            if (t2 == 0)
            {
                for (i = 0; i < n; ++i)
                {
                    //rate = wait / run + 1
                    rate = (temp_work[n - 1].arrive - temp_work[i].arrive) / temp_work[i].run + 1;
                    if (max_rate < rate)
                    {
                        max_rate = rate;
                        flag = i;
                    }
                }
                t2 = temp_work[flag].arrive + temp_work[flag].run;
                sum_turn = t2 - temp_work[flag].arrive;
                sum_dturn = (t2 - temp_work[flag].arrive) / temp_work[flag].run;
                gWork.push_back(temp_work[flag]);
                temp_work.erase(temp_work.begin() + flag);//已经运行的作业要删去
                continue;
            }

            for (i = 0; i < n; ++i)
            {
                if (temp_work[i].arrive <= t2)
                {
                    rate = (t2 - temp_work[i].arrive) / temp_work[i].run + 1;
                    if (max_rate < rate)
                    {
                        max_rate = rate;
                        flag = i;
                    }
                }
                else
                    break;
            }
            t2 += temp_work[flag].run;
            sum_turn = sum_turn + t2 - temp_work[flag].arrive;//turn = finish - arrive
            sum_dturn = sum_dturn + (t2 - temp_work[flag].arrive) / temp_work[flag].run;
            gWork.push_back(temp_work[flag]);
            temp_work.erase(temp_work.begin() + flag);//已经运行的作业要删去
        }
        else
        {
            for (i = 0; i < n; ++i)
            {
                if (temp_work[i].arrive <= t1)
                {
                    rate = (t1 - temp_work[i].arrive) / temp_work[i].run + 1;
                    if (max_rate < rate)
                    {
                        max_rate = rate;
                        flag = i;
                    }
                }
                else
                    break;
            }
            t1 += temp_work[flag].run;
            sum_turn = sum_turn + t1 - temp_work[flag].arrive;//turn = finish - arrive
            sum_dturn = sum_dturn + (t1 - temp_work[flag].arrive) / temp_work[flag].run;
            gWork.push_back(temp_work[flag]);
            temp_work.erase(temp_work.begin() + flag);//已经运行的作业要删去
        }
    }
    Output();
}

数据:

A 0 7
B 2 10
C 5 20
D 7 30
E 12 40
F 15 8
G 4 8
H 6 20
I 8 10
J 10 12

5.2 实验 2:磁盘调度

5.2.1 实验目的

  1. 对磁盘调度的相关知识作进一步的了解,明确磁盘调度的原理。
  2. 加深理解磁盘调度的主要任务。
  3. 通过编程,掌握磁盘调度的主要算法。

5.2.2 实验内容

  1. 对于如下给定的一组磁盘访问进行调度:
请求服务到达ABCDEFGHIJKLMN
访问的磁道号5010018020901507080101601204011030
请求服务到达ABCDEFGHIJKLMN
  1. 要求分别采用先来先服务、最短寻道优先以及电梯调度算法进行调度。
  2. 要求给出每种算法中磁盘访问的顺序,计算出平均移动道数。
  3. 假定当前读写头在 90 号,向磁道号增加的方向移动。

5.2.3 源代码

#include <iostream>
#include <fstream>
#include <vector>
#include <cmath>
#include <stdlib.h>

using namespace std;

struct Disk
{
    char name;//请求服务到达序号
    int number;//访问的磁道号
    Disk() {}
    Disk(char n, int num):name(n), number(num){}
    Disk operator =(const Disk& rhs)
    {
        this->name = rhs.name;
        this->number = rhs.number;
        return *this;
    }
};

const int StartNum = 90;//开始磁道号为90

vector<Disk> gDisk;//存储请求服务到达的序列
vector<char> track;//磁道访问顺序
vector<int> MoveDistance;//移动距离
//int FindOrder[MaxNumber];//寻号序列
double TotalDistance;//总共寻到长度
bool direction;//方向,true为向外,false为向内

void Readfile();//从txt文件中读取请求服务信息
void Sortnum();//根据磁盘号进行排序,升序
void Fcfs();//先来先服务算法
void Sstf();//最短寻道优先算法
void Scan();//电梯调度算法,也是扫描算法
void Output();//输出结果

int main()
{
    Readfile();
    Fcfs();
    Sstf();
    Scan();
    return 0;
}

void Readfile()
{
    ifstream ifile;
    ifile.open("/home/jason/文档/OS/Pro_2/data.txt");
    if(!ifile)
    {
        cout << "文档打开失败!" << endl;
        exit(1);
    }
    char name;
    int num;
    while(ifile >> name >> num)
        gDisk.push_back(Disk(name, num));
    ifile.close();
}
void Sortnum()
{
    int i(0), j(0);
    int n = static_cast<int>(gDisk.size());
    Disk temp;
    for(i = 0; i < n; ++i)
    {
        for(j = 0; j < n - i - 1; ++j)
        {
            if(gDisk[j].number > gDisk[j + 1].number)
            {
                temp = gDisk[j + 1];
                gDisk[j + 1] = gDisk[j];
                gDisk[j] = temp;
            }
        }
    }
}

//先来先服务算法
void Fcfs()
{
    int tempNum = StartNum;//寻道当前所在磁道号
    track.clear();
    MoveDistance.clear();
    int n = static_cast<int>(gDisk.size());//提出的申请数
    for(int i = 0; i < n; ++i)
    {
        MoveDistance.push_back(abs(gDisk[i].number - tempNum));
        track.push_back(gDisk[i].name);
        tempNum = gDisk[i].number;
    }
    cout << "###############先来先服务算法:###############" << endl;
    Output();
}
//最短寻道优先算法
void Sstf()
{
    int tempNum = StartNum;//寻道当前所在磁道号
    track.clear();
    MoveDistance.clear();
    vector<Disk> temp_disk;
    int MinNum = 9999;//最短寻道数
    int flag = 0;//做标记
    int n = static_cast<int>(gDisk.size());//提出的申请数
    for(int i = 0; i < n; ++i)
    {
        temp_disk.push_back(gDisk[i]);
    }

    while(!temp_disk.empty())
    {
        MinNum = 9999;
        int num = 0;//临时存储寻道数
        flag = 0;
        n = static_cast<int>(temp_disk.size());
        for(int i = 0; i < n; ++i)
        {
            num = abs(temp_disk[i].number - tempNum);
            if(MinNum > num)
            {
                MinNum = num;
                flag = i;
            }
        }
        track.push_back(temp_disk[flag].name);//记录第一个寻道号
        tempNum = temp_disk[flag].number;
        MoveDistance.push_back(MinNum);//记录第一个寻道距离
        temp_disk.erase(temp_disk.begin() + flag);//已访问的删去
    }
    cout << "###############最短寻道优先服务算法:###############" << endl;
    Output();
}

//电梯调度算法
void Scan()
{
    track.clear();
    MoveDistance.clear();
    //按磁道号进行排序,升序
    Sortnum();
    int n = static_cast<int>(gDisk.size());
    vector<Disk> temp_disk;
    for(int i = 0; i < n; ++i)
        temp_disk.push_back(gDisk[i]);

    int tempNum = StartNum;
    int flag(0);
    for(int i = 0; i < n; ++i)
    {
        if(temp_disk[i].number < tempNum)
            continue;
        else
        {
            flag = i;
            break;
        }
    }

    //最开始是向外寻道
    for(int i = flag; i < n; ++i)
    {
        track.push_back(temp_disk[i].name);
        MoveDistance.push_back(temp_disk[i].number - tempNum);
        tempNum = temp_disk[i].number;
    }
    //然后向里寻道
    for(int i = flag - 1; i >= 0; --i)
    {
        track.push_back(temp_disk[i].name);
        MoveDistance.push_back(tempNum - temp_disk[i].number);
        tempNum = temp_disk[i].number;
    }
    cout << "###############电梯调度算法:###############" << endl;
    Output();

}
//输出结果
void Output()
{
    int n = static_cast<int>(gDisk.size());//提出的申请数
    TotalDistance = 0;
    cout << "寻道序号:" << "   " << "寻道长度:"<<endl;
    for(int i = 0; i < n; ++i)
    {
        cout << track[i] << "            "<< MoveDistance[i] << endl;
        TotalDistance += MoveDistance[i];
    }
    cout << "平均寻道长度:" << (static_cast<double>(TotalDistance) / n) << endl;
    cout << endl;
}

数据:

A 30 
B 50
C 100
D 180
E 20
F 90
G 150
H 70
I 80
J 10
K 160
L 120
M 40
N 110

5.3 实验 3:熟悉 linux 文件系统调用

5.3.1 实验目的

  1. 掌握 linux 提供的文件系统调用的使用方法;
  2. 熟悉文件系统的系统调用用户接口;
  3. 了解操作系统文件系统的工作原理和工作方式。

5.3.2 实验内容

使用文件系统调用编写一个文件工具 filetools,使其具有以下功能:

1.创建新文件 2.写文件 3.读文件 4.修改文件权限 5.查看当前文件权限 0.退出

提示用户输入功能号,并根据用户输入的功能选择相应的功能。 文件按可变记录文件组织,具体记录内容自行设计。

5.3.3 预备知识

用户在针对文件进行操作之前时一定要先打开它,这是由于系统需要根据用户提供的参数来查找文件 的目录项,并由目录项找到文件的磁盘 i 结点,再将它调到内存 i 结点,才能建立用户进程与该文件之间的联系。

同样,在文件操作完毕后要关闭文件,以切断用户进程与文件的联系,释放相关资源。

Open 系统调用

int open(const char *path, int flags); 
int open(const char *path, int flags,mode_t mode);

一般情况下使用两个参数的格式,只有当打开的文件不存在时才使用 3 个参数的格式。参数:

  • Path 指向所要打开的文件的路径名指针。
  • Flag 标志参数,用来规定打开方式,必须包含以下 3 个之一:
    • O_RDONLY 只读方式
    • O_WRONLY 只写方式
    • O_RDWR 读写方式
  • 利用按位逻辑或“|”对下列标志进行任意组合:
    • O_CREAT 如果文件不存在则创建该文件,若存在则忽略。
    • O_TRUNC 如果文件存在则将文件长度截为 0,属性和所有者不变。
    • C_EXECL 如果文件存在且O_CREAT 被设置则强制 open 调用失败。
    • O_APPEND 每次写入时都从文件尾部开始。
  • Mode 是文件的访问权限,分为文件所有者、文件用户组和其他用户。

Close 系统调用

对于一个进程来说可以同时打开的文件是有限的,为了使文件标识符能够及时释放,系统必须提供关闭文件操作。

Int close(int fd) 
  • Fd 为打开文件时系统返回的文件标识符。
  • 系统执行该系统调用时,根据 fd 值在该进程的进程打开文件表中找到 fd 标识,根据指针找到系统打开 文件表,再找到内存i 结点表中相应的 i 结点,对其i_count 进行减 1 操作, 然后释放系统打开文件表中的表项和进程打开文件表的表项。
  • 结果:调用成功返回 0。

5.3.4 源代码

#include <string.h>
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>//flag标志参数
#include <sys/stat.h>//文件权限标志位
#include <sys/types.h>

#define MAX 10000
int fd;//打开文件时系统返回的文件标识符
char *FileName;//要创建的文件名

void Menu();//显示主界面菜单
void CreateFile();//创建新文件
void WriteFile();//写文件
void ReadFile();//读文件
void ModifyPermission();//修改文件权限
void ShowPermission();//查看文件权限

int main()
{
    int select = 0;
    printf("########欢迎使用文件工具FileTools#######\n");
    while(1)
    {
        Menu();
        scanf("%d", &select);

        switch (select) {
        case 0:
            close(fd);
            return 0;
        case 1:
            CreateFile();break;
        case 2:
            WriteFile();break;
        case 3:
            ReadFile();break;
        case 4:
            ModifyPermission();break;
        case 5:
            ShowPermission();break;
        default:
            printf("输入有误!请重新输入:\n");
        }
    }

    return 0;
}

void Menu()
{
    printf("########################################\n");
    printf("########1.创建新文件####################\n");
    printf("########2.写文件########################\n");
    printf("########3.读文件########################\n");
    printf("########4.修改文件权限##################\n");
    printf("########5.查看文件权限##################\n");
    printf("########0.安全退出######################\n");
    printf("请选择:\n");
}

//创建新文件
void CreateFile()
{
    char name[32];
    printf("请输入要创建的文件名:\n");
    scanf("%s", name);
    FileName = name;
    //O_RDWR:读写方式
    //O_TRUNC:如果文件存在则将文件长度截为0,属性和所有者不变
    //O_CREAT:如果文件不存在则创建该文件,若存在则忽略
    fd = open(FileName, O_RDWR|O_TRUNC|O_CREAT, 0750);

    if(fd < 0)
        perror("Create");
    else
    {
        printf("文件创建成功!\n");
        printf("fd =%d\n", fd);//打印文件的fd
        close(fd);//关闭文件
    }
}

//写文件
void WriteFile()
{
    char name[32];
    printf("请输入要写入信息的文件名:\n");
    scanf("%s", name);
    FileName = name;
    char buffer[MAX];//存取写入的信息
    fd = open(FileName, O_WRONLY|O_APPEND);
    if(fd < 0)
    {
        perror("Write");
        return;
    }
    printf("请输入要写入的信息:\n");
    int num = read(0,buffer, MAX);//读取输入信息,0表示标从键盘标准输入
    write(fd, buffer, num);//将读入的信息写入文件
    printf("文件写入成功!\n");
    close(fd);//关闭文件
}

//读文件
void ReadFile()
{

    char buffer[MAX];
    char name[32];
    printf("请输入要读取的文件名:\n");
    scanf("%s", name);
    FileName = name;
    fd = open(FileName,O_RDONLY);
    if(fd < 0)
    {
        perror("Wrong");
        return;
    }

    int num = read(fd, buffer, MAX);
    printf("\n");
    write(1, buffer, num);//向显示屏输出,1表示标准输出
    printf("\n文件读取成功!\n");
    close(fd);
}

//修改文件权限
void ModifyPermission()
{
    char name[32];
    printf("请输入要修改权限的文件名:\n");
    scanf("%s", name);
    FileName = name;

    fd = open(FileName, O_RDONLY);
    if(fd < 0)
    {
        perror("Modify");
        return;
    }
    printf("0.所有者用户读写执行\n");
    printf("1.所有者用户只可读\n");
    printf("2.所有者用户只可写\n");
    printf("3.所有者用户只可执行\n");
    printf("4.用户组读写执行\n");
    printf("5.用户组只可读\n");
    printf("6.用户组只可写\n");
    printf("7.用户组只可执行\n");
    printf("请选择0-7:");
    int select;
    scanf("%d", &select);
    switch (select)
    {
    case 0:
        chmod(FileName, S_IRWXU);break;//文件所有者读写执行权限
    case 1:
        chmod(FileName, S_IRUSR);break;//文件所有者读权限
    case 2:
        chmod(FileName, S_IWUSR);break;//文件所有者写权限
    case 3:
        chmod(FileName, S_IXUSR);break;//文件所有者执行权限
    case 4:
        chmod(FileName, S_IRWXG);break;//文件所属组读写执行权限
    case 5:
        chmod(FileName, S_IRGRP);break;//文件所属组读权限
    case 6:
        chmod(FileName, S_IWGRP);break;//文件所属组写权限
    case 7:
        chmod(FileName, S_IXGRP);break;//文件所属执行权限
    default:
        printf("错误选择!\n");
    }
    close(fd);
}

//查看文件权限
void ShowPermission()
{
    //stat();获取文件信息
    char name[32];
    printf("请输入要修改权限的文件名:\n");
    scanf("%s", name);
    FileName = name;
    char *path = "/bin/ls";
    char *argv[4] = {"ls", "-l", FileName, NULL};
    execv(path, argv);
}

5.4 实验 4:进程管理

5.4.1 实验目的

  1. 理解进程的概念,明确进程和程序的区别。
  2. 理解并发执行的实质。
  3. 掌握进程的同步、撤销等进程控制方法。

5.4.2 实验内容

  • 父进程使用系统调用 pipe() 建立一个管道,然后使用系统调用 fork() 创建两个子进程:子进程1和子进程2。

  • 子进程1每隔1秒通过管道向子进程2发送数据:I send message x times.(x 初值为1,以后发送一 次后做加一操作)子进程 2 从管道读出信息,并显示在屏幕上。

  • 父进程用系统调用 signal() 来捕捉来自键盘的中断信号(即按Ctrl+C 键);当捕捉到中断信号后,父进程用系统调用 kill() 向两个子进程发出信号,子进程捕捉到信号后分别输出如下信息后终止: Child Process 1 is killed by Parent! Child Process 2 is killed by Parent!

  • 父进程等待两个子进程终止后,释放管道并输出如下的信息后终止 Parent Process is Killed!

5.4.3 源代码

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>

#include <sys/types.h>
#include <sys/wait.h>

pid_t child1, child2;//两个子进程
int fd[2];
void SignalHandler1(int iSignNo);//信号处理
void SignalHandler2(int iSignNo);

int main()
{
    int Count = 1;
    char buffer[40];
    char info[40];
    pid_t getpid();
    printf("Process Parent PID:%d\n", getpid());
    //创建无名管道,只能在父子或兄弟进程间通信
    //fd[0]只能用于读,fd[1]只能用于写
    if(pipe(fd) < 0)
    {
        printf("管道创建失败!\n");
        return -1;
    }
    //设置软中断信号SIGINT,终端输入Ctrl+C,进程终止
    signal(SIGINT, SignalHandler1);
    //创建子进程1,2
    child1 = fork();
    if(child1 == 0) //子进程1
    {
        printf("Child Process 1 PID:%d\n", getpid());
        //设置忽略信号,如果不设置,在子进程运行过程直接Ctrl+C,会造成进程终止,
        //产生僵尸进程,因此需要在子进程中屏蔽,等子进程被父进程kill之后,再在
        //父进程中Ctrl+C使进程完全结束,就不会产生僵尸进程了
        signal(SIGINT, SIG_IGN);
        //设置信号SIGUSR1
        signal(SIGUSR1, SignalHandler2);
        while(1)
        {
            close(fd[0]);
            sprintf(info, "I send message %d times", Count);
            //管道长度受到限制,管道写满时写入操作将被阻塞,直到管道中的数据被读取
            write(fd[1], info, 30);//发送数据至管道数据
            ++Count;//计数器+1
            sleep(1);//睡眠1s
        }
    }
   else if(child1 > 0)//返回父进程
    {
        child2 = fork(); //父进程创建子进程2
        if(child2 == 0) //子进程2
        {
            printf("Child Process 2 PID:%d\n", getpid());
            //设置忽略信号
            signal(SIGINT, SIG_IGN);
            //设置信号SIGUSR1
            signal(SIGUSR2, SignalHandler2);
            while(1)
            {
                close(fd[1]);
                //当数据被读取后,数据将自动被管道清除
                //接受管道数据
                read(fd[0], buffer, 40);
                //显示管道数据
                printf("%s\n", buffer);
            }
        }
        else //返回父进程
        {
            //等待子进程1,2退出
            waitpid(child1, NULL, 0);
            //printf("Child Process 1 is over!\n");
            waitpid(child2, NULL, 0);
            //printf("Child Process 2 is over!\n");
            //关闭管道
            close(fd[0]);
            close(fd[1]);
            printf("Parent Process is Killed!\n");
        }

    }

    return 0;
}

void SignalHandler1(int iSignNo)
{
    printf("\nParent recive signal Ctrl+C\n");
    if(iSignNo == SIGINT)//传递SIGUSR信号给子进程
    {
        //SIGUSR1和SIGUSR2是用户自定义信号,进程终止
        kill(child1, SIGUSR1);
        kill(child2, SIGUSR2);
    }
}

void SignalHandler2(int iSignNo)
{
    close(fd[0]);
    close(fd[1]);
    if(child1 == 0 && iSignNo == SIGUSR1)
    {
        printf("Child Process 1 is killed by Parent!\n");
        exit(0);

    }
    if(child2 == 0 && iSignNo == SIGUSR2)
    {
        printf("Child Process 2 is killed by Parent!\n");
        exit(0);
    }
}

5.5 实验 5:请求分页系统中的置换算法

5.5.1 实验目的

  1. 了解虚拟存储技术的特点;
  2. 掌握请求分页系统的页面置换算法。

5.5.2 实验内容

1、通过如下方法产生一指令序列,共 320 条指令。

A. 在[0,319]的指令地址之间随机选取一起点 M;
B. 顺序执行一条指令,即执行地址为 M+1 的指令;
C. 在前地址[0,M+1]中随机选取一条指令并执行,该指令的地址为 M1;
D. 顺序执行一条指令,其地址为 M1+1;
E. 在后地址[M1+2,319]中随机选取一条指令并执行,该指令的地址为 M2;
F. 顺序执行一条指令,其地址为 M2+1;
G. 重复A—F,直到执行 320 次指令。

2、指令序列变换成页地址流,设 :

(1)页面大小为1K;
(2) 用户内存容量为 4 页到 32 页,步长为 1;
(3)用户虚存容量为 32K。在用户虚存中,按每页存放10 条指令排列虚存地址,即 320 条指令在虚存中的存放方式为: 第0条—第9条指令为第0页(对应虚存地址为[0,9]);第10条—第19条指令为第1页(对应虚存地址为[10,19]) ; …………………… 第310条—第319条指令为第31页(对应虚存地址为[310,319]) ;

按以上方式,用户指令可组成 32 页。

3、计算并输出下述各种算法在不同内存容量下的命中率。

A. 先进先出(FIFO)页面置换算法
B. 最近最久未使用(LRU)页面置换算法–最近最少使用算法
C. 最佳(Optimal)页面置换算法

5.5.3 源代码

#include<iostream>
#include<ctime>
#include<cstdlib>

using namespace std;

int N = 3;//内存
int Process[320];//页面队列
int Memory[32];//块数
int OPTQueue[320];//OPT算法的队列
int FIFOQueue[320];//FIFO算法队列
int LRUQueue[320];//LRU算法队列
int ttime[320];//设置的一个时间标志,FIFO算法时判断哪个是最先进来的
int flag[320];//设置一个标志,LUR算法判断哪个是最近最久未使用的
int ProcessNum;//页面数
int id[320];//320个进程序列
int address[320];//zhilingzidian
int Size = 0;
int Randf(int l, int r) { return (rand() % (r - l + 1) + l); }

void CreateProcess()
{
    srand(static_cast<unsigned>(time(NULL)));
    Size = 0;
    for (int i = 0;i < 64;++i)
    {
        int m = Randf(0, 319);
        id[Size++] = m + 1;
        m = Randf(0, m + 1);
        id[Size++] = m;
        id[Size++] = m + 1;
        m = Randf(m + 2, 319);
        id[Size++] = m;
        id[Size++] = m + 1;
    }
    cout << "指令序列:" << endl;
    for (int i = 0;i < Size;++i)
        cout << id[i] << " ";
    cout << endl;
}

//OPT算法找到最长未使用的
int longest(int start)
{
    int i;
    int count[320];
    for (int i = 0;i < 320;++i)
        count[i] = 0;
    for (int j = 0;j < N;++j)
    {
        for (i = start + 1;i < ProcessNum;i++)
        {
            if (Memory[j] != OPTQueue[i])
                count[j]++;
            if (Memory[j] == OPTQueue[i])
                break;
        }
    }
    int ti = -1, mmax = -1;
    for (int i = 0;i < N;++i)
    {
        if (mmax < Memory[i])
        {
            mmax = Memory[i];
            ti = i;
        }
    }
    return ti;
}
//OPT    算法
void OPT()
{
    int i, j, k;
    int num = N;
    for (i = 0;i < ProcessNum;i++)
    {
        OPTQueue[i] = Process[i];
    }
    for (i = 0;i < N;i++)
    {
        Memory[i] = OPTQueue[i];
    }
    for (i = N;i < ProcessNum; ++i)
    {
        for (j = 0;j < N;j++)
        {
            if (Memory[j] == OPTQueue[i])
                break;
        }
        if (j == N)
        {
            k = longest(i);
            Memory[k] = OPTQueue[i];
            num++;
        }
    }
    cout << "命中次数:" << ProcessNum - num << endl;
    float str;
    str = (float)(ProcessNum - num)  / ProcessNum;
    cout << "命中率=" << str * 100 << "%" << endl;
}
//FIFO算法找到最早进来的那个
int MaxTime()
{
    int ti = -1, mmin = 100000;
    for (int i = 0;i<N;++i)
    {
        if (mmin > ttime[i])
        {
            mmin = ttime[i];
            ti = i;
        }
    }
    return ti;
}
//FIFO算法
void FIFO()
{
    int i, j, k;
    int num = N;
    for (i = 0;i<ProcessNum;i++)
    {
        FIFOQueue[i] = Process[i];
    }
    for (i = 0;i<N;i++)
    {
        Memory[i] = FIFOQueue[i];
        ttime[i] = i;
    }
    for (i = N;i<ProcessNum;i++)
    {
        for (j = 0;j<N;j++)
        {
            if (Memory[j] == FIFOQueue[i])
                break;
        }
        if (j == N)
        {
            k = MaxTime();
            ttime[k] = i;
            Memory[k] = FIFOQueue[i];
            num++;
        }
    }
    cout << "命中次数:" << ProcessNum - num << endl;
    float str;
    str = (float)(ProcessNum - num)  / ProcessNum;
    cout << "命中率=" << str * 100 << "%" << endl;
}
//LRU算法找到最近最久未使用的
int MinFlag()
{
    int ti = -1, mmin = 100000;
    for (int i = 0;i < N;++i)
    {
        if (mmin > flag[i])
        {
            mmin = flag[i];
            ti = i;
        }
    }
    return ti;
}
//LRU算法
void LRU()
{
    int i, j, k;
    int num = N;
    for (i = 0;i<ProcessNum;i++)
    {
        LRUQueue[i] = Process[i];
    }
    for (i = 0;i<N;i++)
    {
        Memory[i] = LRUQueue[i];
        flag[i] = i;
    }
    for (i = N;i<ProcessNum;i++)
    {
        for (j = 0;j<N;j++)
        {
            if (Memory[j] == LRUQueue[i])
            {
                flag[j] = i;
                break;
            }
        }
        if (j == N)
        {
            k = MinFlag();
            flag[k] = i;
            Memory[k] = LRUQueue[i];
            num++;
        }
    }
    cout << "命中次数:" << ProcessNum - num << endl;
    float str;
    str = (float)(ProcessNum - num)  / ProcessNum;
    cout << "命中率=" << str * 100 << "%" << endl;
}
int main()
{
    int i;
    while (N != 0) {
        cout << "-------------------页面置换算法-------------------------" << endl;
        cout << "进程数:320" << endl;
        ProcessNum = 320;
        CreateProcess();//生成320个指令序列
        cout << "指令序列生成完毕" << endl << endl;
        for (i = 0;i < ProcessNum; ++i)
            Process[i] = id[i] / 10;
        N = 4;
        while (N != 1 && N != 0) {
            cout << "请输入内存容量:[4-32]\n1 for 重新生成指令序列\n0 for 退出程序" << endl;
            cin >> N;
            if (N == 0 || N == 1)
                break;
            cout << "OPT最佳置换算法" << endl;
            OPT();
            cout << endl;
            cout << "FIFO先进先出页面置换算法" << endl;
            FIFO();
            cout << endl;
            cout << "LRU最近最久未使用置换算法" << endl;
            LRU();
            cout << endl;
        }
    }
    return 0;
}

5.6 实验 6:进程通信

5.6.1 实验目的

  1. 理解管道机制、消息缓冲队列、共享存储区机制进行进程间的通信;
  2. 理解通信机制。

5.6.2 实验内容

编写一主程序可以由用户选择如下三种进程通信方式:

  1. 使用管道来实现父子进程之间的进程通信:子进程向父进程发送自己的进程标识符,以及字符串 is sending a message to parent。父进程则通过管道读出子进程发来的消息,将消息显示在屏幕上,然后终止。

  2. 使用消息缓冲队列来实现 client 进程和 server 进程之间的通信:server 进程先建立一个关键字为 SVKEY(如75)的消息队列,然后等待接收类型为REQ (例如 1)的消息;在收到请求消息后,它便显示字符串 serving for client 和接收到的 client 进程的进程标识数,表示正在为 client 进程服务;然后再向 client 进程发送应答消息, 该消息的类型是 client 进程的进程标识数,而正文则是 server 进程自己的标识 ID。client 进程则向消息队列发送类型为 REQ 的消息(消息的正文为自己的进程标识 ID) 以取得 sever 进程的服务,并等待 server 进程发来的应答;然后显示字符串 receive reply from 和接收到的 server 进程的标识 ID。

  3. 使用共享存储区来实现两个进程之间的进程通信:进程 A 创建一个长度为 512 字节的共享内存,并显示写入该共享内存的数据;进程 B 将共 享内存附加到自己的地址空间,并向共享内存中写入数据。

5.6.3 源代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <wait.h>

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/msg.h>

struct msgform
{
    long mtype;
    char mtext[250];
}msg;

int msgqid, pid, *pint;
void Menu();
void UsePipe();//使用管道实现父子进程之间的进程通信
void MessageDeque();//使用消息缓冲队列实现C/S进程之间的通信
void Client();
void Server();
void ShareMemory();//使用共享存储区来实现两个进程之间的进程通信
void A();
void B();
int main()
{
    printf("######欢迎使用“进程通信系统”######\n");
    while (1) {
       Menu();
       int select = 0;
       scanf("%d", &select);
       switch (select) {
       case 0:
          return 0;
       case 1:
           UsePipe();break;
       case 2:
           MessageDeque();break;
       case 3:
           ShareMemory();break;
       default:
           printf("您输入的选择无效,请重新输入!\n");
           break;
       }
    }
    return 0;
}

void Menu()
{
    printf("##################################\n");
    printf("1.管道实现父子进程通信\n");
    printf("2.消息队列C/S进程通信\n");
    printf("3.共享存储区的进程通信\n");
    printf("0.安全退出\n");
    printf("请选择通信方式:\n");

}

void UsePipe()
{
    int fd[2];//fd[0]用于读,fd[1]用于写
    pid_t child;
    pid_t parent = getpid();
    printf("Process Parent PID:%d\n", parent);
    if(pipe(fd) < 0)
    {
        printf("管道创建失败!\n");
        return;
    }
    child = fork();
    if(child == 0)//进入子进程
    {

        char info[40];
        close(fd[0]);
        sprintf(info, "PID:%d is sending a message to parent", getpid());
        write(fd[1], info, 40);
        sleep(1);//休眠1s
        exit(0);

    }
    else //返回主进程
    {
        close(fd[1]);
        char buffer[40];
        read(fd[0], buffer, 40);
        printf("%s\n", buffer);

        close(fd[0]);
        close(fd[1]);
        waitpid(child, NULL, 0);
        printf("Process child is over!\n");
    }
}

void MessageDeque()
{
    int i = 0;
    while((i = fork()) == -1);//创建进程1
    if(!i)
        Server();
    while((i = fork()) == -1);//创建进程2
    if(!i)
        Client();
    wait(0);
    wait(0);
}
void Client()
{
    msgqid = msgget(75, 0400);//打开75消息队列
    pid = getpid();
    pint = (int *)msg.mtext;
    *pint = pid;
    msg.mtype = 1;//消息类型为1
    msgsnd(msgqid, &msg, sizeof(int), 0);//发送消息
    msgrcv(msgqid, &msg, 250, pid, 0);//接收消息
    printf("(client):reveice reply from pid=%d\n", *pint);
    exit(0);
}
void Server()
{
    msgqid = msgget(75, 0400|IPC_CREAT);//创建75消息队列
    msgrcv(msgqid, &msg, 250, 1, 0);//接收消息
    //把正文的内容传给pint,并强制转换类型
    pint = (int *)msg.mtext;
    pid = *pint;//获得client进程标识数
    printf("(server):serving for client pid=%d\n", pid);
    msg.mtype = pid;//消息类型为client
    *pint = getpid();
    msgsnd(msgqid, &msg, sizeof(int), 0);//发送消息
    exit(0);
}

void ShareMemory()
{
    //需要包含头文件<sys/types.h>,<sys/ipc.h>,<sys/shm.h>
    //shmget函数创建或打开一个新区,返回一个共享存储区ID
    //int shmget(key_t key, int size, int shmflg)
    //key:共享存储区的名字,关键字,int型
    //size:共享存储区的大小(以字节计)
    //shmflg:用户设置的标志,如IPC_CREAT
    //IPC_CREAT 表示若系统中尚无指名的共享存储区,
    //则由核心建立一个共享存储区;如系统中已有共享存储区,便忽略IPC_CREAT
    //成功返回共享内存的标识符;不成功返回-1,errno 储存错误原因。
    pid_t a = 0;
    while ((a = fork()) == -1);
    if(!a)
        A();
    while ((a = fork()) == -1);
    if(!a)
        B();
    wait(0);
    wait(0);
}
void A()
{
    int shmid = shmget(100, 512, 0777|IPC_CREAT);//创建共享存储区
    char *addr = shmat(shmid, 0, 0);//获取首地址
    printf("get:%s\n", addr);
    if(shmdt(addr) == 0)
        printf("A断开链接成功!\n");
    exit(0);
}
void B()
{
    int shmid = shmget(100, 512, 0777|IPC_CREAT);//打开共享存储区
    char *message[] = {"B writes something!"};
    char *addr = shmat(shmid, 0, 0);//获得共享存储区首地址
    memset(addr, '\0', 512);//addr内容初始化
    strncpy(addr, message[0], 512);//向内存中写信息
    if(shmdt(addr) == 0)
        printf("B断开链接成功!\n");
    exit(0);
}

6. 课程设计报告及源代码

https://download.csdn.net/download/diligent_lee/85015283

  • 5
    点赞
  • 128
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
一、课程设计任务 完成在如下两个题目中任选一个题目。 1、网络数据计算 (1)在Linux系统下,使用TCP协议套接字编程; (2)服务器应具有处理多个客户端连接并计算的能力; (3)过程描述 客户端:连接服务器后,从键盘输入一连串的整数及字符串,并将这些整数及字符串发送到服务器,并发送结束指令(自定义结束指令),等待服务器返回计算结果,显示结果后退出; 服务器:为每个客户连接创建一个进程,处理客户数据,显示每次收到一个整数或字符串,收到结束指令后将整数累加,并将累加结果返回客户端,并关闭连接,结束进程。 2、linux系统下实现聊天室 (1)在Linux系统下,使用TCP协议套接字编程; (2)服务器应具有处理多个客户端连接能力(设定最大连接数,如5个); (3)具有群发和私聊的能力; (4)过程描述 客户端:连接服务器后,应能接收服务器发来信息并处理的能力,当收到聊天信息时,显示群发或私聊、信息发送客户及发送的信息,当收到客户加入或退出时,显示客户登录或退出,并更新客户列表;每次可选择群发或私聊,群发时将键盘输入信息发送给服务器,私聊时,选择私聊客户并将输入信息发送给服务器。选择退出时,结束进程或线程,关闭程序。 服务器:为每个客户连接创建一个进程或线程,处理客户信息,当有新客户加入或有客户退出时,将客户加入或退出信息发送给每个客户端;当收到某客户的群发信息时,将信息转发给每个客户,当收到客户私聊时将信息转发给私聊客户;客户退出时关闭相应的进程或线程。 二、课程设计时间 一周 三、课程设计说明书 每名学生完成课程设计任务的同时,撰写课程设计说明书,见附件,可打印也可手写,但不得抄袭。 四、课程设计成绩评定标准 课程设计根据学生在课程设计过程的出勤、学习态度及课程设计任务完成情况综合评定课程设计成绩,具体分值所占比例为: (1)出勤、上机态度占总成绩20%,若发现做与课程设计无关事情,每次扣5分; (2)课程设计说明书占总成绩40%,若发现有抄袭行为,课程设计成绩为不及格; (3)课程设计完成效果及答辩占总成绩40%。 按照优、良、中、及格和不及格五级给予最终成绩。
### 回答1: 嵌入式Linux系统开发课程是一门涵盖嵌入式开发和Linux操作系统的技术课程。本课程旨在培养学生对嵌入式系统开发的基本理论和实践技能,以及熟悉Linux操作系统的各个方面。 首先,本课程将介绍嵌入式系统开发的基本概念和原理,包括硬件平台、操作系统和应用软件的设计与开发等内容。学生将学习如何选择适合的硬件平台,了解硬件与操作系统之间的交互,以及如何进行嵌入式应用软件的编写和调试。 其次,本课程将重点介绍Linux操作系统在嵌入式系统中的应用。学生将学习Linux系统的基本原理和结构,包括内核和用户空间的概念,进程管理,读写文件系统等。学生还将学习如何在嵌入式系统上配置和编译Linux内核,以及如何使用基本的Linux工具和命令。 此外,本课程还将引导学生进行实际的项目开发。学生将分组或个人完成一个嵌入式Linux系统的开发项目,从需求分析到系统设计、编码和测试等各个阶段。通过实践,学生将掌握系统开发的实际技能,并加深对嵌入式Linux系统开发的理解。 总而言之,嵌入式Linux系统开发课程通过理论学习、实践项目以及相关实验,帮助学生掌握嵌入式系统开发和Linux操作系统的基本知识和技能。这门课程旨在培养学生的创新能力和实践能力,为他们在嵌入式系统领域的职业发展打下坚实的基础。 ### 回答2: 嵌入式Linux系统开发课程是针对嵌入式设备开发人员而设计的一门课程。嵌入式设备是指集成了特定功能的计算机系统,通常被嵌入到其他设备中。Linux是一种开源的操作系统,拥有良好的灵活性和可定制性,因此在嵌入式设备的开发中得到了广泛应用。 在嵌入式Linux系统开发课程中,首先会介绍Linux的基本原理和体系结构。学生将了解Linux内核的组成部分、驱动程序的编写以及文件系统的管理和优化等内容。课程还将涵盖Linux的实时性能和调试技术,以满足嵌入式设备对实时性和稳定性的要求。 此外,课程还将重点介绍如何在开发嵌入式应用程序时有效地利用Linux系统。学生将学习如何使用Linux的工具链和开发环境,如交叉编译器和调试器。课程还会针对不同嵌入式平台的特点进行实际案例分析,并帮助学生掌握如何在特定平台上进行嵌入式应用程序的开发和调试。 通过学习嵌入式Linux系统开发课程,学生将能够掌握Linux系统的原理和开发工具,能够独立地进行嵌入式Linux系统的开发和调试。学生还将能够理解和应用Linux的各种功能和特性,为嵌入式设备的开发提供更高的效率和灵活性。此外,学生还将了解到行业最新的发展动态和趋势,为日后的工作和研究提供良好的基础。 ### 回答3: 嵌入式Linux系统开发课程是针对嵌入式系统工程师或者对Linux系统内核开发有兴趣的人群设计的一门课程课程的目标是让学员了解嵌入式Linux系统的基本原理和开发方法,并具备开发和调试嵌入式Linux系统的能力。 在这门课程中,学员将学习到Linux系统内核的基本概念和工作原理,包括进程管理、内存管理、文件系统等等。学员将会了解如何进行 Linux 内核的配置和编译,并熟悉常用的调试工具和技巧。此外,课程还会介绍常用的嵌入式Linux开发板,以及如何在开发板上进行嵌入式Linux系统的移植和调试。 课程的教学形式通常包括理论讲解和实践操作两个部分。理论讲解会由经验丰富的讲师给出,通过讲解内容和示例代码,帮助学员了解嵌入式Linux系统开发的基本原理和技术。实践操作部分,学员将会亲自操作实验设备,进行内核的编译调试,实践掌握所学知识。 嵌入式Linux系统开发课程对于想要进入嵌入式系统行业的人员来说非常有帮助,因为嵌入式Linux系统已经广泛应用于各种物联网设备、智能家居产品等。通过学习这门课程,学员可以获得开发和调试嵌入式Linux系统的实际经验,为自己的嵌入式开发之路打下坚实的基础。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值