vxworks中任务间的通信支持信号量、消息队列、管道、信号、事件

vxworks中任务间的通信支持信号量、消息队列、管道、信号、事件
2011-07-01 19:50

vxworks中任务间的通信支持信号量、消息队列、管道、信号、事件、共享内存等。
一:信号量
信号量分二进制信号量、互斥信号量、计数信号量。
1:二进制信号量
(1)semBCreate():创建二进制信号量
SEM_ID semBCreate(int options,SEM_B_STATE initialState)
options 可以为基于优先级的队列EM_Q_PRIORITY(0x1)或者是基于先进先出的队列SEM_Q_FIFO(0X0).
initialState 可以是信号量初始化为满SEM_FULL(1)或者为空SEM_EMPTY(0)。
(2)semTake(): 获取信号量
STATUS semTake(SEM_ID semID,int timeout)
semID为信号量ID。
timeout 表示任务一直等待信号量,直到可用WAIT_FOREVER(-1)或者不等待,直接下步操作NO_WAIT(0)。
(3)semGive():释放信号量
STATUS semGive(SEM_ID semID)
(4)semFlush():唤醒信号量
STATUS semFlush(SEM_ID semID)
通过此函数可以将所有阻塞在信号量(WAIT_FOREVER)上的任务释放。
(5)semDelete():删除信号量
STATUS semDelete(SEM_ID semID)
(6)semInfo():得到阻塞在信号量上的所有任务ID列表
int semInfo(SEM_ID semID,int idList[],int maxTasks)
idList[]为要显示任务ID的数组。
maxTasks为要显示的任务最大数。
(7)semShow():显示信号量的相关信息
STATUS semShow(SEM_ID semID,int level)
level分概括(0),详细(1)。

2:互斥信号量
互斥信号量相比二进制信号量增加了防止优先级倒置、递归访问等功能。
(1)semMCreate():创建互斥信号量
SEM_ID semMCreate(int options)
options 的选项有:
- SEM_Q_PRIORITY(0x1):需要获取信号量的任务基于优先级原则排列。
- SEM_Q_FIFO(0x0):需要获取信号量的任务基于先进先出原则排列。
- SEM_DELETE_SAFE(0x4):保护任务防止意外删除,当执行获取信号量(semtake)时操作时会默认执行任务保护

(taskSafe)操作,当执行释放信号量(semGive)操作时会默认执行取消任务保护(taskUnsafe)操作。
- SEM_INVERSION_SAFE(0x8):保护系统,防止系统出现优先级倒置现象。
- SEM_EVENTSEND_ERR_NOTIFY(0X10):任务发送事件失败时,会发送错误号。
(2)semMGiveForce():强制释放互斥信号量
STATUS semMGiveForce(SEM_ID semId)

3:计数信号量
计数信号量主要用于搜集某些信号量。
SEM_ID semCCreate(int options,int initialCount)
options为信号量属性或类型
initialCount 初始化计数
/************************************************************************
示例代码:以二进制信号量为例
STATUS task1(void); //声明两个任务
STATUS task2(void);
int taskId1,taskId2; //全局变量任务ID
SEM_ID semTest1,semTest2; //信号量
int initTask()   //任务初始化
{
taskId1=taskSpawn("task1",120,0,10240,(FUNCPTR)task1,0,0,0,0,0,0,0,0,0); //创建任务1
taskId2=taskSpawn("task2",120,0,10240,(FUNCPTR)task2,0,0,0,0,0,0,0,0,0); //创建任务2
semTest1=semBCreate(SEM_Q_PRIORITY,SEM_EMPTY); //创建信号量1
semTest2=semBCreate(SEM_Q_PRIORITY,SEM_EMPTY); //创建信号量2
if(semTest1==NULL)   //如果创建信号量失败,则返回错误,成功着返回OK
{
   return ERROR;
}
return OK;
}

STATUS task1(void) //任务1主体函数
{
while(1)
{
semGive(semTset1);             //任务1释放信号量1
printf("任务1释放了信号量!\n");
semTake(semTest2,WAIT_FOREVER);//任务1得到信号量2,在任务2没有释放信号量2前,任务1在此等待
printf("任务1得到了信号量!\n");
}
}

STATUS task1(void)//任务2主体函数
{
while(1)
{
semTake(semTest1,WAIT_FOREVER);//任务2得到信号量1,在任务1没有释放信号量1前,任务2在此等待
printf("任务2得到了信号量!\n");
semGive(semTset2);             //任务2释放信号量2
printf("任务2释放了信号量!\n");
}
}
********************************************************************************/

