深入理解进程的退出、等待与替换(Linux系统)

个人主页:敲上瘾-优快云博客

个人专栏:Linux学习游戏数据结构c语言基础c++学习算法

目录

一、进程退出

1.退出场景

2.常见退出方法

3.退出码与退出信号

4._exit函数与exit函数

二、进程等待

1.什么是进程等待(是什么?)

2.为什么要有进程等待(为什么?)

3.如何进行进程等待(怎么办?)

3.1.wait

3.2.waitpid

3.3.获取status

3.4.阻塞与非阻塞等待

三、进程替换

1.进程替换原理

2.进程替换函数


         本文旨在分享我对Linux进程的理解和见解。由于个人知识和经验的限制,文中可能存在错误或遗漏。我真诚地邀请各位读者在阅读过程中发现任何问题时,指出错误并提供宝贵的意见。您的反馈将是我不断进步和完善的重要动力。

一、进程退出

1.退出场景

进程退出一共就三种情况:

  • 进程正常退出,执行结果正确
  • 进程正常退出,执行结果错误
  • 进程异常退出

2.常见退出方法

  • main函数中return退出
  • 使用_exit函数退出
  • 使用exit函数退出

_exit和exit在下文详解。

3.退出码与退出信号

  • 退出码:标识程序的退出状态
  • 退出信号:当程序异常退出后,退出信号标记了进程异常退出的原因,如果是正常退出则退出信号为0。

在对于退出信号可以使用 kill -l 查询,如下:

4._exit函数与exit函数

        _exit是一个系统提供的接口,它的参数是一个int类型,需要传一个退出信号返回。而exit是C语言提供的接口,它同样是让程序退出,需要传一个退出信号返回。

        而_exit与exit一个很大的区别就是_exit不会刷新缓冲区,而exit会刷新缓冲区,其中exit底层还是使用了_exit实现。

可以做一个简单的小测试:

注意:(1)这里程序是从exit和_exit退出的,而不是从return 0退出。(2)这里我有意在printf输出字符串后没有加\n,因为\n会让缓冲区刷新,会干扰测试。

二、进程等待

1.什么是进程等待(是什么?)

进程等待指的是父进程等待子进程结束。

        在子进程结束后它的pcb不会立马释放,而是进入僵尸状态,让父进程回收。当然如果父进程永远不来回收,那么子进程pcb就永远得不到释放,从而内存泄漏

而父进程在等待子进程退出这个过程就叫作进程等待。

2.为什么要有进程等待(为什么?)

  • 父进程创建子进程就是要子进程完成任务,所以父进程需要知道任务的完成情况,从而决定下一步要做什么。
  • ⼦进程退出,⽗进程如果不管不顾,就可能造成 僵⼫进程 的问题,进⽽造成内存泄漏。
  • ⽗进程通过进程等待的⽅式,回收⼦进程资源,获取⼦进程退出信息(任务完成情况)。

3.如何进行进程等待(怎么办?)

3.1.wait

        wait是一个用来进程等待的函数,使用它需要包含的头文件为 sys/types.hsys/wait.h。函数声明如下:

pid_t wait(int *status);
  • 返回值:成功返回被等待进程pid,失败返回-1。
  • 参数:输出型参数,获取⼦进程退出状态,不关⼼则可以设置成为NULL。

3.2.waitpid

        waitpid同样是一个用来进程等待的函数,它的功能要更多,使用它需要包含的头文件为 sys/types.h 和 sys/wait.h。函数声明如下:

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

返回值:

  • 当正常返回的时候waitpid返回收集到的⼦进程的进程ID;
  • 如果第三个参数设置了选项WNOHANG,那么调⽤中waitpid发现没有已退出的⼦进程可收集,否则返回0。
  • 如果调⽤中出错,则返回-1,这时errno会被设置成相应的值以指⽰错误所在;

参数:

  • 1.pid:(1)pid=-1,等待任⼀个⼦进程。与wait等效(2)pid>0,等待其进程ID为pid的⼦进程。
  • 2.status:输出型参数,获取⼦进程退出状态,不关⼼则可以设置成为NULL。下面3.3.再细讲。
  • 3.options:默认为0,表⽰如果子进程没有结束需要等待。如果设置为WNOHANG,子进程没有结束则不需要等待,接着往下执行。
     

3.3.获取status

status可以得到进程的退出码和退出信号。

        它是如何同时储存退出码和退出信号呢?其实用了一个位图的思想。status是一个int类型一共占4*8个比特位。而这里用了它的低16位,如下:

所以退出码我们可以使用(status>>8)&0xFF获取,退出信号通过status&0x7F获取。当然系统给我们提供了WIFEXITED和WEXITSTATUS两个宏来做进程退出状态检查。功能如下:

  • WIFEXITED(status):若为正常终⽌⼦进程返回的状态,则为真(查看进程是否是正常退出)。
  • WEXITSTATUS(status):若WIFEXITED⾮零,提取⼦进程退出码(查看进程的退出码)。

3.4.阻塞与非阻塞等待

        阻塞等待:使用wait或使用waitpid第三个参数传0的话,父进程在等待子进程过程中,如果子进程没有结束,那么父进程就会一直等。直到子进程退出。        

        非阻塞等待:父进程是很忙的,当子进程没有退出的时候也不能在那里干等着。所以正如刚才所讲,waitpid第三个参数中传入WNOHANG就能实现子进程还没退出就不等,继续做自己的其他工作,这就是非阻塞等待。当然刚才没有等到子进程结束,还得找个时间再等,要不然到时候子进程pcd就不能被回收。所以如果使用WNOHANG就需要重复的waitpid,这就是非阻塞轮询

三、进程替换

1.进程替换原理

如下是一个程序替换的程序(execl函数使用在下文会讲解):

#include<stdio.h>
#include<unistd.h>
int main()
{
     execl("/usr/bin/ls","/usr/bin/ls","-la",NULL);//程序替换
     printf("hello linux\n");
     return 0;
 }

        进程替换就是在一个进程执行中把该进程后续的内容替换成其他进程。要知道指令的本质就是一个可执行文件,它是用c语言写的。以上代码就相当于把ls这个程序的代码把后面的原代码覆盖掉,所以其中pcd,虚拟地址空间,页表并没有改变,而是物理内存改变了

注意:(1)进程替换并不会有新的进程生成。(2)进程替换可以替换为任何进程,无论是什么语言,打个比方就是说,x写的程序,在执行过程中可以替换成y写的程序(x,y表示任意语言)。

2.进程替换函数

如下是一个exec函数族,即程序替换函数族:

#include <unistd.h>
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ...,char *const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execve(const char *path, char *const argv[], char *const envp[]);

替换一个程序起码要知道这个程序的路径吧?需要知道它的名字吧?还需要知道命令行参数吧?

  • 第一个参数:需要替换的程序的路径。
  • 第二个参数:需要替换的程序的名字。
  • 第三个参数:给改程序传入命令行参数。
  • 第四个参数:如果有这个参数,需要传入自己组装的环境变量。如果没有改参数不用传,就用当前的环境变量。

上面函数的命名特点如下:

  • l(list):表⽰参数采⽤列表传入。
  • v(vector):参数⽤数组。
  • p(path):有p⾃动搜索环境变量PATH。
  • e(env):表⽰⾃⼰维护环境变量。

这些exec族的返回规则是一样的,如果执行成功没有返回值,如果调用失败返回-1。

非常感谢您能耐心读完这篇文章。倘若您从中有所收获,还望多多支持呀!74c0781738354c71be3d62e05688fecc.png

以下代码是对该篇文章知识的应用:

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

char* const g_argev1[]=
    {(char* const)"/usr/bin/ls",
     (char* const)"-a",
     (char* const)"-l",
     NULL};

char* const g_argev2[]=
    {(char* const)"ls",
     (char* const)"-a",
     (char* const)"-l",
     NULL};

char* const env[]=    //这里的环境变量是随便设的没有实际的意义,只是为了说明问题
    {(char* const)"HPP=128",
     (char* const)"GHH=29",
     (char* const)"p=..",
     NULL};

int main()
{
    pid_t id=fork();
    if(id==0)
    {
        execl("/usr/bin/ls","ls","-l","-a",NULL);
        //execv(g_argev1[0],g_argev1);
        
        //execlp("ls","-ln","-a",NULL);
        //execvp(g_argev2[0],g_argev2);
        
        //execle("./code","ls","-l","-a",NULL,env);
        //execve(g_argev1[0],g_argev2,env);

        exit(1); // 如果所有exec都失败,则执行到这里
    }

    int status;
    pid_t p=waitpid(id,&status,0);
    if(p>0)
    {
        if(WIFEXITED(status))
            printf("exit code:%d, exit signal:%d\n",WEXITSTATUS(status),status&0x7F);
            // 注意:status&0x7F 用于获取终止信号,但通常我们只关心 WEXITSTATUS(status)
        else
            printf("程序执行异常\n");
    }
    else
        printf("waitpid fail:%s",strerror(errno));

    return 0;
}

评论 53
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

敲上瘾

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

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

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

打赏作者

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

抵扣说明:

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

余额充值