Linux学习笔记:
https://blog.csdn.net/2301_80220607/category_12805278.html?spm=1001.2014.3001.5482
前言:
在操作系统中,进程终止是一个至关重要的阶段,它标志着进程的生命周期结束。进程终止可能是因为任务完成,也可能是因为异常或外部干预。本文将详细讲解操作系统中的进程终止相关知识,包括终止的原因、类型、实现方式、Linux系统中的具体操作,以及其影响和管理策略,并配以表格和代码示例,帮助全面掌握这一主题。
目录
[4.1 运行完毕且正常终止](#4.1 运行完毕且正常终止)
[4.1.1 使用return终止进程](#4.1.1 使用return终止进程)
[4.1.2 使用exit终止进程](#4.1.2 使用exit终止进程)
[4.2 errno常量和strerror函数](#4.2 errno常量和strerror函数)
[4.2.1 strerror函数](#4.2.1 strerror函数)
[4.2.2 errno常量](#4.2.2 errno常量)
[4.3 异常终止:abort](#4.3 异常终止:abort)
[4.4 强制终止:kill](#4.4 强制终止:kill)
[4.4 子进程资源回收:wait 和 waitpid](#4.4 子进程资源回收:wait 和 waitpid)
[5.1 资源释放](#5.1 资源释放)
[5.2 僵尸进程](#5.2 僵尸进程)
[7.1 僵尸进程问题](#7.1 僵尸进程问题)
[7.2 非预期终止](#7.2 非预期终止)
一、什么是进程终止?
**进程终止(Process Termination)**是操作系统中进程生命周期的最后一个阶段,意味着操作系统回收该进程的所有资源,包括内存、文件描述符、CPU时间等,使这些资源可以被其他进程使用。
二、进程终止的主要原因
进程可能因多种原因终止:
终止原因 | 描述 |
---|---|
正常终止 | 进程完成所有任务后自然结束,例如程序执行到return 语句或调用exit 函数。 |
异常终止 | 由于未处理的错误或异常导致进程终止,例如除以零、非法访问内存等。 |
外部干预 | 进程被操作系统或其他进程强制终止,例如接收到SIGKILL 信号。 |
父进程终止 | 当父进程终止且子进程未被接管时,子进程可能成为孤儿进程,由init 或systemd 进程接管。 |
资源耗尽 | 进程因超出系统资源限制(如内存、文件句柄等)被操作系统强制终止。 |
三、进程终止的类型
进程终止根据触发方式可以分为以下几类:
类型 | 触发方式 | 常见场景 |
---|---|---|
正常终止 | 调用exit() 、返回主函数 |
程序完成任务后自然结束。 |
异常终止 | 未处理的错误或调用abort() |
例如访问非法地址、未处理的信号等。 |
强制终止 | 外部进程调用kill() 、操作系统干预 |
父进程发送SIGKILL 信号或管理员手动终止进程。 |
核心转储终止 | 错误导致生成核心转储文件 | 例如段错误(SIGSEGV )导致的异常。 |
一般进程终止的场景包含一下三种:
代码运行完毕,结果正常
代码运行完毕,结果不正常
代码异常终止
下面我们会对上面的内容做出讲解
四、Linux中的进程终止实现
在Linux操作系统中,进程终止主要通过以下系统调用和信号实现:
4.1 运行完毕且正常终止
4.1.1 使用return终止进程
我们平时用的最多的方式就是return,我们先来看下面一个简单的代码
cpp
#include<stdio.h>
int main()
{
printf("This is a test message");
return 0;
}
我们平时所写的代码main函数中一般都有一个返回值,那么这个返回值是干什么的呢?
main函数返回值是返回给进程看的,本质表示:进程运行完成时是否是正确的结果,如果是一般返回0,如果不是返回其它数字代表不同的退出信息(退出码)
我们可以通过这个指令打印退出码:
cpp
echo $?
4.1.2 使用exit
终止进程
exit
系统调用用于正常终止进程,并返回一个状态码给操作系统或父进程。
我们使用exit一般是在进程正常终止但没有正常执行的场景,或者是在合适的地方进行截停的场景,我们来看下面一段代码:
cpp
#include<stdio.h>
#include<stdlib.h>
void print()
{
printf("hello linux\n");
printf("hello linux\n");
printf("hello linux\n");
printf("hello linux\n");
exit(20);
printf("hello linux\n");
printf("hello linux\n");
}
int main()
{
print();
return 10;
}
我们来看一下上面内容的执行结果和返回值:
我们发现返回值是exit中的返回值,并不是return的返回值,而且打印也只执行了四行,所以我们可以知道带有exit的进程,在执行到它时会直接返回,并不会再继续执行后面的内容,返回值也返回exit的返回值,这一点与return有较大差别
4.2 errno常量和strerror函数
4.2.1 strerror函数
上面我们提到我们可以通过不同的退出码来代表不同的错误信息,那么不同的退出码究竟各自代表什么信息呢?我们可以通过strerror函数来查看
比如我们来看一下退出码0到10所代表的信息:
cpp
#include<stdio.h>
#include<string.h>
int main()
{
for(int i=0;i<=10;i++)
{
printf("%d: %s\n",i,strerror(i));
}
return 0;
}
运行结果:
4.2.2 errno常量
上面我们讲到进程在退出是会有退出码,我们可以通过echo来查看退出码,那我们如何获取呢?
C/C++中其实还定义了一个叫errno的常量来记录错误码
所以我们就可以将errno常量与strerror函数结合使用,用errno来记录进程的错误码,然后传给strerror函数得到错误信息,比如下面的例子:
cpp
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<stdlib.h>
#include<errno.h> //注意要带好头文件
int main()
{
int ret=0;
char *p=(char*)malloc(1000*1000*1000*4); //这个扩容肯定会出错的,因为扩容空间太大了
if(p==NULL)
{
printf("mallo error, %d:%s\n",errno,strerror(errno)); //errno会记录错误码,将它传到strerror中就可以得到错误信息
ret=errno; //将错误码作为返回值返回,从而让父进程得到返回信息
}
else
{
printf("malloc success\n");
}
return ret;
}
运行结果:
4.3 异常终止:abort
4.2和4.3都牵扯到了信号的内容,这里我们主要还是以了解为主,后期我们会详细讲解信号的知识
abort
函数用于非正常终止进程,通常在遇到不可恢复的错误时调用。
示例代码:abort
cpp
#include <stdlib.h>
#include <stdio.h>
int main() {
printf("遇到严重错误,程序终止!\n");
abort(); // 异常终止
return 0; // 不会被执行
}
调用abort
会产生一个信号(SIGABRT
),通常会生成一个核心转储文件供调试使用。
4.4 强制终止:kill
kill
系统调用或命令用于向目标进程发送信号,例如SIGKILL
信号会立即强制终止目标进程。
示例代码:kill
cpp
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
int main() {
pid_t pid = fork();
if (pid == 0) {
// 子进程
while (1) {
printf("子进程正在运行: PID = %d\n", getpid());
sleep(1);
}
} else if (pid > 0) {
// 父进程
sleep(5);
printf("终止子进程: PID = %d\n", pid);
kill(pid, SIGKILL); // 发送SIGKILL信号
}
return 0;
}
执行结果:
我们发现子进程在被执行了五次后被终止掉了,对应的就是代码中执行了五次后会执行kill指令杀死进程
4.4 子进程资源回收:wait
和 waitpid
进程等待与回收我们会在下一篇详细讲解
当子进程终止后,其状态会保留在系统中,直到父进程回收。这种未被回收的子进程称为僵尸进程。
示例代码:回收子进程资源
cpp
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <unistd.h>
int main() {
pid_t pid = fork();
if (pid == 0) {
// 子进程
printf("子进程开始: PID = %d\n", getpid());
sleep(2);
printf("子进程结束\n");
} else if (pid > 0) {
// 父进程
int status;
wait(&status); // 等待子进程终止
if (WIFEXITED(status)) {
printf("子进程正常终止,状态码: %d\n", WEXITSTATUS(status));
}
}
return 0;
}
执行结果:
五、进程终止的影响
5.1 资源释放
进程终止后,操作系统会回收以下资源:
- 内存:代码段、数据段、堆栈等。
- 文件描述符:关闭该进程打开的所有文件。
- CPU时间:释放进程的时间片。
5.2 僵尸进程
当子进程终止但父进程未调用wait
或waitpid
回收其状态时,子进程会变成僵尸进程。
如何避免僵尸进程?
- 父进程调用
wait
或waitpid
回收子进程。- 使用信号处理机制,如捕获
SIGCHLD
信号。
六、信号与进程终止
常见信号与作用
信号 | 描述 | 默认行为 |
---|---|---|
SIGKILL |
强制终止进程,无法捕获或忽略。 | 终止 |
SIGTERM |
请求终止进程,进程可以选择捕获或忽略。 | 终止 |
SIGABRT |
异常终止进程,通常由abort 触发。 |
终止并生成核心转储 |
SIGCHLD |
子进程终止或停止时通知父进程。 | 忽略 |
示例代码:捕获SIGTERM
信号
cpp
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
void handle_sigterm(int sig) {
printf("接收到SIGTERM信号,进程终止\n");
exit(0);
}
int main() {
signal(SIGTERM, handle_sigterm); // 注册信号处理函数
while (1) {
printf("程序正在运行...\n");
sleep(2);
}
return 0;
}
七、进程终止的常见问题与解决
7.1 僵尸进程问题
问题:父进程未回收子进程,导致系统中出现僵尸进程。
解决:
- 调用
wait
或waitpid
。- 使用
SIGCHLD
信号处理函数自动回收。
7.2 非预期终止
问题:进程意外终止导致数据未保存。
解决:通过信号处理函数捕获终止信号,并在终止前完成必要的清理工作。
八、总结
进程终止是操作系统中管理资源的重要环节。通过本文的讲解,我们了解了进程终止的主要原因、类型以及Linux中的具体实现方式。进程终止不仅影响单个进程的生命周期,还对系统资源的利用和稳定性产生重要影响。
通过合理地使用
exit
、kill
、wait
等系统调用,以及信号机制,可以高效管理进程终止,避免僵尸进程问题,提高系统性能和可靠性。希望通过本文的详细分析和代码示例,你能更加深入理解操作系统的这一重要机制!
本篇笔记:
感谢各位大佬观看,创作不易,还请各位大佬点赞支持!!!