【发布时间】:2023-04-11 00:25:02
【问题描述】:
很简单,问题是如何使用 c/c++ 在 windows 中获得系统启动时间。
搜索这个并没有给我任何答案,我只找到了一个非常老套的方法,它正在读取文件时间戳(不用说,我中途放弃了阅读)。
我发现的另一种方法实际上是读取 Windows 诊断记录的事件?据说这是最后一次启动时间。
有谁知道如何做到这一点(希望不会有太多丑陋的黑客行为)?
【问题讨论】:
很简单,问题是如何使用 c/c++ 在 windows 中获得系统启动时间。
搜索这个并没有给我任何答案,我只找到了一个非常老套的方法,它正在读取文件时间戳(不用说,我中途放弃了阅读)。
我发现的另一种方法实际上是读取 Windows 诊断记录的事件?据说这是最后一次启动时间。
有谁知道如何做到这一点(希望不会有太多丑陋的黑客行为)?
【问题讨论】:
GetTickCount64 "检索自系统启动以来经过的毫秒数。"
一旦您知道系统运行了多长时间,只需从当前时间中减去该持续时间即可确定系统何时启动。例如,使用 C++11 chrono 库(Visual C++ 2012 支持):
auto uptime = std::chrono::milliseconds(GetTickCount64());
auto boot_time = std::chrono::system_clock::now() - uptime;
【讨论】:
GetSystemTime 结合起来,并进行一些日期数学运算以获得问题的完整答案。
system_clock::now 的滴答声与GetTickCount64 不完全同步,或者滴答声发生在两个执行的代码行之间怎么办?我需要一些可以用作查找键的东西。
您也可以使用WMI 来获取准确的启动时间。 WMI 不适合胆小的人,但它会为您提供所需的内容。
相关信息位于LastBootUpTime 属性下的Win32_OperatingSystem 对象上。您可以使用WMI Tools 检查其他属性。
编辑: 如果您愿意,也可以从命令行获取此信息。
wmic OS Get LastBootUpTime
作为 C# 中的示例,它将如下所示 (Using C++ it is rather verbose):
static void Main(string[] args)
{
// Create a query for OS objects
SelectQuery query = new SelectQuery("Win32_OperatingSystem", "Status=\"OK\"");
// Initialize an object searcher with this query
ManagementObjectSearcher searcher = new ManagementObjectSearcher(query);
string dtString;
// Get the resulting collection and loop through it
foreach (ManagementObject envVar in searcher.Get())
dtString = envVar["LastBootUpTime"].ToString();
}
【讨论】:
wmic OS Get LastBootUpTime 似乎返回了PC 首次启动的时间......wmic LOGON Get StartTime 可能更有用
“系统”对象上的“系统运行时间”性能计数器是另一个来源。它可以使用PDH Helper methods 以编程方式使用。但是,它对睡眠/休眠周期并不稳健,因此可能并不比 GetTickCount()/GetTickCount64() 好多少。
读取计数器会返回一个 64 位 FILETIME 值,即自 Windows Epoch (1601-01-01 00:00:00 UTC) 以来的 100-NS 滴答数。您还可以通过读取 WMI 表来查看计数器返回的值,该表公开了用于计算此值的原始值。 (使用 COM 以编程方式读取,或从 wmic 获取命令行:)
wmic path Win32_PerfRawData_PerfOS_System get systemuptime
该查询为我生成 132558992761256000,corresponding to Saturday, January 23, 2021 6:14:36 PM UTC。
您可以使用等效的PerfFormattedData 来获取浮点秒数,或者从wmic 中的命令行读取它,或者在PowerShell 中查询计数器:
Get-Counter -Counter '\system\system up time'
这将返回 427.0152 秒的正常运行时间。
我还实现了其他 3 个答案中的每一个,并且有一些观察结果可以帮助那些尝试选择方法的人。
使用GetTickCount64 并从当前时间中减去
使用WMI查询Win32_OperatingSystem的LastBootUpTime字段
wmic 命令行耗时 202 毫秒。CIM_DATETIME 字符串读取事件日志
这些方法也产生了不同的时间戳:
结论:
事件日志方法与旧 Windows 版本兼容,在 unix 时间生成一致的时间戳,不受睡眠/休眠周期的影响,但也是最慢的。鉴于这不太可能在循环中运行,这可能是可接受的性能影响。但是,使用这种方法仍然需要处理事件日志达到容量并删除旧消息的情况,可能会使用其他选项之一作为备份。
【讨论】:
ABI 中断:更改了 Windows 中的 bootstamp 功能,以使用 EventLog 服务启动时间作为系统启动时间。以前使用的来自 WMI 的
LastBootupTime在时间同步和休眠时不稳定,在实践中无法使用。如果您确实需要获取 Boost 1.54 之前的行为,请从命令行或 detail/workaround.hpp 中定义BOOST_INTERPROCESS_BOOTSTAMP_IS_LASTBOOTUPTIME。
查看 boost/interprocess/detail/win32_api.hpp,在第 2201 行附近,以函数 inline bool get_last_bootup_time(std::string &stamp) 的实现为例。 (如果你想匹配行号,我正在查看版本 1.60。)
以防万一 Boost 以某种方式死亡,而我将您指向 Boost 并没有帮助(是的,正确),您需要的功能主要是 ReadEventLogA 和要查找的事件 ID(“事件日志已启动”根据to Boost cmets) 显然是6005。
【讨论】:
我没有玩过这么多,但我个人认为最好的方法可能是查询“系统”进程的开始时间。在 Windows 上,内核在启动时为自己的目的分配一个进程(令人惊讶的是,快速的谷歌搜索并不能轻易地发现它的实际目的是什么,尽管我确信信息就在那里)。此进程在任务管理器中简称为“系统”,并且在当前 Windows 版本上始终具有 PID 4(显然 NT 4 和 Windows 2000 可能已使用 PID 8)。只要系统正在运行,这个进程就永远不会退出,并且在我的测试中,就其元数据而言,它的行为就像一个成熟的进程。根据我的测试,看起来即使是非提升的用户也可以打开 PID 4 的句柄,请求 PROCESS_QUERY_LIMITED_INFORMATION,并且生成的句柄可以与 GetProcessTimes 一起使用,这将使用 UTC 时间戳填充 lpCreationTime进程开始的时间。据我所知,在系统进程运行之前,Windows 没有任何有意义的运行方式,所以这个时间戳几乎就是 Windows 启动的时间。
#include <iostream>
#include <iomanip>
#include <windows.h>
using namespace std;
int main()
{
unique_ptr<remove_pointer<HANDLE>::type, decltype(&::CloseHandle)> hProcess(
::OpenProcess(
PROCESS_QUERY_LIMITED_INFORMATION,
FALSE, // bInheritHandle
4), // dwProcessId
::CloseHandle);
FILETIME creationTimeStamp, exitTimeStamp, kernelTimeUsed, userTimeUsed;
FILETIME creationTimeStampLocal;
SYSTEMTIME creationTimeStampSystem;
if (::GetProcessTimes(hProcess.get(), &creationTimeStamp, &exitTimeStamp, &kernelTimeUsed, &userTimeUsed)
&& ::FileTimeToLocalFileTime(&creationTimeStamp, &creationTimeStampLocal)
&& ::FileTimeToSystemTime(&creationTimeStampLocal, &creationTimeStampSystem))
{
__int64 ticks =
((__int64)creationTimeStampLocal.dwHighDateTime) << 32 |
creationTimeStampLocal.dwLowDateTime;
wios saved(NULL);
saved.copyfmt(wcout);
wcout << setfill(L'0')
<< setw(4)
<< creationTimeStampSystem.wYear << L'-'
<< setw(2)
<< creationTimeStampSystem.wMonth << L'-'
<< creationTimeStampSystem.wDay
<< L' '
<< creationTimeStampSystem.wHour << L':'
<< creationTimeStampSystem.wMinute << L':'
<< creationTimeStampSystem.wSecond << L'.'
<< setw(7)
<< (ticks % 10000000)
<< endl;
wcout.copyfmt(saved);
}
}
我当前启动的比较:
system_clock::now() - milliseconds(GetTickCount64()):2020-07-18 17:36:41.3284297
2020-07-18 17:36:41.3209437
2020-07-18 17:36:41.3134106
2020-07-18 17:36:41.3225148
2020-07-18 17:36:41.3145312(结果因调用而异,因为
system_clock::now()和::GetTickCount64()不会在完全相同的时间运行并且精度也不相同)
wmic OS Get LastBootUpTime2020-07-18 17:36:41.512344
没有结果,因为此时我的系统上不存在事件日志条目(最早的事件是从 7 月 23 日开始)
GetProcessTimes 在 PID 4 上:2020-07-18 17:36:48.0424863
这与其他方法有几秒钟的不同,但我想不出它本身有任何错误,因为如果系统进程尚未运行,系统真的启动了吗?
【讨论】: