在win下使用C模拟fork()以及进程间通信

前言

这是大三上学期的操作系统的实验,过程感触比较深,但是同时又感觉自己学的很浅薄,所以就将这个自己理解的过程与源码发上来。做为笔记以及方便以后再做查看。如果有实验需求的也可以跑一下这个程序和你所理解的fork()以及进程通信有什么区别。

分块解释

1.fork()实现(部分)

  • 代码如下
void winfork()//fork函数模拟--行为模式:子进程就返回0,非子进程返回该进程的子进程的pid
{
    /**********************判断结构***************/
    if(num==0)
    {
        printf("当前没有运行的进程,无法进行fork()行为\n");
        return;
    }
    if (num==20)
    {
        printf("fork error!");
        return;//内存没空间创建子进程
    }
    /*******************************************/
    /**********模拟复制内容部分*********/

    printf("\n请输入要执行fork()的进程的pid值:\n");    //选择父进程
    scanf("%d",&pid);                                           //此时的pid是父进程的pid
    sign=0;
    for(i=0;i<20;i++)
        {
        if(RAM[i].pid==pid)                                  //在20个内存里查找pid符合条件的父进程
            {
            if(RAM[i].child_sign==0)                                     //pid符合条件且其下没有子进程
                {
                printf("\n请输入子进程的pid\n");          //对新建的子进程的pid赋值
                scanf("%d",&RAM[num+1].pid);
                for(j=0;j<num;j++) /*判断是否之前已存储相同pid进程*/
                if(RAM[j].pid==RAM[num+1].pid)
                    {
                    printf("\n该进程pid已存在于内存中");
                    return;
                    }
                RAM[num+1].priority=RAM[i].priority;
                RAM[num+1].size=RAM[i].size;
                strcpy(RAM[num+1].task,RAM[i].task);
                RAM[num+1].state=1; /*将进程状态设为1,表示在内存中*/
                RAM[num+1].child_sign=0;
                RAM[i].child_sign=RAM[num+1].pid;
                if(RAM[i].fds_0!=2){//权限同步
                    RAM[num+1].fds_0=RAM[i].fds_0;
                    RAM[num+1].fds_1=RAM[i].fds_1;
                }
                num++;/*内存中进程数目加一*/
                sign=1;
                return;
                }
            }
        }
        if(sign==0) printf("\n执行失败!\n" );
}

我做的只是一个完成度很低的模拟(能力不足),这里我主要是模拟了fork会使得子进程基础父进程所有状态(除进程标识符和父子标志外)相同,并且对于父子进程标志会有更改。

RAM[num+1].child_sign=0;
RAM[i].child_sign=RAM[num+1].pid;

以及权限同步

RAM[num+1].fds_0=RAM[i].fds_0;
RAM[num+1].fds_1=RAM[i].fds_1;

2.进程通信模拟(部分)

以下流程有关于函数实现pipe建立管道后建立子进程,并关闭父进程的
管道本质为内核缓冲区,用户态来看是一个伪文件
由于是伪文件,所以对于管道有两个文件描述符引用,一个表示读端,一个表示写端
管道是基于字节流实现通信的
管道依赖于文件系统,其生命周期随进程
管道本身自带同步互斥
fork()进程的时候,子进程会继承父进程的管道与其描述符
单方向流动,半双工通信类型,且只作用于有血缘关系的进程之间
代码如下:

void communication()
{
     if(num==0)
    {
        printf("当前没有运行的进程,无法进行进程通信行为\n");
        return;
    }
    int f_pid,c_pid;//进行进程通信的进程的id(父子)
    char buf[20];//缓存区
    printf("\n 请输入父进程 pid\n");
    scanf("%d",&f_pid);//获取父进程pid

    /************对父进程的确认**********/
     for(i=0;i<20;i++)
        {
        if(RAM[i].pid==f_pid)                                  //在20个内存里查找pid符合条件的父进程
            {
            if(RAM[i].child_sign==0)                                     //pid符合条件且其下没有子进程建立管道
                {
                    if (num==20)return;//无进程空间建立子进程//
                    RAM[i].fds_0=1;     //激活读取权限
                    printf("进程(pid=%d)的读取权限已授权,",RAM[i].pid);
                    RAM[i].fds_1=1;     //激活写入权限
                    printf("进程(pid=%d)的写入权限已授权\n",RAM[i].pid);
                    /******为进程建立子进程并激活权限***********/
                    printf("\n请输入子进程的pid\n");          //对新建的子进程的pid赋值
                    scanf("%d",&RAM[num+1].pid);
                    for(j=0;j<num;j++) /*判断是否之前已存储相同pid进程*/
                    if(RAM[j].pid==RAM[num+1].pid)
                        {
                        printf("\n该pid已被占用");
                        return;
                        }
                    RAM[num+1].priority=RAM[i].priority;
                    RAM[num+1].size=RAM[i].size;
                    strcpy(RAM[num+1].task,RAM[i].task);
                    RAM[num+1].state=1; /*将进程状态设为1,表示在内存中*/
                    RAM[num+1].child_sign=0;
                    RAM[i].child_sign=RAM[num+1].pid;
                    if(RAM[i].fds_0!=2){//权限同步
                        RAM[num+1].fds_0=RAM[i].fds_0;
                        RAM[num+1].fds_1=RAM[i].fds_1;
                        printf("进程(pid=%d)的读取权限已同步,",RAM[num+1].pid);
                        printf("进程(pid=%d)的写入权限已同步\n",RAM[num+1].pid);
                        RAM[i].fds_0=0;
                        printf("进程(pid=%d)的读取权限已关闭,",RAM[i].pid);
                        RAM[num+1].fds_1=0;//关闭父进程的读段,关闭子进程的写段
                        printf("进程(pid=%d)的写入权限已关闭\n",RAM[num+1].pid);
                    }
                    num++;
                    sign=1;
//利用缓存区交流通信
                    printf("输入通信信息");
                    scanf("%s",RAM[i].msg);//只有父进程写入
                    strcpy(buf,RAM[i].msg);//交换进入缓存区
                    strcpy(RAM[num+1].msg,buf);//由缓存区传递至子进程
                    printf("\n 通信的信息是:%s\n",RAM[num+1].msg);//通过子进程读取信息
                    strcpy(buf,"");//清空缓存管道内的信息(只读一次销毁机制)
                    return;
                }
            else{/********为已有子进程激活权限********/
                    RAM[i].fds_0=1;     //激活读取权限
                    printf("进程(pid=%d)的读取权限已授权,",RAM[i].pid);
                    RAM[i].fds_1=1;     //激活写入权限
                    printf("进程(pid=%d)的写入权限已授权\n",RAM[i].pid);
                    c_pid=RAM[i].child_sign;//取得子进程的pid
                      for(j=0;j<20;j++)
                            {
                                if(RAM[j].pid==c_pid){
                                RAM[j].fds_0=RAM[i].fds_0;
                                RAM[j].fds_1=RAM[i].fds_1;
                                printf("进程(pid=%d)的读取权限已同步,",RAM[j].pid);
                                printf("进程(pid=%d)的写入权限已同步\n",RAM[j].pid);
                                RAM[i].fds_0=0;
                                printf("进程(pid=%d)的读取权限已关闭,",RAM[i].pid);
                                RAM[j].fds_1=0;
                                printf("进程(pid=%d)的写入权限已关闭\n",RAM[j].pid);
                                }//关闭父进程的读段,关闭子进程的写段
                        }
                    printf("输入通信信息");
                    scanf("%s",RAM[i].msg);//只有父进程写入
                    strcpy(buf,RAM[i].msg);//交换进入缓存区
                    strcpy(RAM[num+1].msg,buf);//由缓存区传递至子进程
                    printf("\n 通信的信息是:%s\n",RAM[num+1].msg);//通过子进程读取信息
                     strcpy(buf,"");//清空缓存管道内的信息(只读一次销毁机制)
                    return;
            }
            }
            else {
                printf("该pid的进程不存在于内存!");
                return;
            }
        }
}

对于通信模拟来说,我主要是对权限进行了一个文字上的说明和一个变量变动表示权限。

RAM[i].fds_0=1;     //激活读取权限
printf("进程(pid=%d)的读取权限已授权,",RAM[i].pid);
RAM[i].fds_1=1;     //激活写入权限
printf("进程(pid=%d)的写入权限已授权\n",RAM[i].pid);

程序全代码


#include "stdio.h"
#include "conio.h"
#include "stdlib.h"

