1.4 系统服务
应用程序需要进行一些并不纯粹是计算的操作,比如分配内存、打开文件、创建线程等。这些操作最终只能由位于内核模式的代码来进行。那么用户空间的代码怎么进行这些操作呢?我们看一个典型的例子:某个正在运行记事本进程的用户用“文件”菜单请求打开一个文件。记事本的代码通过调用有文档记载的Windows API函数CreateFile
来响应。文档中记载了CreateFile
在kernel32.dll
中实现,这里kernel32.dll
是Windows子系统的一个DLL。这个函数依然在用户模式运行,因此无法直接打开文件。在进行了一些错误检查之后,它调用了NtCreateFile
。这是一个在NTDLL.dll
中实现的函数,而NTDLL.dll
是一个基础的DLL,它实现了被称为“原生API”的API,并且它实际上是位于用户模式的底层代码。这个API(官方未公开)是一个执行到内核模式的转换的API。在进行实际的转换之前,它先把一个叫作系统服务号的数字放到CPU的寄存器里(在Intel/AMD体系结构上是EAX
)。然后它会执行一个特殊的CPU指令(在x64系统里是syscall
,在x86系统里是sysenter
)来实际转换到内核模式,并跳转到一个预定义的被称为系统服务分发器(system service dispatcher)的例程。
系统服务分发器继而使用EAX
寄存器中的值作为系统服务分发表(System Service Dispatch Table,SSDT)的入口索引,代码跳转至相应的系统服务(又称为系统调用)中。对我们的记事本例子来说,SSDT中相应的入口会指向I/O管理器(I/O Manager)的NtCreateFile
函数。请注意,这个函数与NTDLL.dll
里的函数有相同的名称,而且还有一样的参数。当系统服务执行完毕后,线程会返回到用户模式,执行紧接着sysenter/syscall的指令。这些事件的顺序在图1-7中绘出。
图1-7 系统服务函数调用流程