当前位置: 首页 > article >正文

【Linux】-----进程控制

目录

一、进程终止

是什么?

终止的三种情况

终止的方式 

①return 退出

②调用exit函数(库函数)

 ③调用_exit函数(系统调用)

二、进程等待 

为什么要等?

等待方式 

wait

waitpid

获取子进程的status

方式一:位操作(麻烦)

方式二:宏(常用)

非阻塞轮询访问

三、进程替换

什么是?

怎么样?

①execl

②execlp

③execle

④execv

 ⑤execvp

⑥execvpe 


一、进程终止

是什么?

终止实际上是在释放曾经代码和数据所占据的空间,同时也是在释放内核数据结构

终止的三种情况

  • 代码正常跑完,结果正确
  • 代码正常跑完,结果不正确
  • 代码出现异常,提前退出(进程崩溃)

对于前面的两种情况,可以通过退出码来判断 

  • 退出码为0,结果正确
  • 退出码非0,结果不正确

非0之外的值,一方面是为了表示错误,另外一方面是为了告知用户错误的原因。实际上错误的原因在C语言中可以通过strerror函数获取对应的错误信息。里面记录了134个错误信息,除外的都是未知错误。

查看最近一次可执行程序的退出码指令:

echo $?

以ls命令演示

正常运行的退出码

不正常的退出码

对于第三种情况,进程崩溃,因为操作系统会发现你的进程做了不该做的事情(比如野指针等),操作系统会立马杀了进程,会给用户发出退出信号。所以要看具体是什么异常只需要看退出信号即可!此时的退出码没有意义了!

上述的异常为段错误,OS会立刻杀死进程。 实际上这相当于kill -11的指令

小总结:

 衡量一个进程的退出,只需要两个关键字:退出码+退出信号

1.先确定是否异常

2.不是异常,说明代码一定能跑完,此时的看退出码就行!

终止的方式 

①return 退出

return 0 表示成功,非0表示失败,原因在退出码。

在main函数中return,代表该进程结束!

在非main函数中return,代表当前函数结束!

②调用exit函数(库函数)

  • 这是C库函数 ,在进程的任意地方调用,都表示进程的退出!需要注意在进程退出时,该函数会冲刷缓冲区的内容!
#include <unistd.h>
#include <sys/types.h>
#include <string.h>
#include <stdlib.h>
int main()
{
   printf("i am a process\n");
   sleep(1);
   exit(3);

   return 0;
}

 ③调用_exit函数(系统调用)

  •  这个函数是系统调用,但是进程退出时,并不会冲刷缓冲区,会直接释放掉所有资源。
  • 这个函数也是exit函数的底层。
  • 使用和exit类似

二、进程等待 

为什么要等?

  • 解决子进程的僵尸问题,僵尸问题不解决,会造成内存泄漏的问题
  • 其次,僵尸进程刀枪不入,kill命令无法终止,因为无法杀死一个已经死去的进程
  • 另外,父进程需要知道给子进程的任务的完成情况。即退出信息
  • 所以,父进程需要通过等待的方式,回收子进程的资源,并获取退出信息

等待方式 

  • wait

 系统级别的函数调用

参数:

这里的参数为输出型参数,能够获取子进程的退出信息(退出码+退出信号),不关心可以设置为NULL!

返回值:

  • >0,等待成功,返回等待子进程的pid
  • =-1,等待失败

函数特点:

如果子进程一直不退,父进程实际上会一直进行阻塞等待,本质就是父进程的PCB会被链入到子进程的等待队列里面,等待子进程就绪,在此期间,父进程啥也干不了,直到子进程退出才继续往下执行程序!

 示例:

让子进程跑5s后,退出

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

//子进程跑5s退出
void ChildRun()
{
   int cnt=5;
   while(cnt)
   {
    printf("Childprocess runing now,id: %d,pid: %d,cnt:%d\n",getpid(),getppid(),cnt);  
    sleep(1);                                                                          
     cnt--;                                                                             
   }                                                                                    
}  


