Linux进程控制(进程的创建退出,等待与进程替换)

Linux进程控制详解

1. 进程创建

1.1 fork函数

        1. 在linux中fork函数时非常重要的函数,它从已存在进程中创建一个新进程。新进程为子进程,而原进程为父进程。

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

在这里插入图片描述

        当一个进程调用fork之后,就有两个二进制代码相同的进程。而且它们都运行到相同的地方。但每个进程都将可以开始它们自己的旅程。

        2.fork返回值

	子进程返回0,创建失败返回-1;
	父进程返回的是子进程的pid,为了让父进程方便对子进程进行标识,进而进行管理。

2. 进程的退出

2.1 引言

        我们写代码时,main函数结束时的return 0,是干什么的?

2.2 进程退出码

        进程退出时,会return一个值给父进程,这个值就是该进程的退出码。退出码的目的是告诉父进程,我的进程运行结果。而在Linux操作系统中,进程结束会将退出码传给bash,bash会将进程运行结果告诉用户。

2.3 查看退出码
	echo	$?\\echo是内建命令,查看bash内部环境变量的内容;?是bash获取的最近一个子进程退出码。
2.4 操作系统提供的退出码
#include <stdio.h>      
#include <sys/types.h>      
#include <unistd.h>      
#include <string.h>      
      
int main ()      
{      
  for(int errcode=0;errcode<=225;errcode++)      
  {      
      printf("%d:%s\n",errcode,strerror(errcode));                                                       
  }                                                                
  return 0;      
             
}       

strerror:
在这里插入图片描述
该函数可以将错误码,转化成错误含义。
在这里插入图片描述

	退出码,有他们的退出解释。
	当退出码等于0,说明进程成功运行结束。当退出码不等于0,说明进程退出错误。
