Linux进程控制
目录
进程终止
一、进程退出场景
二、进程常见退出方法
进程等待
为什么有进程等待
什么是进程等待
怎么做到进程等待
wait
waitpid
进程终止
在 Linux 编程的世界里,进程的生命周期管理是极为关键的内容,而进程终止作为其中一个重要环节,有着丰富的细节和不同的实现方式。今天,我们就深入探讨 Linux 进程终止的相关知识,带你全面了解进程退出场景、常见退出方法以及背后的原理 。
一、进程退出场景
(一)代码运行完毕,结果正确(不关心)
(二)代码运行完毕,结果不正确
虽然代码完整地执行了,但由于算法逻辑瑕疵、数据处理失误等原因,导致最终结果与预期不符。例如,在一个排序程序中,因排序算法实现有误,输出的序列并非有序的,进程虽然运行结束,但结果不正确,这提示我们需要去排查程序的逻辑错误。
(三)代码异常终止
这种情况就比较糟糕了,进程在运行过程中遇到了意外状况,无法继续正常执行而被迫终止。常见的如访问了非法内存地址( segmentation fault )、除零错误( floating point exception )等,系统会发送相应信号,让进程异常退出,以此来保护系统的稳定性和安全性 。此时不关心退出码,更关心为什么抛异常(进程收到对应信号)
进程是否正确可以通过return返回值进行判断,
返回值表示含义如下
二、进程常见退出方法
(一)正常终止
正常终止的进程,我们可以通过在终端执行 echo $? 命令来查看进程退出码,退出码能反映进程的退出状态。主要有以下几种方式:
1. 从 main 返回
在 C 语言程序中, main 函数是程序的入口,当 main 函数执行到 return 语句时,进程会正常终止。 return 的值会作为进程的退出码,父进程可以通过相关机制获取这个值,以此了解子进程的运行结果。例如:
#include <stdio.h>
int main() {printf("This is a simple program.\n");return 0; // 进程正常终止,退出码为 0
}
这里 return 0 表示程序正常运行结束,若 return 其他非零值,一般可用来表示程序运行中出现了某种自定义的“异常”情况,方便父进程做相应处理。
2. 调用 exit
exit 函数是一个常用的进程正常终止函数,其声明为:
#include <stdlib.h>
void exit(int status);
(注意:头文件是 <stdlib.h> ,不是文中提到的 <unistd.h> ,文中此处可能笔误 )
exit 函数在使进程终止前,会做一系列清理工作:
3. _exit
_exit 函数的声明为:
#include <unistd.h>
void _exit(int status);
参数 status 定义了进程的终止状态,父进程通过 wait 系列函数来获取该值。需要注意的是,虽然 status 是 int 类型,但仅有低 8 位可以被父进程所用。所以当 _exit(-1) 时,在终端执行 echo $? 会发现返回值是 255(因为 -1 的补码在低 8 位表示为 255 )。 _exit 函数相对比较“直接”,它不会像 exit 那样做复杂的清理工作,只是简单地终止进程,关闭进程相关的资源,把状态返回给父进程。
我们来看一个对比 exit 和 _exit 行为的实例:
示例 1:使用 exit 的情况
#include <stdio.h>
#include <stdlib.h>
int main() {printf("dlihmr"); exit(0);
}
这里 printf("hello"); 的输出内容,因为 exit 会刷新缓冲区,所以 “hello” 会被输出到终端。
示例 2:使用 _exit 的情况
#include <stdio.h>
#include <unistd.h>
int main() {printf("dlihmr"); _exit(0);
}
由于 _exit 不会刷新缓冲区, printf 输出的 “hello” 还在缓冲区中,没来得及输出到终端,进程就终止了,所以看不到输出内容 。缓冲区一定不在内核,而是在用户空间。
echo $?是返回上级最近进程的退出码
errno的使用
errno 是记录系统最后一次错误代码的机制 ,它本质是一个 int 类型的值,在 <errno.h> 头文件中定义 。当 Linux C 等环境下的 API 函数发生异常时,通常会将 errno 变量(使用时需包含 errno.h 头文件 )赋予一个整数值,不同数值对应不同错误含义,借助它能推测程序出错原因,是调试程序的重要手段。比如调用 open 函数打开文件失败,可通过查看 errno 知晓是文件不存在、权限不足等具体问题 。
(二)异常退出
最常见的就是我们在终端按下 ctrl + c ,这会向进程发送 SIGINT 信号(中断信号 ),进程接收到这个信号后会异常终止。当然,系统还有其他多种信号可以让进程异常退出,比如 SIGSEGV (段错误信号 )、 SIGFPE (浮点异常信号 )等,当进程触发相应的错误条件时,系统会发送这些信号,促使进程异常退出,以此来告知用户或父进程,程序运行出现了严重问题 。
结合kill演示
给予对应的kill退出指令,进程会按要求结束返回错误码
三、return 退出补充说明
return 是在函数中常用的退出方式,在 main 函数中,执行 return n 等同于执行 exit(n) 。因为调用 main 函数的运行时函数(可以简单理解为系统启动 main 执行的相关底层逻辑 )会将 main 的返回值当做 exit 的参数,进而按照 exit 的流程去终止进程。例如:
#include <stdio.h>
int main() {printf("Program is ending...\n");return 2; // 等同于 exit(2),进程终止,退出码为 2
}
这样的设计让我们在编写简单程序时,使用 return 就能方便地实现进程的正常终止,并且传递退出码 。
四、总结
进程终止在 Linux 进程管理中占据重要地位,不同的退出场景对应着程序不同的运行结果状态。而 return (在 main 中 )、 exit 、 _exit 等不同的退出方法,有着各自的特点和适用场景。 _exit 简单直接,适合对清理要求不高、追求快速终止的情况; exit 因为具备丰富的清理机制,在一般的应用程序中,当需要妥善处理资源释放、保证数据完整性时,更为常用; return 则在 main 函数中提供了简洁的退出方式,和 exit 有着紧密的联系 。
进程等待
为什么有进程等待
僵尸进程无法杀死,需要进程等待来消灭他,进而解决内存泄漏问题--必须解决的
我们要通过进程等待,获得子进程退出情况--知道我们给子进程的任务,他完成的怎么样--不必要
什么是进程等待
通过调用wait/waitpid,来进行堆子进程进行状态检测与回收功能!
怎么做到进程等待
代码:
原理:
我们发现,子进程在运行结束后成为僵尸进程,状态Z。
父进程通过调用wait/waitpid,来进行僵尸进程的回收问题。
wait
运行结果
我们发现5s后子进程变为僵尸进程,当再过5s父进程结束wait等待回收子进程pid,子进程成功释放。
因此,进程等待是必须的(回收僵尸进程,避免内存泄漏)
wait是等待任意子进程
父进程执行for循环,子进程内部创建
回收操作
子进程运行结束变为僵尸进程之后成功回收
如果子进程一直不死,那么父进程也不死,默认wait时,调用这个系统调用时不返回,默认叫做阻塞状态。(类似于scanf等待输入操作)
waitpid
wait的功能waitpid子集
pid_ t waitpid(pid_t pid, int *status, int options);
返回值:
当正常返回的时候waitpid返回收集到的子进程的进程ID;
如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;
如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;
参数:
pid:
Pid=-1,等待任一个子进程。与wait等效。
Pid>0.等待其进程ID与pid相等的子进程。
status:
WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
options:
WNOHANG: 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进程的ID。(见下文第三个参数)
第一个参数:等谁
第二个参数:获取进程退出结果(输出型参数)
操作系统会把id进程推出信息拷贝到status。上文exit(1)
但是status结果是256
子进程退出场景有三种,父进程期望获得子进程什么信息?
1.子女进程代码是否异常
2.没有异常,结果对吗?exitcode,不对为什么?通过退出码表示
statu返回256原因:
status36位,前8位表示进程是否出异常,收到什么样的信号,第8位cure dump
信号表,从1开始,若1-7位是不是0,说明 代码是否跑完
8-16表示退出状态,及退出码。1+0000 0000==256。通过statu反应代码是否异常。
父进程想获取子进程信息为什么需要系统调用,直接传全局变量不好吗??(进程独立性)。
位操作查看(区分256和1)
代码
结果
想让waitpid错误返回示例(父进程等待的子进程不是自己的)
第三个参数 (设置等待方式)
--0:阻塞:子进程退出不满足条件,父进程等待(阻塞,不被调用)
非阻塞轮询。(父进程等待过程中可以做别的事,不会一直扽等子进程)
演示: