“Almost Unlimited Modern C++ in Kernel-Mode Applications” 是对在内核模式(Kernel Mode)中使用现代 C++(C++11/14/17/20)技术的探索。这是一个高级、但越来越实用的话题,尤其是在 Windows 驱动开发、嵌入式系统、操作系统扩展、以及 kernel-mode 应用程序中。
传统上内核代码偏好 C 或 C with Classes,是因为:
传统顾虑 | 原因 |
---|---|
异常处理 | 会引入复杂的栈解构机制,不被内核支持 |
RTTI / vtable | 增加代码大小且带运行时风险 |
动态内存管理 | 不容易控制分配(尤其是在中断或高IRQL时) |
标准库依赖 | std::vector , std::string 等使用 malloc/new,不适合内核 |
模板膨胀 | 编译开销、代码尺寸膨胀难以控制 |
它意味着你可以在内核中使用几乎所有现代 C++ 特性,前提是:
特性 | 可用性 | 说明 |
---|---|---|
constexpr , if constexpr |
编译期优化,无运行时开销 | |
lambda 表达式 | 不捕获 lambda 安全可用 | |
模板 / 泛型编程 | 编译期行为,非常适合内核优化 | |
类型推导 (auto ) |
编译期行为 | |
范围 for (for(auto& x : ...) ) |
如果容器支持 | |
std::optional , std::variant |
✳ | 前提是用你自己的替代或移植库 |
智能指针 (unique_ptr ) |
✳ | 自定义 deleter 或 allocator |
RAII (析构资源管理) | 非常适合管理锁、资源释放等 | |
move 语义 | 没有运行时依赖,适合性能关键代码 | |
✳:你不能直接使用 libc++ 或 libstdc++,但可以实现或移植精简 STL 替代库。 |
特性 | 原因 |
---|---|
Exceptions | Windows 内核禁用 C++ 异常 |
RTTI(如 dynamic_cast ) |
无法启用;不安全;不必要 |
std::thread , std::mutex |
与用户空间线程模型耦合 |
流式 IO (std::cout ) |
依赖 C runtime,不能用 |
new/delete 默认操作 | 非 deterministic,不能直接用内核 heap |
template<typename T>
class kernel_allocator {
public:
using value_type = T;
T* allocate(size_t n) {
return static_cast<T*>(ExAllocatePoolWithTag(NonPagedPoolNx, n * sizeof(T), 'TAG1'));
}
void deallocate(T* p, size_t) {
ExFreePoolWithTag(p, 'TAG1');
}
};
template<typename T>
using kvector = std::vector<T, kernel_allocator<T>>;
要点 | 做法 |
---|---|
编译器支持 | 使用 MSVC 或 Clang with /kernel 编译标志 |
禁用异常 | /EHs- /GR- |
替代 STL | 使用 EASTL, STLPort, 或自己实现子集 |
内存管理 | 使用内核 API,如 ExAllocatePoolWithTag |
日志 / 输出 | 使用 DbgPrint 或 KdPrint 替代 std::cout |
你可以在 kernel mode 中使用大部分现代 C++ 技术,前提是:
“Tale as old as time… In fact, in Linux we did try C++ once already, back in 1992. It sucks. Trust me — writing kernel code in C++ is a BLOODY STUPID IDEA.”
这句话在操作系统社区非常有影响力,它反映了 Linus 对在内核中使用 C++ 的强烈反对。
问题 | 解释 |
---|---|
隐式行为过多 | C++ 有自动构造/析构、隐式复制、临时对象,这些行为在内核中难以控制和调试。 |
异常机制(Exception Handling)不可接受 | Linux 内核无法接受 throw / catch 带来的栈解构复杂性和性能成本。 |
运行时类型信息(RTTI)不可预测 | 引入 dynamic_cast 、虚函数表等运行时机制增加了不确定性和二进制膨胀。 |
编译器行为依赖过重 | 不同编译器对 C++ 特性的支持程度不同,而 Linux 内核要求高可移植性。 |
模板过度膨胀(Code Bloat) | 模板元编程在内核中容易导致二进制体积失控,且调试极其困难。 |
在别的上下文里,比如:
年代 | 主流观点 |
---|---|
1990s | C++ 太复杂,不适合内核 |
2000s | 以 C 为主,但开始关注安全和封装性 |
2010s | Windows 驱动广泛采用 C++ |
2020s | C++20/23 的某些特性(RAII、constexpr、concepts)被视为内核友好 |
很多现代操作系统爱好者或高可靠性内核项目(例如 seL4、Zircon、Redox OS)都在探索如何使用“受控的现代 C++”。 |
Linus 的批评不是对 C++ 本身的否定,而是对 C++ 在 Linux 内核中的不可控性 的否定。
“The fact is, C++ compilers are not trustworthy.”
“事实上,C++ 编译器不可信。”
意思是 C++ 编译器行为复杂、不透明,可能会在不经意间引入 bug,尤其是在底层系统编程中。
“They were even worse in 1992, but some fundamental facts haven’t changed:”
“它们在 1992 年更糟糕,但一些根本问题至今未变。”
指出尽管 C++ 编译器随着时间进步了,但本质上的一些缺陷仍然存在。
“- the whole C++ exception handling thing is fundamentally broken. It’s especially broken for kernels.”
“- C++ 的异常处理机制本质上是有缺陷的,尤其在内核中尤其糟糕。”
C++ 的异常处理(try/catch)会引入不可预测的控制流和隐藏的资源管理逻辑,这对于追求精确控制的内核代码来说是灾难性的。
“- any compiler or language that likes to hide things like memory allocations behind your back just isn’t a good choice for a kernel.”
“- 任何喜欢在背后隐藏内存分配等操作的编译器或语言,都不适合用于内核开发。”
C++ 常常在你不察觉的情况下自动分配内存(比如构造函数、STL 容器),这在内核开发中是不允许的,因为你必须对每一次内存分配有绝对控制。
“- you can write object-oriented code (useful for filesystems etc) in C, without the crap that is C++.”
“- 你可以用 C 写面向对象的代码(例如用于文件系统),而不用 C++ 那些‘垃圾’。”
这是说即便在 C 语言中,也可以实现面向对象的思想(如用结构体+函数指针模拟类和多态),无需引入 C++ 的复杂性和不可控行为。
这段话是从操作系统内核开发者的角度(可能是 Linus Torvalds)对 C++ 的批评。核心观点包括:
“It is ‘advanced’ C++ features such as non-POD (‘plain ol’ data’, as defined by the C++ standard) classes and inheritance, templates, and exceptions that present problems for kernel-mode code.”
“C++ 的一些‘高级’特性——如非 POD(Plain Ol’ Data,简单旧式数据)类、继承、模板和异常——会在内核模式代码中带来问题。”
这些特性会引入复杂的运行时行为,不适合资源敏感、控制精细的内核环境。
“These problems are due more to the C++ implementation and the kernel environment than to the inherent properties of the C++ language.”
“这些问题更多是由 C++ 的实现方式和内核的运行环境造成的,而不是 C++ 语言本身固有的问题。”
说明问题主要出在编译器生成的代码(如隐藏的构造/析构逻辑、异常表、模板展开)与内核运行时模型之间的不兼容。
“Anything involving class hierarchies or templates, exceptions, or any form of dynamic typing is likely to be unsafe.”
“任何涉及类层次结构、模板、异常或动态类型的东西,很可能是不安全的。”
原因是这些机制在底层的二进制代码中不可预测或依赖运行时支持,而内核代码往往没有这些支持。
“Using these constructs requires extremely careful analysis of the generated object code.”
“使用这些构造(features)时,必须对编译器生成的目标代码进行非常仔细的分析。”
强调:如果你非用不可,至少要精确地知道它们在汇编层面具体做了什么。
“Limiting use of classes to POD classes significantly reduces the risks.”
“将类的使用限制在 POD 类型上,会显著降低风险。”
POD 类本质上就是像 C 的结构体那样简单的数据容器——无虚函数、无继承、无构造析构副作用。
如果你打算在内核级别写 C++,要非常有意识地选择语言特性。推荐的做法是:
objdump
或 IDA
)。/kernel
编译选项。这个选项是为内核模块(如驱动程序)量身定做的,目的是确保生成的代码符合内核的稳定性和安全性要求。Visual C++ 的
/kernel
选项
这是一个专门用于内核开发的编译开关,会启用一系列限制来确保生成代码可以安全运行在内核模式下。
禁用异常处理 —— 使用 try/catch 会导致编译错误
异常处理机制依赖运行时栈展开、隐藏表格等复杂机制,这在内核中是不安全的也不可用的。
禁用运行时类型识别(RTTI)—— 使用 dynamic_cast 或 typeid 会导致编译错误
RTTI 会引入额外的运行时信息(如虚函数表的类型信息),不适合在资源受限、必须可预测的内核环境中使用。
必须替换 new 和 delete 运算符
内核不能使用标准的堆分配方式(如
malloc
或new
),需要使用内核特有的分配函数(如 Windows 内核中的ExAllocatePoolWithTag
和ExFreePool
)进行替换。
32 位编译需使用
/arch:IA32
在 32 位内核模式下,要求生成兼容的 IA-32 架构代码,不允许使用高级 SIMD 指令或不兼容指令。
64 位下不支持 AVX 指令集
即使你的 CPU 支持 AVX(Advanced Vector Extensions),内核模式下也不允许使用这些指令。原因是内核线程上下文切换时不能保证正确保存这些扩展寄存器。
2012 年的 Windows C++ 内核开发中,/kernel 编译选项 会对 C++ 特性做如下严格限制,以保证内核代码的稳定与安全:
限制项 | 原因 |
---|---|
禁用 try/catch | 异常机制不安全 |
禁用 RTTI | 运行时类型检查引入不可控行为 |
必须自定义 new/delete |
内核不能用标准堆 |
强制指定架构 | 保证兼容性和正确性 |
禁用 AVX | 避免复杂寄存器上下文切换问题 |
这进一步说明:在内核开发中,C++ 语言只能用它“最简单、最可控的子集”。你写的是“内核中的 C++”,不是“标准 C++”。 |
这说明 InTime 和 RTX 在核心机制上有很多相似之处。
两者都运行在 x86 / x64 架构(Intel 或 AMD CPU)上。
使用“CPU 分离”和“替代调度器”机制:
不与 Windows 共享传统中断(IRQs):
使用自定义的共享库(如 DLL):
这里强调 InTime 和 RTX 的执行模式不同:
InTime 的实时应用运行在用户模式(Ring 3):
RTX 的实时应用运行在内核模式(Ring 0):
特性 | InTime | RTX |
---|---|---|
执行模式 | 用户模式(Ring 3) | 内核模式(Ring 0) |
实时性能 | 较好,延迟低 | 更好,延迟更低 |
安全性/稳定性 | 高 | 较低(容易破坏系统) |
编写/调试难度 | 简单 | 较复杂 |
适用场景 | 一般实时性要求(如控制界面) | 高精度实时性(如伺服控制) |
InTime 和 RTX 都通过独立调度和 CPU 隔离,实现了在 Windows 上的实时性能,但 InTime 更安全易用,RTX 更接近硬实时,适合更严苛的控制场景。
| Partitioned Memory |
+-------------------+
| APP.1 | ← 绑定 CPU1 + 某块内存
|-------------------|
| APP.2 | ← 绑定 CPU2 + 某块内存
|-------------------|
| APP.3 | ← 绑定 CPU3 + 某块内存
|-------------------|
| APP.4 + OS | ← 绑定 CPU4 + 某块内存
+-------------------+
| Node A | Node B | Node C | Node D |
|--------|--------|--------|--------|
| CPU1 | CPU2 | CPU3 | CPU4 |
| OS | OS | OS | OS |
| APP.1 | APP.2 | APP.3 | APP.4 |
| I/O A | I/O B | I/O C | I/O D |
这是一个将 4 个 CPU 核心、内存和 I/O 资源静态隔离成多个独立节点或分区的架构,每个节点/分区可以运行自己的 OS 和 APP,互不干扰,适合高安全、高实时性的应用环境。
RTX(IntervalZero RTX/RTX64) 是一个让 Windows 系统具备实时能力 的扩展,它通过引入一个**并行运行的实时子系统(RTSS)**来实现硬实时控制。你的描述就是 RTX 的典型系统架构,其中:
┌──────────────────────────────┐
│ Windows Processes │
│ (GUI, services, non-RT apps) │
└────────────┬─────────────────┘
│
RtApi
│
┌──────────────────┼────────────────────┐
│ │ │
┌─────▼──────┐ ┌─────▼─────┐ ┌────▼─────┐
│ RTSS Tasks │ │ Rtxtcpip │ ... │ RT Drivers│
│ (real-time │ │ (RT TCP/IP│ │ │
│ apps) │ │ stack) │ │ │
└────────────┘ └───────────┘ └───────────┘
│ │ │
Real-Time Scheduler Memory Management IRQ Handling
│
┌─────▼─────┐
│ RT HAL Ext│
└─────┬─────┘
│
┌─────▼──────┐
│ Hardware │ (CPU, Memory, I/O)
└────────────┘
你提供的是 Windows + RTX 实时架构的模块图描述。它说明了 Windows 和 RTX 是如何共享平台但运行在分离的内核调度器和资源空间中,通过 API 实现数据交换和协作,从而既保证了 Windows 的通用性,又提供了可预测、低延迟的硬实时处理能力。
实时系统是一类在严格时间限制内完成任务的计算机系统,正确性不仅取决于结果本身,还取决于产生结果的时间。简单说:“快”不一定是实时,但实时一定要“准时”。
实时系统是在规定时间内必须对输入作出响应的系统,超过这个时间就视为失败。
类型 | 描述 | 示例 |
---|---|---|
硬实时系统 | 不能超时,一旦超时,系统功能将崩溃或产生灾难性后果。 | 飞机控制、心脏起搏器 |
软实时系统 | 尽量不要超时,但偶尔超时不会导致系统失效。 | 视频播放、在线游戏 |
特性 | 说明 |
---|---|
确定性 | 任务完成时间是可预测的 |
低延迟 | 响应迅速,毫秒级甚至微秒级 |
可靠性高 | 通常用于关键场合,不能容忍系统挂掉 |
资源隔离 | 常通过 CPU 分配、内存分区、I/O 专用等手段实现 |
调度可控 | 通常使用实时调度器,如 Rate Monotonic、EDF |
领域 | 说明 |
---|---|
工业控制 | PLC、机器人、传送带控制等 |
航空航天 | 飞行控制系统、导航 |
汽车电子 | 自动驾驶、刹车控制(ABS) |
医疗设备 | 心电图分析、手术辅助系统 |
音视频处理 | 实时音频效果器、视频编解码等 |
实时系统 ≠ 高性能系统
实时系统强调的是“在正确的时间做正确的事”,不是“尽量快”。
我们理解开发人员(TDMs)喜欢使用 Windows 提供的强大开发环境
→ Windows 开发体验确实好(IDE、调试工具、驱动支持等)。
但是,交叉编译(cross-compilation)完全可以让我们在 Windows 上开发、却部署到更可靠的 Linux 主机上运行。
虽然 Windows 提供了良好的开发工具,但它在任务关键、嵌入式或实时系统中表现出频繁重启、不稳定和难以调试等问题,建议采用更可靠的 Linux 系统进行部署。
参考链接:
使用现代 C++ 在嵌入式实时系统(RTOS)中是可能的,但要注意极端资源受限的环境。
典型硬件环境:
模式 | 特点 |
---|---|
Hosted | 标准完整支持(如在 Linux、Windows 上) |
Freestanding | 用于裸机、RTOS、内核模式。标准库支持非常有限。 |
<atomic>, <exception>, <initializer_list>, <limits>, <new>, <type_traits>,
<ciso646>, <cstddef>, <cfloat>, <climits>, <cstdint>, <cstdlib>,
<cstdarg>, <cstdalign>, <cstdbool>
实际实现 可能无法使用
,
,
等复杂功能。
核心警告:即便硬件很强,语言和标准库的限制仍存在,尤其在 freestanding 模式下。
尽管现代 C++ 在内核模式和嵌入式实时系统中具备巨大潜力(多核、高频、内存充裕),但历史经验清晰地提醒我们:滥用复杂特性、缺乏控制与规范,可能造成灾难性后果。
如你需要,我还可以进一步解释:
std::random_device
或 rand_s
时可能遇到的平台依赖性问题。下面是详细理解与拆解:std::random_device
是什么?std::random_device
是 C++11 引入的**“非确定性随机数生成器”**(如果可用)。它设计用于从系统级熵源(比如 /dev/urandom
、硬件 RNG)中获取随机值。
如果实现无法提供非确定性随机数源,它可以使用一个“伪随机数引擎”作为替代。
也就是说:
#include
int main() {
try {
std::random_device rd;
...
} catch (const std::exception&) {
// implementation-defined exception
}
}
这里的 try-catch 是为了捕获 std::random_device
构造失败的异常。标准没有规定具体会抛什么异常,异常类型是“实现定义的”(implementation-defined)。
rand_s
使用 RtlGenRandom()
(又名 SystemFunction036
)获取安全随机数。cryptbase.dll
或 advapi32.dll
,去找 SystemFunction036
。rand_s
会失败,返回 ENOMEM
(内存不足错误,但其实是找不到 API)abort()
这在某些精简版系统(如内核模式、嵌入式版 Windows)中很容易出现!
SystemFunction036
是什么?extern "C" BOOLEAN WINAPI SystemFunction036(
PVOID buffer,
ULONG buffer_count
);
这是 Windows 的内部加密 API,别名为 RtlGenRandom
,用于生成安全随机数。它不是标准 Win32 API,但被广泛使用在:
rand_s
(安全随机数)std::random_device
(在 MSVC 上的实现依赖于它)项目 | 问题 |
---|---|
std::random_device |
不一定总是可用,可能会 fallback(或抛异常) |
rand_s (MSVC) |
严重依赖 SystemFunction036 ,如果系统组件缺失 → 会失败 |
在内核/精简环境中使用 | 这些 API 很可能不可用,导致崩溃或无法获取随机数 |
std::random_device
和 rand_s
,除非你确信目标环境支持所需的底层 API。RDSEED
, RDRAND
)std::mt19937
)rand_s
在内核环境的困境SystemFunction036
(就是 Windows 里的安全随机数生成函数)rand_s
工作(但 SystemFunction036
异常)rand_s
始终返回失败(ENOMEM),避免假装成功导致错误#include
#include
#include
#include
std::uintmax_t file_size(const std::string& file) {
struct stat s{};
stat(file.c_str(), &s);
return s.st_size;
}
stat
函数rand_s
和随机数功能在内核或 RTX 中可能不可用或表现不稳定,建议谨慎使用。stat
这样的接口读取文件元信息,但在内核或 RTOS 中可用 API 也会更有限。SetCurrentDirectory
GetCurrentDirectory
GetFullPathName
PeekNamedPipe
FindClose
在内核模式或某些实时操作系统里:
..\folder\file
)char
类型字符(ASCII)wchar_t
(宽字符,支持 Unicode)你代码示例:
#include
int main() {
std::thread t([] { });
t.join();
}
是标准 C++11 线程库的使用示范。
理解:
支持可能缺失或者行为不稳定。std::thread
,需要依赖 RTOS 或内核提供的线程/任务API。std::thread
在 Visual C++ 上的问题DuplicateHandle
(Windows API,用于复制句柄)、以及互斥锁和条件变量的初始化代码std::thread
和相关同步原语不能直接使用。boost::thread
的情况WaitForMultipleObjectsEx
:Windows API,用于等待多个对象状态GetLogicalProcessorInformation
:获取 CPU 核心信息,确定物理并发度LocalFree
:释放由 FormatMessage
分配的内存boost::thread
依赖较少,且这些 Windows API 通常在用户模式存在,内核模式下可能缺失。system_error
与 LocalFree
system_error
的错误信息获取机制依赖 FormatMessage
FormatMessage
可能分配内存,释放需要 LocalFree
FormatMessage
自行分配缓冲区方面 | 说明 |
---|---|
std::thread 实现 |
依赖微软的并发运行时,内核模式难支持,导致多链接错误 |
boost::thread 实现 |
依赖较少,但仍依赖部分 Windows API,可能限制内核或实时使用 |
错误处理 | C++库错误消息依赖 Windows 内存管理函数,可能导致额外资源管理难题 |
线程栈 | 内核/RTOS线程栈更小,需注意栈空间节约 |
如果你想,我可以帮你: |
STACK_SIZE_PARAM_IS_A_RESERVATION
指示栈的保留内存。特性 | 支持情况 |
---|---|
原子操作(atomics) | 支持良好 |
锁守卫(lock guards) | 支持良好 |
异步操作(async) | 不支持 |
future、promise、packaged_task | 不支持 |
条件变量(condition_variable) | 不支持 |
互斥锁(mutex) | 不支持 |
协程(coroutines) | 不支持(额外幻灯片有说明) |
call_once | 状况不明 |
struct bar {
std::string s;
};
void foo() {
static bar b; // 静态局部变量,线程安全初始化问题
}
/Zc:threadSafeInit
编译选项。struct clock {
using duration = std::chrono::duration<int64_t, std::ratio<1, 10'000'000>>;
using rep = duration::rep;
using period = duration::period;
using time_point = std::chrono::time_point<clock>;
static constexpr bool is_steady = false;
static time_point now() noexcept;
};
struct hpet {
hpet() {
// 启用定时器
}
~hpet() {
// 恢复系统原状,关闭定时器,计数器清零
}
};
#include
bool bar();
void foo() {
if (!bar()) {
throw std::out_of_range("bar is upset");
}
}
#include
struct bar_upset_exception : std::exception {
const char* what() const noexcept override {
return "bar is upset";
}
};
bool bar();
void foo() {
if (!bar()) {
throw bar_upset_exception();
}
}
/EHa
编译选项支持从 SEH(Structured Exception Handling)抛出 C++ 异常。SetUnhandledExceptionFilter
捕获未处理异常。宏名 | 作用 |
---|---|
BOOST_STACKTRACE_USE_WINDBG |
使用COM接口显示调试信息 |
BOOST_STACKTRACE_USE_CACHED |
缓存COM实例以提高频繁采集时的性能 |
BOOST_STACKTRACE_USE_NOOP |
禁用调用栈追踪,stacktrace::size() 永远返回0 |
pow
)满足符号依赖。/d2MPX
。rdmsr
/wrmsr
访问性能监控寄存器。std::future
和 std::promise
的标准异步机制无法正常工作。assert
断言会尝试弹出消息框,这在内核模式或无图形环境下不适用。