Linux-进程控制

发布时间:2022-06-26 发布网站:脚本宝典
脚本宝典收集整理的这篇文章主要介绍了Linux-进程控制脚本宝典觉得挺不错的,现在分享给大家,也给大家做个参考。

文章目录

  • 进程创建
    • fock函数
    • 写时拷贝
  • 进程终止
  • 进程等待
  • 进程程序替换
  • 简易shell

@H_126_33@进程创建

操作系统允许一个进程创建另一个进程,并且允许子进程继承父进程所拥有的资,当子进程被终止时,其在父进程处继承的资源应当还给父进程。同时,终止父进程同时也会终止其所有子进程。 注意F1a;Linux操作系统对于终止有子进程的父进程,会把子进程交给1号进程接管。

进程创建:1、命令行启动命令(程序、指令等) 2、通过程序自身,fork出子进程

创建进程的过程:

  1. 操作系统为新进程分配一个唯一的进程标识号,并申请一个空白的PCB,PCB是有限的,若申请失败则创建失败。
  2. 为进程分配资源,此处如果资源不足,进程就会进入等待状态,以等待资源。
  3. 初始化PCB
  4. 如果进程的调度队列能够接纳新进程,那就将进程插入到就绪队列,等待被调度运行。

fock函数

父进程通过调用fork函数创建一个新的运行的子进程。 新创建的子进程几乎但不完全与父进程相同。子进程得到与父进程用户级虚拟地址空间相同的(但是独立的)一份副本,包括代码和数据段、堆、共享库以及用户栈。父进程和新创建的子进程之间的最大区别在于它们有不同的PID

#include <unistd.h>
pid_t fork(void);
//返回值:子进程中返回0,父进程返回子进程id,出错返回-1

进程调用fork,当控制转移到内核中的fork代码后,OS做:

  • 分配新的内存块和内核数据结构给子进程
  • 将父进程部分数据结构内容拷贝至子进程
  • 添加子进程到系统进程列表当中
  • fork返回,开始调度器
  1 #include <stdio.h>                                                                                                                                   
  2 #include <unistd.h>
  3 
  4 int main()
  5 {
  6     const char *str = "hello world";
  7 
  8     pid_t pid = fork();
  9     //之后才会运行
 10     if(pid == 0){
 11         while(1){
 12           PRintf("child: ppid: %d, pid: %d, str: %sn", getppid(), getpid(), str);
 13           sleep(1);
 14         }
 15     }
 16     else if(pid > 0){
 17         while(1){
 18           printf("father: ppid: %d, pid: %d, str: %sn", getppid(), getpid(), str);
 19           sleep(1);
 20         }
 21     }
 22     else{
 23         PError("fork");
 24     }
 25     return 0;
 26 }                                                                            

注意:虽然父子进程代码共享,但fork之后才有子进程,所以子进程是执行fork之后的代码。

fork常规用法: 1、一个父进程希望复制自己,使父子进程同时执行不同的代码段。例如:父进程等待客户端请求,生成子进程来处理请求。 2、一个进程要执行一个不同的程序。例如子进程从fork返回后,调用exec函数。

1.为什么fork有两个返回值? 2.一个变量里面,怎么会有两个不同的值,从而让父子进入不同的业务逻辑。 @H_360_361@> fork后父进程返回时,本质是把返回值写入变量pid,而此时子进程已经创建好了,必定发生了写时拷贝。 所以这一个变量名,内容是不同的,而本质是父子页表映射数据到了不同的内存区域。所以接下来父子进程读取pid拿到的值就不一样。

Linux-进程控制

写时拷贝

通常,父子代码共享,父子在不写入时,数据也是共享的,当任意一方试图写入,便以写时拷贝的方式各自一份副本。 写时拷贝的过程实现是由OS参与完成的。

Linux-进程控制

为什么要有写时拷贝(数据的)? 保证父子进程的“独立性” 1.节省资源。父子进程创建时,拷贝不需要写入修改的数据(只读)是没有意义的,如果直接把数据各自拷贝一份,就浪费了内存和系统资源。 2.提高fork创建的效率。fork时创建数据结构,如果还要将数据拷贝一份,fork效率降低 3.减少fork失败的概率。fork本身就是向系统要更多的资源,而要越多的资源就越容易导致fork失败。