2.5 自定义环境退出码
 #include <stdio.h>           
  #include <sys/types.h>       
  #include <unistd.h>          
  #include <string.h>          
                               
  enum                         
  {                            
    Success=0,                 
    Div_zero,                  
    Mod_zero,                  
                               
  };//用枚举的方式定义退出码。                           
  int exit_code=Success;       
  int Div(int a,int b)         
  {                            
    if(b==0)                   
    {                          
      exit_code=Div_zero;      
      return -1;               
    }                          
    return a/b;                
  }                            
  const char *exit_reason(int code)//模拟strerror函数,将退出错误打印出去。    
  {                                                                           
    switch(code)
      {
        case 0:
          return  "Success";
         case 1:
          return "Div_zero";
        case 2:
          return("Mod_zero";
                                                                                                       
      }                                                                                             
}                                                                           
                                                                              
  int main ()                                                                 
  {                                                                           
    Div(10,0);                                                                
                                                                              
    printf("%s",exit_reason(exit_code)); //进程结束,打印进程结束的退出解锁。                                                                
    return exit_code;                                        
                                                             
  }    

运行结果:
在这里插入图片描述

2.6 进程的三种退出
  1. 代码跑完,结果正确,退出码等于0。
  2. 代码跑完,结果错误,退出码不等于0。
  3. 代码运行时,出现了异常,提前退出了。一但出现了异常,退出码就没有意义了。kill -11 pid 该指令给进程发信号,使进程判断出野指针,使进程退出。本质是因为进程收到os发给进程的退出信号
2.7 判断进程退出原因
	进程退出会有一个退出信号和退出码,退出信号不为0,则是代码运行异常;退出信号为0,则判断退出码。
2.8 进程如何终止
  1. return 数字:该退出进程方式只能在main函数中生效;函数中的return是返回函数值,不能结束进程。
  2. exit(数字):等同于return 数字,只不过该函数,可以在别的函数中调用,退出进程。
#include <unistd.h>
#include <stdlib.h>
 void exit(int status);
  1. _exit(数字):作用和exit一样。

exit和_exit区别:

在这里插入图片描述
exit会在执行完清理函数,刷新缓存,关闭流后,调用_exit;而_exit不会执行刷新缓冲,关闭流等。

3.进程等待

3.1 进程等待必要性
  1. 之前讲过,子进程退出,父进程如果不管不顾,就可能造成‘僵尸进程’的问题,进而造成内存泄漏。
  2. 另外,进程一旦变成僵尸状态,那就刀枪不入,“杀人不眨眼”的kill -9 也无能为力,因为谁也没有办法杀死一个已经死去的进程。
  3. 最后,父进程派给子进程的任务完成的如何,我们需要知道。如,子进程运行完成,结果对还是不对,或者是否正常退出。
  4. 父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息
3.2 wait方法
  1. wait函数等待父进程中任意一个子进程退出,获取子进程退出信息,知道子进程退出原因,解决子进程的僵尸问题,回收资源。
    在这里插入图片描述
    成功返回被等待进程pid,失败返回-1
  2. 运行wait观察现象
#include <stdio.h>    
#include <sys/types.h>    
#include <unistd.h>    
#include <string.h>    
 #include <sys/wait.h>    
#include <stdlib.h>    
    
void childrun()    
{    
  int cn=0;    
  for(cn=0;cn<5;cn++)    
  {    
    printf("I am child process, pid: %d, ppid:%d, cnt: %d\n", getpid(), getppid(), cn);    
    sleep(1);    
  }    
    
}    
    
int main ()    
{    
  pid_t id=fork();    
  if(id==0)    
  {    
    childrun(); //运行子进程   
    printf("child quit ...\n");    
    exit(1);//子进程退出    
  }    
    
    sleep(8);    //观察僵尸进程
  wait(NULL);    
  sleep(3);                  //观察wait后,子进程信息                                                                                                                                                     
  return 0;    
    
}    

在这里插入图片描述
看上面进程信息,我们发现子进程先运行,结束后成为僵尸进程,再wait后,僵尸进程消失,最后父进程运行结束。

3.3 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:
status是一个输出型型参数,传一个整型地址,它会改变地址指向内存的内容,即传回子进程退出码和退出信号的信息。
options:
WNOHANG: 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进
程的ID。

 #include <stdio.h>
  2 #include <sys/types.h>
  3 #include <unistd.h>
  4 #include <string.h>
  5  #include <sys/wait.h>
  6 #include <stdlib.h>
  7 
  8 void childrun()
  9 {
 10   int cn=0;
 11   for(cn=0;cn<5;cn++)
 12   {
 13     printf("I am child process, pid: %d, ppid:%d, cnt: %d\n", getpid(), getppid(), cn);
 14     sleep(1);
 15   }
 16 
 17 }
 18 
 19 int main ()
 20 {
 21   pid_t id=fork();
 22   if(id==0)
 23   {
 24     childrun();
 25     printf("child quit ...\n");
 26     exit(1);
 27   }
 28      sleep(8);
 29     int status=0;//给waitpid传入一个输出参数,观察wtatus的值。
 30     pid_t rid=waitpid(id,&status,0);
 31   if(rid>0)
 32   {
 33     printf("wait success,pid=%d,status=%d\n",rid,status);
 34   }   
 34   else{
 35     printf("wait fail\n");
 36   }
 37   sleep(3);
 38   return 0;
 39 
 40 }  

在这里插入图片描述
进程过程和wait一样。
在这里插入图片描述
前面我们讲了status是子进程的退出码和退出信号,那么一个值是如何保存两个信息的呢?
在这里插入图片描述

  1. wait和waitpid,都有一个status参数,该参数是一个输出型参数,由操作系统填充。
  2. 如果传递NULL,表示不关心子进程的退出状态信息。
  3. 否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。
  4. status不能简单的当作整形来看待,可以当作位图来看待,具体细节如下图(只研究status低16比特位):
    status指向的是一个整型空间,有32个比特位,但是我们只用低16个比特位,0到6的7个比特位存储的是终止信号,而7的一个比特位存储的一个叫core dump标志,8到15的8个比特位存储的是退出信号。
3.4 WIFEXITED和WEXITSTATUS

WIFEXITED和WEXITSTATUS为c语言库里面定义的两个宏。
WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)

3.5 status值怎么转换成退出码和终止信号
  1. 进程正常退出
#include <stdio.h>    
#include <sys/types.h>    
#include <unistd.h>    
#include <string.h>    
 #include <sys/wait.h>    
#include <stdlib.h>    
    
void childrun()    
{    
  int cn=0;    
  for(cn=0;cn<5;cn++)    
  {    
    printf("I am child process, pid: %d, ppid:%d, cnt: %d\n", getpid(), getppid(), cn);    
    sleep(1);    
  }    
    
}    
    
int main ()    
{    
  pid_t id=fork();    
  if(id==0)    
  {    
    childrun();    
    printf("child quit ...\n");    
    exit(1);    
  }    
    sleep(8);    
    int status=0;    
    pid_t rid=waitpid(id,&status,0);    
    
  if(rid>0)    
  {    
    printf("wait success,pid=%d,status=%d,quite code=%d,quite signal=%d\n",rid,status,(status>>8)&0xff,status&0x7f);                                                              
  }//这里处理得到两个退出信息    
  else{    
    printf("wait fail,rid=%d\n",rid);    
  }    
  sleep(3);    
    
  return 0;    
    
}    

在这里插入图片描述
2. 进程被终止
我们让子进程访问并修改空指针:

#include <stdio.h>    
#include <sys/types.h>    
#include <unistd.h>    
#include <string.h>    
#include <sys/wait.h>    
#include <stdlib.h>    
    
