Linux 进程终止

引入

在写 C 语言程序的时候,我们必写的结构就是:

c 复制代码
int main()
{
	return 0;
}

在学习 C 语言的时候,我们好像并没有讨论过这个 return 0 有什么用,是干什么的!return 1 可以吗?return 的返回值给谁看?这样的问题!

那么今天我们就会浅浅地解决一下这些问题!

进程退出的场景

一个进程被创建出来就是用来执行特定任务的!比如你写了一个快速排序的代码,他的任务就是排序!那么当这个进程退出的时候会有以下三种情况:

  1. 代码执行完毕,结果正确。
  2. 代码执行完毕,结果不正确。
  3. 代码异常终止。

设想一下,如果你的代码执行完毕,结果正确!我们还要不要关心他为什么正确呢?就比如你考试考了满分💯,你的家长要不要问你为什么要考满分?

因此,只有当进程没有完成任务,我们才会去关心:

  • 代码执行完毕结果不正确,那么是那里不正确呢?
  • 代码异常终止了,又是哪里出了问题呢?

回到一开始的问题,main 函数中 return 的值有什么意义呢?如果 main 函数返回 0,代表代码执行完毕,结果正确(约定俗称 0 表示这个意义);如果 return 返回其他值,我们就能通过返回值的不同得到进程退出时出错的原因!我们称进程结束时,返回给操作系统的数值称为退出码!

进程的退出码

退出码表征了进程运行的状态/结果,关心这个退出码的一定是创建这个进程的父进程!其实本质上关心出错原因的应该是用户!

Linux 操作系统中,有一组通用的退出码以及其大致含义:

  • 0:成功,表示程序或者命令成功执行。
  • 1:通用错误,通常表示一般性错误,没有特定的详细信息。
  • 2:误用命令,表示命令的使用方式错误。
  • 126:表示命令无法执行。
  • 127:表示系统找不到要执行的命令

如果你不想用系统的,进程的退出码可以自定义,我们可以自定义退出码的描述信息,比如你可以设置进程退出码为 1 表示什么什么错误;退出码为 2 表示什么什么错误 ······

查看进程的退出码

想要查看进程的退出码可以使用:

bash 复制代码
echo $?

该命令可以查看最近的一个进程的退出码!

return 设置退出码

在下面的代码中我们通过 return 11 将退出码设置为 11。./test 运行程序,使用 echo $? 打出了最近的一个进程的退出码,的确是 11。我们发现再次使用 echo $? 打印出来的结构就变成了 0,这是为什么呢?因为 echo 本身也是一个进程嘛,在运行 ./test 之后第二次使用 echo 这个 echo 就是最近的那个进程啦~而 echo 又是成功运行的,当然就是 0 啦!

return 设置错误码仅限在 main 函数中呢!因为在其他函数中 return 仅仅表示结束当前函数嘛!

exit 设置退出码

exit 函数可以直接结束当前进程,在任何地方!参数用来设置退出码!

在下面的代码中,我们定义了一个函数 Div,在函数体中检测到除数如果为 0 我们直接终止进程,退出码设置为 1,如果你自定义了退出码 为 1 时为除零错误,就能更好的展示错误信息啦!

c 复制代码
#include<stdio.h>
#include<stdlib.h>

int Div(int x, int y)
{
    if(y == 0)
        exit(1);
    return x / y;
}

int main()
{
    Div(1, 0);
    return 0;
}

我们看到退出码为 1,并不是 0 说明,exit() 的确能够终止进程并设置退出码的!

补充:exit VS _exit

相同点:

  • 可以在程序的任意位置终止进程。
  • 可以设置进程的退出码

不同点:

  • exit 是 C 语言的库函数(#include <stdlib.h>),_exitLinux 操作系统的系统调用函数(#include<unistd.h>)。
  • exit 函数除了终止进程,设置进程的退出码之外,还会执行用户定义的清理函数,冲刷缓冲区,关闭流等!;_exit 仅仅是终止进程,设置进程的退出码!

我们可以用下面的程序进行验证:

我们使用 printf 函数打印 "hello linux" 这个字符串,不加换行符的打印哦!然后使用 exit 函数终止进程!

c 复制代码
#include<stdio.h>
#include<stdlib.h>

int main()
{
    printf("hello linux");
    exit(11);
    return 0;
}

我们可以看到顺利将 hello linux 这个字符串打印出来了!

再来看下面的程序:同样是不带换行符打印 hello linux 这次我们使用 _exit 终止进程!

c 复制代码
#include<stdio.h>
#include<unistd.h>


int main()
{
    printf("hello linux");
    _exit(11);
    return 0;
}

我们看到使用 _exit 函数终止进程没有打印 hello linux

这是为什么呢?我们知道在 Linux 操作系统中,printf 的刷新策略是行刷新!也就是说在没有遇到换行符或者没有将缓冲区写满之前,printf 的数据都是不会刷新到显示器上的!结合 exit 会在进程终止之前刷新缓冲区!因此我们可以得出结论:缓冲区绝对不在内核中!而是在用户区!

如果缓冲区是在内核中的话,_exit 也一定会把数据刷新到显示器!不然为什么要向内核中写数据呢!!!


可见:exit 函数是在刷新缓冲区,关闭流等工作完成之后调用的 _exit 系统调用。

在平时的使用中还是建议使用 exit 因为刷新缓冲区,关闭流的确是需要的!

错误码

再讲完了退出码,就不得不提错误码的概念!错误码通过全局变量 errno 来表示,errno 是一个整数,用于最近一次系统调用或者库函数导致的错误,这个错误提供了一种标准化的方式来处理和传递错误信息!

我们也不知道系统提供了多少错误码,我们可以打印错误码,以及他的描述信息来看看!查看错误码对应描述信息可以使用 strerror 函数哈!

c 复制代码
#include<stdio.h>
#include<string.h>

int main()
{
    for(int i = 0; i < 200; i++)
    {
        printf("%d: %s\n", i, strerror(i));
    }
    return 0;
}

我们看到错误码系统提供到了 133 号!

如果你不喜欢系统提供的错误码,也可以自定义错误码对应的描述信息!


我们来使用使用错误码:

我们调用 fopen 函数以读的方式打开一个不存在的文件,在 fopen 函数的描述中,可以看到如果有错误,错误码就会被设置,我们可以通过 errno 变量来打印错误的描述信息。也可以通过 perror 函数来打印错误的描述信息,这些都是在 C 语言阶段学习过的,就不详细解释了!

errno 的使用需要包含头文件:#include<errno.h>

c 复制代码
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>

int main()
{
    extern int errno;
    fopen("test.txt", "r");
    printf("%d: %s\n", errno, strerror(errno));
    perror("fopen");
    return 0;
}

代码异常

如果代码出现了异常,那么退出码还有意义嘛?

代码异常说明代码很可能没跑完,return 语句都没有执行,用户都不知道这个退出码怎么来的,因此是没有任何意义的!就比如你作弊考了 100 分,你爸会关心你的 100 分嘛,直接就是辣椒炒肉了!

代码异常引起的进程终止,我们要关心的是为什么异常了!发生了什么异常!

Linux 中代码异常的本质是进程收到了信号!信号我们后续会详解!之前我们不是还用过 9 号信号嘛:杀死一个进程!

我们可以写一个会发生异常的代码:

c 复制代码
#include<stdio.h>

int main()
{
    int a = 10;
    a /= 0;

    return 0;
}

怎么验证代码异常的本质是进程收到信号了呢?很简单只需要我们手动给进程发信号就行了,上面的异常是几号信号呢?我们使用 kill -l 查看一下所有信号,仔细找找发现是 8 号信号:

因此我们手动给进程发 8 号信号看看结果:

c 复制代码
#include<stdio.h>
#include<unistd.h>

int main()
{
    while(1)
    {
        printf("hello linux, pid: %d\n", getpid());
        sleep(1);
    }

    return 0;
}

可以看到发送 8 号信号之后,进程也是出现了浮点异常!说明代码异常的本质就是进程受到了信号

知识点小结:

  • 进程终止的三种情况
  • 退出码
  • 错误码
相关推荐
阿赭ochre13 分钟前
Linux环境变量&&进程地址空间
linux·服务器
honey ball13 分钟前
仪表放大器AD620
运维·单片机·嵌入式硬件·物联网·学习
Iceberg_wWzZ14 分钟前
数据结构(Day14)
linux·c语言·数据结构·算法
秋已杰爱16 分钟前
进程间关系与进程守护
运维·服务器
微尘816 分钟前
C语言存储类型 auto,register,static,extern
服务器·c语言·开发语言·c++·后端
可儿·四系桜29 分钟前
如何在多台Linux虚拟机上安装和配置Zookeeper集群
linux·服务器·zookeeper
Flying_Fish_roe33 分钟前
linux-软件包管理-包管理工具(Debian 系)
linux·运维·debian
BLEACH-heiqiyihu1 小时前
红帽9中nginx-源码编译php
运维·nginx·php
大广-全栈开发1 小时前
centos 7 安装gitlab
linux·git·centos
666786661 小时前
Mysql高级篇(中)—— SQL优化
linux·运维·服务器·数据库·sql·mysql