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

目录

🍔1.进程创建

🍉🍉1.1 fork概念

🍉🍉1.2 fork用法

       🥩 1.2.1 fork函数用法演示

        🍖1.2.2 fork函数用途

🍉🍉1.3 fork的特性

🍉🍉1.4fork内部原理

🥩2.进程终止

🍒🍒2.1进程终止概念

🍒🍒2.2正常终止

🍒🍒2.3异常终止

🍒🍒  2.4 exit和_exit的区别

🍞3.进程等待

🍅🍅3.1概念

🍅🍅3.2wait函数

🍅🍅3.3waitpid函数

🍅🍅3.4僵尸进程解决

🍗4.进程程序替换

🍋🍋4.1原理

🍋🍋4.2exec函数簇

🍋🍋4.3代码演示

🍔1.进程创建

在Linux中,第一个进程是由操作系统创建的,其它进程都是由第一个进程通过不断创建子进程分裂出来的。而创建子进程的函数就是fork();

🍉🍉1.1 fork概念

        在⼀个进程中,可以⽤fork()创建⼀个⼦进程,当该⼦进程创建时,它从fork()指令的下⼀条开始执行与父进程相同的代码。

🍉🍉1.2 fork用法

1.2.1 fork函数用法演示

#include<stdio.h>    
#include<unistd.h>    
int main(){    
  pid_t p=fork();    
  if(p>0){    
    printf("I am parent,ret=%d\n",p);    
    printf("I am parent,pid=%d\n",getpid());                                                                                                                            
  }    
  else if(p==0){    
    printf("I am child,ret=%d\n",p);     
    printf("I am child,pid=%d\n",getpid());    
  }else{

    printf("创建失败");
  }    
  return 0; 

         分析:fork函数返回值是pid_t类型的,实际上是一个整形。在执行fork后,创建成功会返回两个值,在父进程中返回子进程的进程ID,在子进程中,返回0。创建失败就返回-1.

        结论:由于fork函数有两个不同的返回值,所以我们可以用fork函数返回值加条件语句来操作这个函数。

1.2.2 fork函数用途

        守护进程:父进程创建出子进程后,让子进程执行相关的业务。父进程负责守护相关的子进程,若子进程崩溃,父进程并不会收到影响,同时父进程重新拉起一个子进程,让子进程继续提供服务。

🍉🍉1.3 fork的特性

        fork()函数产⽣了⼀个和当前进程完全⼀样的新进程 ,并和当前进程⼀样从fork()函数⾥返回。

        父子进程是独立运行的,相互不干扰。各自有各自的进程虚拟地址空间和页表,数据具有安全性。

        父子进程是抢占式执行,谁先谁后本质上是操作系统调度调度决定的。

        子程序拷贝了父进程的PCB,上下文信息和程序计数器是和父进程一样的,所以代码开始执行位置也和父进程是一样的。

        在刚创建进程时,子进程完全复制父进程信息,甚至公用进程虚拟地址空间。当子进程发生改变时,才以写实拷贝重新拷贝一份,父子进程就有了各自的页表以及进程虚拟地址空间。

🍉🍉1.4fork内部原理

 在使用fork函数创建子进程的原理就是子进程直接拷贝父进程的PCB(PCB在进程原理篇

        1.分配新的内存块和内核数据结构(_task_struct)给子进程

        2.将父进程内容拷贝给子进程

        3.添加子进程到系统进程列表中,添加至pcb的双向链表中。

        4.fork返回,操作系统开始调度。

🥩2.进程终止

🍒🍒2.1进程终止概念

        进程的生命周期的结束叫做进程终止,进程终止一般分为进程正常终止和进程异常终止。

🍒🍒2.2正常终止

进程正常终止在Linux中可以通过 echo $? 查看进程退出码

正常终止一般有这么几种方法

2.2.1 调用exit函数

        exit函数是C语言标准库的函数,底层封装的还是操作系统提供的_exit函数接口

        头文件:stdlib.h

        函数定义:void exit(int status);     status是退出码,调用时传进去

        作用:谁调用终止谁

2.2.2 调用_exit函数

       exit函数是系统调用函数,是操作系统内核暴露出来的供程序员终止进程的接口。

        头文件:<unistd.h>

       函数定义:void _exit(int status);         status是退出码,调用时传进去

       作用:谁调用终止谁

2.2.3 使用return语句终止进程

        这里需要注意的是只有在main函数中的return 语句才可以结束进程,子函数中的return语句作用是退出子函数。

2.2.4 代码验证

#include<stdlib.h>
#include<unistd.h> 
int main(){

  while(1){
    exit(0);        //第一种
    _exit(0);       //第二种                                                                                                                                                    
  }                                                            
  return 0;         //第三种                                           
}  

         结论:通过设置退出码,我们就可以知道程序是否正常退出。这解释了我们刚学C语言时写return 0。

🍒🍒2.3异常终止

        代码异常终止,一般是访问空指针或者内存越界,我相信大家肯定各种花活让它异常终止。

        注意:Linux下的ctrl+c也是代码异常终止

        代码验证:

 #include<stdlib.h>    
  #include<unistd.h>    
  int main(){    
      
    int a[10]={0};    
    int b= a[10000];                                                                                                                                                    
    return 0;    
  }

 结论:此处演示的是越界访问。

🍒🍒  2.4 exit和_exit的区别

exit在底层实现比_exit多了两个步骤,即执行用户自定义清理函数,冲刷缓冲区。

 exit函数的底层封装了_exit函数,所以exit = _exit + 清理函数 + 冲刷缓冲区

         由于硬件之间读取速度不匹配,即cpu读写速度远大于io操作速度,所以c标准库引入了缓冲区。假如需要完成一次写操作,_exit函数没有缓冲区,一个字节一个字节进行IO操作,非常浪费时间。exit函数引入缓冲区,将一部分内容先写入缓冲区,等等到达一定量的时候再进行一次 IO操作,极大提高了效率。

🍞3.进程等待

🍅🍅3.1概念

        我们在写进程方面的代码时,很容易出现僵尸进程(进程原理篇),即子进程先于父进程退出且父进程未回收子进程退出状态信息,僵尸进程使用kill -9指令不能终止,但是进程等待能够解决。

🍅🍅3.2wait函数

        3.2.1函数定义

        pid_t wait(int* status);        等待任意子进程

        作用:等待退出的子进程,回收退出子进程的返回信息。

        成功:返回被等待进程的PID

        失败:返回-1;

        参数:输出型参数,获取子进程退出状态,没这个需求则可以设置成NULL;

                这个参数需要程序员自己定义,定义一个整型,将地址传入参数列表,调用wait函数时,回收信息被存入这个int中。

 int status:    int一共有四个字节,实际上只使用了int的最后两个字节,一个字节存的是退出码,另一个存的是coredump标志位和退出信号。

🥗正常退出情况下,退出码被设置,coredump标志位为0,不会设置退出信号

🌮异常退出时,退出码不会被设置,coredump标志位为1,退出信号被设置。

代码演示

#include<stdio.h>    
#include<unistd.h>    
#include<sys/types.h>    
#include<wait.h>    
#include<stdlib.h>    
    
int main(){    
  pid_t pid=fork();    
    if(pid>0){    
    int status;    
      printf("I am parent,my pid=%d\n",getpid());    
      int ret=wait(&status);    
      printf("ret is %d\n",ret);    
      if(ret>0 && ((status & 0x7f)==0 )){    
          //正常退出,求退出码和coredump    
          printf("退出码是%d\n",(status>>8)&(0xff));    
          printf("coredump是%d\n",(status>>7)&(1));    
      }    
      else if(ret>0){    
      //异常退出,求退出信号和coredump    
    
          printf("退出信号是%d\n",(status & 0x7f));    
          printf("coredump是%d\n",(status>>7)&(1));    
      }    
  }    
  else if(pid==0){    
        printf("I am child,my pid=%d\n",getpid());    
        //exit(101);    //正常退出    
           int* a=NULL;       //异常退出    
          *a=20;    
       }    
  else{                                                                                                                                                                 
    return -1;    
  }    
} 

🍚正常退出情况下的status

        分析:此处使用exit函数正常退出,设置退出码为101

🍤异常退出情况下的status

        分析:次数解引用空指针,程序异常退出,未设置退出码,设置了退出信号11,core dump为0,是因为我们未设置打开coredump文件。 

🍅🍅3.3waitpid函数

  3.2.1函数定义

        pid_t waitpid(pid_t pid,int* status,int options);        

        作用:等待某一个子进程,回收退出子进程的返回信息。

返回值:

         当正常返回的时候,waitpid返回收集到的子进程的进程ID;

        如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;

        如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;

        参数:pid为等待进程的进程号

                   status为输出型参数,和wait函数的参数作用一样,都是获取子进程退出状态,没这个需求则可以设置成NULL;

                    options有个属性为WOHANG,它可以设置为非阻塞调用,即在调用waitpid函数时,若子进程未退出,则主函数继续向下执行。

对比:

        waitpid函数比wait函数参数更加丰富,这也就意味着waitpid函数的灵活度更高。waitpid函数的第一个参数pid设置为-1时,他的作用和wait函数作用一样,都是等待任意一个退出的进程。

🍅🍅3.4僵尸进程解决

         在父进程中调用wait函数,可以阻塞主进程,等待回收子进程信息,也可以调用waitpid函数回收信息。

#include<stdio.h>    
#include<unistd.h>    
#include<wait.h>    
int main(){    
  pid_t pid=fork();    
  if(pid>0){    
      sleep(20);    
      wait(NULL);                                                                                                                                                       
      while(1){    
      printf("I am parent,my pid=%d\n",getpid());    
      }    
    
  }    
  else if(pid==0){    
        printf("I am child,my pid=%d\n",getpid());    
  }else{    
    return -1;    
  }    
}  

结论:wait函数能够等待子进程,回收子进程消息,避免成为僵尸进程 

🍗4.进程程序替换

🍋🍋4.1原理

        父进程在创建出子程序后,子进程拷贝出一份父进程的PCB,若想让子进程执行其它的任务,就得替换掉子进程中的数据段和代码段。那么就让子进程调用程序替换的接口,从而让子进程执行不一样的代码。而这个接口,就是exec函数簇。

        本质上进程程序替换是更新子进程的堆栈信息,替换掉数据段和代码段。

🍋🍋4.2exec函数簇

execl

        int execl(const  char* path,const char*arg,...);   //可变参数列表

        🌯参数:

                path:带路径的可执行程序

                arg:传递给可执行程序的命令行参数,第一个参数是可执行程序本身,最后一个参数是NULL,中间的参数用 , 隔开

        🥩返回值:调用成功直接执行新的代码 不返回,失败则返回-1

excel("/usr/bin/ls","-l",NULL);        //栗子

execlp

     int execlp(const  char* file,const char*arg,...);   //可变参数列表

        🌯参数:

                file:可执行程序,可带路径,也可不带路径(需要在环境变量中)

                arg:传递给可执行程序的命令行参数,第一个参数是可执行程序本身,最后一个参数是NULL,中间的参数用 , 隔开

        🥩返回值:调用成功直接执行新的代码 不返回,失败则返回-1

excelp("ls","-l",NULL);        //栗子

execle

     int execle(const  char* path,const char*arg,...,char* const envp[ ]);   //可变参数列表

        🌯参数:

                path:带路径的可执行程序

                arg:传递给可执行程序的命令行参数,第一个参数是可执行程序本身,最后一个参数是NULL,中间的参数用 , 隔开

                envp:程序员传递环境变量给函数

        🥩返回值:调用成功直接执行新的代码 不返回,失败则返回-1

excel("/usr/bin/ls","-l",NULL,envp[]);        //栗子

execv

     int execv(const  char* path,const char*argv[ ]);   //可变参数列表

        🌯参数:

                path:带路径的可执行程序

                argv[]:传递给可执行程序的命令行参数,以指针数组的方式传递。第一个参数是可执行程序本身,最后一个参数是NULL,中间的参数用 , 隔开

        🥩返回值:调用成功直接执行新的代码 不返回,失败则返回-1

excev("/usr/bin/ls",argv[]);        //栗子

execvp

     int execvp(const  char* file,const char*arg[ ]);   //可变参数列表

        🌯参数:

                path:可执行程序,可带路径,也可不带路径(需要在环境变量中)

                argv[]:传递给可执行程序的命令行参数,以指针数组的方式传递。第一个参数是可执行程序本身,最后一个参数是NULL,中间的参数用 , 隔开

        🥩返回值:调用成功直接执行新的代码 不返回,失败则返回-1

excvp("ls",argv[]);        //栗子

execve

     int exeve(const  char* path,const char*argv[ ],char *const envp[],);   //可变参数列表

        🌯参数:

                path:带路径的可执行程序

                arg:传递给可执行程序的命令行参数,第一个参数是可执行程序本身,最后一个参数是NULL,中间的参数用 , 隔开

                envp:需要程序员自己提供环境变量

        🥩返回值:调用成功直接执行新的代码 不返回,失败则返回-1

excel("/usr/bin/ls",argv[],envp[]);        //栗子

表格总结 

🍋🍋4.3代码演示

#include<unistd.h>    
int main(){      
    execlp("ls","ls","-l","-r",NULL);          
    printf("会不会打印我?")                                                                                                                         
  return 0;    
}   

结论:我们将主程序替换成ls程序,且未执行exclp后边的代码。

  • 11
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

一颗二叉树_

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

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

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

打赏作者

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

抵扣说明:

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

余额充值