void childrun()    
{    
  int cn=0;    
  for(cn=0;cn<5;cn++)    
  {    
    printf("I am child process, pid: %d, ppid:%d, cnt: %d\n", getpid(), getppid(), cn);    
    sleep(1);    
  }    
    
}    
    
int main ()    
{    
  pid_t id=fork();    
  int *p=NULL;    
  if(id==0)    
  {    
    childrun();    
    *p=20//这里使用空指针;                                                                                                                                                                        
    printf("child quit ...\n");    
    exit(1);    
  }    
    sleep(8);    
    int status=0;    
    pid_t rid=waitpid(id,&status,0);    
    
  if(rid>0)    
  {    
    printf("wait success,pid=%d,status=%d,quite code=%d,quite signal=%d\n",rid,status,(status>>8)&0xff,status&0x7f);    
  }    
  else{    
    printf("wait fail,rid=%d\n",rid);    
  }    
  sleep(3);    
    
  return 0;    
    
}    

在这里插入图片描述
终止信号为11,此时退出码无意义。
在这里插入图片描述
终止信号为11)SIGSEGV为访问无效空间,的终止信号。

3.6 等待阻塞
  1. 如果子进程已经退出,调用wait/waitpid时,wait/waitpid会立即返回,并且释放资源,获得子进程退出信息。
  2. 如果在任意时刻调用wait/waitpid,子进程存在且正常运行,则进程阻塞
  3. 如果不存在该子进程,则立即出错返回。
#include <stdio.h>    
#include <sys/types.h>    
#include <unistd.h>    
#include <string.h>    
 #include <sys/wait.h>    
#include <stdlib.h>    
    
void childrun()    
{    
  int cn=0;    
  for(cn=0;cn<5;cn++)    
  {    
    printf("I am child process, pid: %d, ppid:%d, cnt: %d\n", getpid(), getppid(), cn);    
    sleep(1);    
  }    
    
}    
    
int main ()    
{    
  pid_t id=fork();    
  if(id==0)    
  {    
    childrun();    
    printf("child quit ...\n");    
    exit(1);    
  }    
    sleep(8);    
    int status=0;    
    pid_t rid=waitpid(id+1,&status,0); //这里我们接受一个错误的进程id  
  if(rid>0)    
  {    
    printf("wait success,pid=%d,status=%d\n",rid,status);    
  }    
  else{    
    printf("wait fail,rid=%d\n",rid);                                                                                                                                             
  }    
  sleep(3);    
    
  return 0;    
    
}    

在这里插入图片描述
发现wait返回了-1,说明等待失败

在这里插入图片描述
僵尸进程没有消失,资源没有被回收。

3.7 非阻塞等待

waitpid等待子进程时,返回值大于0,则等待成功;小于0,等待失败,可能是没有该子进程;等于0,有该子进程,但是该子进程还在运行,需要再次重复等待。

	我们需要使waitipd的options为WNOHANG,使waitpid默认非阻塞等待。父进程不是一直阻塞等待,而是先去执行其他代码,过一会重复等
待子进程的过程,就叫非阻塞等待。

非阻塞等待实现:
        非阻塞等待一般都是使用“非阻塞等待 + 循环 = 非阻塞轮询”的方法实现。

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

void childrun()
{
  int cn=0;
  for(cn=0;cn<5;cn++)
  {
    printf("I am child process, pid: %d, ppid:%d, cnt: %d\n", getpid(), getppid(), cn);
    sleep(1);
  }

}

int main ()
{
    pid_t id=fork();//创建子进程

      if(id==0)//子进程运行
       {
         childrun();

         printf("child quit ...\n");
         exit(123);
       }
      else//父进程运行
      {
        while(1)
        {
         int status=0;
         pid_t rid=waitpid(id,&status,WNOHANG);//非阻塞等待

       if(rid>0)//非阻塞等待结束
       {
         if(WIFEXITED(status))
         {
         printf("wait success,pid=%d,status=%d,quite code=%d\n",rid,status,WEXITSTATUS(status));
        }
         else
         {
           printf("exit unnormal!,WEXITSTATUS=%d\n",WEXITSTATUS(status));
         }
         break;
       }
       else if(rid==0)//继续等待
       {
        printf("wait child\n");//父进程可以在这里执行别的任务了。
       }
       else{//等待失败
         printf("wait fail,rid=%d\n",rid);
        break;
       }
      }
      }
       sleep(3);

  return 0;                                                                                     
}

在这里插入图片描述

4. 进程程序替换

4.1 什么是进程替换

在这里插入图片描述
进程替换指的是将进程的数据和代码替换成,磁盘中要执行的代码和数据,并不创建新的进程

4.2 用子进程替换

为了不影响父进程的代码,我们可以用fork创建子进程,替换程序。
在这里插入图片描述
子进程创建,还没有修改时,和父进程的虚拟地址相同,虚拟地址指向的,物理地址相同。但子进程程序替换时,为了不影响父进程,子进程页表指向的物理内存,会程序开一块新的地址,存放代码和数据

