指向固定地址的函数指针

Function pointers pointing to fixed addresses

我正在查看 MINIX 3 headers,在 include/signal.h 中有一些看似不寻常的定义:

/* Macros used as function pointers */
#define SIG_ERR ((sig_handler_t) -1)    /* error return */
#define SIG_DFL ((sig_handler_t) 0)     /* default signal handling */
#define SIG_IGN ((sig_handler_t) 1)     /* ignore signal */
#define SIG_HOLD ((sig_handler_t) 2)    /* block signal */ 
#define SIG_CATCH ((sig_handler_t) 3)   /* catch signal */

首先,SIG_ERR如何有效?其次(对于我的主要问题),源代码中还有其他指针映射到其中一些地址(例如 NULL)。如果取消引用这些指针之一会发生什么?这些地址中的数据是否有效?

像这样的转换不是由 C 标准定义的,但是您的特定实现允许它检查各种错误或 return 代码。

无法取消这些值,但可以将它们与针对各种信号处理函数 return 编辑的值进行比较。

这些不是内存地址,取消引用它们没有意义。它们是“魔法”值,表示这不是处理函数的地址,而是将信号处理状态设置为“运行 此函数”以外的指令。

选择的值与任何有效函数的地址不同,否则无法判断 signal 函数的调用者传递的是函数地址还是魔术价值。实际上,所有具有 MMU 的系统都安排在第一页中不映射任何内容,因此页面大小以下的地址不能是函数变量的地址。例如,这使 NULL 成为地址 0。

-1 通常映射到可能的最高地址(全位一),就像 (unsigned)(-1) 是全位一一样。但这是一个实现选择(不像 (unsigned)(-1),它是完全定义好的,因为无符号整数定义为模 2N 其中 N 是位大小)。例如,在某些 int 是 32 位类型但地址有 64 位的实现中,((sig_handler_t) -1) 将映射到地址 0xffffffff,这是函数的合理地址。

请注意,这些是操作系统实现者可以做的事情,因为他们知道指针在特定平台上的表示方式。 C 标准未指定指针的表示形式(具体而言,将整数转换为指针的效果是实现定义的)并且约束因系统而异。作为 C 程序员,您不能这样做(更准确地说:您 可以 ,但除非您确切地知道自己在做什么,否则很可能会出错)。您不仅必须知道特定平台如何表示指针以及它将整数转换为指针的方式,而且您还必须知道编译器对您的代码所做的假设。 OS 代码可能需要使用特定的编译器或特定的编译器标志进行编译,以启用必要的特定于实现的行为。

signal 系统调用以如下方式使用它们(大大简化,但您明白了):

enum signal_disposition {
    SIGNAL_DISPOSITION_IGNORE,
    SIGNAL_DISPOSITION_KILL,
    SIGNAL_DISPOSITION_RUN_HANDLER,
    SIGNAL_DISPOSITION_STOP,
};

sighandler_t sys_signal(struct task *calling_task, int signum, sighandler_t handler)
{
    if (signum > SIGMAX || signum == SIGKILL) return SIG_ERR;
    sighandler_t previous_handler =
        calling_task->signal_disposition == SIGNAL_DISPOSITION_IGNORE ? SIG_IGN :
        calling_task->signal_disposition == SIGNAL_DISPOSITION_RUN_HANDLER ?
        calling_task->signal_handler[signum] :
        SIG_DFL;
    if (handler == SIG_DFL) {
        calling_task->signal_disposition[signum] =
            signum == SIGTSTP ? SIGNAL_DISPOSITION_STOP :
            signum == SIGALRM ? SIGNAL_DISPOSITION_IGNORE :
            SIGNAL_DISPOTITION_KILL;
        calling_task->signal_handler[signum] = NULL;
    } else if (handler == SIG_IGN) {
        calling_task->signal_disposition[signum] = SIGNAL_DISPOSITION_IGNORE;
        calling_task->signal_handler[signum] = NULL;
    } else {
        calling_task->signal_disposition[signum] = SIGNAL_DISPOSITION_RUN_HANDLER;
        calling_task->signal_handler[signum] = handler;
    }
    return previous_handler;
}

这是内核中用于触发进程中信号的相应代码 运行(再次大大简化):

void handle_signal(struct task *calling_task, int signum) {
    switch (calling_task->signal_disposition[signum]) {
    case SIGNAL_DISPOSITION_IGNORE:
        break;
    case SIGNAL_DISPOSITION_KILL:
        kill_task(task, signum);
        break;
    case SIGNAL_DISPOSITION_RUN_HANDLER:
        task->registers->r0 = signum;
        task->registers->pc = calling_task->signal_handler;
        wake_up(task);
        break;
    case SIGNAL_DISPOSITION_STOP:
        stop_task(task);
        break;
    }
}