进程终止

进程退出的情况分类:

1.代码跑完,结果正确。退出码:0 2.代码跑完,结果不正确。逻辑问题,但是没有导致程序崩溃。退出码:!0 3.代码没有运行完毕,程序崩溃了,退出码没有意义。

进程常见退出方法:

正常终止(可以通过echo $?查看进程退出码): 1.main函数return 2.任何函数exIT 异常退出: ctrl+c,信号终止

main函数中,return的值(退出码)代表进程退出,结果是否运行正确。0代表成功。而return的0是给系统看的,以此确认进程执行结果是否正确。如果我们想看最近一次执行的一个程序运行结束时的退出码,可以用echo $?来查看

Linux-进程控制

退出码:可以认为定义,也可以使用系统的错误码list 当程序运行失败时,最关心的是失败的原因。而计算机擅长处理整数类型的数据(0, 1, 2, 3…)。 int(整数)-> string(错误码描述)

@H_827_406@

父进程一般需要知道子进程退出的结果,即进程的退出码。但父进程也可以不关心子进程的运行结果。 进程非正常结束:野指针、/0、越界等,此时退出码无意义。(此时是由信号来终止的)

main函数return。非main函数的return不是终止进程,而是结束函数。例如:

int show()
{
	return 0;
}

int main()
{
	show();
	return 0;
}

这里main函数中调用完show,这个进程并不会终止。

exit:在任何函数中exit都表示直接终止进程

Linux-进程控制

Linux-进程控制

exit:在退出时会执行用户定义的资源清理函数,包括刷新缓冲区,关闭流等。 _exit:在退出时不会进行后续资源处理,直接终止进程。

Linux-进程控制

可以看到使用_exit时,退出码照样是11。

站在OS角度,如何理解进程终止? 核心思想:归还资源 1."释放"曾经为了管理进程所维护的所有的数据结构对象。 2."释放"程序代码和数据占用的内存空间。 3.取消曾经该进程的链接关系。

释放:不是真的把数据结构对象销毁,而是设置为不用状态,然后保存起来。如果不用的对象多了,就有了一个"数据结构的池"。 内存池:先申请分配一定大小的空间,在需要使用时再使用内存池中的空间,就不需要每次需要内存时都进行new/malloc申请空间,提高了用户的效率。 释放数据结构对象:当要创建进程时,需要将内存池中拿出一块空间,并将这块空间强转成task_struct*,再进行访问。但如果每次都要强转就太麻烦。当一个pcb没人用时,可以将该pcb取出并链接到数据结构池中,该过程就是释放不用的数据结构对象,而需要用时再从池中取出,就不用进行强转了。这种释放规则叫做Slab分派器。 释放代码:不是将代码和数据结构清空,而是把内存设置为无效即可。 例如我们在下载电影资源时,所需下载拷进脑的时间很多,删除却很快,说明写入和删的逻辑是不同的。写入时需要开辟空间,而删的本质是标识数据对应在磁盘上无效,一旦标识无效即意味着可以被覆盖,在写入新数据时,将该无效数据被覆盖也就是被清除了。

Linux-进程控制

进程等待

如果一个父进程终止了,内核会安排init进程成为它的孤儿进程的养父。init进程的PID为1,是在系统启动时由内核创建的,它不会终止,是所有进程的祖先。如果父进程没有回收它的僵死子进程就终止了,那么内核会安排init进程去回收它们。不过,长时间运行的程序,比如shell或者服务器,总是应该回收它们的僵死子进程。即使僵死子进程没有运行,它们依然消耗系统的内存资源。 一个进程可以通过调用waitpid函数来等待它的子进程终止或停止。

等待的必要性:

  1. 回收僵尸,解决内存泄漏。僵尸状态无法被杀死
  2. 父进程需要获取子进程的运行结束状态(不是必须的)
  3. 父进程要尽量晚于子进程退出,可以规范化进行资源回收。(编码相关)