4.3 进程替换函数
 	   #include <unistd.h>
       extern char **environ;//操作系统的环境变量

	   int execl(const char *path, const char *arg, ...);
       int execlp(const char *file, const char *arg, ...);
       int execle(const char *path, const char *arg,..., char * const envp[]);

       int execv(const char *path, char *const argv[]);
       int execvp(const char *file, char *const argv[]);
       int execvpe(const char *file, char *const argv[],char *const envp[]);
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~       
       l(list) : 表示参数采用列表
	   v(vector) : 参数用数组
       p(path) : 有p自动搜索环境变量PATH
       e(env) : 表示自己维护环境变量

函数解释 :
这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回。
如果调用出错则返回-1。
所以exec函数只有出错的返回值而没有成功的返回值。

  1. execv和execl

int execl(const char *path, const char *arg, …):

	execlp("/usr/bin/ls","ls","-a","-l",NULL);//第一个是执行文件的绝对路径,第二个是执行文件名,
和所带的指令,以NULL结尾。

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

		char *const argv[]=    
        {    
        	"ls",    
        	"-l",                       
        	"-a",    
          	NULL//数组以NULL结尾。     
        };    
        execv("/usr/bin/ls",argv);//第一个是执行文件的绝对路径,第二个是执行文件名,和所带的指令所
 存放的数组。   
  1. execvp和execlp

int execlp(const char *path, const char *arg, …):

	execlp("ls","ls","-a","-l",NULL);//第一个是执行文件的文件名,第二个是执行文件名,
和所带的指令,以NULL结尾。

int execvp(const char *path, char *const argv[]):

		char *const argv[]=    
        {    
        	"ls",    
        	"-l",                       
        	"-a",    
          	NULL//数组以NULL结尾。     
        };    
        execv("ls",argv);//第一个是执行文件的文件名,第二个是执行文件名,和所带的指令所
 存放的数组。   

这两个函数可以不带绝对路径,只带文件名,但是必须依靠环境变量PATH中有的路径来查找。

  1. execvpe和execle

int execvpe(const char *file, char *const argv[],char *const envp[]):
我们要让子进程进程替换一个需要执行的cpp程序,可以先创建一个代码,用makefile自动编译:

.PHONY:all    
all:process test//定义一个为伪目标。    
    
test:test.cpp                                                                                                                                                                     
  @g++ -o $@ $^                  
process:myprocess.c              
  @gcc -o $@ $^                  
  @echo "success..."             
.PHONY:clean               
clean:                     
  @rm -f process           
  @echo "clean Process"    

替换的程序test.cpp:

 #include <iostream>    
  using namespace std;    
  int main(int argve,char *argv[],char *env[])//命令行参数个数,命令行参数,环境变量    
  {    
    int i=0;    
      for(i;i<argve;i++)    
      {    
          cout<<argv[i]<<endl;    
      }    
      for(i=0;env[i];i++)    
        cout<<env[i]<<endl;          
      return 0;   
  }    

父进程myprocess.c:

  #include <stdio.h>    
  #include <sys/types.h>    
  #include <unistd.h>    
  #include <string.h>    
   #include <sys/wait.h>    
  #include <stdlib.h>    
      
  int main()    
  {    
      pid_t id=fork();    
    char *const argv[]=    
      {    
      "ls",    
      "-l",    
      "-a",    
        NULL    
      };    
        if(id==0)    
         {    
           printf("child pid:%d \n",getpid());    
        char *const argv[]=    
        {    
        "test",    
        "-a",    
        "-b",    
          NULL    
        };    
          extern char ** environ;//声明系统中自带的环境变量,否则编译器可能找不到。    
           execvpe("./test",argv,environ); //这里我们文件名没在环境变量PATH中,所以要带上路径。
           exit(123);    
         }    
        else    
        {    
          sleep(1);    
           int status=0;    
         pid_t rid=waitpid(id,&status,WNOHANG);        
        }    
         sleep(3);     
    return 0;          
  }    

int execle(const char *path, const char *arg,…, char * const envp[]):
这个函数除第二个变量用法不同外,其他都一样

4.4 三种环境变量的使用
  1. char ** environ:系统自带的环境变量表。
	 extern char ** environ;
  1. char * envp[]:自定义的环境变量表。
	char * envp[]=
	{
		"HAHA=112233",
		"HEHE=334455",
		NULL//结尾
	}

3.char ** environ + int putenv(char *string):在系统环境变量基础上更改环境变量。

	#include <stdlib.h>
    int putenv(char *string);
	
	char ** environ
	putenv("HAHA=112233");
	purenv("HEHE=334455");

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

aaa最北边

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

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

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

打赏作者

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

抵扣说明:

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

余额充值