Linux系统编程---进程

目录

1.进程的概念

1.1 进程的定义

1.2 在Linux系统中如何查看进程

进程号

案列1

案例2:查看进程中的PID号

2进程的创建 

2.1 进程的创建 fork 函数

C程序的存储空间是如何分配

地址空间:包括进程上下文、进程堆栈、打开的文件描述符、信号控制设定、进程优先级、进程组号等。子进程所独有的只有它的进程号,计时器等。因此,使用fork函数的代价是很大的。

案例3:创建子进程(不区分父子进程 不推荐写)

案例4:区分父子进程

2.2 子进程继承父进程的空间

案例5:父子进程空间问题 

案例6: 子进程继承父进程的空间

案例7: 父子进程的pid返回值(补充)

2.3 创建新进程的目的

案例8:创建多个进程的作用

2.4进程的创建 vfork 函数

案例9:使用vfork创建函数

3.1 进程的退出

3.1正常退出和异常退出

3.2 等待子进程退出 

案例10:僵尸进程,子进程退出时,没有被收集

防止出现僵尸进程

案例11:添加wait后的效果

​编辑

案例12:检测子进程退出的状态

waitpid()函数

​编辑

案例13:使用waitpid()     (用的不多)

结果:父子进程可以交替运行,父进程不用阻塞 

孤儿进程

案例14 验证②

4.exec族函数 

案例15:验证execl返回值 (ps:注意路径的问题,只有在当前的路径才行)

案例16:用excel函数代替ls显示文件列表

​编辑

案例18:execlp() 不用加绝对路径实现ps命令

案例19:使用execvp() 函数

案例20:exec和fork配合使用

5.system函数 

案例21:用system函数代替execl函数

案例21:用system函数代替ps命令,查看进程

6.popen函数

好处:相比于system的好处就是,可以获取程序运行时的输出结果

案例22:获取指令的结果,并且打印出来


1.进程的概念

1.1 进程的定义

程序:程序是存放在存储介质上的一个可执行文件。
进程:进程是程序的执行实例,包括程序计数器、寄存器和变量的当前值。

程序是静态的,进程是动态的

在 linux 系统中进程号(进程标识符)由 0 开始。
进程号为 0 及 1 的进程由内核创建。
进程号为 0 的进程通常是调度进程,常被称为交换进程(swapper)。进程号为 1 的进程通常是 init 进程。
除调度进程外,在 linux 下面所有的进程都由进程 init 进程直接或者间接创建。

1.2 在Linux系统中如何查看进程

ps命令、ps -aux命令

-------------------------------------------------------------------------------------------------------------------------------

查看特定的字段进程-----管道命令(grep)

grep init

------------------------------------------------------------------------------------------------------------------------------- 

查看进程的占用率的情况----top命令

 

------------------------------------------------------------------------------------------------------------------------------- 

进程号

每个进程都由一个进程号来标识,其类型为pid_t,进程号的范围:0~32767
进程号是由操作系统随机给当前进程分配的,不能自己控制进程号总是唯一的,但进程号可以重用。当一个进程终止后,其进程号就可以再次使用了

进程号(PID)

标识一个进程的非负整型数
父进程号(PPID)
任何进程(除 init 进程)都是由另一个进程创建,该进程称为被创建进程的父进程,对应的进程号称为父进
程号(PPID)。
进程组号(PGID)
进程组是一个或多个进程的集合。他们之间相互关联,进程组可以接收同一终端的各种信号,关联的进
程有一个进程组号(PGID) 。
Linux 操作系统提供了三个获得进程号的函数 getpid()、getppid()、getpgid()。
需要包含头文件:

1 #include <sys/types.h>
2 #include <unistd.h>
3 pid_t getpid(void);
4 功能:获取当前进程的进程号
5 pid_t getppid(void);
6 功能:获取当前进程的父进程的进程号
7 pid_t getpgid(pid_t pid);
8 功能:获取当前进程所在进程组的
案列1
#include <stdio.h>
2 #include <sys/types.h>
3 #include <unistd.h>
4
5 int main(int argc, char const *argv[])
6 {
7     //获取当前进程的进程号
8     printf("pid = %d\n", getpid());
9
10     //获取当前进程的父进程的id
11     printf("ppid = %d\n", getppid());
12
13     //获取当前进程所在组的id
14     printf("pgid = %d\n", getpgid(getpid()));
15
16     while(1)
17     {
18
19     }
20
21     return 0;
22 }

运行结果: 

案例2:查看进程中的PID号
#include<stdio.h>
#include<sys/tyoes.h>
#include<unistd.h>

int main()
{
        pid_t pid;
        pid = getpid(); 
        prinf("my pid is = %d\n",pid);  
        
        while(2);  //防止进程终止

        return 0;
}
~                                                                                             
~                                                                                             
~                                                                                             
~                                                                                             
~                               

运行结果:

使用top查看进程占用率

结果:可以看到当前进程由于while一直循环未退出,导致进程占用率百分之百

2进程的创建 

2.1 进程的创建 fork 函数

在 linux 环境下,创建进程的主要方法是调用以下两个函数:

#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);pid_tvfork(void)

创建一个新进程

pid_t fork(void)


功能:
fork()函数用于从一个已存在的进程中创建一个新进程,新进程称为子进程,原进程称为父进程。
返回值:
成功:子进程中返回 0,父进程中返回子进程 ID。
失败:返回-1。

C程序的存储空间是如何分配

地址空间:
包括进程上下文、进程堆栈、打开的文件描述符、信号控制设定、进程优先级、进程组号
等。子进程所独有的只有它的进程号,计时器等。因此,使用fork函数的代价是很大的。

示意图(用fork函数的代价还是比较大的):

案例3:创建子进程(不区分父子进程 不推荐写)
#include <stdio.h>
2 #include <stdlib.h>
3 #include <unistd.h>
4 int main(int argc, char *argv[])
5 {
6     //通过fork函数创建一个子进程
7
8     //注意:主要执行一次fork,就会在原有的进程基础上创建一个新的子进程
9     //而且如果fork之后不区分父子进程的代码区,则后面所有的代码都会执行
10     fork();
11     printf("hello world\n");
12     
13     while(1)
14     ;
15     return 0;
16 }

hello打印两次,也就以为着fork函数创建了一个子进程 

案例4:区分父子进程
#include <stdio.h>
2 #include <stdlib.h>
3 #include <unistd.h>
4 int main(int argc, char *argv[])
5 {
6     //通过fork函数创建一个子进程7
#if 0
9     //注意:主要执行一次fork,就会在原有的进程基础上创建一个新的子进程
10     //而且如果fork之后不区分父子进程的代码区,则后面所有的代码都会执行
11     fork();
12     printf("hello world\n");
13     
14     while(1)
15     ;
16 #endif
17
18     //通过fork函数的返回值来区分父子进程的独立的代码区
19     //父子进程是来回交替执行的,谁先运行,谁后运行是不确定的,不要认为父进程执
行完之后才会执行子进程
20     pid_t pid;
21
22     pid = fork();
23     if(pid < 0)
24     {
25         perror("fail to fork");
26         return ‐1;
27     }
28     else if(pid > 0) //父进程的代码区
29     {
30         while(1)
31         {
32             printf("parent: pid = %d, ppid = %d\n", getpid(), getppid());
33             printf("pid = %d\n", pid);
34             printf("this is a parent process\n");
35             sleep(1);
36             printf("****************\n");
37         }
38     }
39     else //子进程的代码区
40     {
41         while(1)
42         {
43             printf("son: pid = %d, ppid = %d\n", getpid(), getppid());
44             printf("this is a son process\n");
45             sleep(1);
46             printf("‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐\n");

        }
48     }
49
50     return 0;
51 }