二:消息队列
Vxworks系统中提供了两种消息队列库:msgQLib和mqPxLib。
msgQLib提供了标准的vxworks消息队列,而msPxLib提供了POSIX消息队列。
(1)msgQCreate():创建或者初始化消息队列
MSG_Q_ID msgQCreate(int maxMsgs,int maxMsgLength,int options)
maxMsgs 为最大消息数
maxMsgLength 消息的最大字节数
options 信息的属性
其中options可以为
-MSG_Q_FIFO(0x00):任务以FIFO原则排队。
-MSG_Q_PRIORITY(0X01):任务以基于优先级的原则排队。
-MSG_Q_EVENTSEND_ERR_NOTIFY(0X02):消息队列向他的注册任务发送事件失败时,将返回一个ERROR的值,并且正确地设置errno。成功创建消息队列后会返回消息队列ID。
(2)msgQDelete():删除消息队列
STATUS msgQDelete(MSG_Q_ID msgQId) //要删除的消息队列
(3)msgQSend():发送消息队列
STATUS msgQSend(MSG_Q_ID msgQId, //要发送的消息队列
                char* buffer,    //要发送的消息指针
                UINT nBytes,     //消息长度
                int timeout,     //等待的tick数 当为NO_WAIT(0)时,表示消息没有发送出去也立刻返回,当为WAIT_FOREVER(-1)时,则一直等待发送,知道消息发生发送出                                   去
                int priority     //普通(MSG_PRI_NORMAL(0))还是紧急模式(MSG_Q_PRI_URGENT(1))
               )
(4)msgQReceive():从消息队列中收取消息
int msgQReceive(MSG_Q_ID msgQId, //接收的消息队列ID
                char* buffer,     //接收消息的BUFFER指针,如果消息超过了BUFFER的最大字节数,则超出的部分将被丢弃。
                UINT maxNBytes,   //BUFFER的最大字节数
                int timeout       //等待的tick数,当为NO_WAIT(0)时,表示消息没有发送出去也立刻返回,当为WAIT_FOREVER(-1)时,则一直等待发送,知道消息发生发送                                      出去
               )
(5)msgQNumMSgs():得到排队到消息队列中的消息数
int msgQNumMsgs(int MSG_Q_ID msgQId) //消息队列的ID
该函数返回排队到消息队列中的消息数。
/**********************************************************************************
示例代码:
STATUS task1(void); //声明两个任务
STATUS task2(void);

int taskId1,taskId2; //全局变量任务ID
MSG_Q_ID msgQId1; //消息队列ID
char buffer[50]; //接收消息的buffer

int initTask()   //任务初始化
{
taskId1=taskSpawn("task1",120,0,10240,(FUNCPTR)task1,0,0,0,0,0,0,0,0,0); //创建任务1
taskId2=taskSpawn("task2",120,0,10240,(FUNCPTR)task2,0,0,0,0,0,0,0,0,0); //创建任务2
msgQId=msgQCreate(20,50,MSG_Q_PRIORITY); //创建消息队列
if(msgQId==NULL)   //如果创建信号量失败,则返回错误,成功着返回OK
{
   return ERROR;
}
return OK;
}

STATUS task1(void) //任务1主体函数
{
while(1)
{
if(msgQSend(msgQId1,MSG,sizeof(MSG),WAIT_FOREVER,MSG_PRI_NORMAL)==ERROR)//向消息队列发送消息MSG
{
    return ERROR;
}
taskDelay(10);
}
}

STATUS task1(void)//任务2主体函数
{
while(1)
{
if(msgQReceive(msgQId1,buffer,50,WAIT_FOREVER)==ERROR)//从消息队列接收消息
{
    return ERROR;
}
printf("消息是%s\n",buffer);//将消息的内容显示出来
}
}
*********************************************************************************/