/*************字典*************/
/*
优先级:priority
大小:size
任务:task
状态:state
标记:sign
挂起:suspend
内存:RAM
*/
struct PCB_type
{
    int pid;                        //pid设定
    int priority;              //优先级
    int size;                //大小
    char task[10];         //进程任务内容
    int state;                      /* 0表示不在内存,1表示在内存,2表示挂起*/
    int child_sign;                         //标记子进程
    int fds_0;                          //0表示无权限,1表示有权限,2表示未被pipe调用,即无效状态
    int fds_1;                       //文件描述符************fds[0]读取数据,fds[1]写入数据
    char msg[20];               //进程内信息传递用
};
struct PCB_type RAM[20];             //定义20个”内存“的结构体
int num=0,suspend=0,sign,i,j,pid;               /*定义相关参数变量  num用于这里的空间存储判定
                    /创建新进程****/
void create()
{
    if(num>=20) /*判断是否有存储空间*/
    printf("\n 内存已满,请删除或挂起其他程序");
    else{
    for(i=0;i<20;i++)
        if(RAM[i].state==0) break; /*按顺序在内存中寻找创建新进程的空间*/
    printf("\n请输入新建进程的pid值\n");
    scanf("%d",&RAM[i].pid);
    for(j=0;j<i;j++) /*判断是否之前已存储相同pid进程*/
        if(RAM[j].pid==RAM[i].pid)
        {
            printf("\n该进程已存在于内存中");
            return;
        }
    printf("\n请输入新进程的优先级 \n" );
    scanf("%d",&RAM[i].priority);
    printf("\n请输入新进程的大小\n" );
    scanf("%d",&RAM[i].size);
    printf("\n请输入新进程的内容\n" );
    scanf("%s",&RAM[i].task);
    RAM[i].child_sign=0 ;
    RAM[i].state=1; /*将进程状态设为1,表示在内存中*/
    RAM[i].fds_0=2;//读取权限无效状态
    RAM[i].fds_1=2;//赋值权限无效状态
    num++;/*内存中进程数目加一*/
    }
}
/*查看当前运行进程******/
void run()
{
    sign=0;
    for(i=0;i<20;i++)
    if(RAM[i].state==1) /*若进程状态设为1,表示在内存中,输出查看*/
{
    printf("\n pid=%d   |",RAM[i].pid);
    printf(" priority=%d   |",RAM[i].priority);
    printf(" size=%d   |",RAM[i].size);
    printf(" task=%s   |",RAM[i].task);
    printf(" child_sign=%d   |",RAM[i].child_sign);
    if(RAM[i].fds_0==1)printf("读取权限已授权,");
    else if(RAM[i].fds_0==0)printf("读取权限未授权,");
    else if(RAM[i].fds_0==2) printf("读取权限未激活,");
    if(RAM[i].fds_1==1)printf("写入权限已授权。");
    else if(RAM[i].fds_1==0)printf("写入权限未授权。");
    else if(RAM[i].fds_1==2)printf("写入权限未激活。");
    sign=1;/*创建成功,标记为1 */
}
if(sign==0)
    printf("\n当前没有运行该进程\n" );/*标记为0,表示该进程未创建于内存中*/

}
/*换出进程*******/

void huanchu()
{
    if(num==0) /*num=0,表示内存中无进程*/
    {
    printf("\n当前没有运行的进程\n" );
    return;
    }
    printf("\n请输入要换出的进程的pid值\n" );
    scanf("%d",&pid);
    sign=0;
    for(i=0;i<20;i++)
    {
        if(pid==RAM[i].pid)
        {
            if(RAM[i].state==1)
            {
            RAM[i].state=2; /*将该进程的状态设为挂起*/
            suspend++;
            printf("\n该进程已成功挂起换出\n" );
            }
            else if(RAM[i].state==0)
                printf("\n要换出的进程不存在\n" );
            else printf("\n要换出的进程已经被挂起\n" );
            sign=1;
            break;
        }
    }
    if(sign==0) printf("\n要换出的进程不存在\n" );
}


