我正在浏览MINIX 3标头,在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
)。如果取消引用这些指针之一会发生什么情况?这些地址中的数据是否有效?
这些不是内存地址,取消引用它们是没有意义的。它们是"魔术"值,指示这不是处理程序函数的地址,而是将信号处理状态设置为"运行此函数"以外的其他内容的指令。
选择这些值以不同于任何有效函数的地址,因为否则将无法判断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;
}
}
像这样的强制转换不是由 C 标准定义的,但是您的特定实现允许它检查各种错误或返回代码。
这些值不能被取消引用,但可以将它们与为各种信号处理函数返回的值进行比较。