多进程开发

2、多进程开发

2.1 进程相关命令

  1. 实施显示进程状态

    top

    可以在使用top命令加上 -d 来指定显示信息更新的时间间隔,在top执行后,可以按以下按键对显示的结果进行排序:
    M  根据内存进行排序
    P  根据CPU进行排序
    T  根据进程运行时间长短排序
    K  输入指定的PID杀死进程
    U  根据用户名来筛选进程
    
  2. 杀死进程

    Kill

    • kill -l 列出所有信号

    • kill -9 进程ID 杀死指定ID的进程

    • killall name 根据进程名杀死进程

      image-20240318223516229

2.2 父子进程

1. 创建子进程

fork()函数

返回值:

​ 父进程中: >0 返回的是子进程的ID

​ 子进程中:=0

注意:fork函数使用的是写时拷贝:一种推迟甚至避免拷贝数据的技术

内核并不复制整个进程地址空间,而且父子共享同一个地址空间。

只在写入的时候才会复制地址空间,从而拥有各自的地址空间。

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

int main(){

    //创建子进程
    pid_t pid = fork();
    // 判断父进程还是子进程
    if(pid>0){
        printf("I am parent process,pid: %d, ppid:%d\n",getpid(),getppid());
        printf("%d\n",pid);

    }else if (pid == 0){
        sleep(10);
        printf("I am son process,pid:%d\n",getpid());
    }
    return 0;
}

image-20240318231113142

2.GDB多进程调试

GDB调试的时候,默认只跟踪一个进程(父进程)。

比如:当你在if(为父进程){断点A}else if(子进程){断点B}

默认只会在断点A出停下来,子进程会一直往下执行直到结束。

常见命令:

  • 切换默认调试进程:set follow-fork-mode [parent(默认) | child]

    image-20240319005133180

  • 设置调试模式:set detach-on-fork [on | off]

    默认为on,表示调试当前进程的时候,其他进程继续向下执行。

    如果为off,则调试当前进程的时候,其他进程会被挂起

  • 查看所有调试的进程,以及当前调试的进程:info inferiors

  • 切换当前调试的进程:inferior id

  • 是进程脱离GDB调试:detach inferiors id

2.3 exec函数族

1.execl()

execl()函数:使用命令man exec查看介绍

  1. 用法

    int execl(const char *pathname, const char *arg, ...);
    - 参数:
        - path:需要指定的执行的文件的路径或者名称
            a.out /home/wangfan/a.out
            /bin/ps  执行ps命令
        - arg:可执行文件需要的参数列表
            第一个参数一般没用,写的是执行程序的名称
            从第二个参数开始往后,就是程序执行所需要的参数列表。
            注意:参数最好需要以NULL结束,充当哨兵
        - 返回值:
            只有当调用失败才会有返回值,返回-1,并且设置eoono。
            如果调用成功,没用返回值。
        - ***功能
            注意,当execl()函数执行成功以后,进程后续用户区会被pathname所指定的进程所取代,原本写在execl()后续的代码不会被执行。如果执行失败则后续代码会被执行。
    
  2. 测试代码

    #include<unistd.h>
    #include<stdio.h>
    
    int main(int argc, char const *argv[])
    {
        // 创建一个子进程,在子进程中执行exec函数族中的函数
        pid_t pid = fork();
        if(pid>0){
            printf("我是父进程 pid = %d, 子进程pid=%d\n",getpid(),pid);
        }else if(pid == 0){
            printf("我是子进程,execl()函数执行前\n");
            execl("./hello","hello",NULL);//输出hello,world
            //execl("/bin/ps","ps","aux",NULL);   //执行系统命令
            //execl("ps","ps","aux",NULL);   /模拟execl()函数执行失败
            printf("我是子进程 pid = %d 父进程ppid=%d\n",getpid(),getppid()); //执行成功,该代码不会被执行。
        }
    
        for(int i=0;i<3;i++){
            printf("%d  我的pid = %d\n",i,getpid());
        }
    
        return 0;
    }
    
  3. 输出结果

    image-20240319174329240