//杀死进程*******/
void kill()
{
    if(num==0)
    {
         printf("\n当前没有运行的进程\n" );
        return;
    }
    printf("\n请输入要杀死的进程的pid值\n" );
    scanf("%d",&pid);
    sign=0;
    for(i=0;i<20;i++)
    {
        if(pid==RAM[i].pid)
        {
            if(RAM[i].state==1)
            {
                RAM[i].state=0; //将该进程的状态设为不在内存中/
                num--; //内存中的进程数目减少一个/
                printf("\n该进程已成功杀死\n" );
            }
            else if(RAM[i].state==0)
                printf("\n要杀死的进程不存在\n" );
            else printf("\n要杀死的进程已经被挂起\n" );/*剩余状态为2,挂起*/
            sign=1;
            break;
        }
    }
    if(sign==0) printf("\n要杀死的进程不存在\n" );
}
/*唤醒进程*******/
void wake_up()
{
    if(num==0)
    {
        printf("当前没有运行的进程\n");
        return;
    }
    if(suspend==0) //suspend=0,表示没有挂起的进程/
    {
        printf("\n当前没有换出的进程\n");
        return;
    }
    printf("\n请输入要唤醒的进程的pid值:\n");
    scanf("%d",&pid);
    for(i=0;i<20;i++)
        {
        if(pid==RAM[i].pid)
            {
            if(RAM[i].state==2)
            {
                RAM[i].state=1; //将该进程的状态设为挂起/
                suspend--;
                num++;
                printf("\n该进程已成功唤醒\n" );
            }
            else if(RAM[i].state==0)
            printf("\n要唤醒的进程不存在\n" );
            else printf("\n要唤醒的进程已经在内存中\n" );
            }
        }
}
void winfork()//fork函数模拟--行为模式:子进程就返回0,非子进程返回该进程的子进程的pid
{
    /**********************判断结构***************/
    if(num==0)
    {
        printf("当前没有运行的进程,无法进行fork()行为\n");
        return;
    }
    if (num==20)
    {
        printf("fork error!");
        return;//内存没空间创建子进程
    }
    /*******************************************/
    /**********模拟复制内容部分*********/

    printf("\n请输入要执行fork()的进程的pid值:\n");    //选择父进程
    scanf("%d",&pid);                                           //此时的pid是父进程的pid
    sign=0;
    for(i=0;i<20;i++)
        {
        if(RAM[i].pid==pid)                                  //在20个内存里查找pid符合条件的父进程
            {
            if(RAM[i].child_sign==0)                                     //pid符合条件且其下没有子进程
                {
                printf("\n请输入子进程的pid\n");          //对新建的子进程的pid赋值
                scanf("%d",&RAM[num+1].pid);
                for(j=0;j<num;j++) /*判断是否之前已存储相同pid进程*/
                if(RAM[j].pid==RAM[num+1].pid)
                    {
                    printf("\n该进程pid已存在于内存中");
                    return;
                    }
                RAM[num+1].priority=RAM[i].priority;
                RAM[num+1].size=RAM[i].size;
                strcpy(RAM[num+1].task,RAM[i].task);
                RAM[num+1].state=1; /*将进程状态设为1,表示在内存中*/
                RAM[num+1].child_sign=0;
                RAM[i].child_sign=RAM[num+1].pid;
                if(RAM[i].fds_0!=2){//权限同步
                    RAM[num+1].fds_0=RAM[i].fds_0;
                    RAM[num+1].fds_1=RAM[i].fds_1;
                }
                num++;/*内存中进程数目加一*/
                sign=1;
                return;
                }
            }
        }
        if(sign==0) printf("\n执行失败!\n" );
}
//**************************************************进程间通信**********************************************//
/***********以下流程有关于函数实现pipe建立管道后建立子进程,并关闭父进程的
    管道本质为内核缓冲区,用户态来看是一个伪文件
    由于是伪文件,所以对于管道有两个文件描述符引用,一个表示读端,一个表示写端
    管道是基于字节流实现通信的
    管道依赖于文件系统,其生命周期随进程
    管道本身自带同步互斥
    fork()进程的时候,子进程会继承父进程的管道与其描述符
    单方向流动,半双工通信类型,且只作用于有血缘关系的进程之间
    *****************/
