自学内容网 自学内容网

Linux - 进程等待和进程替换

进程等待

前面我们了解了如果父进程没有回收子进程, 那么当子进程接收后, 就会一直处于僵尸状态, 导致内存泄漏, 那么我们如何让父进程来回收子进程的资源.

waitpid

我们可以通过 Linux 提供的系统调用函数 wait 系列函数来等待子进程死亡, 并回收资源.

#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
pid_t waitpid(pid_t pid, int *status, int options);

wait函数用于等待任何一个子进程结束, 并回收其资源. 

  • status:指向整数的指针, 用于存储子进程的退出状态. 如果不需要这个信息, 可以传递NULL.
  • 成功时返回被等待的子进程的PID, 失败时返回 -1, 并设置 errno.

waitpid函数允许父进程等待特定的子进程结束

  • pid:子进程的PID. 如果为 -1, 则等待任何一个子进程。
  • status:同wait函数.
  • options:等待选项, 常用的有 WNOHANG (非阻塞等待). 
  • 成功时返回被等待的子进程的PID. 失败时返回-1,并设置errno

一般来说, 用 waitpid 多一点, 因为 waitpid 提供的更为细致的操作.

int main()    
{    
    pid_t id = fork();    
    if(id<0)    
    {    
        perror("fork");    
        exit(1);    
    }    
    if(id==0)//子进程代码    
    {    
        int count = 5;    
        while(count)    
        {    
            printf("[%d]我是子进程,我的pid是: %d\n",count,getpid());    
            sleep(1);    
            count--;    
        }    
        exit(0);//子进程执行完代码后退出, exit 会直接终止本进程                                                                        
    }                                                                                                    
    //父进程代码                                                                                         
    waitpid(id,NULL,0);                                                                                  
    printf("等待子进程成功!\n");                                                                         
    return 0;     
}

可以观察到, 在等待子进程结束之前, 父进程卡在了 waitpid(), 直到子进程都被等待成功, 父进程才会继续向后执行.

status 参数

在 wait 和 waitpid 函数中都存在一个 status 的参数.
在 status 中存储着子进程的退出码和退出信号.
如果父进程想要了解子进程的退出信息, 可以通过 status 来了解.

status 是一个 int 类型的变量, 一共有 32 个bit, 我们主要看它的低 16 位bit.

那么如何获取退出状态 (退出码) 和 信号

退出状态 (退出码):
(status >> 8) & 0xFF
退出信号:
status & 0x7F
int main()    
{    
    pid_t id = fork();    
    if(id<0)    
    {    
        perror("fork");    
        exit(1);    
    }    
    
    if(id==0)//子进程代码    
    {    
        int count = 5;    
        while(count)    
        {    
            printf("[%d]我是子进程,我的pid是: %d\n",count,getpid());    
            sleep(1);    
            count--;    
        }    
        exit(55);//子进程执行完代码后退出                                                                                                                               
    }    
    //父进程代码    
    int status = 0;    
    waitpid(id,&status,0);    
    printf("等待子进程成功!\n");    
    printf("进程退出码: %d,进程退出信号: %d\n",(status >> 8) & 0xFF,status & 0x7F);    
    return 0;    
}  

当子进程在运行时, 使用 kill -9 617714命令, 来终止子进程, 那么也就能观察到, 退出信号为 9.

option

在前面的参数解释中说到, 这是一个等待选项.
父进程可以选择一直阻塞下去, 直到等到子进程的死亡,
或者当子进程还没死亡时, 父进程去执行其他的代码. 等一会再来查看子进程是否死亡.

waitpid(pid,&status,WNOHANG); // 非阻塞等待
waitpid(pid,&status,0); // 阻塞等待

