14

很简单,问题是如何使用 c/c++ 在 windows 中获得系统启动时间。

搜索这个并没有给我任何答案,我只找到了一种非常hacky的方法,它正在读取文件时间戳(不用说,我中途放弃了阅读)。

我发现的另一种方法实际上是读取 Windows 诊断记录的事件?据说这是最后一次启动时间。

有谁知道如何做到这一点(希望没有太多丑陋的黑客)?

4

5 回答 5

22

GetTickCount64“检索自系统启动以来经过的毫秒数。”

一旦您知道系统运行了多长时间,只需从当前时间中减去此持续时间即可确定系统何时启动。例如,使用 C++11 chrono 库(Visual C++ 2012 支持):

auto uptime = std::chrono::milliseconds(GetTickCount64());
auto boot_time = std::chrono::system_clock::now() - uptime;
于 2012-06-01T16:26:10.197 回答
8

您还可以使用WMI获取准确的启动时间。WMI 不适合胆小的人,但它会为您提供所需的东西。

有问题的信息是关于该Win32_OperatingSystem属性下的对象的LastBootUpTime您可以使用WMI Tools检查其他属性。

WMI Explorer 显示属性实例

编辑: 如果您愿意,也可以从命令行获取此信息。

wmic OS Get LastBootUpTime

作为 C# 中的示例,它看起来如下所示(使用 C++ 相当冗长):

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();
}
于 2012-06-01T16:49:15.727 回答
4

“系统”对象上的“系统启动时间”性能计数器是另一个来源。它可以使用PDH Helper 方法以编程方式使用。但是,它对睡眠/休眠周期并不健壮,因此可能并不比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,对应于 UTC 时间 2021 年 1 月 23 日星期六 6:14:36 PM

您可以使用PerfFormattedData等效项来获取浮点秒数,或者从命令行中读取该秒数或wmic在 PowerShell 中查询计数器:

Get-Counter -Counter '\system\system up time'

这将返回 427.0152 秒的正常运行时间。

我还实现了其他 3 个答案中的每一个,并有一些观察结果可以帮助那些试图选择一种方法的人。

当前时间的使用GetTickCount64和减去

  • 最快的方法,时钟为 0.112 毫秒。
  • 在其参数的 100 ns 分辨率下不会产生唯一/一致的值,因为它取决于时钟滴答。返回值都在 1/64 秒之内。
  • 需要 Vista 或更新版本。如果您的应用程序/库必须支持较旧的 Windows 版本,XP 的 32 位计数器会在约 49 天后翻转并且不能用于此方法

使用 WMI 查询LastBootUpTime字段Win32_OperatingSystem

  • 使用 COM 需要 84 毫秒,使用wmic命令行需要 202 毫秒。
  • 生成一致的值作为CIM_DATETIME字符串
  • WMI 类需要 Vista 或更高版本。

读取事件日志

  • 最慢的方法,耗时 229 ms
  • 以秒为单位生成一致的值(Unix 时间)
  • 适用于 Windows 2000 或更新版本。
  • 正如Jonathan Gilbert在评论中指出的那样,不保证会产生结果。

这些方法还产生了不同的时间戳:

  • 正常运行时间:1558758098843 = 2019-05-25 04:21:38 UTC(有时:37)
  • WMI:20190524222528.665400-420 = 2019-05-25 05:25:28 UTC
  • 事件日志:1558693023 = 2019-05-24 10:17:03 UTC

结论:

事件日志方法与旧的 Windows 版本兼容,在 unix 时间产生一致的时间戳,不受睡眠/休眠周期的影响,但也是最慢的。鉴于这不太可能在循环中运行,这可能是可接受的性能影响。但是,使用这种方法仍然需要处理事件日志达到容量并删除旧消息的情况,可能使用其他选项之一作为备份。

于 2019-06-10T00:52:19.217 回答
2

C++ Boost曾经使用 WMI LastBootUpTime,但在 1.54 版中切换到检查系统事件日志,显然是有充分理由的:

ABI 中断:更改了 Windows 中的 bootstamp 功能,以使用 EventLog 服务启动时间作为系统启动时间。以前LastBootupTime从 WMI 使用的时间同步和休眠不稳定,在实践中无法使用。如果您确实需要BOOST_INTERPROCESS_BOOTSTAMP_IS_LASTBOOTUPTIME从命令行或 detail/workaround.hpp 获取 Boost 1.54 之前的行为定义。

查看 boost/interprocess/detail/win32_api.hpp,在第 2201 行附近,该函数的inline bool get_last_bootup_time(std::string &stamp)实现作为示例。(如果您想匹配行号,我正在查看版本 1.60。)

以防 Boost 以某种方式死去,而我将您指向 Boost 并没有帮助(是的,对),您需要的功能主要是ReadEventLogA要查找的事件 ID(根据 Boost 评论“事件日志已启动”)显然6005.

于 2019-01-18T18:02:33.947 回答
1

我没有玩过这么多,但我个人认为最好的方法可能是查询“系统”进程的开始时间。在 Windows 上,内核在启动时为自己的目的分配一个进程(令人惊讶的是,快速的谷歌搜索并不能轻易地发现它的实际目的是什么,尽管我确信信息就在那里)。此进程在任务管理器中简称为“系统”,并且在当前 Windows 版本上始终具有 PID 4(显然 NT 4 和 Windows 2000 可能已使用 PID 8)。只要系统正在运行,这个进程就永远不会退出,并且在我的测试中,就其元数据而言,它的行为就像一个成熟的进程。从我的测试来看,即使是非提升用户也可以打开 PID 4 的句柄,请求PROCESS_QUERY_LIMITED_INFORMATIONGetProcessTimes,它将lpCreationTime使用进程启动时间的 UTC 时间戳填充 。据我所知,在系统进程运行之前,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
453:17.

(结果因调用而异,因为system_clock::now()并且::GetTickCount64()不会在完全相同的时间运行并且没有相同的精度)

  • wmic OS Get LastBootUpTime

2020-07-18 17:36:41.512344

  • 事件簿

没有结果,因为此时我的系统上不存在事件日志条目(最早的事件是从 7 月 23 日开始)

  • GetProcessTimes在 PID 4 上:

2020-07-18 17:36:48.0424863

这与其他方法有几秒钟的不同,但我想不出它本身有什么问题,因为如果系统进程还没有运行,系统是否真的启动了

于 2020-07-23T10:11:37.067 回答