三:管道
管道是一种虚拟的I/O设备,所以对管道可以通过标准的I/O口函数进行操作。
(1)pipeDevCreate():创建管道
STATUS pipeDevCreate(char* name,int nMessages,int nBytes)
name 为管道名
nMessages 为管道中的最大数目
nBytes 为每个消息的字节数
(2)pipeDevDelete():删除管道
STATUS pipeDevDelete(char* name,BOOL force)
force 为是否要强制删除
(3)pipeDrv():初始化管道
STATUS pipeDrv(void)
/*********************************************************************************
示例代码:管道
#define MSG "hello"

STATUS task1(void); //声明两个任务
STATUS task2(void);

int taskId1,taskId2; //全局变量任务ID
int pipeId;
int temp;
char buffer[50];

int initTask()   //任务初始化
{
taskId1=taskSpawn("task1",120,0,10240,(FUNCPTR)task1,0,0,0,0,0,0,0,0,0); //创建任务1
taskId2=taskSpawn("task2",120,0,10240,(FUNCPTR)task2,0,0,0,0,0,0,0,0,0); //创建任务2
temp=pipeDevCreate("/pipe/pipeTest",50,50); //创建管道
if(temp==ERROR)
{
return ERROR;
}
pipeId=open("/pipe/pipeTest",O_RDWR,0); //打开管道
if(pipeId==ERROR)
{
return ERROR;
}
return OK;
}

STATUS task1(void) //任务1主体函数
{
while(1)
{
if(write(pipeId,MSG,sizeof(MSG))==ERROR)//向管道写信息
{
    return ERROR;
}
taskDelay(10);
}
}

STATUS task2(void)//任务2主体函数
{
while(1)
{
if(read(pipeId,buffer,50)//向管道里读信息
{
    return ERROR;
}
printf("消息是%s\n",buffer);//将消息的内容显示出来
}
}
*************************************************************************/

四:信号
信号是由事件引发的软中断,中断并非由外界硬件产生,而是由内部程序自己产生。需要特别注意的是,信号的处理程序并非在中断上下文中进行处理,而是在接受任务本身的上下文中进行处理。
(1)signal():将信号与特定的处理程序绑定
void (*signal(int signo,void(*pHandler)()))
signo 为信号ID,
*pHandler () 为信号处理程序
(2)kill():向指定的任务发送信号
int kill(int tid,int signo)
tid 为任务ID
signo 为要发送的信号
/**************************************************************************************
示例代码:信号
#define MSG "hello"

STATUS task1(void); //声明两个任务
STATUS task2(void);

int taskId1,taskId2; //全局变量任务ID
int signalTest=5;

int initTask()   //任务初始化
{
taskId1=taskSpawn("task1",120,0,10240,(FUNCPTR)task1,0,0,0,0,0,0,0,0,0); //创建任务1
taskId2=taskSpawn("task2",120,0,10240,(FUNCPTR)task2,0,0,0,0,0,0,0,0,0); //创建任务2
return 0;
}

STATUS task1(void) //任务1主体函数
{
signal(signalTest,sigISR); //将信号与信号处理程序绑定
while(1)
{

}
}

void sigISR(int sig)//任务2主体函数
{

if(sig==signalTest)
{
   logMsg("信号数是%d\n",sig,0,0,0,0,0); //输出信息
}
return;

}

STATUS task2(void) //任务1主体函数
{
while(1)
{
kill(taskId1,signalTest);//发送信号给任务1
taskDelay(10);
}
}
******************************************************************************************/