2.execlp()

  1. 用法:同上,p指的是在path环境下查找指定文件

  2. 代码

    #include<unistd.h>
    #include<stdio.h>
    
    int main(int argc, char const *argv[])
    {
        // 创建一个子进程,在子进程中执行exec函数族中的函数
        pid_t pid = fork();
        if(pid>0){
            printf("我是父进程 pid = %d, 子进程pid=%d\n",getpid(),pid);
        }else if(pid == 0){
            printf("我是子进程,execl()函数执行前\n");
            //execl("./hello","hello",NULL);
            execlp("ps","ps_wf","aux",NULL);   //执行系统命令
            printf("我是子进程 pid = %d 父进程ppid=%d\n",getpid(),getppid()); 
        }
    
        for(int i=0;i<3;i++){
            printf("%d  我的pid = %d\n",i,getpid());
        }
    
        return 0;
    }
    
    
  3. 演示

    image-20240319175350224

3.execv()

  1. 用法

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

    与上面的区别在:传递的参数是指针数组

  2. 代码

    #include <stdlib.h>  
    #include <stdio.h>  
    #include <unistd.h>  
    #include <errno.h>  
        
    int main(void)  
    {  
     char *argv[]={"ls","-l","/home",(char *)0};
      pid_t pid = fork();  
     
      if( pid == 0 ) // this is the child process  
      {  
         execv("/bin/ls", argv);  
      }  
      return 0;
    }  
    
  3. 演示

    image-20240319181254097

4.总结

l(list): 参数列表地址,一空指针结尾

v(vector): 指针数组

p(path):按照PATH环境变量指定的目录搜索文件

e(environment):存有环境变量字符串地址的指针数组的地址

2.4 进程控制

1. 进程退出

#include<stdlib.h>
#include<unistd.h>
void exit(int status);//会刷新I/0缓冲区
void _exit(int status);

image-20240319204128897

  1. 测试代码

    #include<stdio.h>
    #include<stdlib.h>
    #include<unistd.h>
    
    
    int main(int argc, char const *argv[])
    {
        printf("hello\n");
        printf("world");
        //exit(0);
        _exit(0);
    
        return 0;
    }
    
    
  2. 演示

    仅有hello,并没有输出world,因为printf没有遇见\n来输出该结果

    image-20240319204301268

2. 孤儿进程

父进程运行结束,子进程还在运行,这样的子进程叫做孤儿进程。

当出现孤儿进程的时候,内核会把孤儿进程的父进程设置为init,而init进程会循环地wait()它的已经退出的子进程。这样,当一个孤儿进程结束生命周期的时候,init进程会处理它的善后工作。

因此孤儿进程没有什么危害。

  1. 代码

    #include <sys/types.h>
    #include <unistd.h>
    #include <iostream>
    using namespace std;
    
    int main(){ 
    
        //创建子进程
        pid_t pid = fork();
        // 判断父进程还是子进程
        if(pid>0){
            printf("I am parent process,pid: %d, ppid:%d\n",getpid(),getppid());
            sleep(1);
        }else if (pid == 0){
            printf("I am son process,pid: %d, ppid:%d\n",getpid(),getppid());
            sleep(5);
            printf("I am son process,pid: %d, ppid:%d\n",getpid(),getppid());
        }
        for(int i=0;i<3;i++){
            printf("i = %d,pid = %d\n",i,getpid());
        }
        return 0;
    }
    
    
  2. 演示

    image-20240319211452040

image-20240319211541854