说明两个情况:1.上面的等号关系

                        2.父子进程是交替执行

      

2.2 子进程继承父进程的空间

案例5:父子进程空间问题 

结果:子进程是重新开辟的空间,与父进程无关

 //子进程会复制父进程fork之前的所有内容

   //但是fork之后,父子进程完全独立,所以不管双方怎么改变(堆区、栈区、数据区等),都不会收对方影响

#include <stdio.h>
2 #include <stdlib.h>
3 #include <unistd.h>
4
5 int a = 666;
6
7 int main(int argc, char *argv[])
8 {
9     pid_t pid;
10     static int b = 777;
11     int c = 888;
12
13     //子进程会复制父进程fork之前的所有内容
14     //但是fork之后,父子进程完全独立,所以不管双方怎么改变(堆区、栈区、数据区
等),都不会收对方影响
15
16     pid = fork();
17     if(pid < 0)
  {
19         perror("fail to fork");
20         return ‐1;
21     }
22     if(pid > 0)  //父进程的代码区
23     {
24         printf("This is a parent process\n");
25         a++;
26         b++;
27         c++;
28         printf("a = %d, b = %d, c = %d\n", a, b, c);
29     }
30     else  //子进程的代码区
31     { 
32         sleep(1);
33         printf("This is a son process\n");
34         printf("a = %d, b = %d, c = %d\n", a, b, c);
35     }
36
37     while(1)
38     {
39
40     }
41     
42     return 0;
43 }

案例6: 子进程继承父进程的空间

结果:

子进程会继承父进程的一些公有的区域,不如磁盘空间,内核空间
文件描述符的偏移量保存在内核空间中,所以父进程改变偏移量,则子进程获取的偏移量是改变之后的