int main()
{
    pid_t id = fork();
    if(id<0)
    {
        perror("fork");
        exit(1);
    }

    if(id==0)//子进程代码
    {
        int count = 5;
        while(count)
        {
            printf("[%d]我是子进程,我的pid是: %d\n",count,getpid());
            sleep(1);
            count--;
        }
        exit(55);//子进程执行完代码后退出
    }
    //父进程代码
    while(1)//循环访问子进程退出情况
    {
        int wait = waitpid(id,NULL,WNOHANG);
        if(wait>0)//子进程退出成功
        {
            printf("子进程退出成功,子进程pid: %d\n",wait);
            break;
        }
        else if(wait==0)//子进程还没退出,父进程干自己的事情
        {
            //此处简单模拟父进程干的事情
            printf("我是父进程\n");
        }
        else //等待子进程退出失败
        {
            perror("waitpid");
            exit(1);
        }
        sleep(1);
    }
    return 0;
}

 执行上面的代码就能观察到, 在子进程结束前, 父进程还在向频幕上打印文字.

进程替换

创建子进程是为了完成一些工作, 但是子进程的代码和父进程是一样的,
有可能子进程并不需要执行父进程的代码, 而是执行一些其他代码.
这种场景下, 就可以使用进程替换.

我们为什么不直接将子进程要执行的代码写在父进程中呢, 还要去使用进程替换?

1. 如果所有的代码都放在父进程中, 那么父进程的代码会有多么的庞大,

这会提高代码编写和维护的成本. 

2. 进程所执行的一定是我们的C/C++程序吗? 进程也可以执行其他的非 C/C++ 程序,

那对于那些非 C/C++ 程序 (java程序), 我们无法将他们和我们所写的 C/C++ 代码整合在一起, java 和 C/C++ 的运行环境都不同.

exec 系列函数

如果想要创建出来的子进程执行全新的程序, 可以使用 exec 系列函数进行程序替换.

一共有6个函数, 其中主要分为两类
1. execl 系列
2. execv 系列

execl

int main()    
{    
    printf("进行程序替换了\n");    
    int n = execl("/usr/bin/ls","ls","-a","-l",NULL);                                                                                                                   
    if(n==-1)    
    {    
        perror("execl");    
    }    
    printf("程序替换完毕!\n");    
    return 0;    
}    

execl参数: 第一个是要执行程序的路径 (/usr/bin/ls),
第二个参数是要执行的程序的名称 (ls),

后面的参数到 NULL 之前, 都是要替换的程序参数 (-a, -l, 都是 ls 程序的参数).

execl 中 l, 表示如何将参数传递要替换的程序. l 表示通过一个列表的方式,
即向上面的 "-l", "-a"..., 一个列表的形式.

execlp 和 execle 两个函数则分别多了 p 和 e.

p 则代表要执行的程序可以从环境变量 PATH 中找到, 所以不用写执行程序的路径.

e 则表示, 可以传入用户自己定义的环境变量 (_env[]) 给程序使用.

int main()    
{    
    printf("我要进行程序替换了...\n");    
    int n = execlp("ls","-l",NULL);                                                                                                                                     
    if(n==-1)    
    {    
        perror("execl");    
    }    
    printf("程序替换完毕!\n");    
    return 0;    
} 

int main()    
{    
    const char* _env[]={"MY_ENV=666",NULL};    
    printf("我要进行程序替换了...\n");    
    int n = execle("/usr/bin/ls","ls","-l",NULL,_env);//自己定义一个环境变量MY_ENV=666传递给要去执行的程序                                                              
    if(n==-1)    
    {    
        perror("execl");    
    }    
    printf("程序替换完毕!\n");    
    return 0;    
}  

execv

上面的 execl 中的 l, 代表传参使用列表的形式.
那么 v 很容易就想到了是vector.
所以 execv 函数在给替换的程序传参时, 是通过一个 vector 来传参的.

int main()    
  {    
  char* const set[]={"ls","-a","-l",NULL};  
      printf("我要进行程序替换了...\n");    
      int n = execv("/usr/bin/ls",set);                                                                             
      if(n==-1)    
      {    
          perror("execl");    
      }    
      printf("程序替换完毕!\n");    
      return 0;    
  } 

那么剩下的 execvp 和 execvpe 和之前的 execl 系列中的一样.
p 代表在环境变量 PATH 中查找, e 可以传入自己定义的环境变量.

  • l (list): 传参的方式为使用列表来传递
  • v (vector): 使用数组来传递参数
  • p (path): 会在环境变量 PATH 中查找程序
  • e (env): 可以传递自己定义的环境变量

原文地址:https://blog.csdn.net/m0_74506452/article/details/144357632

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!