void communication()
{
     if(num==0)
    {
        printf("当前没有运行的进程,无法进行进程通信行为\n");
        return;
    }
    int f_pid,c_pid;//进行进程通信的进程的id(父子)
    char buf[20];//缓存区
    printf("\n 请输入父进程 pid\n");
    scanf("%d",&f_pid);//获取父进程pid

    /************对父进程的确认**********/
     for(i=0;i<20;i++)
        {
        if(RAM[i].pid==f_pid)                                  //在20个内存里查找pid符合条件的父进程
            {
            if(RAM[i].child_sign==0)                                     //pid符合条件且其下没有子进程建立管道
                {
                    if (num==20)return;//无进程空间建立子进程//
                    RAM[i].fds_0=1;     //激活读取权限
                    printf("进程(pid=%d)的读取权限已授权,",RAM[i].pid);
                    RAM[i].fds_1=1;     //激活写入权限
                    printf("进程(pid=%d)的写入权限已授权\n",RAM[i].pid);
                    /******为进程建立子进程并激活权限***********/
                    printf("\n请输入子进程的pid\n");          //对新建的子进程的pid赋值
                    scanf("%d",&RAM[num+1].pid);
                    for(j=0;j<num;j++) /*判断是否之前已存储相同pid进程*/
                    if(RAM[j].pid==RAM[num+1].pid)
                        {
                        printf("\n该pid已被占用");
                        return;
                        }
                    RAM[num+1].priority=RAM[i].priority;
                    RAM[num+1].size=RAM[i].size;
                    strcpy(RAM[num+1].task,RAM[i].task);
                    RAM[num+1].state=1; /*将进程状态设为1,表示在内存中*/
                    RAM[num+1].child_sign=0;
                    RAM[i].child_sign=RAM[num+1].pid;
                    if(RAM[i].fds_0!=2){//权限同步
                        RAM[num+1].fds_0=RAM[i].fds_0;
                        RAM[num+1].fds_1=RAM[i].fds_1;
                        printf("进程(pid=%d)的读取权限已同步,",RAM[num+1].pid);
                        printf("进程(pid=%d)的写入权限已同步\n",RAM[num+1].pid);
                        RAM[i].fds_0=0;
                        printf("进程(pid=%d)的读取权限已关闭,",RAM[i].pid);
                        RAM[num+1].fds_1=0;//关闭父进程的读段,关闭子进程的写段
                        printf("进程(pid=%d)的写入权限已关闭\n",RAM[num+1].pid);
                    }
                    num++;
                    sign=1;
//利用缓存区交流通信
                    printf("输入通信信息");
                    scanf("%s",RAM[i].msg);//只有父进程写入
                    strcpy(buf,RAM[i].msg);//交换进入缓存区
                    strcpy(RAM[num+1].msg,buf);//由缓存区传递至子进程
                    printf("\n 通信的信息是:%s\n",RAM[num+1].msg);//通过子进程读取信息
                    strcpy(buf,"");//清空缓存管道内的信息(只读一次销毁机制)
                    return;
                }
            else{/********为已有子进程激活权限********/
                    RAM[i].fds_0=1;     //激活读取权限
                    printf("进程(pid=%d)的读取权限已授权,",RAM[i].pid);
                    RAM[i].fds_1=1;     //激活写入权限
                    printf("进程(pid=%d)的写入权限已授权\n",RAM[i].pid);
                    c_pid=RAM[i].child_sign;//取得子进程的pid
                      for(j=0;j<20;j++)
                            {
                                if(RAM[j].pid==c_pid){
                                RAM[j].fds_0=RAM[i].fds_0;
                                RAM[j].fds_1=RAM[i].fds_1;
                                printf("进程(pid=%d)的读取权限已同步,",RAM[j].pid);
                                printf("进程(pid=%d)的写入权限已同步\n",RAM[j].pid);
                                RAM[i].fds_0=0;
                                printf("进程(pid=%d)的读取权限已关闭,",RAM[i].pid);
                                RAM[j].fds_1=0;
                                printf("进程(pid=%d)的写入权限已关闭\n",RAM[j].pid);
                                }//关闭父进程的读段,关闭子进程的写段
                        }
                    printf("输入通信信息");
                    scanf("%s",RAM[i].msg);//只有父进程写入
                    strcpy(buf,RAM[i].msg);//交换进入缓存区
                    strcpy(RAM[num+1].msg,buf);//由缓存区传递至子进程
                    printf("\n 通信的信息是:%s\n",RAM[num+1].msg);//通过子进程读取信息
                     strcpy(buf,"");//清空缓存管道内的信息(只读一次销毁机制)
                    return;
            }
            }
            else {
                printf("该pid的进程不存在于内存!");
                return;
            }
        }
}
//主程序*******/
void main()
{
    int option,n=1,i;
    for(i=0;i<20;i++)
    RAM[i].state=0; //使所有进程都初始设为不在内存中/
    while(n)
        {
        printf("\n*****************************************");
        printf("\n*    win系统下模拟进程控制演示系统    *");
        printf("\n*****************************************");
        printf("\n |1.创建新的进程      2.查看运行进程    |");
        printf("\n |3.换出某个进程      4.杀死运行进程    |");
        printf("\n |5.唤醒某个进程      6.win下模拟fork   |");
        printf("\n |7.通信             8.退出               |");
        printf("\n*****************************************");
        printf("\n请选择(1~8)");
        scanf("%d",&option);
        switch(option)
            {
                case 1: create( ); break;
            case 2 :run(); break;
            case 3 :huanchu();break;
            case 4 :kill();break;
            case 5 :wake_up();break;
            case 6: winfork();break;
            case 8 :exit(0);
            case 7:communication();break;
            default: n=0;
            break;
            }
        }
}