进程等待的方法: wait/waitpid

  • 如果子进程已经退出,调用wait/waitpid时,wait/waitpid会立即返回,并且释放资源,获得子进程退出信息。
  • 如果在任意时刻调用wait/waitpid,子进程存在且正常运行,则进程可能阻塞。
  • 如果不存在该子进程,则立即出错返回。

**wait:**等待任意一个子进程。当子进程退出,wait就可以返回。

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

pid_t wait(int* status);

返回值:成功则返回被等待进程pid,失败返回-1
参数:输出型参数,获取子进程退出状态,不关心则可以设置为NULL
  1 #include <stdio.h>
  2 #include <stdlib.h>
  3 #include <unistd.h>
  4 #include <sys/types.h>                                                                                                                               
  5 #include <sys/wait.h>
  6 
  7 int main()
  8 {
  9     pid_t id = fork();
 10     if(id < 0){
 11         perror("fork");
 12         return 1;//自定义
 13     }
 14     else if(id == 0){
 15         //child
 16         int count = 5;
 17         while(count){
 18             printf("child is running: %d, ppid: %d, pid: %dn", count--, getppid(), getpid());
 19             sleep(1);
 20         }
 21         printf("child quit...n");
 22         exit(0);
 23     }
 24     else{
 25         printf("father is waiting...n");
 26         pid_t ret = wait(NULL);
 27         printf("father is wait done, ret: %dn", ret);                                                                                               
 28     }
 29 }

Linux-进程控制

  int main()
  {
      pid_t id = fork();
      if(id < 0){
          perror("fork");
          return 1;//自定义
      }
      else if(id == 0){
          //child
          int count = 5;
          while(count){
              printf("child is running: %d, ppid: %d,pid:%dn", count-    -, getppid(), getpid());
              sleep(1);
          }
          printf("child quit...n");
          exit(0);
      }
      else{
          printf("father is waiting...n");
          sleep(10);
          pid_t ret = wait(NULL);
          printf("father is wait done, ret: %dn", ret);
          sleep(3);
          printf("father quit...n");
      }
  
      return 0;
  }                                                       

可以看到,5s后子进程变为僵死状态,再过5s后子进程被回收。

Linux-进程控制

一般而言,我们需要fork之后,让父进程等待。

waitpid方法:

pid_ t waitpid(pid_t pid, int *status, int options); 返回值: 当正常返回的时候waitpid返回收集到的子进程的进程ID; 如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0; 如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在; 参数: pid: pid=-1,等待任一个子进程。与wait等效。 pid>0.等待其进程ID与pid相等的子进程。 (等待指定的进程) status: WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出) WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码) options: WNOHANG: 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进程的ID。

默认options设置为0,是阻塞式等待。

  int main()
  {
      pid_t id = fork();
      if(id == 0){
          int count = 5;
          while(count){
              printf("child is runing: %d, ppid: %d, pid:%dn", count--    , getppid(),getpid());                                             
              sleep(1);
          }
          printf("child quit...n");
          exit(0);
      }
      //father
      sleep(8);
      pid_t ret = waitpid(id, NULL, 0);
      printf("father wait done, ret : %dn", ret);
      sleep(3);
  }

Linux-进程控制

获取子进程status wait和waitpid都有一个status参数,该参数是一个输出型参数,由操作系统填充。如果传递NULL,则表示不关心子进程的退出状态信息。否则,操作系统会根据该参数,将子进程的推出信息反馈给父进程。status不能简单地当作整型来看待,可以当作位图来看待。(之研究status低16bit位)

Linux-进程控制

正常终止:

int main()
{
    pid_t id = fork();
    if(id == 0){
        int count = 5;                                                                              
        while(count){
            printf("child is runing: %d, ppid: %d, pid: %dn", count--, getppid(), getpid());
            sleep(1);
        }
        printf("child quit...n");
        exit(123);
    }
    //father
    int status = 0;
    pid_t ret = waitpid(-1, &amp;status, 0);
    int code = (status >> 8) & 0xFF;
    printf("father wait done, ret : %dn, exit code: %dn", ret, code);
    if(code == 0){
        printf("漂亮,事情办成了!n");
    } 
    else{
        printf("完了,需要重来了!n");
    }
}   

Linux-进程控制

