Linux 进程控制

目录

一、进程终止

1、进程退出场景

2、进程常见退出方法

[a. 正常终止(可以通过 echo $? 查看进程退出码):](#a. 正常终止(可以通过 echo $? 查看进程退出码):)

[b. 异常退出:](#b. 异常退出:)

3、分类

[a. main函数返回值(return 退出)](#a. main函数返回值(return 退出))

[I. 退出码:](#I. 退出码:)

[II. 错误码:](#II. 错误码:)

[III. 异常信号:用 kill -l 查看](#III. 异常信号:用 kill -l 查看)

[b. _exit 函数&&exit 函数](#b. _exit 函数&&exit 函数)

二、进程等待

1、为什么要进行进程等待?

2、进程等待的方法

[a. wait && waitpid](#a. wait && waitpid)

[b. 获取子进程status](#b. 获取子进程status)

[​编辑 3、阻塞等待&&非阻塞等待](#编辑 3、阻塞等待&&非阻塞等待)

[a. 阻塞等待](#a. 阻塞等待)

[b. 非阻塞等待](#b. 非阻塞等待)

三、进程程序替换

1、替换原理

2、替换函数

execlp:

execv:

execvp:

execle,execve,execvpe:

总结:


一、进程终止

1、进程退出场景

1、代码运行完毕,结果正确2、代码运行完毕,结果错误3、代码异常终止

2、进程常见退出方法

a. 正常终止(可以通过 echo $? 查看进程退出码):

I. 从main返回 II. 调用 exit III. _exit

b. 异常退出:

ctrl + c,信号终止

3、分类

a. main函数返回值(return 退出)

main函数返回值,叫做进程的退出码,一般0,表示进程执行成功,非0表示失败。

I. 退出码:

进程的退出码(exit_code),也称为退出状态或返回值,是一个整数,用于表示进程执行结束时的状态,通常由进程的主函数(C语言中是main函数)返回,或者是在进程遇到异常或者错误是由操作系统进行设置。可以使用****echo ?****命令查看最近一次进程退出的退出码信息。? 是一个特殊的shell变量,保存了上一个命令的退出码

II. 错误码:

进程的错误码( errno )用于表示进程在执行系统调用或库函数时遇到的错误情况,每个错误码都对应一个特定的错误情况,是的程序能够识别并处理这些错误。而进程的退出码是一个整数,用于表示进程执行结束时的状态。

错误码errno通常是一组预定义的数值,每个值对应一个特定的错误情况。这些定义通常包含在系统的头文件<errno.h>中。当系统调用或库函数失败时,它们会设置全局变量errno为相应的错误码。

如果errno的值为0,则表示系统调用成功,如果errno的值不为0,则表示系统调用失败,并且其值对应着特定的错误代码。常见处理errno的方法,如使用perror函数或者strerror函数**,perror函数可以将errno的值映射为对应错误信息,并将其打印到标准错误流(stderr),而strerror函数则可以将errno的值转化为对应的错误字符串。**

III. 异常信号:用 kill -l 查看

所以,任何进程最终的执行情况,我们都可以使用两个数字表明具体的执行情况

b. _exit 函数&&exit 函数

cpp 复制代码
 #include <stdio.h>
 #include <errno.h>
 #include <string.h>
 #include <unistd.h>
 #include <stdlib.h>
 
 int main()
 {
   while(1)
   {
     printf("I am a process: %d\n", getpid());
     sleep(1);
 
     exit(3); // exit 终止进程,status:进程退出时候,退出码                                                                                                            
   }                                         
 }  
cpp 复制代码
 #include <stdio.h>
 #include <errno.h>
 #include <string.h>
 #include <unistd.h>
 #include <stdlib.h> 
 
 void Print()
 {
   printf("hello\n");
   exit(5);
 }
 

 int main()
 {
   while(1)
   {
     printf("I am a process: %d\n", getpid());
     sleep(1);
 
     Print();                                                                                                                                                           
     //exit(3); // exit 终止进程,status:进程退出时候,退出码
   }
 }

这就告诉我们,exit就是用来终止进程的,exit(退出码)。

那么这个_exit又是什么呢?把上面的代码中的 exit 改成 _exit 验证一下,结果和exit是一样。难道说exit 和 _exit 是一样的吗?它们到底有什么区别?

所以,可以得出,exit 会支持刷新缓冲区,而_exit不支持。

二、进程等待

1、为什么要进行进程等待?

2、进程等待的方法

a. wait && waitpid

对于waitpid中的参数:

status:
WIFEXITED(status): 若为正常终止子进程返回的状态,则为真(用于查看进程是否退出)。
WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码(用于查看你进程的退出码)。

options:

WNOHANG: 若pid指定的子进程还没有结束,则waitpid()函数返回0,不予等待。若正常结束,则返回该子进程的ID。

对于waitpid中的返回值:

正常返回的时候waitpid返回收集到的子进程的进程ID;
如果设置了选项WNOHANG,而调用中的waitpid发现没有已退出的子进程可收集,则返回0;
如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;

如果子进程已经退出,调用wait/waitpid时,wait/waitpid会立即返回,并且释放资源,获得子进程退出信息。

如果在任意时刻调用wait/waitpid,子进程存在且正常运行,则进程可能阻塞。

如果不存在该子进程,则立即出错返回。

cpp 复制代码
 #include <stdio.h>
 #include <errno.h>
 #include <string.h>
 #include <unistd.h>
 #include <stdlib.h>
 #include <sys/types.h>
 #include <sys/wait.h>  
                                            
 int main()  
 {                                          
   pid_t id = fork();                       
   if(id == 0)  
   {  
     //child  
     int cnt = 5;  
     while(cnt)  
     {  
       printf("Child is running, pid: %d, ppid: %d\n",getpid(),getppid());  
       sleep(1);  
       cnt--;  
     }  
     exit(1);  
   }  
   int status = 0;  
   pid_t rid = waitpid(id, &status, 0);//阻塞等待  
   if(rid > 0)  
   {  
     printf("wait sucess, rid: %d, status: %d\n", rid, status);                                                                                                         
   }                                                                                                                               
   return 0;                                                                                                                       
 }  

等待成功,但是我们发现这个status怎么是256呢?这256从哪来的?

b. 获取子进程status

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

可以用下图表示(只研究status低16比特位):

那么回到刚刚的问题,上面那个代码的256是怎么算的呢?

**我们上面说过,**任何进程最终执行情况,我们都可以使用两个数字表明具体执行情况。

3、阻塞等待&&非阻塞等待

阻塞和非阻塞指的是调用者(程序)在等待返回结果(或输入)时的状态。阻塞时,在调用返回结果前,当前线程会被挂起,并在得到结果之后返回。非阻塞时,如果不能立刻得到结果,则该调用者不会阻塞当前进程。

a. 阻塞等待

cpp 复制代码
#include <stdio.h>    
#include <errno.h>    
#include <string.h>    
#include <unistd.h>    
#include <stdlib.h>    
#include <sys/types.h>    
#include <sys/wait.h>    
    
    
int main()    
{    
  //阻塞等待    
  pid_t id = fork();    
  if(id<0) return 1;    
  else if(id == 0)    
  {    
    printf("I am child, pid: %d, ppid: %d\n",getpid(), getppid());    
    sleep(3);    
    exit(100);    
  }    
  else{    
    int status = 0;    
    pid_t rid = waitpid(id, &status, 0);    
    if(rid > 0)    
    {    
      printf("wait sucess, rid: %d, status: %d, exitnum: %d, signo1: %d, signo2:: %d, isexit: %d\n",    
              rid, status, status&0x7f, (status>>8)&0xff, WEXITSTATUS(status), WIFEXITED(status));    
    }                                                                                                                                                                      
  }    
  return 0;    
}

看这串代码中,status&0x7f,(status>>8)&0xff,这两个是什么啊?

b. 非阻塞等待

cpp 复制代码
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>

int main()
{
  pid_t id= fork();
  if(id < 0) return 1;
  else if( id == 0)
  {
    printf("child is running..., pid is: %d\n",getpid());
    sleep(5);
    exit(10);
  }
  else
  {
    int status = 0;
    pid_t rid = 0;
    do
    {
      rid = waitpid(-1, &status, WNOHANG);//非阻塞等待
      if(rid == 0)
      {
        printf("child is running...\n");
      }
      sleep(1);                                                                                                                                                            
    }while(rid == 0);

    if(WIFEXITED(status) && rid == id)
    {
      printf("wait child 5s success, child return code is: %d\n",WEXITSTATUS(status));
    }
    else{
      printf("wait child failed\n");
      return 1;
    }
  }

三、进程程序替换

1、替换原理

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

下面我们来用一个最简单的exec的接口来表示一下替换原理:

cpp 复制代码
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>    
    
    
int main()    
{    
  printf("I am a process, pid: %d\n",getpid());    
  printf("exec begin...\n");    
    
  execl("/usr/bin/ls", "ls", "-a", "-l", NULL);//注意这里是NULL,不是"NULL"    
    
  printf("exec end ...\n");                                                                                                                                                
  return 0;    
}

这里代码最后的printf并没有被执行?为什么?

上面替换原理说了:当进程调用一种exec函数时,该进程的用户空间和数据被新程序替换,从新程序的启动例程开始执行。

2、替换函数

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

那这么多我们该如何记住它们呢?---看命名

带p:PATH,你不用告诉系统,程序在哪里,只要告诉我名字是什么,系统替换的时候,会自动去PATH环境变量中查找。

execlp:
cpp 复制代码
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>    
                                                                                                                                                                           

int main()
{         
  printf("I am a process, pid: %d\n",getpid());
                                               
  pid_t id=fork();
  if(id == 0)     
  {          
    sleep(3);
    printf("exec begin...\n");
                              
   // execl("/usr/bin/ls", "ls", "-a", "-l", NULL);//注意这里是NULL,不是"NULL"
    execlp("ls", "ls","-a","-l",NULL);                                        
    printf("exec end...\n");          
    exit(1);                
  }         
   
  pid_t rid= waitpid(id,NULL,0);
  if(rid>0)                     
  {        
    printf("wait sucess\n");
  }                         
       
  exit(1);
} 

带v**:vector 数组** 带l**:list,列表,参数列表**

execv:
cpp 复制代码
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>        
                             
int main()                   
{                            
  printf("I am a process, pid: %d\n",getpid());      
                             
  pid_t id=fork();           
  if(id == 0)                
  {                          
    char *const argv[] = {      
      (char*)"ls",           
      (char*)"-a",           
      (char*)"-l",
       NULL            
    };                       
    sleep(3);                
    printf("exec begin...\n");      
                             
    execv("/usr/bin/ls", argv);                                                                                                                                            
    printf("exec end...\n");                                                                                             
    exit(1);                                                                                                             
  } 
  
  pid_t rid= waitpid(id,NULL,0);                                                                                         
  if(rid>0)                                                                                                              
  {                                                                                                                      
    printf("wait sucess\n");
  }

  exit(1);
}
execvp:
cpp 复制代码
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>        
                             
int main()                   
{                            
  printf("I am a process, pid: %d\n",getpid());      
                             
  pid_t id=fork();           
  if(id == 0)                
  {                          
    char *const argv[] = {      
      (char*)"ls",           
      (char*)"-a",           
      (char*)"-l",
       NULL            
    };                       
    sleep(3);                
    printf("exec begin...\n");                                 
    execvp("ls", argv);                                                                                                                                           
    printf("exec end...\n");                                                                                             
    exit(1);                                                                                                             
  } 
  
  pid_t rid= waitpid(id,NULL,0);                                                                                         
  if(rid>0)                                                                                                              
  {                                                                                                                      
    printf("wait sucess\n");
  }

  exit(1);
}

当然对于所有的替换函数,我们都不仅可以和上面一样执行系统的指令,也可以执行自己的程序。

cpp 复制代码
//test.c

#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>        
                             
int main()                   
{                            
  printf("I am a process, pid: %d\n",getpid());      
                             
  pid_t id=fork();           
  if(id == 0)                
  {                          
    char *const argv[] = {      
      (char*)"ls",           
      (char*)"-a",           
      (char*)"-l",
      NULL           
    };                       
    sleep(3);                
    printf("exec begin...\n");                                 
    execl("./mytest", "mytest", "-a", "-b", NULL);//注意这里是NULL,不是"NULL"                                                                                                                                            
    printf("exec end...\n");
                                                                                            
    exit(1);                                                                                                             
  } 
  
  pid_t rid= waitpid(id,NULL,0);                                                                                         
  if(rid>0)                                                                                                              
  {                                                                                                                      
    printf("wait sucess\n");
  }

  exit(1);
}

//mytest.cc

#include <iotream>
using namespace std;

int main()
{
    cout<<"hello world"<<endl;
    cout<<"hello world"<<endl;
    cout<<"hello world"<<endl;
    return 0;
}
execle,execve,execvpe:

带e:自己维护环境变量 execle,execve,execvpe

先观察下面代码:

cpp 复制代码
//test.c
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main
{
    printf("I am a process, pid: %d\n", getpid());

    pid_t id=fork();
    if(id == 0)
    {
        sleep(1);
        execl("./mytest","mytest",NULL);
        printf("exec end...\n");
        exit(1);
    }

    pid_t rid = waitpid(id, NULL, 0);
    if(rid > 0)
    {
        printf("wait sucess\n");
    }
    exit(1);
}


//mytest.cc
#include <iostream>    
#include <unistd.h>    
using namespace std;    
    
int main()    
{    
  for(int i = 0; environ[i]; i++)                                                                  
  {    
        printf("env[%d]: %s\n", i, environ[i]);    
  }    
  cout<<"hello world"<<endl;    
    
  return 0;    
}

这里我们传递环境变量表了吗?? ---没有,子进程默认就拿到了,它是怎么做到的?

默认可以通过地址空间继承的方式,让所有子进程拿到环境变量,进程替换不会替换环境变量数据

1、如果我们想让子进程继承全部的环境变量,直接能拿到

2、如果单纯的新增我们可以使用putenv

3、那么我想设置全新的环境变量给子进程呢?

cpp 复制代码
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>

int main()    
{    
  char* const env[]={    
    (char*)"one=1111111111111",    
    (char*)"two=2222222222222", 
    NULL
  };    
    
    
    
  printf("I am a process, pid: %d\n",getpid());    
    
  pid_t id=fork();    
  if(id == 0)    
  {    
    sleep(1);    
    execle("./mytest","mytest",NULL,env);   
    printf("exec end...\n");
    exit(1);
  }
  pid_t rid= waitpid(id,NULL,0);
  if(rid>0)
  {
    printf("wait sucess\n");
  }

  exit(1);
}

这样我就设置了全新的环境变量给子进程!

总结:

事实上,只有execve是真正的系统调用,其它五个函数最终都调用 execve,都是系统调用 execve的封装,所以execve在man手册的第2节,其它函数在man手册的第3节。

相关推荐
Ahern_1 分钟前
RHEL 7.5 源码安装 mysql-5.7.17 数据库
linux·数据库·mysql
代码中の快捷键39 分钟前
MVCC了解
运维·服务器·数据库
初学者丶一起加油1 小时前
C语言基础:数组(字符数组)
linux·c语言·开发语言·数据结构·vscode·算法·ubuntu
编码浪子1 小时前
devops和ICCID简介
运维·ci/cd·docker·devops
vvw&1 小时前
如何在 Ubuntu 22.04 服务器上安装 Jenkins
linux·运维·服务器·ubuntu·ci/cd·自动化·jenkins
青灯文案11 小时前
Linux 显示系统活动进程状态命令 ps 详细介绍
linux·运维
童安格粉丝1 小时前
centos7使用haproxy+keepalived搭建负载均衡调度器--yum方式
运维·nginx·centos·负载均衡·keepalived·haproxy
aashuii1 小时前
linux上抓包RoCEv2
linux·运维·服务器