为 Windows 编写的程序如何与 Windows NT 内核交互或向其发出命令?
内核如何返回任何数据?
为了回答这个问题,了解用户模式和内核模式之间的区别很重要。内核模式是特权最高的 CPU 模式,执行代码可以完全访问硬件。它用于最低级别的操作系统功能。用户模式是一种更受限制的 CPU 模式。它可以防止代码直接访问硬件。应用程序在用户模式下运行。当然,他们仍然需要以某种方式访问硬件,所以他们需要调用内核。
这就是你的问题导致的地方。为了允许用户模式代码调用内核,Windows 内核设置了一个入口点。在基于 x86 的系统上,此入口点是软件中断 (int 2e) 或 sysenter/syscall 指令。执行这些指令会导致 CPU 模式切换,将 CPU 从用户模式转换为内核模式。一旦 CPU 切换了模式,它就会调用内核指定的函数。在 Windows 中,此函数是系统服务调度程序。
系统服务调度器负责调用用户态代码想要的内核服务。它获取由用户模式代码指定的函数号,并在系统服务描述符表 (SSDT) 中查找它。SSDT 基本上是指向每个内核服务的函数指针列表。一旦它识别出正确的内核服务,它就会使用用户模式应用程序也指定的参数调用它。内核服务完成后,CPU 通过 iret 指令(如果来自软件中断)或 sysexit/sysret(如果来自 sysenter/syscall)返回应用程序。
所有这些听起来都相当复杂,这就是 Windows 对程序员隐藏这些细节的原因。Windows 不要求他们通过内核设置的入口点直接与内核通信,而是为程序员提供了几个 DLL,它们为他们执行此操作。
现在这里再次变得更加复杂。从用户态调用内核服务的过程是在ntdll.dll中实现的,但是ntdll.dll并不是大多数程序员直接使用的。相反,它导出了一组称为 Native API 的通用内核服务。在此之上,Win32 API 在 kernel32.dll 中实现。kernel32.dll 中的大多数函数只是 ntdll.dll 中函数的包装器。
你可能会问为什么这样做。为什么不让 kernel32.dll 直接调用内核函数呢?这样做的原因是允许不同的多用户模式 API。Windows NT 旨在支持多种 API,不仅是 Win32,还支持 POSIX 和 OS/2。每个用户态 API 调用 ntdll.dll 来实现自己的 API,无需自己直接调用内核服务。
兄弟,这是一个非常广泛的问题。
如果您真的想理解这一点,我推荐Mark Russinovich 等人的 Windows Internals一书。另一本好书是Deitel 等人的经典操作系统。
然而,从 Helen Custer 的 Inside Windows NT(第 1 版)开始——这是一本非常基本的书(请注意,最后一个链接有第 2 版封面的图片——这要详细得多)。
简而言之,好的。
Windows 组件之间的通信有多种协议。他们中的大多数将在一天结束时通过一些共享内存(例如缓冲区、堆栈等)使用传递数据。但是协议可能非常复杂,并且对于不同的通信是不同的。
我给你的建议是看看上面的书,确定 Windows 操作系统的体系结构是如何结合在一起的。从这里您将看到各种组件如何通信。
(装出书呆子的脸)——相信我,这些都是了解 Windows 和一般操作系统的好书,如果这就是你的船的话。
尝试阅读:第 5 章 - Windows NT 4.0 工作站体系结构。开始应该足够了。
最后,一些 API 直接构建在一些用户态 DLL 中。这些是直接执行的。其他需要内核模式帮助/服务。
对于这些(我从上面的链接中引用)
该应用程序使用 API 对用户模式动态链接库进行图形调用。实现该调用的组件对Executive 进行内核模式陷阱调用,以切换其线程并将其调用参数从其用户模式堆栈复制到其内核模式堆栈。然后,处理器的堆栈寄存器切换为指向内核模式。现在线程可以在Executive中运行了。
应用程序通过使用本地过程调用 (LPC) 与受保护的子系统进行通信,这是一种独立于应用程序的在同一台计算机上的组件之间进行通信的方法。线程切换到内核模式后,微内核调度 LPC 进行交付。
使用优化的通信方法 Fast LPC,微内核识别出来自应用程序的调用涉及环境子系统中的线程。它认为调用应用程序线程和接收子系统线程是配对的。接收线程现在可以使用来自发送线程的未过期时间。
图形调用参数被传递给接收子系统线程。接收线程切换回用户模式以完成图形请求。
子系统完成其任务,然后通过相同的方法将控制权返回给应用程序中的等待调用线程。
调用线程(来自 DLL)在将控制权返回给应用程序之前切换回用户模式。
Microsoft 操作系统工程师还使用共享内存窗口的概念来加速通信。数据被放置在由执行中的进程管理器管理的临时共享内存窗口中。这让应用程序可以在不使用 LPC 的情况下查看子系统的内存并共享数据。但是,由于应用程序线程仍必须在Executive 中运行,因此仍需要内核/用户模式转换和线程切换。
这里有一些关于如何exactly
完成调用的注释(使用什么 ASM 命令):x86 上的系统调用调度程序(如果需要)。