介绍
在许多 C++ 程序中,main
函数#include
是世界,完全缺乏结构。本文介绍如何以结构化方式初始化系统。然后讨论如何通过快速重新初始化系统子集而不是重新启动其可执行文件来改进设计以支持从严重错误(通常是损坏的内存)中恢复。
使用代码
本文中的代码取自Robust Services Core (RSC)。如果这是您第一次阅读有关 RSC 某个方面的文章,请花几分钟时间阅读本前言。
初始化系统
我们将首先查看系统启动时 RSC 是如何初始化的。
模块
每个Module
子类代表一组相互关联的源代码文件,它们提供了一些逻辑能力。1这些子类中的每一个负责
- 实例化它所依赖的模块(在其构造函数中)
- 启用它所依赖的模块(在其
Enable
功能中) - 在启动可执行文件时初始化它所代表的源代码文件集(在其
Startup
函数中)
每个Module
子类当前与一个静态库一一对应。这运作良好,因此不太可能改变。必须在构建可执行文件之前定义静态库之间的依赖关系,因此很容易在模块之间应用相同的依赖关系。并且由于没有一个静态库很大,每个模块都可以很容易地初始化它所属的静态库。
下面是一个典型模块的概要:
如果每个模块的构造函数都实例化了它所依赖的模块,那么叶模块是如何创建的呢?答案是main
创造它们。的代码main
将很快出现。
启用模块
对模块框架的最新更改意味着实例化模块不再导致调用其Startup
功能。现在还必须启用Startup
一个模块,然后才能调用其功能。
就像模块的构造函数实例化它需要的模块一样,它的Enable
函数启用这些模块,也启用它自己。这引发了一个类似的问题:叶模块是如何启用的?答案是当叶模块调用基类Module
构造函数时,它必须提供一个唯一标识它的符号。然后可以通过在配置参数 OptionalModules
中包含此符号来启用叶模块,该参数可在配置文件中找到。
为什么要添加一个单独的步骤来启用已经在构建中的模块?答案是一个产品可以有许多可选的子系统,每个子系统都由一个或多个模块支持。在某些情况下,可能会为仅需要一组模块的特定角色部署产品。在其他情况下,产品可能会扮演多个角色,因此需要几组模块。必须为每个可能的角色组合创建一个独特的构建是一种管理负担。这种负担可以通过提供一个结合了所有角色的单一超集构建来避免。然后可以通过使用OptionalModules
配置参数以启用实际需要的模块。如果客户后来决定需要一组不同的角色,这可以通过简单地更新配置参数并重新启动系统来实现。
例如,考虑互联网。IETF 定义了许多支持各种角色的应用层协议。在大型网络中,支持其中许多协议的产品可能会部署为专用边界网关路由器。但在小型网络中,该产品还可以充当 DNS 服务器、邮件服务器 (SMTP) 和呼叫服务器 (SIP)。
模块注册表
单例ModuleRegistry
出现在上述构造函数的最后一行。它包含系统的所有模块,按它们的依赖关系排序(部分排序)。ModuleRegistry
还具有Startup
通过调用Startup
每个启用的模块来初始化系统的功能。
线程、根线程和初始化线程
在 RSC 中,每个线程都派生自基类Thread
,基类封装了一个本地线程,并提供了与异常处理、调度和线程间通信等相关的各种功能。
RSC 创建的第一个线程是RootThread
,它很快由 C++ 运行时系统创建的用于运行的线程创建main
。RootThread
只需将系统带到可以创建下一个线程的地步。该线程InitThread
负责初始化大部分系统。初始化完成后,InitThread
充当看门狗以确保正在调度线程,并RootThread
充当看门狗以确保InitThread
正在运行。
主要的()
在它回显并保存命令行参数后,main
只需实例化叶模块。RSC 目前有 15 个静态库,因此有 15 个模块。通过这些模块的构造函数传递实例化的模块不需要通过以下方式实例化main
:
如果基于 RSC 构建的应用程序不需要特定的静态库,则可以注释掉其模块的实例化,并且链接器将从可执行文件中排除该库的所有代码。即使库的模块被实例化,它仍然可以通过从OptionalModules
配置参数中排除其符号来禁用。
main
是唯一在静态库之外实现的代码。它位于rsc目录中,其唯一的源代码文件是main.cpp。所有其他软件,无论是框架的一部分还是应用程序,都驻留在静态库中。
根线程::主线程
最后一件事main
是调用RootThread::Main
,它是一个static
函数,因为RootThread
还没有被实例化。