注意:不能定义全局变量code来拿到子进程的退出结果,因为父子进程是独立的。当写入变量时,会进行写时拷贝,此时父进程看不到该变量,也就无法取得子进程退出状态。

子进程虽然已经结束了,但子进程还是僵尸,子进程数据结构并没有完全被释放,当进程退出时,如task_struct里会被填上子进程退出时的退出码,所以waitpid拿到的status的值,是通过task_struct内部拿到的。

异常终止: 一般进程提前终止,本质是该进程收到了os发送的信号。 此时status的低7位标识当前进程退出时的终止信号。 信号是从1开始的,也就是说如果检测到低7位全是0,那就是正常终止,此时的退出状态才有意义。

	  int main()
    8 {
    9     pid_t id = fork();
   10     if(id == 0){
   11         int count = 5;
   12         while(count){
   13             printf("child is runing: %d, ppid: %d, pid: %dn", count--, getppid(), getpid());
   14             sleep(1);
E> 15             int *p = 0x12345;
   16             *p = 100;                                                                               
   17         }
   18         printf("child quit...n");
   19         exit(10);
   20     }
   21     //father
   22     int status = 0;
   23     pid_t ret = waitpid(-1, &status, 0);
   24     int code = (status >> 8) & 0xFF;
   25     int sig = status & 0x7F; //0111 1111
   26     printf("father wait done, ret : %dn, exit code: %d, sig: %dn", ret, code, sig);
      }

野指针操作,程序崩溃,sigle不为0,父进程得知子进程异常终止。

Linux-进程控制

Linux-进程控制

完整的等待过程:

  7 int main()  
  8 {  
  9     pid_t id = fork();  
 10     if(id == 0){  
 11         int count = 5;  
 12         while(count){  
 13             printf("child is runing: %d, ppid: %d, pid: %dn", count--, getppid(), getpid());  
 14             sleep(1);
 15         }
 16         printf("child quit...n");
 17         exit(10);
 18     }
 19     int status = 0;
 20     pid_t ret = waitpid(id, &status, 0);
 21     if(ret > 0){
 22         printf("wait success!n");                                                                    
 23         if((status & 0x7F) == 0){
 24             printf("process quit normal!n");
 25             printf("exit code: %dn", (status>>8)&0xFF);
 26         }
 27         else{
 28             printf("process quit error!n");
 29             printf("sig: %dn", status&0x7F);
 30         }                                                                                             
 31     }
	}

Linux-进程控制

系统提供了一堆的宏(函数),可以用来判断退出码,退出状态。

Linux-进程控制

  7 int main()
  8 {
  9     pid_t id = fork();
 10     if(id == 0){
 11         int count = 5;
 12         while(count){
 13             printf("child is runing: %d, ppid: %d, pid: %dn", count--, getppid(), getpid());
 14             sleep(1);
 15         }
 16         printf("child quit...n");
 17         exit(10);
 18     }
 19     int status = 0;                                                                                   
 20     pid_t ret = waitpid(id, &status, 0);
 21     if(ret > 0){
 22         printf("wait success!n");
 23         if(WIFEXITED(status)){
 24             printf("process quit normal!n");
 25             printf("exit code: %dn", WEXITSTATUS(status));
 26         }
 27         else{
 28             printf("process quit error!n");
 29         }
 30     }       
 	 }

Linux-进程控制

如果options传WNOHANG,等待方式为非阻塞,如果传0,默认是阻塞的。我们目前所调用的函数(都是单执行流,简单),都是阻塞函数。阻塞等待:调用方一直在等待,期间不做任何事。而非阻塞是在不断检测状态。 非阻塞轮询方案:父进程多次调用waitpid,检测子进程的运行状态,最终如果检测到了子进程的退出状态,waitpid才成功返回,而在此之前都是失败返回。 注意:waitpid的失败返回有两种意思:1、并不是真正失败,仅仅是对方的状态还没有达到预期(下次再检测)。2、真的失败了

阻塞等待中,是父进程在等待,子进程在跑代码。 “等”:将当前进程放入等待队列,并将进程状态设置为非R状态。 唤醒进程->等待队列->运行队列->R 当我们运行过多程序,计算机卡住时,有可能就是因为运行进程太多,导致OS把进程放入等待队列。

