【发布时间】:2019-07-11 14:08:26
【问题描述】:
在回答问题之前进行快速的思考实验。想象一下有人正在实现 std::malloc(比如说,JEMalloc 或 TCMalloc 的人之一)。他们需要的最基本的东西之一是能够知道一旦执行进入 std::malloc 的实现,程序将不会回调到 malloc。
例如,
void* malloc(...) {
auto lck = std::unique_lock{malloc_mutex};
// .. memory allocation business logic
}
现在,如果在锁和分配的业务逻辑之间有信号,如果信号处理程序回调到 std::malloc,我们就会死锁。它不是为可重入而设计的,C++ 标准要求向 std::signal 注册的信号处理程序不回调到 operator new (可能回调到 malloc,因此需要用户定义的信号处理程序不会回调到 malloc,如果它被认为是可移植的语言的所有实现)。
最新版本的标准中的§[support.signal]p3 概述了这一要求
- 评估是信号安全的,除非它包含以下内容之一:
对任何标准库函数的调用,除了普通的无锁原子操作和明确标识为信号安全的函数。 [注意:这隐含地排除了依赖库提供的内存分配器的 new 和 delete 表达式的使用。 — 结束注释]
但是,C++ 标准似乎没有说明如何为执行线程实现函数堆栈(请参阅此问题:C++ threads stack address range),这意味着 std::malloc 实现中的函数调度可能会调用 @987654327 @如果程序是用分段堆栈编译的。
在这种情况下,如何实现std::malloc 之类的功能?如果 C++ 标准确实没有提供这样的保证,那又是什么呢?我们怎么知道一个正则函数的实现经过了正则的栈分配过程(栈指针递增)?哪个标准(例如 ABI、编译器、POSIX)涵盖了这一点?
【问题讨论】:
-
既然您在谈论信号,那么最相关的标准就是 POSIX 标准。它没有在其异步信号安全功能中列出
malloc。简而言之,malloc(或其兄弟)应该永远在信号处理程序中被调用。如果在信号处理程序中有这样的调用,那么这是编写的程序的错误,而不是作为malloc的实现者应该打扰的任何事情。 -
关于 POSIX,参见例如this official reference about signals.
-
@Someprogrammerdude 我的意思是少问一些关于 malloc 及其信号安全性的问题,但更多的是关于我在哪里可以保证函数堆栈不会对分配器进行一些疯狂的魔术调用,而只是用一个简单的堆栈指针递增/递减实现。似乎程序员应该能够以某种方式断言这个基本要求。但我正在阅读的任何标准似乎都没有在任何地方强制要求这一点。
-
@AlexeyFrunze 你的意思是没有 ABI 编译?严格的单独编译是针对 ABI。 ABI 中没有“有效类型”这样的东西。
标签: c++ multithreading stack language-lawyer abi