【Linux C | 进程】进程终止、等待 | exit、_exit、wait、waitpid

2024-01-23 09:44

本文主要是介绍【Linux C | 进程】进程终止、等待 | exit、_exit、wait、waitpid,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

😁博客主页😁:🚀https://blog.csdn.net/wkd_007🚀
🤑博客内容🤑:🍭嵌入式开发、Linux、C语言、C++、数据结构、音视频🍭
🤣本文内容🤣:🍭介绍 🍭
😎金句分享😎:🍭你不能选择最好的,但最好的会来选择你——泰戈尔🍭

本文未经允许,不得转发!!!

目录

  • 🎄一、进程终止
    • ✨1.1 正常终止
    • ✨1.2 异常终止
  • 🎄二、孤儿进程、僵死进程
    • ✨2.1 孤儿进程
    • ✨2.2 僵死进程
  • 🎄三、等待子进程结束 | wait、waitpid
    • ✨3.1 wait 函数
    • ✨3.2 waitpid 函数
  • 🎄四、总结


在这里插入图片描述

🎄一、进程终止

✨1.1 正常终止

进程有下面5种正常终止方式:

  • 1、在main函数内执行return语句。这等效于调用exit。
    在子函数中调用return不会导致进程退出,而调用exit会导致整个进程退出。

    #include <stdio.h>
    #include <stdlib.h>
    int child_fun()
    {//return 0;	// 不导致进程退出exit(0);	// 会导致进程退出
    }
    int main()
    {child_fun();while(1)sleep(1);exit(0);	// 等效于 return 0;
    }
    
  • 2、调用exit函数。此函数由ISO C定义,其操作包括调用各终止处理程序(终止处理程序在调用atexit函数时登记),然后关闭所有标准IO流等。因为ISO C并不处理文件描述符、多进程(父、子进程)以及作业控制,所以这定义对UNIX系统而言是不完整的。

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    void end_deal()
    {printf("atexit deal ...\n");
    }
    int main()
    {printf("程序开始...\n");printf("调用 atexit 注册\n");atexit(end_deal);printf("程序结束\n");exit(0);
    }
    

    在这里插入图片描述

  • 3、调用_exit_Exit函数。ISO C定义_Exit,其目的是为进程提供一种无需运行终止处理程序或信号处理程序而终止的方法。对标准IO流是否进行冲洗,这取决于实现。在UNIX系统中,_Exit_exit是同义的,并不清洗标准I/O流。_exit函数由exit调用,它处理UNIX特定的细节。_exit是由POSIX.1说明的。

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    void end_deal()
    {printf("atexit deal ...\n");
    }
    int main()
    {printf("程序开始...\n");printf("调用 atexit 注册\n");atexit(end_deal);printf("程序结束\n");_exit(0);
    }
    

    _exit退出进程时,不会调用atexit注册的函数。
    在这里插入图片描述

  • 4、进程的最后一个线程在其启动例程中执行返回语句。但是,该线程的返回值不会用作进程的返回值。当最后一个线程从其启动例程返回时,该进程以终止状态0返回。

  • 5、进程的最后一个线程调用pthread_exit函数。如同前面一样,在这种情况中,进程终止状态总是0,这与传送给pthread_exit的参数无关。


✨1.2 异常终止

三种异常终止方式如下:

  • 1、调用abort。它产生SIGABRT信号,这是下一种异常终止的一种特例
  • 2、当进程接收到某些信号时。信号可由进程自身(例如调用abort函数)、其他进程或内核产生。例如,若进程越出其地址空间访问存储单元或者除以0,内核就会为该进程产生相应的信号。
  • 3、最后一个线程对“取消”(cancellation)请求做出响应。按系统默认,“取消”以延迟方式发生:一个线程要求取消另一个线程,一段时间之后,目标线程终止。

不管进程如何终止,最后都会执行内核中的同一段代码。这段代码为相应进程关闭所有打开描述符,释放它所使用的存储器等。

