【问题标题】:OSX "Illegal instruction 4" with nested function具有嵌套功能的 OSX“非法指令 4”
【发布时间】:2013-08-27 21:43:03
【问题描述】:

这里的目标是捕获 SIGINT 以关闭小型套接字服务器上的服务器套接字。我尝试使用嵌套函数来保持代码清洁。但是……

当我执行 Ctrl-C(SIGINT,对吗?)时,我得到 Illegal instruction: 4。阅读this post 后,我尝试将-mmacosx-version-min=10.8 添加到编译标志中,因为我使用的是10.8。 Ctrl-C 时同样的错误。

这里有两个问题:为什么我会得到`Illegal instruction 4"?如何在不使用全局变量的情况下关闭服务器套接字?

我的软件:

Mac OSX 10.8.4
GCC 4.2.1

这是我的编译方式:

gcc -fnested-functions main.c

代码如下:

#include <sys/socket.h>
#include <unistd.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>

void register_sigint_handler(int *serverSocket)
{
    void sigint_handler(int signal) {
        printf("Shutting down...\n");
        printf("Server socket was %d\n", *serverSocket);
        close(*serverSocket);
        exit(0);
    }
    signal(SIGINT, &sigint_handler);
}

int main(void) {
    int serverSocket = 0, guestSocket = 0;

    register_sigint_handler(&serverSocket);

    serverSocket = socket(PF_INET, SOCK_STREAM, 0);

    while (1) {}

    close(serverSocket);
    return 0;
}

【问题讨论】:

  • 您是否尝试过使用调试器 (gdb/lldb)?
  • 这不会导致您的问题,但从信号处理程序调用 printf 是不安全的。信号处理程序在其中可以做的事情非常有限——最安全的做法是设置一个标志(volatile sigatomic_t 类型),然后在主循环期间定期检查该标志,并注意EINTR来自accept(2)select(2)等系统调用的错误代码。
  • @AdamRosenfield:由于代码使用的是套接字,它可能可以使用POSIX 提供的不太严格的规则,这些规则可以安全地从信号处理程序中调用。安全列表仍然不包括printf(),但它确实允许使用许多其他功能,包括write()。但是,您评论的主旨仍然准确:(1) 不要致电 printf() 等人,以及 (2) 在信号处理程序中的操作要谨慎。

标签: c macos gcc


【解决方案1】:

虽然我不能具体告诉你发生了什么,但gcc docs 有一个概括:

如果你试图通过它后面的地址调用嵌套函数 包含函数退出,所有的地狱都松散了。

将函数指针传递给 signal() 就可以做到这一点,在包含函数退出后调用本地函数。所以你不应该将嵌套函数指针传递给 signal()

您可能应该只为处理程序使用一个普通函数,该函数设置一个标志。

static volatile int do_exit;
void sigint_handler(int sig)
{
   do_exit = 1;
}

在服务器中,通常有某种主循环,例如select或poll,你的主循环,空的while循环现在可以变成

while (!do_exit) { 
  pause(); 
}

(注意socket在进程退出时会被操作系统自动关闭;)

【讨论】:

【解决方案2】:

Don't do that, then.

GCC 的nested-functions-in-C 扩展确实 提供真正的闭包。当你取sigint_handler的地址时,一个“蹦床”(一小段自修改代码)被写入堆栈; register_sigint_handler 一退出,蹦床就被销毁;随后尝试调用蹦床(通过内核,以调度信号)会导致未定义的行为。

根据定义,信号处理程序是进程全局的。因此,原则上是不正确的,试图避免在信号处理程序中使用全局变量。想象一下,为了使这段代码能够处理两个 服务器套接字,你会做些什么:你只能注册一个SIGINT 处理程序,因此它必须以某种方式关闭两个 套接字。

当您的进程终止时,所有打开的文件描述符都会自动关闭。因此,没有必要先手动关闭它们。此外,^C 上成功退出是违反惯例的。如果这个程序是由一个监督进程驱动的,这个进程会想知道(通过 @987654326 @ 的状态码)它因为 SIGINT 而退出。把这两件事放在一起,你根本不应该有这个信号处理程序

(如果您需要在退出时对活动的套接字一些事情,那就不再正确了。例如,如果您想在退出时为每个活动的客户端连接写一些东西,那么您需要一个信号处理程序。但此时您希望信号处理程序向主事件循环发出警报,并在那里完成工作。)

(考虑将libevent 用于此类事情,而不是自己做所有低级的事情。)

【讨论】:

    猜你喜欢
    • 2021-06-14
    • 1970-01-01
    • 1970-01-01
    • 2019-06-15
    • 2018-03-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-02-07
    相关资源
    最近更新 更多