非阻塞等待:

  1 #include <stdio.h>
  2 #include <stdlib.h>
  3 #include <unistd.h>
  4 #include <sys/types.h>
  5 #include <sys/wait.h>
  6 
  7 int main()
  8 {
  9 
 10     pid_t id = fork();
 11     if(id == 0){
 12         int count = 3;                                                                                                                              
 13         while(count){
 14             printf("child is running: %d, ppid: %d, pid: %dn", count--, getppid(), getpid());
 15             sleep(1);                                
 16         }                               
 17         printf("child quit..n");
 18         exit(10);                              
 19     }                                
 20     int status = 0;    
 21     while(1){                           
 22         pid_t ret = waitpid(id, &status, WNOHANG);
 23         if(ret == 0){
 24             printf("wait next!n");                                
 25             printf("father do other thing!n");                  
 26         }                                                 
 27         else if(ret > 0){             
 28             printf("wait success, ret: %d, code: %dn", ret, WEXITSTATUS(status));
 29             break;                            
 30         }                                                                                                          
 31         else{         
 32             printf("wait failedn");
 32             printf("wait failedn");
 33             break;
 34         }
 35     }
 36 }  

进程程序替换

创建子进程的目的有:1.执行父进程的部分代码。2.执行其它的代码。 进行进程替换的目的就是让子进程执行其它程序的代码。

子进程不改变进程内核的数据结构,只修改部分的页表数据,然后将新程序的代码和数据加载到内存,重新构建映射关系,和父进程彻底脱离关系,就是进程替换。 在进行程序替换的时候,没有创建新的进程。子进程的pid没改变。

Linux-进程控制

用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。

如何进行进程替换?

替换函数:

Linux-进程控制

Execl 其中l代表list,列表

int execl(const char *path, const char *arg, ...);

Linux-进程控制

  int main()
  {
      printf(";my process beginn");
      execl("/usr/bin/ls", "ls", "-a", "-l", "-i", NULL);
      printf("my process end!n");                                                                      
  
      return 0;
  }

Linux-进程控制

exec* 程序替换,一旦替换完成,原程序后面的代码就不再执行,所以end也没有输出,返回值也没有意义。所以exec*函数不用考虑返回值,只要返回。 exec是特殊的加载器,当要运行软件时,可以直接将进程读取进内存。

  1 #include <stdio.h>
  2 #include <stdlib.h>
  3 #include <unistd.h>
  4 #include <sys/types.h>
  5 #include <sys/wait.h>
  6 
  7 int main()
  8 {
  9     pid_t id = fork();
 10     if(id == 0){
 11         //child
 12         printf("I am child, pid: %d, ppid: %dn", getpid(), getppid());
 13         execl("/usr/bin/ls", "ls", "-l", NULL);
 14         exit(1);
 15     }
 16 
 17     int count = 3;
 18     while(count){
 19         printf("I am father, pid: %dn", getpid());
 20         sleep(1);
 21         count--;                                                                                                                                    
 22     }
 23     int status = 0;
 24     //father
 25     pid_t ret = waitpid(id, &status, 0);
 26     if(ret > 0){
 27         printf("child status -> sig: %d, code: %dn", status&0x7F, (status >> 8) & 0xFF);
 28     }
 29     else{
 30         printf("wait error!n");
 31     }
 32     return 0;
	 }

这样让创建子进程后让子进程去执行新的程序(没有创建新进程),父进程,父进程得到结果,检测命令并回收子进程的退出信息 这里的退出码code是子进程的退出码,但是是执行完ls后的退出码。

Linux-进程控制

其它接口:

int execv(const char *path, char *const argv[]);

将child中代码改为

char *const my_argv[]={                                                          
   "ls",                                                                        
   "-l",                                                                        
   "-a",                                                                        
   "-i",                                                                        
   NULL                                                                         
};                                                                               
execv("/usr/bin/ls", my_argv);  

结果同上

命名理解:

l(list):表示参数采用列表 v(vector):参数用数组 p(path):有p自动搜索环境变量PATH e(env):表示自己维护环境变量