int main()                                                                             
{                                                                                      
   printf("i am father process,id: %d,ppid: %d\n",getpid(),getppid());                  
   pid_t id=fork();                                                                     
   if(id==0)                                                                            
   {                                                                                    
       ChildRun();                                                                                                                                                
       printf("child quit.....\n");                                                       
       exit(1);                                                                           
   }
   sleep(7);                                                                            
   pid_t rid=wait(NULL);                                                                
   if(rid > 0)
   {
     printf("wait sucessful..\n");
   } 
    printf("father quit.....\n");
    sleep(2); 
    return 0;
 }

 可以看到5s后,子进程的状态变为了僵尸,而后由于父进程在等待,子进程退出后就被在等待的父进程回收了!

  • waitpid

 

返回值pid_t:

  • >0,等待成功,返回的是回收子进程的id
  • <0,等待失败
  • =0,检测到子进程,但是没有完成任务,父进程需要下一次重复等待(非阻塞等待时判断)

参数: 

①pid

  • -1,等待任一子进程退出,功能和wait一样。
  • =某子进程id,等待进程为id的进程退出。

②status

和wait一样,可以查看退出信息,不关心可以设置为NULL

③options

设置父进程的等待方式:

0:阻塞等待

!0:非阻塞等待(一般设置为宏WNOHANG)

阻塞和非阻塞的区别:

  • 父进程处于阻塞状态时,什么事情都做不了,必须等待子进程退出,回收子进程后才能去完成自己的事情,工作效率低
  • 若处于非阻塞状态,在等待子进程退出的期间,可以去完成其他的任务,工作效率高,这种方式只需父进程每隔一段时间去检测子进程的状态,退出就回收,没退出就继续自己的任务,也称为非阻塞轮询访问!

举例:

pid_t rid=waitpid(-1,NULL,0);//这一句等同于wait(NULL)  
//pid_t rid=waitpid(-1,NULL,WNOHANG);//非阻塞等待                                                           
if(rid > 0)
{
    printf("wait sucessful..\n");
} 
  printf("father quit.....\n");

从上面两种等待方式可以看出,waitpid可选择的选项比wait要多,所以实际用的比较多! 

获取子进程的status

方式一:位操作(麻烦)

上面说过wait和waitpid都有一个参数status,用于查看子进程的退出状态信息,是一个输出型参数,若不关心可以设置为NULL!若想要查看可以采用如下方式:

int status=0;
pid_t rid=waitpid(id,&status,0);
//wait(&status)
printf("father quit..status is:%d\n
child exit code:%d,child exit signal:%d\n",status,(status>>8)&0xFF,status&0x7F);

可以看到,上述的退出信息status的值为256,实际上这包含了两个部分退出码+退出信号。一个int有32个位,status只使用低16位来存储退出信息,高8位为退出码,低7位表示退出信号,还有1位是core dump位(不理)

进程正常退出:使用高8位来表示退出码,低8位全为0

异常退出:高8位不使用,低7位存储退出信号

 上述例子,子进程退出码是1,正常退出,退出信号为0,所以二进制序列为00000001 00000000,即256!

所以根据这一原理,要想明确的获取子进程的退出码和退出信号,方式一可以采用移位操作。获取退出码:(status>>8)&0xFF,退出信号:status&0x7F

方式二:宏(常用)

WIFEXITED(status):查看是否正常退出

WEXITSTATUS(status):提取正常退出进程的退出码

示例:

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

//子进程跑5s退出
void ChildRun()
{
   int cnt=5;
   while(cnt)
   {
    printf("Childprocess runing now,id: %d,pid: %d,cnt:%d\n",getpid(),getppid(),cnt);  
    sleep(1);                                                                          
     cnt--;                                                                             
   }                                                                                    
}  


int main()                                                                             
{                                                                                      
   printf("i am father process,id: %d,ppid: %d\n",getpid(),getppid());                  
   pid_t id=fork();                                                                     
   if(id==0)                                                                            
   {                                                                                    
       ChildRun();                                                                                                                                                
       printf("child quit.....\n");                                                       
       exit(1);//正常退出码为1                                                                           
   }
  sleep(7);                                                                            
  int status=0;//获取退出信息

  pid_t rid=waitpid(id,&status,0);
  if(rid > 0)
  {
    //正常退出,只关注退出码就行
    if(WIFEXITED(status))
    {
       printf("child quit sucess,child exit code: %d\n",WEXITSTATUS(status));
    }
    else
    {
        printf("child quit unnormal")
    }
  }            
   printf("father quit.....\n");
    sleep(2); 
    return 0;
 }

一句话:等待是必须的,但是获取退出信息,看情况而定 !!

非阻塞轮询访问

非阻塞轮询=非阻塞等待+循环!

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

int status=0;//获取退出信息
pid_t rid=waitpid(id,&status,WNOHANG);//非阻塞等待

示例:

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

//子进程跑5s退出
void ChildRun()
{
   int cnt=5;
   while(cnt)
   {
    printf("Childprocess runing now,id: %d,pid: %d,cnt:%d\n",getpid(),getppid(),cnt);  
    sleep(1);                                                                          
     cnt--;                                                                             
   }                                                                                    
}  


int main()                                                                             
{                                                                                      
   printf("i am father process,id: %d,ppid: %d\n",getpid(),getppid());                  
   pid_t id=fork();                                                                     
   if(id==0)                                                                            
   {                                                                                    
       ChildRun();                                                                                                                                                
       printf("child quit.....\n");                                                       
       exit(1);//正常退出码为1                                                                           
   }
while(1)
{                                                                        
  int status=0;//获取退出信息
  pid_t rid=waitpid(id,&status,WNOHANG);//非阻塞等待
  if(rid==0)
  {
    usleep(100000);
    printf("child is running now,check next time\n");
    printf("DoOtherting()\n");
  }
  else if(rid > 0)
  {
    //正常退出,只关注退出码就行
    if(WIFEXITED(status))
    {
       printf("child quit sucess,child exit code: %d\n",WEXITSTATUS(status));
    }
    else
    {
        printf("child quit unnormal")
    }
    break;
  }
}            
   printf("father quit.....\n");
   sleep(1); 
   return 0;
 }

三、进程替换

什么是?

  • 进程的替换本质就是将当前程序的代码+数据替换成新程序的代码和数据
  • 新程序的代码+数据也会被加载到内存中,通过exec*系列的替换函数加载替换
  • 替换的过程没有创建新进程,替换前后的pid都没有改变,只是在用老程序的壳子执行新程序的代码

怎么样?

  • exec*系列函数能完成进程的替换工作,头文件#include <unistd.h>
  • 有返回值,但是不用关心,只要替换成功,就不会向后继续执行;反之,程序会继续向下执行

①execl

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

path:你想替换的程序路径

*arg:参数列表,你想怎么执行程序(命令行参数怎么写就怎么写,最后以NULL结尾)

...:表示可变参数

示例:

#include <stdio.h>
#include <unistd.h>

int main()
{
  printf("process begin....\n");
  execl("/usr/bin/ls","ls","-l","-a",NULL);                                                                                                                             
  printf("process end...\n");
  return 0;
}

 上述代码表示以ls -l -a的方式执行/usr/bin/ls程序

可以看到替换成功后,程序后续代码就不执行了,因为已经替换了!

②execlp

p:查找这个程序,系统会自动在环境变量中找

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

 和上面的不同在于:此函数无需带路径,直接传文件名即可!

#include <stdio.h>
#include <unistd.h>

int main()
{
  printf("process begin....\n");
  execlp("ls","ls","-l","-a","--color",NULL);                                                                                                                             
  printf("process end...\n");
  return 0;
}

③execle

e:代表环境变量

能将环境变量导入要执行的程序中!

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

 示例:

#include <stdio.h>
#include <unistd.h>

int main()
{
  extern char**environ;//用父进程的环境变量,也可以自定义
  printf("process begin....\n");
  execle("/usr/bin/ls","ls","-l","-a","--color",NULL,environ);                                                                                                                             
  printf("process end...\n");
  return 0;
}

④execv

V:类似于数组,这个函数就是将执行程序的选项都放入一个数组中,以NULL结尾,传数组

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

示例:

#include <stdio.h>
#include <unistd.h>


int main()
{
  char* const arg[]={
     (char*)"ls",
     (char*)"-l",
     (char*)"-a",
     (char*)"--color",
     NULL
   };
 
   printf("process begin....\n");
   execv("/usr/bin/ls",arg);                                                                                                                                             
   printf("process end...\n");
   return 0;
}

⑤execvp

int execvp(const char *file, char *const argv[]);

可以不带路径

#include <stdio.h>
#include <unistd.h>


int main()
{
  char* const arg[]={
     (char*)"ls",
     (char*)"-l",
     (char*)"-a",
     (char*)"--color",
     NULL
   };
 
   printf("process begin....\n");
   execvp("ls",arg);                                                                                                                                             
   printf("process end...\n");
   return 0;
}

⑥execvpe 

int execvpe(const char *file, char *const argv[],
                   char *const envp[]);

 以下示例为用C语言替换一个C++程序

C++程序

#include <iostream>
#include <unistd.h>
using namespace std;


int main(int argc,char* argv[],char* env[])
{
   int i=0;                                                                                                                                                            
   for(;argv[i];i++)
   {
      printf("argv[%d]: %s\n",i,argv[i]);
   }
  printf("-------------------------------------\n");
  for(i=0;env[i];i++)
  {
     printf("env[%d]: %s\n",i,env[i]);
  }

   printf("-------------------------------------\n");
   cout<<"hello i am c++ pragme!"<<endl;
   cout<<"hello i am c++ pragme!"<<endl;
   cout<<"hello i am c++ pragme!"<<endl;
   cout<<"hello i am c++ pragme!"<<endl;
  return 0;
}

C程序 

#include <stdio.h>
#include <unistd.h>

int main()
{
   char* const arg[]={
       (char*)"myprame",
       NULL
    };
    //自定义环境变量
  char* const envp[]={
    (char*)"HAHA=111111",
    (char*)"HEHE=222222",
    NULL
    };

  printf("process begin....\n");
  execvpe("./cpppragme",arg,envp);//替换成自己写的C++程序                                                                                                                                      
  printf("process end...\n");                      
  return 0;                  
} 

可以看到替换后的C++程序使用的参数以及环境变量是从C语言程序传入的。这也再一次说明父进程为子进程创建两张表,一张为环境变量表,一张为参数表,最终都会想办法传给子进程!!

 



http://www.kler.cn/news/366377.html

相关文章:

  • C#的自定义Tip窗体 - 开源研究系列文章
  • 【优先算法】双指针 --(结合例题讲解解题思路)(C++)
  • Mysql主主互备配置
  • crc, md5 和 sha的区别
  • 第二代 GPT-SoVITS V2:解锁语音克隆与合成的无限可能
  • 如何使用gitlab切换分支
  • IDEA如何将一个分支的代码合并到另一个分支(当前分支)
  • PyCharm 2023 版本之后使用本地 conda 已存在环境的方法
  • Golang 怎么高效处理ACM模式输入输出
  • 实验一 嵌入式开发基础 4-6学时实践
  • 【ubuntu20联网】ubuntu20.04正常使用网络,解决校园以太网无法连接问题
  • ​​Spring6梳理17——基于XML的自动装配
  • Spring Retry框架
  • 从视频中学习的SeeDo:VLM解释视频并生成规划、代码(含通过RGB视频模仿的人形机器人OKAMI、DexMV)
  • 中国电信笔试2025年度校园招聘笔试题-(开发类)-4(AK)
  • 接口 vs 抽象类:谁是更好的工具?了解它们的区别和使用场景
  • @moohng/postcss-px2vw,如何配置响应式参数?
  • qt QVariant详解
  • Termius工具在MAC的使用出现的问题:
  • 【MySQL】:库的操作
  • JDK源码系列(一)——Object 类
  • 阿里云地图选择器
  • 开源呼叫中心系统FreeIPCC:呼叫中心的重要性
  • CSP-J2024 全网首发
  • 机器学习2
  • Spring Bean 的生命周期解析