目录
[1. Console (/SUBSYSTEM)](#1. Console (/SUBSYSTEM))
[2. Windows (/SUBSYSTEM)](#2. Windows (/SUBSYSTEM))
[Windows 应用程序的典型特征](#Windows 应用程序的典型特征)
[3. Native (/SUBSYSTEM)](#3. Native (/SUBSYSTEM))
[4. EFI Application (/SUBSYSTEM)](#4. EFI Application (/SUBSYSTEM))
[EFI 应用程序的典型特征](#EFI 应用程序的典型特征)
[5. EFI Boot Service Driver (/SUBSYSTEM)](#5. EFI Boot Service Driver (/SUBSYSTEM))
[EFI 启动服务驱动程序的典型特征](#EFI 启动服务驱动程序的典型特征)
[6. EFI Runtime Driver (/SUBSYSTEM)](#6. EFI Runtime Driver (/SUBSYSTEM))
[EFI 运行时驱动程序的典型特征](#EFI 运行时驱动程序的典型特征)
[7. EFI ROM (/SUBSYSTEM:EFI_ROM)](#7. EFI ROM (/SUBSYSTEM:EFI_ROM))
在Visual Studio中项目->链接器->系统->子系统可以指定应用程序的执行环境,下面来介绍下这个。
1. Console (/SUBSYSTEM)
用途
/SUBSYSTEM:CONSOLE
子系统用于开发控制台应用程序。控制台应用程序通常在命令行界面(CLI)中运行,可以通过标准输入(stdin)和标准输出(stdout)与用户交互。这种类型的应用程序适合处理文本数据、文件操作、脚本执行、系统工具等任务。
描述
当使用 /SUBSYSTEM:CONSOLE
子系统时,程序将被配置为在控制台窗口中运行。Windows 会为该程序自动分配一个控制台窗口,或者如果程序是在已有控制台窗口中启动的(例如从命令提示符启动),则继续使用该控制台窗口。
-
入口点 : 对于控制台应用程序,默认的入口点是
main
或wmain
函数。main
函数用于标准的ASCII字符集。wmain
函数用于Unicode字符集(宽字符),可以处理宽字符的命令行参数。
-
输入输出 : 控制台应用程序通过标准输入输出流与用户交互。可以使用
printf
或std::cout
输出文本,也可以使用scanf
或std::cin
接收用户输入。
控制台应用程序的典型特征
- 没有图形用户界面(GUI),所有交互都通过文本完成。
- 常用于脚本、批处理文件、命令行工具、系统工具和开发调试。
- 在后台运行时,可以重定向输入输出流到文件或其他进程。
下面是一个简单的控制台应用程序示例,展示了如何使用 main
作为入口点,处理命令行参数并与用户进行简单的输入输出交互。
cpp
#include <iostream>
#include <string>
int main(int argc, char* argv[]) {
// 打印命令行参数
std::cout << "Number of arguments: " << argc << std::endl;
for (int i = 0; i < argc; ++i) {
std::cout << "Argument " << i << ": " << argv[i] << std::endl;
}
// 获取用户输入
std::string name;
std::cout << "Enter your name: ";
std::getline(std::cin, name);
// 输出问候语
std::cout << "Hello, " << name << "!" << std::endl;
return 0; // 程序以状态码0成功退出
}
2. Windows (/SUBSYSTEM)
用途
/SUBSYSTEM:WINDOWS
子系统用于开发 Windows 图形用户界面(GUI)应用程序。这类应用程序通过窗口与用户交互,而不是通过命令行。常见的桌面应用程序(如文本编辑器、浏览器、游戏等)通常使用这个子系统。
描述
当使用 /SUBSYSTEM:WINDOWS
子系统时,应用程序以窗口模式运行,而不是在控制台窗口中执行。Windows 不会为应用程序自动创建控制台窗口,默认情况下也不会显示命令行界面。相反,应用程序通常提供一个或多个窗口,通过这些窗口来显示图形界面并与用户交互。
- 入口点 : 对于 Windows GUI 应用程序,默认的入口点是
WinMain
或wWinMain
函数。WinMain
: 使用标准的ASCII字符集。wWinMain
: 使用Unicode字符集(宽字符),通常用于处理国际化字符串。
Windows 应用程序的典型特征
- 有图形用户界面,与用户的交互通过窗口、按钮、菜单等控件完成。
- 适用于需要复杂用户界面和多媒体功能的应用程序。
- 没有控制台窗口,所有输入输出都是通过窗口控件或图形界面完成。
下面是一个简单的 Windows GUI 应用程序示例,展示了如何使用 WinMain
作为入口点,创建一个基本的窗口,并在其中显示一条消息。
cpp
#include <windows.h>
// 窗口过程函数,用于处理窗口消息
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
switch (uMsg) {
case WM_DESTROY:
PostQuitMessage(0);
return 0;
case WM_PAINT: {
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
// 在窗口中显示一段文本
TextOut(hdc, 50, 50, L"Hello, Windows GUI!", 19);
EndPaint(hwnd, &ps);
}
return 0;
default:
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
// 定义窗口类
const wchar_t CLASS_NAME[] = L"Sample Window Class";
WNDCLASS wc = {};
wc.lpfnWndProc = WindowProc; // 指定窗口过程函数
wc.hInstance = hInstance;
wc.lpszClassName = CLASS_NAME;
RegisterClass(&wc);
// 创建窗口
HWND hwnd = CreateWindowEx(
0,
CLASS_NAME,
L"Sample Windows Application",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, 800, 600,
nullptr,
nullptr,
hInstance,
nullptr
);
if (hwnd == nullptr) {
return 0;
}
ShowWindow(hwnd, nCmdShow);
// 消息循环
MSG msg = {};
while (GetMessage(&msg, nullptr, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
代码解析
-
窗口过程函数 (
WindowProc
):- 这是 Windows 应用程序的核心,它处理来自系统的消息,如键盘输入、鼠标点击、窗口绘制等。在这个例子中,
WindowProc
处理了两个消息:WM_DESTROY
: 当窗口关闭时,程序发出退出消息。WM_PAINT
: 当窗口需要绘制时,程序在窗口中显示一段文本。
- 这是 Windows 应用程序的核心,它处理来自系统的消息,如键盘输入、鼠标点击、窗口绘制等。在这个例子中,
-
WinMain 函数:
- 这是应用程序的入口点。
WinMain
函数用于初始化窗口、注册窗口类、创建主窗口,并启动消息循环。
- 这是应用程序的入口点。
-
注册窗口类:
WNDCLASS
结构体用于定义窗口的特性,如窗口过程函数、实例句柄、窗口类名称等。通过调用RegisterClass
注册窗口类。
-
创建窗口:
CreateWindowEx
函数创建主窗口,指定窗口类、窗口标题、窗口样式及窗口的初始位置和大小。
-
显示窗口:
ShowWindow
函数显示窗口,nCmdShow
参数指定窗口显示的方式(如正常显示、最小化、最大化等)。
-
消息循环:
GetMessage
,TranslateMessage
, 和DispatchMessage
共同组成消息循环,处理所有发往窗口的消息,使得应用程序可以响应用户的操作。
总结
/SUBSYSTEM:WINDOWS
是用于开发 Windows 图形用户界面应用程序的子系统选项。这种应用程序通过窗口与用户交互,通常适用于桌面应用程序、图形工具和游戏开发等场景。使用 Visual Studio 或其他 IDE 可以轻松创建和调试此类应用程序,通过处理消息循环和窗口过程函数实现与用户的交互。
3. Native (/SUBSYSTEM)
用途
/SUBSYSTEM:NATIVE
子系统用于开发直接在 Windows NT 操作系统的内核模式下运行的应用程序或驱动程序。这些程序通常是操作系统的一部分,或者是用于实现底层系统功能的驱动程序。与通常在用户模式下运行的应用程序不同,/SUBSYSTEM:NATIVE
的程序在内核模式下运行,不依赖于标准的用户模式环境(如 Win32 API 和 GUI 界面)。
描述
应用程序在 /SUBSYSTEM:NATIVE
下编译后,不会像常规的用户模式程序那样运行。相反,它们在系统引导期间或作为特定的系统组件被加载和执行。通常,这种类型的程序只有在开发操作系统组件、文件系统驱动程序、设备驱动程序或其他与操作系统核心交互的低级别程序时才会使用。
由于这些程序直接与操作系统内核交互,因此它们具有极高的权限,同时也有可能影响整个系统的稳定性。因此,开发这类程序要求开发人员具备深入的系统编程知识,并且要非常小心地进行调试和测试。
注意
普通应用程序开发者通常不会使用 /SUBSYSTEM:NATIVE
,因为它要求对操作系统内部机制有深入理解,并且稍有不慎可能导致系统崩溃。
程序示例
编写一个实际使用 /SUBSYSTEM:NATIVE
的程序是比较复杂的,因为它通常涉及编写设备驱动程序或系统服务。然而,为了展示基本概念,这里提供一个极简的示例,说明如何创建一个能够在内核模式下执行的"Hello World"类型的内核模式程序。
这个示例是一个内核模式驱动程序的基本模板。需要注意的是,开发和运行内核模式驱动程序具有很高的风险,可能会导致系统崩溃或蓝屏,因此在实际开发中需要非常小心。
cpp
#include <ntddk.h>
// 驱动入口点
extern "C" NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) {
UNREFERENCED_PARAMETER(RegistryPath);
// 打印调试消息到调试器
DbgPrint("Hello, Kernel World!\n");
// 设置卸载例程
DriverObject->DriverUnload = DriverUnload;
return STATUS_SUCCESS;
}
// 卸载例程
extern "C" VOID DriverUnload(PDRIVER_OBJECT DriverObject) {
UNREFERENCED_PARAMETER(DriverObject);
// 打印卸载消息到调试器
DbgPrint("Driver Unloaded!\n");
}
代码解析
-
DriverEntry 函数:
DriverEntry
是内核模式驱动程序的入口点函数,类似于用户模式应用程序中的main
函数。当驱动程序被加载时,操作系统会调用此函数。- 在这个例子中,
DriverEntry
主要用于初始化驱动程序,设置一些基本的驱动参数,并注册卸载例程。 DbgPrint
函数用于向调试器输出调试信息。开发人员可以使用调试工具(如 WinDbg)查看这些输出。
-
DriverUnload 函数:
DriverUnload
是驱动程序的卸载例程。当驱动程序被卸载时,系统会调用这个函数。- 在此例中,
DriverUnload
函数仅用于向调试器输出一条消息,表示驱动程序已卸载。
-
UNREFERENCED_PARAMETER:
- 由于未使用
RegistryPath
参数,所以用UNREFERENCED_PARAMETER
宏来避免编译器的未使用参数警告。
- 由于未使用
编译和运行
编译:
- 创建一个新的"空项目"或"Windows Driver"项目。
- 将上述代码粘贴到项目中。
- 在项目属性中,将"链接器 -> 系统 -> 子系统"设置为
Native (/SUBSYSTEM:NATIVE)
。 - 编译项目。编译生成的文件将是
.sys
文件,即 Windows 内核模式驱动程序。
运行:
- 警告 : 内核模式驱动程序开发和调试具有高风险,可能会导致系统崩溃或蓝屏。
- 要运行内核模式驱动程序,必须以管理员身份加载驱动程序。可以使用
sc
命令或通过设备管理器加载驱动程序。 - 使用调试工具(如 WinDbg)连接到系统并监视调试输出。
例如,使用 sc
命令加载驱动程序:
cpp
sc create MyDriver binPath= "C:\path\to\your\driver.sys" type= kernel
sc start MyDriver
总结
/SUBSYSTEM:NATIVE
是用于开发内核模式驱动程序和其他低级别系统组件的子系统选项。使用这个子系统开发的程序直接在操作系统内核模式下运行,具有极高的权限和复杂性。开发此类程序需要对 Windows 内核和驱动开发有深入的了解,并且需要严格的测试和调试,以确保系统稳定性。
这类开发主要用于操作系统开发者、设备驱动程序开发者或需要实现深度系统集成的场景。普通应用程序开发者通常不会接触到这个子系统。
4. EFI Application (/SUBSYSTEM)
用途
/SUBSYSTEM:EFI_APPLICATION
子系统用于开发在 EFI(Extensible Firmware Interface)环境下运行的应用程序。EFI 是 BIOS 的继任者,提供了更强大的接口用于初始化硬件和加载操作系统。EFI 应用程序通常是在系统启动过程中执行的低级别程序,它们运行在操作系统加载之前,具有对系统硬件的直接访问能力。
描述
EFI 应用程序通常是用于特定任务的工具或实用程序,例如系统诊断、固件更新、启动管理等。它们在 EFI 环境中运行,而不是在传统操作系统环境中。EFI 应用程序可以从 EFI shell 中运行,也可以由固件自动加载。
-
入口点 : EFI 应用程序的入口点是
efi_main
或者EfiMain
函数,而不是传统的main
或WinMain
。EFI 环境为程序提供了一个EFI_SYSTEM_TABLE
,通过它可以访问各种 EFI 服务和功能。 -
硬件访问: EFI 应用程序可以直接与硬件交互,例如访问磁盘、网络设备,甚至修改系统配置。这使得 EFI 应用程序在启动过程中能够执行许多关键任务。
EFI 应用程序的典型特征
- 无操作系统依赖,可以直接与系统硬件交互。
- 通常用于固件级任务,例如启动管理器、系统恢复工具、诊断工具等。
- 可以通过 EFI Shell 手动启动,也可以配置为系统自动加载。
下面是一个简单的 EFI 应用程序示例,展示如何编写一个基础的 "Hello, EFI World!" 程序。
cpp
#include <efi.h>
#include <efilib.h>
// EFI 应用程序的入口点
EFI_STATUS EFIAPI efi_main(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable) {
// 初始化 EFI 库
InitializeLib(ImageHandle, SystemTable);
// 输出 "Hello, EFI World!" 消息
Print(L"Hello, EFI World!\n");
// 等待按键
SystemTable->ConIn->Reset(SystemTable->ConIn, FALSE);
EFI_INPUT_KEY Key;
SystemTable->ConIn->ReadKeyStroke(SystemTable->ConIn, &Key);
return EFI_SUCCESS; // 返回成功状态
}
代码解析
-
EFI 库的初始化 (
InitializeLib
):InitializeLib
是用于初始化 EFI 库的函数。它将ImageHandle
和SystemTable
注册为全局变量,使得 EFI 环境中的服务函数可以使用这些资源。
-
打印消息 (
Print
):Print
函数是一个 EFI 库函数,类似于标准 C 的printf
,它将消息输出到控制台。在 EFI 环境中,输出是直接发送到控制台或相关输出设备的。
-
等待按键:
- 该示例使用
ReadKeyStroke
函数等待用户按下任意键,然后程序结束。这是为了在输出消息后暂停程序,便于用户查看输出。
- 该示例使用
-
返回值:
- 程序返回
EFI_SUCCESS
表示执行成功,这是 EFI 应用程序的惯例做法。
- 程序返回
编译和运行
编译
要编译这个 EFI 应用程序,你需要一个支持生成 EFI 二进制文件的编译工具链,如 GCC for UEFI 或 Visual Studio 的适当配置。
使用 GCC 编译器的步骤如下:
- 安装 GCC for UEFI(可通过
gnu-efi
库)。 - 将上述代码保存为
hello_efi.c
。 - 编译代码生成 EFI 可执行文件 (
.efi
文件)。
cpp
gcc -I/path/to/gnu-efi/inc -fpic -fshort-wchar -mno-red-zone -ffreestanding -c hello_efi.c -o hello_efi.o
ld -nostdlib -znocombreloc -T /path/to/gnu-efi/gnuefi/elf_x86_64_efi.lds -shared -Bsymbolic -L /path/to/gnu-efi/lib -lgnuefi -lefi -o hello_efi.so hello_efi.o
objcopy -j .text -j .sdata -j .data -j .dynamic -j .dynsym -j .rel -j .rela -j .reloc --target=efi-app-x86_64 hello_efi.so hello_efi.efi
运行
- 将生成的
hello_efi.efi
文件复制到 USB 驱动器或其他 EFI 文件系统。 - 进入目标计算机的 BIOS 设置,配置从 USB 驱动器启动,或使用 EFI Shell 直接运行该文件。
- 在 EFI Shell 中,导航到文件所在目录并执行
hello_efi.efi
。
cpp
fs0:
cd \path\to\efi
hello_efi.efi
程序运行后,你应该会在屏幕上看到 "Hello, EFI World!" 的消息,程序会等待用户按下一个键后退出。
总结
/SUBSYSTEM:EFI_APPLICATION
子系统用于编写运行在 EFI 环境中的应用程序,这些应用程序通常在操作系统加载之前执行,并且能够直接与系统硬件交互。EFI 应用程序在启动过程中的作用至关重要,适用于固件级任务、启动管理、系统恢复等领域。开发 EFI 应用程序要求开发者具备底层系统编程技能,并使用合适的编译工具链生成 .efi
可执行文件。
5. EFI Boot Service Driver (/SUBSYSTEM)
用途
/SUBSYSTEM:EFI_BOOT_SERVICE_DRIVER
子系统用于开发 EFI 启动服务驱动程序。这类驱动程序专门设计用于在 EFI 环境中的启动服务阶段运行。EFI 启动服务(Boot Services)提供了一组 API,用于在操作系统启动之前进行设备初始化、硬件抽象和其他低级别任务。EFI 启动服务驱动程序通常用于硬件设备的初始化或设置,这些操作必须在操作系统接管控制之前完成。
描述
EFI 启动服务驱动程序是专门为在 EFI 环境的启动服务阶段加载和运行的驱动程序。它们通常负责初始化硬件设备,使这些设备能够在系统引导过程中正常工作。一旦操作系统开始加载,EFI 启动服务将被禁用,所有启动服务驱动程序也会被卸载。
-
入口点 : 与其他 EFI 应用程序一样,EFI 启动服务驱动程序的入口点通常也是
efi_main
或EfiMain
,但其执行环境更侧重于与硬件交互和设备初始化。 -
硬件访问: 这些驱动程序有权访问 EFI 提供的所有启动服务,包括内存管理、设备访问、文件系统支持、协议处理等。
-
生命周期: 启动服务驱动程序的生命周期仅限于操作系统加载之前。操作系统加载后,这些驱动程序通常被卸载,相关的资源也会被释放。
EFI 启动服务驱动程序的典型特征
- 通常在系统启动时自动加载。
- 负责初始化特定硬件设备,使其在系统引导过程中可以正常工作。
- 在操作系统接管系统控制之前运行,并在操作系统加载后被卸载。
下面是一个简单的 EFI 启动服务驱动程序示例,展示如何编写一个基础的驱动程序,初始化一些设备并打印一条调试消息。
cpp
#include <efi.h>
#include <efilib.h>
// EFI 驱动程序入口点
EFI_STATUS EFIAPI efi_main(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable) {
// 初始化 EFI 库
InitializeLib(ImageHandle, SystemTable);
// 打印调试信息
Print(L"EFI Boot Service Driver Loaded\n");
// 你可以在这里执行设备初始化或其他启动服务操作
// 表示驱动程序加载成功
return EFI_SUCCESS;
}
代码解析
-
EFI 库的初始化 (
InitializeLib
):- 与标准的 EFI 应用程序类似,启动服务驱动程序也需要初始化 EFI 库。这使得驱动程序可以访问
SystemTable
中的各类 EFI 服务。
- 与标准的 EFI 应用程序类似,启动服务驱动程序也需要初始化 EFI 库。这使得驱动程序可以访问
-
调试信息输出 (
Print
):- 使用
Print
函数输出驱动程序加载时的调试信息。这有助于开发和调试时验证驱动程序是否正确加载。
- 使用
-
设备初始化:
- 在实际的 EFI 启动服务驱动程序中,这里将包含设备初始化逻辑。具体操作可能包括配置硬件寄存器、设置设备模式、注册协议等。
- 这些初始化步骤确保硬件设备在系统启动时处于正确的状态,能够与引导加载程序或操作系统协同工作。
-
返回值:
- 驱动程序返回
EFI_SUCCESS
表示加载成功。这是 EFI 规范中的惯例,表示驱动程序初始化正常完成。
- 驱动程序返回
6. EFI Runtime Driver (/SUBSYSTEM)
用途
/SUBSYSTEM:EFI_RUNTIME_DRIVER
子系统用于开发在 EFI (Extensible Firmware Interface) 环境中的运行时阶段执行的驱动程序。这些驱动程序在系统引导过程中被加载,并继续在操作系统接管控制权后运行,为操作系统提供服务。EFI 运行时驱动程序的作用通常包括管理非易失性变量、系统时间、硬件状态等,它们在操作系统运行时保持活跃,以支持特定的系统功能。
描述
EFI 运行时驱动程序在系统启动的 EFI 阶段加载,并继续在操作系统接管系统控制后运行。这些驱动程序在操作系统加载后提供重要的系统功能支持,如:
- 管理 EFI 变量(例如启动配置)。
- 提供系统时间服务。
- 管理硬件设备的状态,确保其在操作系统运行时正常工作。
与启动服务驱动程序不同,运行时驱动程序在操作系统加载后仍然保持活动状态,直到系统关机或重启。这些驱动程序需要在操作系统和 EFI 固件之间进行有效的通信,因此它们的开发和测试更加复杂。
EFI 运行时驱动程序的典型特征
- 具有在操作系统运行期间提供服务的能力。
- 必须与操作系统共享硬件资源,因此需要非常谨慎地管理资源以避免冲突。
- 需要处理操作系统通知的事件,如关机、重启等。
下面是一个基本的 EFI 运行时驱动程序示例。这个示例展示了如何编写一个简单的运行时驱动程序,设置系统时间并在操作系统接管后继续提供时间服务。
cpp
#include <efi.h>
#include <efilib.h>
EFI_EVENT ExitBootServicesEvent;
// 退出引导服务处理程序
VOID EFIAPI OnExitBootServices(EFI_EVENT Event, VOID *Context) {
Print(L"OS is taking over control.\n");
// 在这里,你可以处理任何需要在退出EFI引导服务之前完成的工作
}
// EFI 驱动程序入口点
EFI_STATUS EFIAPI efi_main(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable) {
// 初始化 EFI 库
InitializeLib(ImageHandle, SystemTable);
// 注册退出引导服务事件
EFI_STATUS Status = SystemTable->BootServices->CreateEvent(
EVT_SIGNAL_EXIT_BOOT_SERVICES,
TPL_CALLBACK,
OnExitBootServices,
NULL,
&ExitBootServicesEvent
);
if (EFI_ERROR(Status)) {
Print(L"Failed to register ExitBootServices event.\n");
return Status;
}
Print(L"EFI Runtime Driver Loaded.\n");
// 设置系统时间(仅作为示例,具体时间设置逻辑根据需要定制)
EFI_TIME Time;
Time.Year = 2023;
Time.Month = 8;
Time.Day = 19;
Time.Hour = 10;
Time.Minute = 30;
Time.Second = 0;
Time.Nanosecond = 0;
Status = SystemTable->RuntimeServices->SetTime(&Time);
if (EFI_ERROR(Status)) {
Print(L"Failed to set system time.\n");
} else {
Print(L"System time set successfully.\n");
}
return EFI_SUCCESS; // 驱动程序初始化成功
}
7. EFI ROM (/SUBSYSTEM:EFI_ROM)
用途
/SUBSYSTEM:EFI_ROM
子系统用于指定生成的输出文件是一个 EFI ROM 文件。这类文件通常是设备固件或在 EFI 环境下运行的驱动程序,并且会被直接烧录到硬件设备的只读存储器(ROM)中。这些 ROM 文件在系统启动时由 EFI 固件加载,用于初始化硬件设备或提供启动服务。
描述
EFI ROM 文件是直接嵌入到硬件设备(如网络卡、显卡、存储控制器等)中的固件,通常在系统启动过程中自动加载。EFI ROM 文件包含了在启动阶段执行的驱动程序或固件代码,这些代码用于初始化硬件、设置设备工作模式或提供基础服务,确保设备在操作系统加载前能够正常工作。
- 入口点 : EFI ROM 文件通常没有用户空间程序常见的
main
函数,而是通过 EFI 的efi_main
或类似的入口点函数进行加载和执行。 - 硬件交互: 这些驱动程序或固件直接与硬件设备交互,可能涉及到底层硬件寄存器的读写操作,设备状态的监控等。
- 固件集成: 生成的 EFI ROM 文件通常被集成到设备的固件中,成为设备不可分割的一部分。
下面是一个简单的 EFI ROM 文件示例,它在加载时会输出一条调试信息,表示它已被加载。
cpp
#include <efi.h>
#include <efilib.h>
// EFI 驱动程序入口点
EFI_STATUS EFIAPI efi_main(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable) {
// 初始化 EFI 库
InitializeLib(ImageHandle, SystemTable);
// 打印固件加载消息
Print(L"EFI ROM Driver Loaded\n");
// 执行设备初始化或其他固件相关操作
return EFI_SUCCESS;
}
代码解析
-
EFI 库的初始化 (
InitializeLib
):- 与其他 EFI 程序类似,EFI ROM 文件中的代码也需要初始化 EFI 库,以便使用系统提供的各种服务。
-
调试信息输出 (
Print
):- 在固件加载时,使用
Print
函数输出调试信息。这条信息可以帮助开发者确认固件已正确加载。
- 在固件加载时,使用
-
设备初始化:
- 在实际的 EFI ROM 文件中,这里会包含设备的初始化逻辑。比如设置硬件寄存器、初始化设备的工作模式等,以确保设备在操作系统加载前处于正常工作状态。
-
返回值:
- 返回
EFI_SUCCESS
表示固件加载成功。EFI 的标准规范要求返回此状态代码表示操作成功。
- 返回
总结
/SUBSYSTEM:EFI_ROM
子系统用于生成 EFI ROM 文件,这些文件通常是嵌入设备中的固件或驱动程序。它们在系统启动时自动加载并初始化相关硬件设备。开发 EFI ROM 文件要求对设备硬件有深入了解,并且通常需要使用特定的工具将生成的文件烧录到设备的 ROM 中。这些文件对设备的启动和初始化至关重要,确保在操作系统加载之前设备能够正常工作。
什么是EFI环境?
EFI(Extensible Firmware Interface) 是一种用于计算机系统的固件接口规范,它是传统BIOS的继任者。EFI提供了一个灵活、模块化的环境,允许系统和设备制造商创建可扩展的固件程序,用于初始化硬件、加载操作系统、提供系统服务等。EFI后来演化为UEFI(Unified Extensible Firmware Interface),是当前大多数现代计算机系统的固件标准。
EFI环境的关键组成部分
-
固件接口:
- EFI提供了一个标准化的接口,供操作系统加载程序(如引导加载器)与底层固件进行交互。这种接口取代了传统BIOS的中断调用机制,提供了更高级的功能和更广泛的硬件支持。
-
EFI应用程序:
- EFI允许开发者编写特定的应用程序,这些应用程序在操作系统加载之前运行,可以执行各种任务,如系统诊断、硬件初始化、固件更新等。EFI应用程序通常以
.efi
文件的形式存在,可以通过EFI Shell手动加载和执行,或由固件自动运行。
- EFI允许开发者编写特定的应用程序,这些应用程序在操作系统加载之前运行,可以执行各种任务,如系统诊断、硬件初始化、固件更新等。EFI应用程序通常以
-
启动服务(Boot Services):
- EFI环境提供了一组启动服务,用于在操作系统启动之前进行硬件初始化、内存管理、设备驱动加载等任务。这些服务在操作系统接管控制权之前有效。一旦操作系统加载,这些服务将被禁用。
-
运行时服务(Runtime Services):
- 与启动服务不同,EFI的运行时服务在操作系统加载后依然有效。这些服务包括时间服务、变量存储管理、重启和关闭系统等操作。这使得EFI环境能够在操作系统运行期间继续提供关键的系统功能支持。
-
EFI变量:
- EFI环境支持持久化的变量存储,这些变量可以跨系统重启保存,通常用于存储启动配置、硬件设置等关键信息。EFI变量可以通过运行时服务访问和修改。
-
EFI Shell:
- EFI Shell是一个命令行接口,允许用户直接与EFI环境交互。通过EFI Shell,用户可以执行EFI应用程序、访问文件系统、修改启动配置、执行硬件诊断等操作。EFI Shell提供了一个灵活的工具集,适合高级用户和开发者。
EFI的主要特性和优势
-
灵活性和可扩展性:
- EFI的模块化设计允许硬件制造商和开发者添加特定功能或扩展现有功能。例如,硬件厂商可以为特定设备编写EFI驱动程序,确保设备在系统启动时正确初始化。
-
支持图形用户界面:
- 与传统BIOS的字符界面不同,EFI支持图形用户界面(GUI),允许用户通过更直观的界面进行系统配置、固件更新等操作。
-
更大的寻址能力:
- EFI支持更大的硬盘(超过2TB)和更高级的存储设备,因为它支持GPT(GUID Partition Table)而不是传统的MBR(Master Boot Record)。
-
安全启动(Secure Boot):
- EFI环境支持安全启动机制,防止未授权或恶意的操作系统和驱动程序在系统启动时加载。这一特性增强了系统的安全性,特别是在防范引导级恶意软件方面。
-
多平台支持:
- EFI并不仅限于x86架构,还支持ARM等其他处理器架构,使其成为广泛适用的固件标准。
EFI与BIOS的区别
-
启动速度:
- EFI通常比传统BIOS启动更快,因为它能够并行初始化硬件设备,并且提供了更高效的驱动程序接口。
-
硬件支持:
- EFI提供了对更多种类硬件的原生支持,尤其是在新型存储设备和高容量硬盘方面,EFI比BIOS有明显的优势。
-
开发与维护:
- EFI的模块化和编程接口使得固件的开发和维护更为灵活,厂商可以为不同的硬件平台定制专用的EFI固件,而不需要修改整个系统固件。
EFI环境中的工作流程
-
电源启动和硬件初始化:
- 当系统启动时,EFI固件首先运行,初始化CPU、内存、以及其他基本硬件资源。
-
加载EFI驱动程序:
- EFI固件加载并执行必要的设备驱动程序,这些驱动程序可以存储在ROM、硬盘或者网络上。
-
执行启动管理器:
- 启动管理器确定要加载的操作系统或EFI应用程序,并从指定的存储设备中读取操作系统启动程序。
-
启动操作系统:
- 一旦操作系统启动程序被加载并执行,控制权从EFI转移到操作系统。此时,EFI的启动服务被禁用,但运行时服务继续有效。
-
操作系统运行:
- 操作系统可以通过EFI运行时服务与EFI固件进行通信,进行时间管理、变量操作等系统任务。
总结
EFI环境是一个比传统BIOS更加现代化和功能强大的固件接口,它为系统启动过程提供了更强的灵活性、可扩展性和安全性。EFI的出现使得系统启动过程更加高效,并为操作系统加载之前的系统初始化和诊断提供了强大的支持。随着UEFI的普及,EFI已经成为现代计算机系统的标准启动环境,为用户和开发者提供了一个更为强大和安全的固件平台。
总结
在Visual Studio项目的链接器设置中,子系统选项决定了应用程序的执行环境。不同的子系统适用于不同类型的应用程序或驱动程序,每种子系统针对特定的开发场景和需求。理解这些子系统的用途和特点,有助于开发者更好地选择和配置项目,以满足不同的开发要求。
-
Console (/SUBSYSTEM
) 子系统用于开发控制台应用程序,这些程序通常在命令行界面运行,适用于脚本、系统工具等任务。其典型特征是不依赖于图形界面,所有交互都通过文本完成,入口点为
main
或wmain
函数。 -
Windows (/SUBSYSTEM
) 子系统用于开发Windows图形用户界面(GUI)应用程序。此类应用程序通过窗口与用户交互,入口点为
WinMain
或wWinMain
。它们通常用于桌面应用程序或需要复杂用户界面的场景。 -
Native (/SUBSYSTEM
) 子系统主要用于开发直接在Windows NT内核模式下运行的程序或驱动程序。这类程序通常是操作系统的一部分,具有高权限和高风险,适用于操作系统组件或设备驱动开发。
-
EFI Application (/SUBSYSTEM
) 子系统用于开发在EFI环境中运行的应用程序。这些应用程序通常在操作系统加载之前运行,适合用于系统诊断、固件更新等任务,能够直接与系统硬件交互。
-
EFI Boot Service Driver (/SUBSYSTEM
) 子系统用于开发在EFI启动服务阶段运行的驱动程序。这些驱动程序在操作系统加载前负责设备初始化或设置,确保系统引导过程中的设备功能正常。
-
EFI Runtime Driver (/SUBSYSTEM
) 子系统用于开发在EFI环境中的运行时阶段执行的驱动程序。这些驱动程序在系统启动时加载,并在操作系统加载后继续提供关键的系统服务,如时间管理、硬件状态维护等。
-
EFI ROM (/SUBSYSTEM
) 子系统用于生成EFI ROM文件,这些文件通常嵌入设备的固件中,在系统启动时加载,用于初始化硬件设备或提供启动服务。EFI ROM文件直接与硬件设备交互,确保在操作系统加载前设备正常工作。
通过以上总结,我们可以看到不同子系统选项的作用和应用场景,从控制台应用程序到内核模式驱动,再到EFI环境下的驱动开发,每种子系统都有其独特的用途。选择合适的子系统有助于确保程序在正确的环境中执行,并且满足预期的功能需求。