execlp: 含有p:可以自动搜索环境变量PATH(系统的命令才可以找到,或者把自己的命令导入到PATH中),不用写路径

int execlp(const char *file, const char *arg, ...);

例如:

execlp("ls", "ls", "-l", "-a", NULL);
execlp("top", "top", NULL);

execvp类似,只不过后面是用一个指针数组传参

execle: e:传入默认的或者自定义的环境变量给目标可执行程序

int execle(const char *path, const char *arg, ...,char *const envp[]);
   20         char *const my_env[] = {                                                                  
E> 21             "MYENV=helloworld!",                                                                
   22             NULL                                                                                  
   23         };                                                                                        
   24         execle("./mycmd", "mycmd", NULL, my_env);                                                   
   25         exit(1);                                                                      
   26     }    

Linux-进程控制

在exec_cmd中调用execl传入环境变量MYENV的值,mycmd中接收并打印出。如果单独运mycmd依旧为空。

Linux-进程控制

exec_cmd能执行系统的命令,也可以执行自己写的命令。 如果想要跨语言之间耦合,如c语言想调C++的代码,就可以exec这样的程序替换。

execve:

 int execve(const char *path, char *const argv[], char *const envp[]);
   20         char *const my_argv[] = {                                                     
W> 21             "mycmd",                                                                  
   22             NULL                                                                      
   23         };                                                                            
   24         char *const my_env[] = {                                                      
W> 25             "MYENV=helloworld!",                                                      
   26             NULL                                                                      
   27         };                                                                            
   28         execve("./mycmd", my_argv, my_env);   

也可以将main函数中的环境变量参数env传入,但需要导出环境变量export MYENV=helloworld。main函数可以获得这个环境变量,并把这个环境变量导给子进程。

1.什么是程序替换:通过exec系列的函数,让特定进程去加载磁盘中的其它程序,以达到运行的目的,期间不创建新的进程。 2.为什么要程序替换:子进程执行新的程序的需求。 3.如何进行程序替换:原理->进程地址空间的问题->磁盘换入程序到内存->对可执行程序的理解(exe 文件) exec* 4.后续:a.exec*只要返回了,就说明出错了。b.各种借口的理解:l,v,p,e

简易shell

用户在命令行输入某些命令,交给shell解释器。shell解释器解释命令时,并不是自己解释,而是调用fork创建子进程,子进程再执行命令(其实就是OS执行命令),OS再把结果通过shell解释器返回给用户。而子进程是通过exec系列函数执行命令的。

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

#define NUM 128
#define SIZE 32

char command_line[NUM];
char *command_parse[SIZE];

int main()
{
    while(1){
        memset(command_line, '', sizeof(command_line));
        printf("[ymz@myhost 我的shell]$ ");
        fflush(stdout);
        //1. 数据读取
        if(fgets(command_line, NUM-1, stdin)){
            command_line[strlen(command_line) - 1] = '';
            //ls -a -l -i
            //2. 字符串(命令行数据分析)
            int index = 0;
            command_parse[index] = strtok(command_line, " ");
            while(1){
                index++;
                command_parse[index] = strtok(NULL, " ");
                if(command_parse[index] == NULL){
                    break;
                }
            }
            //3. 判断命令
            //a. 内置命令
            //b. 第三方命令
            if(strcmp(command_parse[0], "cd") == 0 && chdir(command_parse[1]) == 0){
                continue;
            }
            //4. 执行非内置命令
            if(fork() == 0){
                //子进程
                execvp(command_parse[0], command_parse);
                exit(1);
            }
            int status = 0;
            pid_t ret = waitpid(-1, &status, 0);
            if(ret > 0 && WIFEXITED(status)){
                printf("Exit Code: %dn", WEXITSTATUS(status));
            }
        }
        
    }
    return 0;
}

脚本宝典总结

以上是脚本宝典为你收集整理的Linux-进程控制全部内容,希望文章能够帮你解决Linux-进程控制所遇到的问题。

如果觉得脚本宝典网站内容还不错,欢迎将脚本宝典推荐好友。

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
如您有任何意见或建议可联系处理。小编QQ:384754419,请注明来意。