Linux进程控制

                                    Linux进程控制

1.基本知识:

   (1)fork系统调用:创建一个新进程。即完成创建子进程,也返回一个值。

    <0,创建失败;=0,子进程执行中;〉0,主进程进行中。

   (2)getid:获得一个进程的id。

   (3)lockf:在进程同步控制中为进程加锁。

   (4)wait(等待子进程中断或结束):  进程一旦调用了wait,就立即阻塞自己,由wait自动分析是否当前进程的某个子进程已经退出,如果让它找到了这样一个已经变成僵尸的子进程,wait 就会收集这个子进程的信息,并把它彻底销毁后返回;如果没有找到这样一个子进程,wait就会一直阻塞在这里,直到有一个出现为止。

   (5)不像fork那么难理解,从exit的名字就能看出,这个系统调用是用来终止一个进程的。无论在程序中的什么位置,只要执行到exit系统调用,进程就会停止剩下的所有操作,清除包括PCB在内的各种数据结构,并终止本进程的运行。


2.简单操作:

     (1)  编写一C语言程序(程序名为fork.c),使用系统调用fork( )创建两个子进程。当程序运行时,系统中有一个父进程和两个子进程在并发执行。父亲进程执行时屏幕显示“I am father”,儿子进程执行时屏幕显示“I am son”,女儿进程执行时屏幕显示“I am daughter”。

     (2)  多次连续反复运行这个程序,观察屏幕显示结果的顺序,直至出现不一样的情况为止。

     fork.c:

# include<stdio.h>
# include<sys/types.h>
# include<unistd.h>

int main()
{
    int pid1,pid2
    printf(“I am father!\n”);
   if ((pid1 = fork())<0)
   {
      printf(“Child1 fail create!\n”);
      return 1;
   }
   else if (pid1 == 0)
   {
      printf(“I am son!\n”);
      return;
   }
   if ((pid2 = fork())<0)
   {
       printf(“Child2 fail create!\n”);
       return 1;
    }
    else if (pid2 == 0)
   {
     printf(“I am daughter!\n”);
     return;
    }
}

      (3)  编写一C语言程序(程序名为fork.c),使用系统调用fork( )创建一个子进程,然后在子进程中再创建子子进程。当程序运行时,系统中有一个父进程、一个子进程和一个子子进程在并发执行。父亲进程执行时屏幕显示“I am father”,儿子进程执行时屏幕显示“I am son”,孙子进程执行时屏幕显示“c”。

      (4)  多次连续反复运行这个程序,观察屏幕显示结果的顺序,直至出现不一样的情况为止。

      fork.c:

# include<stdio.h>
# include<sys/types.h>
# include<unistd.h>

int main()
{
   int pid1,pid2
   printf(“I am father!\n”);
   if ((pid1 = fork())<0)
   {
      printf(“Child1 fail create!\n”);
      return 1;
   }
   else if (pid1 == 0)
   {
  if ((pid2 = fork())<0)
  {
         printf(“c fail create!\n”);
   return 1;
 }
  else if (pid2 == 0)
 { 
  printf(“c\n”);
  return;
       }
   else
 {

  printf(“I am son!\n”);
  return;
  }
 }
}

     (5) 修改程序,在父、子进程中分别使用wait、exit、lockf等系统调用“实现”其同步推进,多次反复运行改进后的程序,观察并记录运行结果。

     Linux进程控制wait()函数解析

     wait()会暂时停止目前进程的执行,直到有信号来到或子进程结束。如果在调用wait()时子进程已经结束,则          wait()会立即返回子进程结束状态值。子进程的结束状态值会由参数status 返回,而子进程的进程识别码也会一快返回。如果不在意结束状态值,则参数status可以设成NULL。子进程的结束状态值请参考waitpid()。返回值如果执行成功则返回子进程识别码(PID),如果有错误发生则返回-1。

     wait(等待子进程中断或结束),相关函数waitpid,fork。

     范例:

#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
int main()
{
  pid_t pid;
  int status,i;
  if(fork()= =0){
    printf(“This is the child process .pid =%d\n”,getpid());
    exit(5);
  }
  else{
    sleep(1);
    printf(“This is the parent process ,wait for child...\n”;
    pid=wait(&status);
    i=WEXITSTATUS(status);
    printf(“child’s pid =%d .exit status=%d\n”,pid,i);
  }
}
     (6)  Linux 进程控制  exit()

     实例: 

/* exit_test1.c */
#include<stdlib.h>
main()
{
	printf("this process will exit!\n");
	exit(0);
	printf("never be displayed!\n");
}
      编译后运行: 

$gcc exit_test1.c -o exit_test1
$./exit_test1
this process will exit!
      我们可以看到,程序并没有打印后面的"never be displayed!\n",因为在此之前,在执行到exit(0)时,进程就已经终止了。

      exit 系统调用带有一个整数类型的参数status,我们可以利用这个参数传递进程结束时的状态,比如说,该进程是正常结束的,还是出现某种意外而结束的,一般来说,0表示没有意外的正常结束;其他的数值表示出现了错误,进程非正常结束。

     (7)  Linux 进程控制 lockf()

     利用系统调用lockf(fd,mode,size),对指定区域(有size指示)进行加锁或解锁,以实现进程的同步或互斥。其中,fd是文件描述字;mode是锁定方式,mode=1表示加锁,mode=0表示解锁;size是指定文件fd的指定区域,用0表示从当前位置到文件结尾(注:有些Linux系统是locking(fd,mode,size))。

    范例:

#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
int main(void)
{
	int	 pid1,pid2;
	lockf(1,1,0);
	printf(“Parent process:a\n”);
	if((pid1=fork())<0)
	{
		printf(“child1 fail create\n”);
		return 1;
	}
	else if(pid1= =0)
	{
		lockf(1,1,0);
		printf(“This is child1(pid=%d) process:b\n”,getpid());
		lockf(1,0,0);
		return;
	}
	if((pid2=fork())<0)
	{
		printf(“child2 fail create\n”);
		return 1;
	}
	else if(pid2= =0)
	{
		lockf(1,1,0);
		printf(“This is child2(pid=%d) process:c\n”,getpid());
		lockf(1,0,0);
		return;
	}
}
     (8) wait()会暂时停止目前进程的执行,直到有信号来到或子进程结束。如果在调用wait()时子进程已经结束,则wait()会立即返回子进程结束状态值。子进程的结束状态值会由参数status 返回,而子进程的进程识别码也会一快返回。参数status用来保存被收集进程退出时的一些状态,它是一个指向int类型的指针。但如果我们对这个子进程是如何死掉的毫不在意,只想把这个僵尸进程消灭掉,(事实上绝大多数情况下,我们都会这样想),我们就可以设定这个参数为NULL,pid = wait(NULL)。返回值如果执行成功wait会返回被收集的子进程的进程ID,如果调用进程没有子进程,调用就会失败,此时wait返回-1,同时errno被置为ECHILD。

     实例A:

/* wait1.c */
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>
main()
{
	pid_t pc,pr;
	pc=fork();
	if(pc<0) 		/* 如果出错 */
		printf("error ocurred!\n");
	else if(pc==0){		/* 如果是子进程 */ 
		printf("This is child process with pid of %d\n",getpid());
		sleep(10);	/* 睡眠10秒钟 */
	}
	else{			/* 如果是父进程 */
		pr=wait(NULL);	/* 在这里等待 */
		printf("I catched a child process with pid of %d\n"),pr);
	}		
	exit(0);
}

     编译并运行:

$ cc wait1.c -o wait1
$ ./wait1
This is child process with pid of 1608
I catched a child process with pid of 1608

     可以明显注意到,在第2行结果打印出来前有10 秒钟的等待时间,这就是我们设定的让子进程睡眠的时间,只有子进程从睡眠中苏醒过来,它才能正常退出,也就才能被父进程捕捉到。其实这里我们不管设定子进程睡眠的时间有多长,父进程都会一直等待下去,读者如果有兴趣的话,可以试着自己修改一下这个数值,看看会出现怎样的结果。

     (9) 参数status

      如果参数status的值不是NULL,wait就会把子进程退出时的状态取出并存入其中,这是一个整数值(int),指出了子进程是正常退出还是被非正常结束的(一个进程也可以被其他进程用信号结束,我们将在以后的文章中介绍),以及正常结束时的返回值,或被哪一个信号结束的等信息。由于这些信息被存放在一个整数的不同二进制位中,所以用常规的方法读取会非常麻烦,人们就设计了一套专门的宏(macro)来完成这项工作,下面我们来学习一下其中最常用的两个:

     1)  WIFEXITED(status) 这个宏用来指出子进程是否为正常退出的,如果是,它会返回一个非零值。

   (请注意,虽然名字一样,这里的参数status并不同于wait唯一的参数--指向整数的指针status,而是那个指针所指向的整数,切记不要搞混了)

     2)  WEXITSTATUS(status) 当WIFEXITED返回非零值时,我们可以用这个宏来提取子进程的返回值,如果子进程调用exit(5)退出,WEXITSTATUS(status) 就会返回5;如果子进程调用exit(7),WEXITSTATUS(status)就会返回7。请注意,如果进程不是正常退出的,也就是说, WIFEXITED返回0,这个值就毫无意义。

    实例B:

/* wait2.c */
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
main()
{
	int status;
	pid_t pc,pr;
	pc=fork();
	if(pc<0)	/* 如果出错 */
		printf("error ocurred!\n");
	else if(pc==0){	/* 子进程 */
		printf("This is child process with pid of %d.\n",getpid());
		exit(3);	/* 子进程返回3 */
	}
	else{		/* 父进程 */
		pr=wait(&status);
		if(WIFEXITED(status)){	/* 如果WIFEXITED返回非零值 */
			printf("the child process %d exit normally.\n",pr);
			printf("the return code is %d.\n",WEXITSTATUS(status));
		}else			/* 如果WIFEXITED返回零 */
			printf("the child process %d exit abnormally.\n",pr);
	}
}
      编译并运行:

$ cc wait2.c -o wait2
$ ./wait2
This is child process with pid of 1838.
the child process 1838 exit normally.
the return code is 3.
     父进程准确捕捉到了子进程的返回值3,并把它打印了出来。

     当然,处理进程退出状态的宏并不止这两个,但它们当中的绝大部分在平时的编程中很少用到,就也不在这里浪费篇幅介绍了,有兴趣的读者可以自己参阅Linux man pages去了解它们的用法。

     实例C:

#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
int main()
{
   pid_t pid;
   int status,i;
   if(fork()= =0)
  {
    printf(“This is the child process .pid =%d\n”, getpid());
    exit(5);
  }
  else{
     sleep(1);
     printf(“This is the parent process ,wait for child...\n”);
     pid = wait(&status);
     i = WEXITSTATUS(status);
     printf(“child’s pid =%d. exit status=%d\n”,pid,i);
  }
}

    执行结果:

This is the child process.pid=1902
This is the parent process .wait for child...
child’s pid =1902,exit status =5
      (10) waitpid(等待子进程中断或结束)

      从本质上讲,系统调用waitpid和wait的作用是完全相同的,但waitpid多出了两个可由用户控制的参数pid和options,从而为我们编程提供了另一种更灵活的方式。下面我们就来详细介绍一下这两个参数: 

      1) pid :从参数的名字pid和类型pid_t中就可以看出,这里需要的是一个进程ID。但当pid取不同的值时,在这里有不同的意义。

      pid>0时,只等待进程ID等于pid的子进程,不管其它已经有多少子进程运行结束退出了,只要指定的子进程还没有结束,waitpid就会一直等下去。

      pid=-1时,等待任何一个子进程退出,没有任何限制,此时waitpid和wait的作用一模一样。

      pid=0时,等待同一个进程组中的任何子进程,如果子进程已经加入了别的进程组,waitpid不会对它做任何理睬。         pid<-1时,等待一个指定进程组中的任何子进程,这个进程组的ID等于pid的绝对值。

     

     2) option:

      参数option 可以为0 或下面的OR 组合:
      WNOHANG 如果没有任何已经结束的子进程则马上返回,不予以等待。WUNTRACED 如果子进程进入暂停执行情况则马上返回,但结束状态不予以理会。子进程的结束状态返回后存于status,底下有几个宏可判别结束情况:

      WIFEXITED(status)如果子进程正常结束则为非0 值。

      WEXITSTATUS(status)取得子进程exit()返回的结束代码,一般会先用WIFEXITED 来判断是否正常结束才能使用此宏。
      WIFSIGNALED(status)如果子进程是因为信号而结束则此宏值为真。
      WTERMSIG(status) 取得子进程因信号而中止的信号代码,一般会先用WIFSIGNALED 来判断后才使用此宏。
      WIFSTOPPED(status) 如果子进程处于暂停执行情况则此宏值为真。一般只有使用WUNTRACED 时才会有此情况。
      WSTOPSIG(status) 取得引发子进程暂停的信号代码,一般会先用WIFSTOPPED 来判断后才使用此宏。
      返回值如果执行成功则返回子进程识别码(PID),如果有错误发生则返回-1。失败原因存于errno 中。

      options提供了一些额外的选项来控制waitpid,目前在Linux中只支持WNOHANG和WUNTRACED两个选项,这是两个常数,可以用"|"运算符把它们连接起来使用,比如:ret=waitpid(-1,NULL,WNOHANG| WUNTRACED);如果我们不想使用它们,也可以把options设为0,如:ret=waitpid(-1,NULL,0);如果使用了WNOHANG参数调用waitpid,即使没有子进程退出,它也会立即返回,不会像wait那样永远等下去。

      而WUNTRACED参数,由于涉及到一些跟踪调试方面的知识,加之极少用到,这里就不多费笔墨了,有兴趣的读者可以自行查阅相关材料。

      看到这里,聪明的读者可能已经看出端倪了--wait不就是经过包装的waitpid吗?

      没错,查看<内核源码目录>/include/unistd.h文件349-352行就会发现以下程序段:

static inline pid_t wait(int * wait_stat)
{
	return waitpid(-1,wait_stat,0);
}
      实例:

/* waitpid.c */
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
main()
{
	pid_t pc, pr;
		
	pc=fork();
	if(pc<0)		/* 如果fork出错 */
		printf("Error occured on forking.\n");
	else if(pc==0){		/* 如果是子进程 */
		sleep(10);	/* 睡眠10秒 */
		exit(0);
	}
	/* 如果是父进程 */
	do{
		pr=waitpid(pc, NULL, WNOHANG);	/* 使用了WNOHANG参数,waitpid不会在这里等待 */
		if(pr==0){			/* 如果没有收集到子进程 */
			printf("No child exited\n");
			sleep(1);
		}
	}while(pr==0);				/* 没有收集到子进程,就回去继续尝试 */
	if(pr==pc)
		printf("successfully get child %d\n", pr);
	else
		printf("some error occured\n");
}

     编译并运行:

$ cc waitpid.c -o waitpid
$ ./waitpid
No child exited
No child exited
No child exited
No child exited
No child exited
No child exited
No child exited
No child exited
No child exited
No child exited
successfully get child 1890

      父进程经过10次失败的尝试之后,终于收集到了退出的子进程。

       因为这只是一个例子程序,不便写得太复杂,所以我们就让父进程和子进程分别睡眠了10秒钟和1秒钟,代表它们分别作了10秒钟和1秒钟的工作。父子进程都有工作要做,父进程利用工作的简短间歇查看子进程的是否退出,如退出就收集它。

     (11) exit()函数

      实例:

/* exit_test1.c */
#include<stdlib.h>
main()
{
	printf("this process will exit!\n");
	exit(0);
	printf("never be displayed!\n");
}
    编译后运行:

$gcc exit_test1.c -o exit_test1
$./exit_test1
this process will exit!
     我们可以看到,程序并没有打印后面的"never be displayed!\n",因为在此之前,在执行到exit(0)时,进程就已经终止了。

     exit 系统调用带有一个整数类型的参数status,我们可以利用这个参数传递进程结束时的状态,比如说,该进程是正常结束的,还是出现某种意外而结束的,一般来说,0表示没有意外的正常结束;其他的数值表示出现了错误,进程非正常结束。理论上exit可以返回小于256的任何整数。返回的不同数值主要是给调用者作不同处理的。单独的进程是返回给操作系统的。如果是多进程,是返回给父进程的。父进程里面调用waitpid()等函数得到子进程退出的状态,以便作不同处理。根据相应的返回值来让调用者作出相应的处理。总的说来,exit()就是当前进程把控制权返回给调用该程序的程序,括号里的是返回值,告诉调用程序该程序的运行状态。我们在实际编程时,可以用wait系统调用接收子进程的返回值,从而针对不同的情况进行不同的处理。

     (12) exit和_exit

     作为系统调用而言,exit_exit是一对孪生兄弟。

     这时随便一个懂得C语言并且头脑清醒的人都会说,exit_exit没有任何区别,但我们还要讲一下这两者之间的区别,这种区别主要体现在它们在函数库中的定义。_exit在Linux函数库中的原型是:

        #include<unistd.h>
        void _exit(int status);
和exit比较一下,exit()函数定义在stdlib.h中,而_exit()定义在unistd.h中,从名字上看,stdlib.h似乎比unistd.h高级一点,那么,它们之间到底有什么区别呢?让我们先来看流程图,通过下图,我们会对这两个系统调用的执行过程产生一个较为直观的认识。
      

       从图中可以看出,_exit()函数的作用最为简单:直接使进程停止运行,清除其使用的内存空间,并销毁其在内核中的各种数据结构;exit()函数则在这些基础上作了一些包装,在执行退出之前加了若干道工序,也是因为这个原因,有些人认为exit已经不能算是纯粹的系统调用。
       exit()函数与_exit()函数最大的区别就在于exit()函数在调用exit系统调用之前要检查文件的打开情况,把文件缓冲区中的内容写回文件,就是图中的“清理I/O缓冲”一项。
       在Linux 的标准函数库中,有一套称作“高级I/O”的函数,我们熟知的printf()、fopen()、fread()、fwrite()都在此列,它们也被称作“缓冲I/O(buffered I/O)”,其特征是对应每一个打开的文件,在内存中都有一片缓冲区,每次读文件时,会多读出若干条记录,这样下次读文件时就可以直接从内存的缓冲区中读取,每次写文件的时候,也仅仅是写入内存中的缓冲区,等满足了一定的条件(达到一定数量,或遇到特定字符,如换行符\n和文件结束符EOF),再将缓冲区中的内容一次性写入文件,这样就大大增加了文件读写的速度,但也为我们编程带来了一点点麻烦。如果有一些数据,我们认为已经写入了文件,实际上因为没有满足特定的条件,它们还只是保存在缓冲区内,这时我们用_exit()函数直接将进程关闭,缓冲区中的数据就会丢失,反之,如果想保证数据的完整性,就一定要使用exit()函数。

      请看以下例程:

      实例A:

/* exit2.c */
#include<stdlib.h>
main()
{
	printf("output begin\n");
	printf("content in buffer");
	exit(0);
}

      编译并运行:

$gcc exit2.c -o exit2
$./exit2
output begin
content in buffer
     实例B:
/* _exit1.c */
#include<unistd.h>
main()
{
	printf("output begin\n");
	printf("content in buffer");
	_exit(0);
}

      编译并运行:

$gcc _exit1.c -o _exit1
$./_exit1
output begin

    (13) return函数与exit函数的总结

    通常情况:exit(0)表示程序正常, exit(1)/exit(-1)表示程序异常退出,exit(2)表示表示系统找不到指定的文件。用Error lookup可以查看。

     exit()结束当前进程/当前程序/,在整个程序中,只要调用exit就结束(当前进程或者在main时候为整个程序)

return()是当前函数返回,当然如果是在主函数main, 自然也就结束当前进程了,如果不是,那就是退回上一层调用。在多个进程时.如果有时要检测上进程是否正常退出的.就要用到上个进程的返回值,依次类推。

      1) 进程的开始:
      C程序是从main函数开始执行, 原型如下:
      int main(int argc, char *argv[]);
      通常main的返回值是int型, 正确返回0.
      如果main的返回值为void或者无, 某些编译器会给出警告, 此时main的返回值通常是0。

      关于main的命令行参数不做过多解释, 以下面的程序展示一下:

#include <stdio.h>
int main(int argc, char *argv[])
{
     int i;
     for (i = 0; i < argc; i++)
         printf("argv[%d]: %s\n", i, argv[i]);
     return 0;
}

       2) 进程终止:
      C程序的终止分为两种: 正常终止和异常终止。
      正常终止分为: return, exit, _exit, _Exit, pthreade_exit。
      异常中指分为: abort, SIGNAL, 线程响应取消。
      主要说一下正常终止的前4种, 即exit系列函数.

#include <stdlib.h>    /* ISO C */
void exit(int status);
void _Exit(int status);
#include <unistd.h>    /* POSIX */
void _exit(int status);
      以上3个函数的区别是:
      exit()(或return 0)会调用终止处理程序和用户空间的标准I/O清理程序(如fclose),_exit和_Exit不调用而直接由内核接管进行清理。因此, main函数中exit(0)等价于return 0.

      3) atexit终止处理程序:
      ISO C规定, 一个进程最对可登记32个终止处理函数, 这些函数由exit按登记相反的顺序自动调用. 如果同一函数登记多次, 也会被调用多次.
      原型如下:

#include <stdlib.h>
int atexit(void (*func)(void));
     其中参数是一个函数指针,指向终止处理函数,该函数无参无返回值。atexit函数本身成功调用后返回0。

     以下面的程序为例:

#include <stdlib.h>
static void myexit1()
{
     printf("first exit handler\n");
}
static void myexit2()
{
     printf("second exit handler\n");
}
int main()
{
     if (atexit(my_exit2) != 0)
         printf("can't register my_exit2\n");
     if (atexit(my_exit1) != 0)
         printf("can't register my_exit1\n");
     if (atexit(my_exit1) != 0)
         printf("can't register my_exit1\n");
     printf("main is done\n");
     return 0;
}
     运行结果:

$ ./a.out
main is done
first exit handler
first exit handler
second exit handler

        注意上面的结果,可以发现这些函数由exit按登记相反的顺序自动调用(先myexit1后myexit2)。如果同一函数登记多次,也会被调用多次(如这里的myexit1)。而这些处理函数都是在程序推出的时候利用atexit函数调用了这些处理函数。但是如果用_exit()退出 程序,则它不关闭任何文件,不清除任何缓冲器、也不调用任何终止函数!

    (14) return函数exit函数区别

     1)  exit用于在程序运行的过程中随时结束程序,exit的参数是返回给OS的。main函数结束时也会隐式地调用 exit函数。exit函数运行时首先会执行由atexit()函数登记的函数,然后会做一些自身的清理工作,同时刷新所有输出流、关闭所有打开的流并且 关闭通过标准I/O函数tmpfile()创建的临时文件。exit是结束一个进程,它将删除进程使用的内存空间,同时把错误信息返回父进程,而 return是返回函数值并退出函数
     2)  return是语言级别的,它表示了调用堆栈的返回;而exit是系统调用级别的,它表示了一个进程的结束。
     3)  exit函数是退出应用程序,并将应用程序的一个状态返回给OS,这个状态标识了应用程序的一些运行信息。
     4) 和机器和操作系统有关一般是   0 为正常退出  非0 为非正常退出
     5) void exit(int status);
     6) atexit() 函数的参数是一个函数指针,函数指针指向一个没有参数也没有返回值的函数。atexit()的函数原型是:int atexit (void (*)(void));在一个程序中最多可以用atexit()注册32个处理函数,这些处理函数的调用顺序与其 注册的顺序相反,也即最先注册的最后调 用,最后注册的最先调用。
     一般程序执行到 main() 的结束就完成了, 如果想在程序结束时做一些事情, 可以尝试着用这个函数。

     example:

#include 
#include 
void f1(void)
{
    printf("exit f1\n");
}
void f2(void)
{
    printf("exit f2\n");
}
int main()
{
    atexit(f1);
    atexit(f2);
    printf("exit main\n");
    return 0;
}

  • 4
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值