3. 僵尸进程

  1. 介绍

    每个进程结束之后,都会自己是否自己的用户区数据,但是内核区的PCB没有办法回收,只能由父进程释放。如果父进程在子进程之前结束,则子进程的父进程会被设置为init进程,由init进程进行回收。

    进程终止时,父进程还没有回收,子进程PCB存放在内核中,变成僵尸进程。

    僵尸进程不能被kill -9杀死

    僵尸进程会一直占用进程号,但是系统所能使用的进程号是有限的,如果有大量的僵尸进程,那么系统无法产生新的进程,产生大量的危害。

  2. 代码

    #include <sys/types.h>
    #include <unistd.h>
    #include <iostream>
    using namespace std;
    
    int main(){ 
    
        //创建子进程
        pid_t pid = fork();
        // 判断父进程还是子进程
        if(pid>0){
            while(1){
                printf("I am parent process,pid: %d, ppid:%d\n",getpid(),getppid());
                sleep(1);
            }
        }else if (pid == 0){
            printf("I am son process,pid: %d, ppid:%d\n",getpid(),getppid());
        }
        for(int i=0;i<3;i++){
            printf("i = %d,pid = %d\n",i,getpid());
        }
        return 0;
    }
    
  3. 演示

    image-20240319211907301


    image-20240319211851187

4.进程等待(回收)

  1. 什么是进程等待

    我们知道一般我们在父进程fork出一个子进程,我们是希望子进程完成某些功能,也就是帮助父进程完成某些任务的;所以我们父进程就需要知道子进程完成的状态如何,是成功还是失败;
    所以我们就需要父进程通过wait 或者 waitpid 函数等在子进程退出;

  2. 为什么需要父进程等待子进程退出

    父进程需要子进程退出的信息和完成功能的状态。

    可以保证时序问题:子进程先退出,父进程再退出。以此预防子进程变为僵尸进程,防止内存泄漏的问题。

  3. 方式

    • wait函数

      • #include<sys/types.h>
        #include<sys/wait.h>
        pid_t wait(int*status);
        /*
        返回值:
        	成功返回被等待进程pid,失败返回-1。
        参数:
        	输出型参数,获取子进程退出状态,不关心则可以设置成为NULL
        	
        注意:
        	- 调用wait函数的进程会被挂起(阻塞),直到它的一个子进程退出或者收到一个不能被忽略的信号时才	   被唤醒(相当于继续往下执行)
        	- 如果没有子进程了,函数立刻返回,返回-1;如果子进程都已经结束了,也会立即返回,返回-1
        */
        
      • 测试代码

        #include<string.h>
        #include<stdio.h>
        #include<stdlib.h>    
        #include<unistd.h>    
        #include<sys/wait.h>    
        int main()    
        {    
          if(fork() == 0){    
            //child process    
            printf("i am a child pid=%d\n",getpid());    
            exit(0); //让子进程退出    
            
          }    
          //parent process执行,这里不会执行子进程了,因为子进程被我退出了 
           sleep(2); //休息2s,为的是观察监控消息,是否子进程成为僵尸进程
          printf("wait函数开始执行\n");
          pid_t ret = wait(NULL);          
          if(ret ==-1){              
            perror("wait error\n");
          }                            
          //wait返回成功
          printf("wait返回的是子进程的ret=%d执行结束,注意观察监控窗口是否>僵尸进程被回收\n",ret);  
          sleep(2); //不让父进程那么快退出,观察窗口僵尸进程是否被回收     
          return 0;
                       
        } 
        
      • 演示结果

        img

    • waitpid函数

      • pid_t waitpid(pid_t pid, int *wstatus, int options);
        	- 功能: 回收指定进程号的子进程,可以设置是否阻塞
            - 参数:
                - pid:
                	pid > 0: 某个子进程的pid
                	pid = 0:  等待任何某个子进程的组ID等于调用waitpid()函数的进程的组ID
                    pid = -1: 表示回收任意一个子进程
                    pid < -1: 表示等待任何进程组ID等于pid绝对值的子进程。
                - wstatus:见下文
                - opt:
                    参数为0: 也就是阻塞版本的等待,也就是说该waitpid在子进程没有退出情况下就不会返回,就和wait的使用一模一样,因为wait的使用就是阻塞版本的等待方式;
        			参数为WNOHANG: 这是一个宏,表示调用wait为非阻塞的版本,非阻塞也就以为执行带waitpid函数会立即返回;
        				而设置这个参数:返回情况有以下几种:
                        - 若pid指定的子进程没有结束,则waitpid()函数返回0,父进程不予以等待;
        				- 若正常结束,则返回该子进程的ID;
        				- 若等待失败,即返回小于0
  • 29
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值