运行结果

fork()部分

在这里插入图片描述

通信部分

在这里插入图片描述

小结

由于个人编码能力,对链表掌握一般,所以不敢用链表写,使得很多逻辑的表现实际上过于表面了,没能触及实质,PCB的定义也是十分一般,定义的变量依旧不充足。如果以后有时间一定把这个东西再完善完善。对于除去这两个模块的代码之外,其他的基础代码都是参考了别人的例子。

  • 17
    点赞
  • 48
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
好的,我可以为您解答。首先,让我们来认识一下`fork()`函数和线程通信。 `fork()`函数是一个在Linux系统中常用的系统调用函数,它的作用是创建一个新的进程,这个新的进程称为子进程,原来的进程称为父进程。子进程是父进程的一个完整的复制,包括所有的数据和代码,但是两个进程是独立的,它们有各自的地址空,各自的文件描述符等。 线程通信是指在多线程编程中,不同的线程之进行数据交换或者协调工作的一种机制。线程通信的常用方式有共享内存、消息队列、信号量等。 在C++中,我们可以使用`fork()`函数和线程通信机制来实现进程和线程的通信。下面是一个简单的示例程序: ```c++ #include <iostream> #include <unistd.h> #include <pthread.h> #include <cstring> #include <cstdlib> #include <cstdio> using namespace std; // 全局变量 int count = 0; pthread_mutex_t mutex; // 互斥锁 // 子进程函数 void child_process() { cout << "This is child process." << endl; for (int i = 0; i < 5; i++) { pthread_mutex_lock(&mutex); cout << "Child process count: " << ++count << endl; pthread_mutex_unlock(&mutex); usleep(1000000); } exit(0); } // 主函数 int main() { // 初始化互斥锁 pthread_mutex_init(&mutex, NULL); pid_t pid = fork(); if (pid == -1) { cerr << "Fork error!" << endl; return -1; } else if (pid == 0) { // 子进程 child_process(); } else { // 父进程 cout << "This is parent process." << endl; for (int i = 0; i < 5; i++) { pthread_mutex_lock(&mutex); cout << "Parent process count: " << ++count << endl; pthread_mutex_unlock(&mutex); usleep(1000000); } } // 销毁互斥锁 pthread_mutex_destroy(&mutex); return 0; } ``` 在这个示例程序中,我们定义了一个全局变量`count`和一个互斥锁`mutex`。然后,我们使用`fork()`函数创建了一个子进程,在子进程中不断地增加`count`的值并输出,在父进程中也不断地增加`count`的值并输出。在输出过程中,我们使用了互斥锁来保证两个进程对`count`的操作不会发生竞争条件。 另外,这个示例程序还使用了`usleep()`函数来让进程睡眠1秒钟,以便我们观察进程的输出结果。 这就是一个简单的使用`fork()`函数和线程通信机制来实现进程和线程的通信的示例程序。希望能对您有所帮助。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

小灰不停前进

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

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

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

打赏作者

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

抵扣说明:

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

余额充值