【问题标题】:Thread-local storage in kernel mode?内核模式下的线程本地存储?
【发布时间】:2012-04-05 12:01:12
【问题描述】:

Windows 中的内核模式驱动程序是否有等效的线程本地存储 (TLS)(确切地说是 Win32)?

我想达到的目标:

最终,在我的驱动程序的调度例程中,它可能会调用许多其他函数(可能有很深的调用堆栈)。我想提供一些特定于正在处理的请求的上下文信息。也就是说,我有一些结构,指向它的指针应该在所有被调用的函数中都是可见的,而不是显式地将它作为参数传递给每个函数。

使用静态/全局不是一个完美的选择(多线程、同步对象等)。

如果那是用户模式代码 - 在这种情况下显然会使用 TLS。但是AFAIK没有像TlsGetValue/TlsSetValue这样的内核模式函数。这是有道理的——要使这些功能正常工作,必须首先分配一个进程范围的 TLS 索引。 OTOH驱动代码可以在任意线程上调用,不限于特定进程。

但是,我实际上并不需要 持久 线程专用存储。我只需要一个特定于线程的存储来进行顶级函数调用。

我想我知道如何“实施” TLS,尽管是以一种骇人听闻的方式。我不会分配 TLS 索引,而是始终使用预定义的索引(例如 index=0)。在顶级函数中,我将保存存储的 TLS 值,并用所需的值覆盖它。完成后将恢复保存的值。

幸运的是,我知道 TLS 在 Win32 中是如何实现的。每个线程都有一个TIB 结构(线程信息块)。在每个线程中,可以使用FS:[18h] 选择器访问它。 TIB 包含(除其他外)TLS 使用的数组。其余的都很简单。

但是我更喜欢使用官方 API 来实现类似的功能。

  • 是否有官方的内核模式 API 可以满足我的需求?
  • 是否有理由避免我计划做的事情?我知道重入可能存在问题(即某些代码调用我,我覆盖 TLS 值,然后最终调用可能依赖于 TLS 的原始代码)。但这在我的具体情况下是不可能的?
  • 有没有更脏的方法来解决这个问题?

提前致谢。

附:理论上可以使用 SEH(它还存储了每个线程的信息)。即用__try/__except包装顶层代码,然后在需要上下文信息的地方——用一些参数引发continuable异常,在__except块中用上下文信息填充参数,然后继续执行。这是一个 100% 有效的程序流程,没有使用未记录的功能。但尽管如此,这对我来说似乎是一个丑陋的 hack,更不用说性能问题了。

【问题讨论】:

    标签: windows winapi kernel thread-local-storage


    【解决方案1】:

    您应该使用 PsGetCurrentThreadTeb,而不是使用 FS:[18h]。即便如此,我认为您仍会依赖在未来操作系统版本(可能包括服务包)中可能发生变化的细节。

    相反,您不能使用 KeGetCurrentProcessorNumber 作为数组的索引,您可以在其中存储指向上下文信息的指针吗? (当然,前提是您运行在 DISPATCH_LEVEL 或更高级别,这样您就不会意外切换到不同的处理器。)

    如果您不能保证在 DISPATCH_LEVEL 上运行,您可以使用表或链表,每个条目(表示当前正在运行您的代码的线程)都标有 PsGetCurrentThread 的值。

    【讨论】:

    • 优秀的答案,非常准确。我同意依赖特定的未记录功能不是一个好习惯。但是,我发现 非常 很难想象 TIB 布局或访问方式可能会发生变化。此外,司机对便携性的要求不那么严格,恕我直言。使用全局数组并通过KeGetCurrentProcessorNumber 访问它是一个非常好的主意。在我的特殊情况下,请求可能会到达任意 IRQL,我不确定是否可以提出它(我称之为其他例程),但绝对值得一试。
    • P.S. “链接列表表”正是我想要摆脱的东西。非常感谢。
    • 为什么您很难想象 TIB 的结构会发生变化? Win64 应该改了吧? ARM 可能又有所不同了。即使字段保持不变,内核也不会将它们重用于其他用途并吹走您的数据(或者更糟的是,您会吹走它的数据)。
    • @Stewart:您并没有真正进入驱动程序开发,是吗?驱动程序代码的可移植性通常要低得多,您并不希望为 x86 编写的驱动程序能够编译并为 ARM 工作!而且,不管你信不信,深入研究操作系统内部在驱动程序开发中也更受欢迎。这也是MS一旦被劫持就很难改变其内部结构布局的原因。
    • @valdo - 不。不是真正的驱动程序开发。只是内核。我仍然认为依赖 TIB 是一个坏主意,无论它是否改变。你真的认为通过不遵守规则来阻止微软改变内核是一件好事吗?
    【解决方案2】:

    不要对 TEB 这样做! TIB 和 TEB 是用户模式结构。用户模式应用程序可以在驱动程序运行时从另一个线程/处理器随意修改这些内容。这将是您的驱动程序中的权限提升漏洞。

    我建议为与您的请求相关的临时上下文传递上下文结构。如果您需要更永久的东西,您可以使用 AVL 表或哈希表,在线程退出时清理它们。

    【讨论】:

    • +1 用于发现漏洞。但是请注意,OP 已经解释了为什么他不能使用上下文结构(他需要来自对象析构函数的上下文,不能传递参数)并且他已经在使用表并且想要更高效的东西。
    【解决方案3】:

    您可以创建一个包含传入请求的结构,然后将其传递而不是实际请求,然后您只需输入您需要的任何字段。显然,这并不能完全消除传递对象的需要,但通常无论如何您都会传递请求。

    从我所见过的大多数驱动程序(诚然数量不多)来看,一切都始终以请求为中心。因此,他们总是将事情与请求联系起来,而不是试图将其保留在其他位置。

    【讨论】:

    • 我的情况有点问题。我使用 C++(不要告诉我我是个变态),并且我在对象析构函数中执行了一些代码,我无法传递额外的参数。
    • +1 用于避免奇怪的事情,如 TLS 和恐怖。所以有一些背景。它只是一个对象实例指针。如果有 IO 请求,请使用上下文实例 ptr 以及所有其他 gunge 加载它,就像你说的那样。
    • @Martin James:我不知道为什么 TLS 被认为是“奇怪的”。这在用户模式代码中非常有用。
    猜你喜欢
    • 2013-07-05
    • 2011-09-16
    • 2021-01-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-04-08
    相关资源
    最近更新 更多