#include <stdio.h>
2 #include <stdlib.h>
3 #include <unistd.h>
4 #include <sys/types.h>
5 #include <sys/stat.h>
6 #include <fcntl.h>
int main(int argc, char *argv[])
9 {
10     int fd;
11     if((fd = open("file.txt", O_RDONLY)) == ‐1)
12     {
13         perror("fail to open");
14         return ‐1;
15     }
16
17     //子进程会继承父进程的一些公有的区域,不如磁盘空间,内核空间
18     //文件描述符的偏移量保存在内核空间中,所以父进程改变偏移量,则子进程获取的
偏移量是改变之后的
19     pid_t pid;
20     pid = fork();
21     if(pid < 0)
22     {
23         perror("fail to fork");
24         return ‐1;
25     }
26     if(pid > 0)
27     {
28         printf("This is a parent process\n");
29
30         char buf[32] = "";
31         if(read(fd, buf, 30) == ‐1)
32         {
33             perror("fail to read");
34             return ‐1;
35         }
36
37         printf("buf = [%s]\n", buf);
38
39     }
40     else 
41     {
42         sleep(1);
43         printf("This is a son process\n");
44
45         char buf[32] = "";
46         if(read(fd, buf, 30) == ‐1
    {
48             perror("fail to read");
49             return ‐1;
50         }
51
52         printf("buf = [%s]\n", buf);
53     }
54
55     while(1)
56     {
57
58     }
59     
60     return 0;
61 }

执行结果

案例7: 父子进程的pid返回值(补充)

结果:fork函数产生两个进程的原因是拷贝父进程直接给子进程

也就是说子进程是通过父进程直接拷贝而来的

运行结果

2.3 创建新进程的目的

目的:

①一个父进程希望复制自己,使父、子进程同时执行不同的代码段。这在网络服务进程中是常见的一一父进程等待客户端的服务请求。当这种请求到达时,父进程调用fork,使子进程处理此请求。父进程则继续等待下一个服务请求到达。

②一个进程要执行一个不同的程序。这对shell是常见的情况。在这种情况下,子进程从fork返回后立即调用exec.

案例8:创建多个进程的作用

结果:输入对应的数字即可创建出新进程,并且创建后还能输入其他数字

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
 
 
int main()
{
    pid_t pid;
    int data = 10;
 
    while(1){
 
        printf("please input a data\n");
        scanf("%d",&data);  
        if(data == 1){
                         
                pid = fork();
                     
                if(pid > 0)
                {
                     
                }
                else if(pid == 0){
                 
                    while(1){
                        printf("do net request,pid=%d\n",getpid());
                        sleep(3);
                    }
                }
 
        }
        else{
            printf("wait ,do nothing\n");
        }
    }
 
    return 0;
}

运行结果:

2.4进程的创建 vfork 函数

案例9:使用vfork创建函数

结果:使用fork创建函数,父子进程交替运行

使用vfork创建函数,保证子进程先运行 , 当子进程调用 exit 退出后,父进
程才执行。

补充:vfork 直接使用父进程存储空间,不拷贝。

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


int main()
{
    pid_t pid;
    int cnt = 0;
 
    pid = vfork();
         
    if(pid > 0)
    {
        while(1){
            printf("cnt=%d",cnt);
            printf("this is father print, pid = %d\n",getpid());
            sleep(1);
        }   
    }
     
    else if(pid == 0){
        while(1){

            printf("this is chilid print, pid = %d\n",getpid());
            sleep(0);
            cnt++;
                if(cnt == 3){
                   exit(0);
                //      break; bunengshiyongbreak
                }
        }
    }

    return 0;
}

运行结果:

3.1 进程的退出

3.1正常退出和异常退出

正常退出

1 . Main 函数调用 return
2. 进程调用 exit(), 标准 c 库
3. 进程调用 _exit() 或者 _Exit() ,属于系统调用

补充:
4. 进程最后一个线程返回
5. 最后一个线程调用 pthread_exit

exit(0);//也就是里面的数字作为一种退出时的状态,返回给调用函数

函数原型 :3种都可以使用,首要推荐exit

异常退出 :

1. 调用 abort
2. 当进程收到某些信号时,如 ctrl+C
3. 最后一个线程对取消( cancellation )请求做出响应

3.2 等待子进程退出 

问题:为啥要等待子进程退出

答:收集子进程的状态

案例10:僵尸进程,子进程退出时,没有被收集

结果:子进程变成“Z+“

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


int main()
{
    pid_t pid;
    int cnt = 0;

    pid = vfork();

    if(pid > 0)
    {
        while(1){
            printf("cnt=%d\n",cnt);
            printf("this is father print, pid = %d\n",getpid());
            sleep(1);
        }
    }

    else if(pid == 0){
        while(1){

            printf("this is chilid print, pid = %d\n",getpid());
            sleep(0);
            cnt++;
                if(cnt == 3){
                        //exit(0);
                        break;// bunengshiyongbreak
                }
        }
    }

    return 0;
}

运行结果:

防止出现僵尸进程

防止出现僵尸进程,可用wait函数来进行操作 

也就是说exit()里面的返回值就是给wait进行调用的

案例11:添加wait后的效果

结果:如红字,并且还是使用的fork创建的更能体现先后顺序

#include <stdio.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
 
 
int main()
{
    pid_t pid;
 
    int cnt = 0;
     
    pid = fork();
         
    if(pid > 0)
    {
 
        wait(NULL);
        while(1){
            printf("cnt=%d\n",cnt);
            printf("this is father print, pid = %d\n",getpid());
            sleep(1);
        }   
    }
     
    else if(pid == 0){
         
        while(1){
            printf("this is chilid print, pid = %d\n",getpid());
            sleep(1);
            cnt++;
            if(cnt == 5){
                exit(0);
            }
        }   
    }
 
    return 0;
}
案例12:检测子进程退出的状态

使用WEXITSTATU()函数收集退出的状态

运行结果

waitpid()函数

回顾一下案例12中的 wait(),使用wait时,必须要等子进程退出之后,父进程才能够运行,此时的父进程处于阻塞的状态,所以waitpid()就是为了解决这个阻塞  

案例13:使用waitpid()     (用的不多)
结果:父子进程可以交替运行,父进程不用阻塞 
#include <stdio.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
 
 
int main()
{
    pid_t pid;
 
    int cnt = 0;
    int status = 10;
 
     
    pid = fork();
         
    if(pid > 0)
    {
 
//      wait(&status);
        waitpid(pid,&status,WNOHANG);
        printf("child quit, child status = %d\n",WEXITSTATUS(status));
        while(1){
            printf("cnt=%d\n",cnt);
            printf("this is father print, pid = %d\n",getpid());
            sleep(1);
        }   
    }
     
    else if(pid == 0){
         
        while(1){
            printf("this is chilid print, pid = %d\n",getpid());
            sleep(1);
            cnt++;
            if(cnt == 5){
                exit(3);
            }
        }   
    }
 
    return 0;
}

运行结果:

孤儿进程

①父进程如果不等待子进程退出,在子进程之前就结束了自己的“生命”,此时子进程叫做孤儿进程

②Linux 避免系统存在过多孤儿进程,init 进程收留孤儿进程,变成孤儿进程的父进程 

案例14 验证②
#include <stdio.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
 
 
int main()
{
    pid_t pid;
 
    int cnt = 0;
    int status = 10;
 
     
    pid = fork();
         
    if(pid > 0)
    {
 
            printf("this is father print, pid = %d\n",getpid());
    }
     
    else if(pid == 0){
         
        while(1){
            printf("this is chilid print, pid = %d,my father pid=%d\n",getpid(),getppid());
            sleep(1);
            cnt++;
            if(cnt == 5){
                exit(3);
            }
        }   
    }
 
    return 0;
}

运行结果: 

4.exec族函数 

https://blog.csdn.net/u014530704/article/details/73848573

案例15:验证execl返回值 (ps:注意路径的问题,只有在当前的路径才行)

结果:exec函数族的函数执行成功后不会返回,调用失败时,会设置errno并返回-1,然后从原程序的调用点接着往下执行。并且把语句全部打印了

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
//函数原型:int execl(const char *path, const char *arg, ...);
 
int main(void)
{
    printf("before execl\n");
    if(execl("./echoarg","echoarg","abc",NULL) == -1)
    {
        printf("execl failed!\n");      
 
    perror("why");
    }
    printf("after execl\n");
    return 0;
}

运行结果:说明excel没有调用成功 

案例16:用excel函数代替ls显示文件列表

结果:显示成功,前提要知道ls在哪个位置,可以用whereis命令

实现ls -l则继续修改参数即可

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
//函数原型:int execl(const char *path, const char *arg, ...);
 
int main(void)
{
    printf("before execl\n");
    if(execl("/bin/ls","ls",NULL,NULL) == -1)
    {
        printf("execl failed!\n");      
 
    perror("why");
    }
    printf("after execl\n");
    return 0;
}

运行结果

案例17:execlp() 不用加绝对路径实现ps命令

原理就是通过修改环境变量来实现

修改环境变量本质上就是不在当前目录下的文件也可以运行

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
//函数原型:int execl(const char *path, const char *arg, ...);
 
int main(void)
{
    printf("this pro get system date:\n");
     
    if(execlp("ps","ps",NULL,NULL) == -1)
    {
        printf("execl failed!\n");      
 
    perror("why");
    }
    printf("after execl\n");
    return 0;
}

运行结果:execlp()会在系统里面自己寻找环境变量

案例18:使用execvp() 函数

更加要强化argv数组指针的作用

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
//函数原型:int execl(const char *path, const char *arg, ...);
 //函数原型:int execvp(const char *file, char *const argv[]);
int main(void)
{
    printf("this pro get system date:\n");
 
    char *argv[] = {"ps",NULL,NULL};
 
    if(execvp("ps",argv) == -1)
    {
        printf("execl failed!\n");      
 
    perror("why");
    }
    printf("after execl\n");
    return 0;
}

运行结果:完全一样

案例19:exec和fork配合使用

直接用execl()函数使得代码更加简洁

原先的代码1

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
 
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
 
 
int main()
{
    pid_t pid;
    int data = 10;
 
    while(1){
 
        printf("please input a data\n");
        scanf("%d",&data);  
        if(data == 1){
                int fdSrc;          
             
                pid = fork();
                if(pid > 0){
                    wait(NULL);
                }
                     
                if(pid == 0){
                 
                    char *readBuf=NULL;
 
                    fdSrc = open("config.txt",O_RDWR);
                    int size = lseek(fdSrc,0,SEEK_END);
                    lseek(fdSrc,0,SEEK_SET);
 
                    readBuf=(char *)malloc(sizeof(char)*size + 8);
 
                    int n_read = read(fdSrc, readBuf, size);
 
                    char *p = strstr(readBuf,"LENG=");
                    if(p==NULL){
                        printf("not found\n");
                        exit(-1);
                    }
 
                    p = p+strlen("LENG=");
                    *p = '5';
 
                    lseek(fdSrc,0,SEEK_SET);
                    int n_write = write(fdSrc,readBuf,strlen(readBuf));
                     
                    close(fdSrc);
                    exit(0);
                }
 
        }
        else{
            printf("wait ,do nothing\n");
        }
    }
 
    return 0;
}

修改后的代码

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
 
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
 
 
int main()
{
    pid_t pid;
    int data = 10;
 
    while(1){
 
        printf("please input a data\n");
        scanf("%d",&data);  
        if(data == 1){
                int fdSrc;          
             
                pid = fork();
                if(pid > 0){
                    wait(NULL);
                }
                     
                if(pid == 0){
                     
                    execl("./changData","changData","config.txt",NULL);
                }
 
        }
        else{
            printf("wait ,do nothing\n");
        }
    }
 
    return 0;
}

运行结果

使用execl后

5.system函数 

说白了就是封装好的excel()

https://www.cnblogs.com/leijiangtao/p/4051387.html

system源码

案例20:用system函数代替execl函数
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
 
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
 
 
int main()
{
    pid_t pid;
    int data = 10;
 
    while(1){
 
        printf("please input a data\n");
        scanf("%d",&data);  
        if(data == 1){
                int fdSrc;          
             
                pid = fork();
                if(pid > 0){
                    wait(NULL);
                }
                     
                if(pid == 0){
                     
    //              execl("./changData","changData","config.txt",NULL);
                    system("./changData config.txt");
                }
 
        }
        else{
            printf("wait ,do nothing\n");
        }
    }
 
    return 0;
}

运行结果:一样

案例21:用system函数代替ps命令,查看进程
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
//函数原型:int execl(const char *path, const char *arg, ...);

int main(void)
{

    //char ret[1024] = {0};

    if(system("ps")==-1){
                printf("execl failed!\n");
                perror("why:");
        }
   // system("ps");
    printf("after execl\n");

    return 0;
}
      

运行结果:相较于execl不同的是,system最后还会返回到源程序中执行后面的代码3

6.popen函数

好处:相比于system的好处就是,可以获取程序运行时的输出结果

参考文章:linux下popen的使用心得_linux c++ popen 命令执行结束-CSDN博客

首先要搞清楚的就是popen的函数原型

 #include “stdio.h”

  FILE popen( const char command, const char* mode )

可以看到该函数的返回值是一个 “文件流”

案例22:获取指令的结果,并且打印出来
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
//函数原型:int execl(const char *path, const char *arg, ...);
//size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream)
int main(void)
{
    char ret[1024] = {0};
    FILE *fp;
 
    fp = popen("ps","r");      
    int nread = fread(ret,1,1024,fp);   //将fp里面的东西读到ret里面 
 
    printf("read ret %d byte, ret=%s\n",nread,ret);
         
    return 0;
}

运行结果:

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值