五:事件
事件用于任务之间或者任务与ISR,或者任务与系统之间。任务最多可以注册24个事件,每个任务的TCB中有专门的事件寄存器,但此寄存器不可直接访问,有事件发生变化的时候会改变相应的寄存器值。
(1)eventReceive():接收事件
STATUS eventReceive(UINT32 events,UINT8 options,int timeout,UINT32* pEventsReceived)
events 为等待的事件号
timeout 为等待的事件
pEventsReceived 为反映接收到的事件变量
其中options可选项为:
-EVENTS_WAIT_ANY(0X1):等待任何一个事件发生了就就绪
-EVENTS_WAIT_ALL(0X0):等待所有事件发生了再就绪
-EVENTS_RETURN_ALL(0X2):返回所有等待的和不等待的事件
-EVENTS_FETCH(0X80):pEventsReceived变量设置已经接收到的事件相应位,并且立刻返回。
(2)evenSend():发送事件
STATUS eventSend(int taskId,UINT32 events)
taskId 为要发送的任务
events 为要发送的事件
(3)evenClear():清除当前任务的所有事件
(4)semGive():将事件拷贝到在该信号量注册的事件寄存器
(5)msgQSend():将事件拷贝到在该队列注册的事件寄存器
/**********************************************************************************************
示例代码:事件

STATUS task1(void); //声明两个任务
STATUS task2(void);

int taskId1,taskId2; //全局变量任务ID
SEM_ID semId1; //促使事件发生的资源

int initTask()   //任务初始化
{
taskId1=taskSpawn("task1",120,0,10240,(FUNCPTR)task1,0,0,0,0,0,0,0,0,0); //创建任务1
taskId2=taskSpawn("task2",110,0,10240,(FUNCPTR)task2,0,0,0,0,0,0,0,0,0); //创建任务2
semId1=semBCreate(SEM_Q_PRIORITY,SEM_EMPTY); //创建二进制信号量
if(semId1==NULL)
{
return ERROR;
}
return OK;
}

STATUS task1(void) //任务1主体函数
{
UINT32 tmp_receive;
semEvStart(semId1,VXEV01,EVENTS_OPTIIONS_NONE);//任务1在资源:二进制信号量semId1上注册,如果资源发生变化,则任务会收到相应的事件
while(1)
{
eventReceive(VXEV01,WAIT_ANY,WAIT_FOREVER,&tmp_receive);//等待事件发生
if((tmp_receive&VXEV01)!=0)
{
    printf("收到事件!\n");
}
}
}

STATUS task2(void)//任务2主体函数使信号量的可用状态发生变化,促使事件发生
{
while(1)
{
semGive(semId1);
semTake(semId1,NO_WAIT);
taskDelay(10);
}
}
 


  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
#include #include #include "vxWorks.h" #include "msgQLib.h" #include "taskLib.h" /*#include "memPartLib.h"*/ #include "memLib.h" /*宏定义*/ #define MAX_MSGS (10) /* the length of msg*/ #define MAX_MSG_LEN sizeof(MESSAGE) /*the length of message*/ #define STACK_SIZE 20000 /*the stack size of task*/ #define DELAY_TICKS 50 /*the time of sending message*/ #define MAX_point 5 /*用户从系统内存池获得内存的最大次数*/ #define size_1 30 /*用户分区的分配的大小*/ #define size_2 40 /*全局变量*/ int tidtask1; int tidtask2; int tidtask3; SEM_ID syncSemId; SEM_ID waitSemId; MSG_Q_ID myMsgQId1; MSG_Q_ID myMsgQId2; MSG_Q_ID myMsgQId3; typedef struct _MESSAGE { int mSendId; /*发送任务 ID*/ int mRecvId; /*接收任务 ID*/ int mData; /*消息传递的数据*/ char Data[14]; } MESSAGE; /*内存管理*/ char* usermem1; char* usermem2; MESSAGE *point1[MAX_point]; MESSAGE *point2[MAX_point]; MESSAGE *point3[MAX_point]; int point1_index=0; int point2_index=0; int point3_index=0; PART_ID partid1; PART_ID partid2; #define MID_MESSAGE(id) (id) /*函数声明*/ int start(void); int task1(void); int task2(void); int task3(void); template T* mymalloc(unsigned nBytes); void myfree(void); void bye(void); /***************************************[progStart]*******************************************/ /*启动程序,创建息队例,任务*/ int start(void) { tidtask1=taskSpawn("tTask1", 220, 0, STACK_SIZE, (FUNCPTR)task1,0,0,0,0,0,0,0,0,0,0); usermem1=malloc(200); partid1=memPartCreate(usermem1,200); usermem2=malloc(400); partid2=memPartCreate(usermem2,400); return; } /**************************************[test_end]********************************************/ /*是否相等,相等返回1*/ int test_end(char *end,char *target) { int ret; if(!strcmp(end,target)) ret=1; else ret=0; return ret; } /****************************************[task1]***********************************************/ /*管理Task。负责系统启动时同步系统其他Task的启动同步,利用信号量的semFlush()完成。同时接收各*/ /*Task的告警信息,告警信息需编号以logmsg方式输出。本task负责系统结束时的Task删除处理*/ int task1(void) { int singal; int message; MESSAGE *rxMsg=mymalloc(26); /*define messages,and alloc memory*/ memset(rxMsg,0,26); syncSemId=semBCreate(SEM_Q_FIFO,SEM_EMPTY); /*creat semaphore*/ waitSemId=semBCreate(SEM_Q_PRIORITY,SEM_EMPTY); myMsgQId1=msgQCreate(MAX_MSGS,MAX_MSG_LEN,MSG_Q_PRIORITY); /*create msgQ*/ myMsgQId2=msgQCreate(MAX_MSGS,MAX_MSG_LEN,MSG_Q_PRIORITY); myMsgQId3=msgQCreate(MAX_MSGS,MAX_MSG_LEN,MSG_Q_PRIORITY); tidtask2=taskSpawn("tTask2", 200, 0, STACK_SIZE, (FUNCPTR)task2,0,0,0,0,0,0,0,0,0,0); /*create task*/ tidtask3=taskSpawn("tTask3", 210, 0, STACK_SIZE, (FUNCPTR)task3,0,0,0,0,0,0,0,0,0,0); printf("Please input one of the following commands:add,sub,multiply,divide,testcommand\n"); /*the command we should put into the console*/ semFlush(syncSemId); /*release semaphore*/ semGive(waitSemId); while(1) { singal=1; msgQReceive(myMsgQId1,(char*)&rxMsg,sizeof(rxMsg),WAIT_FOREVER); if(rxMsg->mRecvId==MID_MESSAGE(3)) /*receive MsgQ from task3*/ { singal=test_end(rxMsg->Data,"wrong length")-1; logMsg("task3 receiveing a:%s\n",rxMsg->Data); /*put the warn from task3*/ logMsg("Please reput the other command!\n"); msgQReceive(myMsgQId1,(char*)&rxMsg,MAX_MSG_LEN,WAIT_FOREVER); /*recive MsgQ from task3*/ } if(rxMsg->mRecvId==MID_MESSAGE(2)) /*receive MsgQ from task2*/ { message=test_end(rxMsg->Data,"sysend"); if(message) { /*if the message from task2 is "sysend" and did not receive the warn from task3, close the system*/ if(singal) { bye(); } } else {/*if the message from task2 is "sysend" and receive the warn from task3, reput the command*/ if(singal) logMsg("task2 receiveing a %s\n",rxMsg->Data); logMsg("please reput the correct command!\n"); } } } return; } /********************************************************************************************/ int change_buf(char *command) { int ret; if(!strcmp(command,"add")) ret=1; else if(!strcmp(command,"sub")) ret=2; else if(!strcmp(command,"multiply")) ret=3; else if(!strcmp(command,"divide")) ret=4; else if(!strcmp(command,"testcommand")) ret=5; else ret=0; return ret; } /****************************************[task2]*********************************************/ /*console 命令行接收Task。接收并分析console发来的命令行及参数。自行设置5种以上命令,并根据命*/ /*令的内容向Task3发送激励消息。同时实现系统退出命令,使系统采用适当方式安全退出。收到非法命令*/ /*向Task1告警*/ int task2(void) { char buf[100]; int command; char *str=mymalloc(35); MESSAGE *txMsg=mymalloc(26); memset(str,0,35); memset(txMsg,0,26); txMsg->mSendId=MID_MESSAGE(2); txMsg->mRecvId=MID_MESSAGE(2); FOREVER { semTake(syncSemId,WAIT_FOREVER); semTake(waitSemId,WAIT_FOREVER); gets(buf); command=change_buf(buf);/*change the commands into numbers*/ switch(command) { case 0:/*receive uncorrect command*/ txMsg->mData=0; strcpy(txMsg->Data,"wrong command");/*send warn to task1*/ msgQSend(myMsgQId1,(char*)&txMsg,sizeof(txMsg),WAIT_FOREVER,MSG_PRI_NORMAL); break; case 1:/*receive add command*/ strcpy(str,"This an add caculate!\0"); txMsg->mData=1; break; case 2:/*receive sub command*/ strcpy(str,"This a sub caculate!\0"); txMsg->mData=2; break; case 3:/*receive multiply command*/ strcpy(str,"This a multiply caculate!\0"); txMsg->mData=3; break; case 4:/*receive divide command*/ strcpy(str,"This a divide caculate!\0"); txMsg->mData=4; break; case 5:/*receive testcommand,send a long string to task3*/ strcpy(str,"This a testcommand to warn task1!\0"); txMsg->mData=5; break; default: break; } if(txMsg->mData!=0) {/*send along string to task3,and send a message to taks3*/ msgQSend(myMsgQId3,(char*)&str,sizeof(str),WAIT_FOREVER,MSG_PRI_NORMAL); msgQSend(myMsgQId3,(char*)&txMsg,sizeof(txMsg),WAIT_FOREVER,MSG_PRI_NORMAL); } semGive(waitSemId); semGive(syncSemId); taskDelay(DELAY_TICKS); if(txMsg->mData!=0) {/*send sysend to task1 to let task1 close system*/ strcpy(txMsg->Data,"sysend"); msgQSend(myMsgQId1,(char*)&txMsg,sizeof(txMsg),WAIT_FOREVER,MSG_PRI_NORMAL); } } return; } /****************************************[task3]********************************************/ /*console输出Task。接收需打印输出的字串消息(命令),输出到console。收到长度为0或超常字串向*/ /*Task1告警*/ int task3(void) { int firstData=100; int secondData=10; MESSAGE *rxMsg=mymalloc(26); MESSAGE *txMsg=mymalloc(26); char *rstr=mymalloc(35); memset(txMsg,0,26); memset(txMsg,0,26); memset(rstr,0,35); txMsg->mSendId=MID_MESSAGE(3); txMsg->mRecvId=MID_MESSAGE(3); while(1) { semTake(syncSemId,WAIT_FOREVER); msgQReceive(myMsgQId3,(char*)&rstr,sizeof(rstr),WAIT_FOREVER); if(strlen(rstr)=26) {/*make sure whether the string is too long or short*/ strcpy(txMsg->Data,"wrong length"); msgQSend(myMsgQId1,(char*)&txMsg,sizeof(txMsg),WAIT_FOREVER,MSG_PRI_NORMAL); /*msgQReceive(myMsgQId3,(char*)&rxMsg,sizeof(rxMsg),WAIT_FOREVER);*/ } semTake(waitSemId,WAIT_FOREVER); msgQReceive(myMsgQId3,(char*)&rxMsg,sizeof(rxMsg),WAIT_FOREVER); if(rxMsg->mData!=5) {/*when it is not testcommand,printf these*/ printf("%s\n",rstr); printf("there are two datas!\n"); printf("firstData:100\n"); printf("secondData:10\n"); } switch(rxMsg->mData) { case 1:/*printf add caculate*/ printf("The result is:%d\n",firstData+secondData); break; case 2:/*printf sub caculate*/ printf("The result is:%d\n",firstData-secondData); break; case 3:/*printf multiply caculate*/ printf("The result is:%d\n",firstData*secondData); break; case 4:/*printf divide caculate*/ printf("The result is:%d\n",firstData/secondData); break; case 5: break; default: break; } semGive(waitSemId); semGive(syncSemId); taskDelay(DELAY_TICKS); } return; } template T* mymalloc(unsigned nBytes) { T* point; int i=0; /*用户分区一是否能分配的标志位*/ int j=0; /*用户分区二是否能分配的标志位*/ if(nBytes=size_1 && nBytes=size_2) && point3_index<MAX_point) /*若用户分区二不能分配,由系统内存池来分配,且只能从系统内存池分配MAX_point次*/ { point=malloc(nBytes); point3[point3_index]=point; printf("the number of the point3_index is:%d\n",point3_index); point3_index++; } return point; } void myfree(void) { int i=0; for (i=0;i<point1_index;i++) { memPartFree(partid1,point1[i]); } for (i=0;i<point2_index;i++) { memPartFree(partid2,point2[i]); } for (i=0;i<point3_index;i++) { free(point3[i]); } free(usermem1); free(usermem2); printf("The memory have freed!\n"); } void bye(void) { myfree(); logMsg("Bye-bye\n"); taskDelete(tidtask2); taskDelete(tidtask3); msgQDelete(myMsgQId1); msgQDelete(myMsgQId2); msgQDelete(myMsgQId3); semDelete(syncSemId); taskDelete(tidtask1); }

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值