系统编程:进程的8种终止方式

2021-08-24  本文已影响0人  梁帆

一、5种正常终止

1.从main函数返回

int main()
{
    printf("Hello World!\n");
    return 0;
}

进程的返回值是给其父进程看的
如我们这里,父进程就是shell,程序返回的0就是由shell接收的。
在命令行输入:

echo $?

这条shell命令是打印上一条指令的执行状态,输出如下:

0

可以看到,父进程shell看到子进程的退出状态就是返回值0。
下面我们将return 0语句注释掉,如下:

int main()
{
    printf("Hello World!\n");
    //return 0;
}

这里我们再执行一下这个程序,再用shell命令行查下返回值,可以看到输出:

13

因为字符串"Hello World!\n",就占用了13个字符空间,printf函数的返回值就是返回的输出字符数目,即13。

2.调用exit

基本调用方法就是将exit(0)替换掉上文的return 0,如下:

int main()
{
    ......
    exit(0);
}

exit的函数声明是:

void exit(int status);

执行exit函数会导致进程正常地终止,并且会将 status & 0377 这个值返回给父进程。
0377是8进制,把它转换成二进制即为:

011,111,111

总共有8个1,status和低位8个1相与,意味着这个数值就是保留着低8位数值。而status是有符号型整数,就意味着8位数值能表示的范围是-127~128,即

exit能带回去的环境为256种,值范围是-127~128.

此外,exit()退出时会执行退出处理程序,如atexit()和on_exit()。两个函数大同小异,我们只举一个例子。

atexit():钩子函数

钩子函数的作用就相当于一些面向对象语言的析构函数。
atexit的函数声明如下:

int atexit(void (*function)(void));

钩子函数就类似注册函数,传入的参数是一个函数指针,函数指针的函数签名必须是void参数和void返回值。
有示例代码如下:

#include <stdio.h>
#include <stdlib.h>

static void f1(void)
{
    puts("f1 is working!");
}

static void f2(void)
{
    puts("f2 is working!");
}

static void f3(void)
{
    puts("f3 is working!");
}


int main()
{
    puts("Begin!");

    atexit(f1);     // 只是把函数f1、f2、f3挂到钩子上,还未被调用
    atexit(f2);
    atexit(f3);

    puts("End!");
                    // 在执行exit时,才会逆序调用挂在钩子上的函数    
    exit(0);
}

输出如下:

[root@localhost fs]# ./atexit 
Begin!
End!
f3 is working!
f2 is working!
f1 is working!

可以看到如我们所料,f1、f2和f3这三个函数,调用顺序和注册顺序是相反的。
那么钩子函数到底有什么作用呢?
钩子函数的作用就相当于go语言中的defer语句。
试想一下这样的情景,我们要依次open 100个文件,最后也需要在主进程末尾 close100次,这就显得很不美观,而且很容易漏掉。这个时候最好的方法就是open一个,挂钩子一个,如下:

fd1 = open("/tmp/file1", O_RDONLY);
atexit();    // --->close(fd1)

fd2 = open("/tmp/file2", O_RDONLY);
atexit();    // --->close(fd2)
...

3.调用_exit或_Exit

首先要明确一点:
exit是库函数,_exit和_Exit是系统调用。
_exit的函数声明:

#include <unistd.h>
void _exit(int status);

_Exit的函数声明:

#include <stdlib.h>
void _Exit(int status);

这两个函数相比exit()函数的区别在于:

_exit()和_Exit()是不执行任何钩子函数程序标准I/O清理程序的。eixt()函数会先调用钩子函数atexit(),再刷新stdio流缓冲区,最后由status提供的值执行_exit()系统调用。

有时候为了避免故障扩大,不想要exit的钩子调用和I/O清理,就可以直接调用_exit()函数。

4.最后一个线程从其启动例程返回

5.最后一个线程调用pthread_exit

二、3种异常终止

1.调用abort

2.接到一个信号并终止

3.最后一个线程对其取消请求作出响应

上一篇 下一篇

猜你喜欢

热点阅读