美文网首页
系统编程:进程的8种终止方式

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

作者: 梁帆 | 来源:发表于2021-08-24 15:55 被阅读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.最后一个线程对其取消请求作出响应

    相关文章

      网友评论

          本文标题:系统编程:进程的8种终止方式

          本文链接:https://www.haomeiwen.com/subject/zdudiltx.html