终止进程时,我们希望能够通知其父进程它时如何终止的。对于三个终止函数((exit、_exit和_Exit),实现这一点的方法是,将其退出状态(exit status)作为参数传送给函数。在异常终止情况下,内核(不是进程本身)产生一个指示其异常终止原因的终止状态(termination status)。在任意一种情况下,该终止进程的父进程都能用waitwaitpid函数取得其终止状态。


在这里插入图片描述

🎄二、孤儿进程、僵死进程

✨2.1 孤儿进程

父进程先于子进程结束,子进程变成孤儿进程,该孤儿进程会被1号进程(init进程)领养

其操作过程大致如下:在一个进程终止时,内核逐个检查所有活动进程,以判断它是否是正要终止进程的子进程,如果是,则将该进程的父进程ID更改为1 (init进程的ID)。这种处理方法保证了每个进程都有一个父进程。

#include <stdio.h>
#include <unistd.h>int main()
{printf("父进程%d开始运行,并创建子进程\n", getpid());pid_t pid = fork();if(pid == 0){printf("子进程%d被创建并开始运行,父进程是%d\n",getpid(),getppid());printf("子进程进入睡眠状态");sleep(3);printf("子进程睡醒后,发现父进程变为%d\n",getppid());return 0;}printf("父进程继续运行....\n");sleep(1);printf("父进程执行结束\n");return 0;
}

运行结果如下:
在这里插入图片描述


✨2.2 僵死进程

在UNIX术语中,一个已经终止、但是其父进程尚未对其进行善后处理(获取终止子进程的有关信息,释放它仍占用的资源)的进程被称为僵死进程(zombie)。ps(1)命令将僵死进程的状态打印为Z。如果编写一个长期运行的程序,它调用fork产生了很多子进程,那么除非父进程等待取得子进程的终止状态,否则这些子进程终止后就会变成僵死进程。

如果子进程先结束,会给父进程发信号,如果父进程没有收到该信号或没有及时处理,子进程将变成僵死进程,直到父进程处理了该信号或者父进程退出。

内核为每个终止子进程保存了一定量的信息,所以当终止进程的父进程调用waitwaitpid时,可以得到这些信息。这些信息至少包括进程ID、该进程的终止状态、以及该进程使用的CPU时间总量。

#include <stdio.h>
#include <unistd.h>int main()
{printf("%d 进程:我要调用fork()了...\n", getpid());pid_t pid = fork();if(pid == 0){printf("%d进程:我是%d的子进程,即将成为僵尸...\n",getpid(),getppid());return 0;//子进程结束,自动发信号}sleep(1);printf("%d进程: 我是%d的父进程\n", getpid(),pid);getchar();return 0;
}

运行结果打印:
在这里插入图片描述
打开另一个命令行窗口,查看子进程状态为僵死进程:
在这里插入图片描述


在这里插入图片描述

🎄三、等待子进程结束 | wait、waitpid

本节介绍两个函数(wait、waitpid),用于等待调用进程的子进程中的状态更改,并获取子进程状态更改的信息。
状态变化被认为是:①子进程被终止;②子进程被一个信号拦住了;③子进程被一个信号恢复了。
在终止子进程的情况下,执行wait函数允许系统释放与该子进程相关联的资源;如果不执行等待,则终止的子进程将保持“僵尸”状态。

下表是判断退出状态的宏

解释
WIFEXITED(status)若为正常终止子进程返回的状态,则为真。对于这种情况可执行WEXITSTATUS( status),取子进程传送给exit、_exit或_Exit参数的低8位
WEXITSTATUS(status)可以获取退出码
WIFSIGNALED(status )若为异常终止子进程返回的状态,则为真(接到一个不捕捉的信号)。对于这种情况,可执行WTERMSIG(status),取使子进程终止的信号编号。另外,有些实现(非Single UNIX Specification)定义宏WCOREDUMP status),若已产生终止进程的core文件,则它返回真
WIFSTOPPED(siatus)若为当前暂停子进程的返回的状态,则为真。对于这种情况,可执行wSTOPSIG(status),取使子进程暂停的信号编号
WIFCONTINUED(status)若在作业控制暂停后已经继续的子进程返回了状态,则为真。(POSIX.1的XSI扩展,仅用于waitpid。)

✨3.1 wait 函数

函数原型:

#include <sys/wait.h>
pid_t wait(int *status);
  • 功能:wait函数会使调用进程一直阻塞,直到它的一个子进程终止为止,它等效于:waitpid(-1, &status, 0);。如果调用进程没有子进程则立即返回。
  • 参数:status,是传出参数,用来带出结束子进程的退出码和退出状态;
  • 返回值:成功返回终止的子进程的进程ID,失败返回 -1

看例子:

#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
int main()
{pid_t pid = fork();if(pid == 0){printf("子进程开始执行,即将睡眠...\n");sleep(3);printf("子进程运行结束\n");exit(132);//该值不应该超过255}//父进程printf("父进程开始执行,子进程的PID=%d\n",pid);printf("父进程等待子进程结束....\n");int status = 0;/*如果子进程不退出,该函数一直阻塞*/pid_t res = wait(&status);printf("发现%d子进程结束", res);printf("status = %d\n", status);/*如果子进程正常结束,该宏的操作结果为1,反之为0*/if(WIFEXITED(status)){printf("子进程正常结束\n");printf("返回值为:%d\n", WEXITSTATUS(status));}return 0;
}

运行结果:
在这里插入图片描述


✨3.2 waitpid 函数

函数原型:

#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *status, int options);
  • 功能:waitpid函数会使调用进程一直阻塞,直到指定的子进程终止为止。

  • 参数:

    • pid:
      <-1:等待进程组ID等于pid绝对值的任何子进程;
      -1:等待任意子进程,与wait等效;
      0:等待进程组ID等于调用进程的进程组ID的任何子进程;
      >0:等待进程ID等于pid值的子进程。

    • status:是传出参数,用来带出结束子进程的退出码和退出状态;

    • options:
      0:调用进程阻塞等待;
      WNOHANG:如果没有子进程终止,则立即返回。

  • 返回值:成功时,返回状态已更改的子进程的进程ID;如果指定了WNOHANG,并且pid指定的一个或多个子级存在,但尚未更改状态,则返回0。出现错误时,返回-1。

看例子:

#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
int main()
{int pid1 = 0;int pid2 = 0;pid1 = fork();if(pid1>0){pid2 = fork();}if(pid1 == 0)//子进程一{printf("子进程一开始执行 PID=%d,睡3秒\n",getpid());sleep(3);printf("子进程一结束\n");exit(55);}if(pid2 == 0)//子进程二{printf("子进程二开始执行 PID=%d,睡1秒\n",getpid());sleep(1);printf("子进程二结束\n");exit(200);}//父进程printf("父进程等待子进程一结束\n");int status = 0;/*阻塞等待子进程一结束*/waitpid(pid1, &status, 0);if(WIFEXITED(status)){printf("子进程一正常结束,返回值:%d\n",WEXITSTATUS(status));}printf("父进程结束\n");
}

运行结果:
在这里插入图片描述


在这里插入图片描述

🎄四、总结

本文先介绍了进程终止的8个方式,然后介绍孤儿进程、僵死进程,最后介绍了父进程等待子进程的两个函数wait、waitpid。

在这里插入图片描述
如果文章有帮助的话,点赞👍、收藏⭐,支持一波,谢谢 😁😁😁

这篇关于【Linux C | 进程】进程终止、等待 | exit、_exit、wait、waitpid的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/635997

相关文章

防止Linux rm命令误操作的多场景防护方案与实践

《防止Linuxrm命令误操作的多场景防护方案与实践》在Linux系统中,rm命令是删除文件和目录的高效工具,但一旦误操作,如执行rm-rf/或rm-rf/*,极易导致系统数据灾难,本文针对不同场景... 目录引言理解 rm 命令及误操作风险rm 命令基础常见误操作案例防护方案使用 rm编程 别名及安全删除

Linux下MySQL数据库定时备份脚本与Crontab配置教学

《Linux下MySQL数据库定时备份脚本与Crontab配置教学》在生产环境中,数据库是核心资产之一,定期备份数据库可以有效防止意外数据丢失,本文将分享一份MySQL定时备份脚本,并讲解如何通过cr... 目录备份脚本详解脚本功能说明授权与可执行权限使用 Crontab 定时执行编辑 Crontab添加定

使用docker搭建嵌入式Linux开发环境

《使用docker搭建嵌入式Linux开发环境》本文主要介绍了使用docker搭建嵌入式Linux开发环境,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面... 目录1、前言2、安装docker3、编写容器管理脚本4、创建容器1、前言在日常开发全志、rk等不同

linux系统上安装JDK8全过程

《linux系统上安装JDK8全过程》文章介绍安装JDK的必要性及Linux下JDK8的安装步骤,包括卸载旧版本、下载解压、配置环境变量等,强调开发需JDK,运行可选JRE,现JDK已集成JRE... 目录为什么要安装jdk?1.查看linux系统是否有自带的jdk:2.下载jdk压缩包2.解压3.配置环境

Linux搭建ftp服务器的步骤

《Linux搭建ftp服务器的步骤》本文给大家分享Linux搭建ftp服务器的步骤,本文通过图文并茂的形式给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录ftp搭建1:下载vsftpd工具2:下载客户端工具3:进入配置文件目录vsftpd.conf配置文件4:

Linux实现查看某一端口是否开放

《Linux实现查看某一端口是否开放》文章介绍了三种检查端口6379是否开放的方法:通过lsof查看进程占用,用netstat区分TCP/UDP监听状态,以及用telnet测试远程连接可达性... 目录1、使用lsof 命令来查看端口是否开放2、使用netstat 命令来查看端口是否开放3、使用telnet

Linux系统管理与进程任务管理方式

《Linux系统管理与进程任务管理方式》本文系统讲解Linux管理核心技能,涵盖引导流程、服务控制(Systemd与GRUB2)、进程管理(前台/后台运行、工具使用)、计划任务(at/cron)及常用... 目录引言一、linux系统引导过程与服务控制1.1 系统引导的五个关键阶段1.2 GRUB2的进化优

Linux查询服务器 IP 地址的命令详解

《Linux查询服务器IP地址的命令详解》在服务器管理和网络运维中,快速准确地获取服务器的IP地址是一项基本但至关重要的技能,下面我们来看看Linux中查询服务器IP的相关命令使用吧... 目录一、hostname 命令:简单高效的 IP 查询工具命令详解实际应用技巧注意事项二、ip 命令:新一代网络配置全

linux安装、更新、卸载anaconda实践

《linux安装、更新、卸载anaconda实践》Anaconda是基于conda的科学计算环境,集成1400+包及依赖,安装需下载脚本、接受协议、设置路径、配置环境变量,更新与卸载通过conda命令... 目录随意找一个目录下载安装脚本检查许可证协议,ENTER就可以安装完毕之后激活anaconda安装更

Linux查询服务器系统版本号的多种方法

《Linux查询服务器系统版本号的多种方法》在Linux系统管理和维护工作中,了解当前操作系统的版本信息是最基础也是最重要的操作之一,系统版本不仅关系到软件兼容性、安全更新策略,还直接影响到故障排查和... 目录一、引言:系统版本查询的重要性二、基础命令解析:cat /etc/Centos-release详