Windows Embedded CE 6.0 Internals (5) The Mechanism of API



引言

写用户态程序(包括普通的应用程序、服务程序、用户态驱动、一些扩展插件)时,每当我们遇到一个不熟悉的API,我们就会打开开发文档,或者MSDN,查查每个参数是怎么填的。(我想你应该没把这些API给背下来了吧^^)

我们在调试时发现执行到这些API时是跟不进去的,那么你想过它们是如何进入操作系统里面工作的吗?它们又是如何在操作系统里面工作的?

比如文件系统的一个API:

?
1
2
3
4
5
6
7
8
9
HANDLE CreateFile(
   LPCTSTR lpFileName,
   DWORD dwDesiredAccess,
   DWORD dwShareMode,
   LPSECURITY_ATTRIBUTES lpSecurityAttributes,
   DWORD dwCreationDisposition,
   DWORD dwFlagsAndAttributes,
   HANDLE hTemplateFile
);

那么这系列文章就是尝试弄清楚这些的一些问题。但是在实际摸索过程中我发现这些远比我想象中复杂,甚至经常迷失了。如果你发现文章有不对的或者可以改进的地方请不吝赐教。

在没有文档的情况下去研究CE的源码是件比较吃力的事,这就是我分享这一系列文章的原因。不管你是在CE平台还是在嵌入式Linux平台下面开发,深入一下CE内部都是件有趣并且有意义的事。而且这些知识对在CE上从事较靠近Kernel的开发以及对问题的深一层次追踪是非常有帮助的。

再说这些机制在不同操作系统上也大同小异(它们本身也会相互学习和借鉴),熟悉一个之后这些知识能够很好的重用在别的地方。所以与只知道拖控件相比,知道How does xxx work?会更有意义

 

概述

这篇文章就是对《Windows Embedded CE 6.0 Internals (4) The Mechanism of API》的进一步补充和深入,把一些迷糊的问题搞清楚点。

我大体罗列了这篇文章涉及的问题:

  • What is PSL(process server library)?
  • What is a Kernel-mode/User-mode server?
  • What is Trap?
  • What is Thunking?
  • How Thread Transition when API call?
  • 以下API的作用以及意义:CreateAPISet/CreateAPIHandle/RegisterAPISet/RegisterDirectMethods/WaitForAPIReady(IsAPIReady)/GetAPIAddress/QueryAPISetID

在文章《Inside Windows CE API Calls》非常好的描述了CE的API机制,我在这里简单翻译和整理一下,以便先从整体上了解一下:

CE的API是由若干个服务进程(PSL)实现的:内核进程(NK.exe)、filesys.exe、gwes.exe、device.exe、services.exe(在CE6.0之前是这样的,而在CE6.0/7.0时这些exe大多已经被实现成dll,比如kernel.dll、filesys.dll、device.dll、gwes.dll等。并且PSL分成了Kernel-mode server和User-mode server,这个我在后面详细介绍。)

当我们的应用程序调用这些PSL的API时,我们一般会跳进这些PSL去执行对应的API实现。

大部分API都是从coredll.dll导出的,我们的应用程序会链接到这个DLL文件。当我们调用API时,比如GetTickCount,我们其实是调用coredll.dll导出的GetTickCount(coredll.dll相当于一个wrapper,我们经常也叫做“thunk”,kcoredll.dll在后面介绍。):

?
1
2
3
4
DWORD xxx_GetTickCount()
{
     return GetTickCount();
}

你能从这里看到更多的thunk,并能看到更多的xxx_前缀的thunk(但并不是全部都是):

?
1
%_WINCEROOT%\private\winceos\coreos\core\thunks\*

我们发现这里的函数名前面都有xxx_,但是实际我们在文档里看到的并没有xxx_前缀,这是因为在这里我们重命名这些API了:

?
1
%_WINCEROOT%\private\winceos\coreos\core\coredll.def

像这样:

?
1
GetTickCount=xxx_GetTickCount

在上面的xxx_GetTickCount函数里面我们就直接return一个函数:GetTickCount,那么这个函数到底是什么呢?从这里你能找到答案:

?
1
%_WINCEROOT%\public\common\oak\inc\*

它其实只是个宏定义,像这样:

?
1
define GetTickCount    COMPLICATED_MACRO(..., SH_WIN32, W32_GetTickCount, ...)

SH_WIN32在SDK中被定义成0(GetTickCount所在的API set table所在的ID),而W32_GetTickCount在另一个OAK头文件中被定义成13(GetTickCount在API set table里面的索引)。

为了思路清晰,COMPLICATED_MACRO我们不再展开了,实际上当调用这个宏时它就会跳转到一个地址,一个什么地址呢?在文章《Windows Embedded CE 6.0 Internals (4) The Mechanism of API》中提到了KDataStruct这个结构体,其开始地址是固定的PUserKData,对于ARM处理器是0xFFFFC800,而其它处理器是0x00005800。恩,就是跳转到偏移PUserKData一定量的位置,不管是0xFFFF…还是0x0000…,都是个特殊的位置,这些位置都是Trap,向这些Trap的跳转会产生Exception,《Inside Windows CE API Calls》用一句小幽默解释道:

All exceptions go to the kernel first, and the kernel says,"A-ha!  I know this invalid address.  It's the encoding for an API, index 13 of API table 0."

之后Kernel会去做一些工作:marshals (maps) arguments, adjusts permissions, flushes cache and TLB(页表缓冲),如果需要的话。一起都准备好之后,请求的API所在的PSL进程里的线程将会被执行。

可以参考:

?
1
2
%_WINCEROOT%\private\winceos\coreos\nk\kernel\x86\fault.c, Int20SyscallHandler.
%_WINCEROOT%\private\winceos\coreos\nk\kernel\objdisp.c, ObjectCall()

当PSL进程里的API调用执行完之后,调用返回时会触发另一个Exception(Kernel设置了返回地址为另一个指定的代码地址),Kernel再一次adjusts arguments, permissions, and other state,比如必要的话。就这样CPU执行权再一次返回到原调用进程。

可以参考:

?
1
%_WINCEROOT%\private\winceos\coreos\nk\kernel\x86\fault.c, ServerCallReturn.

从应用程序开始的API调用的一个简单的流程我们搞清楚了。(实际上整个操作系统的API调用机制是非常复杂的,这里说的只是最一般的情况。)

文章《Inside Windows CE API Calls》后面关于KMODE部分的介绍是针对CE 6.0之前版本的,在CE 6.0时有较大变化。另外作者说到:

In Windows CE, "kernel mode" threads have permission to access memory addresses outside of their own process.

这是不准确的(你从后面的留言中能够了解到更多),这在CE 6.0/7.0上就更不准确了,因为连slot都没有了。

作者: 王克伟
出处: http://wangkewei.cnblogs.com/

你可能感兴趣的:(Windows Embedded CE 6.0 Internals (5) The Mechanism of API)