代码改变世界

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

2010-06-13 23:16  王克伟  阅读(...)  评论(... 编辑 收藏

引言

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

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

比如文件系统的一个API:

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在后面介绍。):

DWORD xxx_GetTickCount()
{
    return GetTickCount();
}

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

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

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

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

像这样:

GetTickCount=xxx_GetTickCount

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

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

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

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进程里的线程将会被执行。

可以参考:

%_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执行权再一次返回到原调用进程。

可以参考:

%_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都没有了。

 

后续内容

在下一篇文章中我将扩展开来介绍API的机制,包括更多的API调用情景,比如:

  • 从应用程序(或者User-mode server)调用Kernel-mode server
  • 从Kernel-mode server调用Kernel-mode server
  • 从应用程序调用User-mode server
  • 从Kernel-mode调用User-mode server
  • 从Kernel调用Kernel

在调用中遇到的Calling back & forward问题,以及如何Mapping指针以及访问其它进程的内存和API是如何注册的。我把在学习过程中得到的收获拿出来跟大家分享和讨论,请大家能够指正我的错误。

 

更多参考

Windows Embedded CE 6.0 Internals (1) Kernel Overview

Windows Embedded CE 6.0 Internals (2) Memory

Windows Embedded CE 6.0 Internals (3) Memory Continuation

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

 

PS:1.技术这个东西是个非常有意思的东西, 一些东西你能讪讪道来它是个什么,但是实际上你并不一定知道它到底是个什么东西,大部分需要结合实践。近一年前我刚刚开始接触CE内核部分知识时感觉它是如此的抽象,能坚持下去完全靠毅力,但是直到前几天我尝试解决一些可能与内核有关的Bug时我发现我对以前那些知识不再那么陌生了。这就是为什么我不建议很多朋友一直抱着经典书去啃,而从不尝试去用。

2.半年前我对我的知识结构里面的大部分知识感到怀疑,带着这个态度开始我另一个崭新的学习阶段。我也建议读者在读我的文章时要持怀疑的态度,尽管大部分知识我尽力不让它是“错”的。在九年义务教育这么基础的教科书上面你都能看到错误的地方,更不要说这么随便的一个技术博客。错误的知识不可怕,可怕的是你记住了它,并认为它是对的。你辛辛苦苦花了很多时间去学习,最后掌握了一大堆“错”的知识,感觉如何?是不是很过瘾?

3.我对UI/UX技术感兴趣,但是我为什么去学习内核方面的知识?原因很简单——就是了解如何更好的去实现UI。

记得酷派的一个朋友说我过于研究理论知识了。其实不然,事物的表象变化很快,事物的本质变化很慢;事物的表象很繁多,而本质会很单一。朋友也问我如何在技术更新如此之快的环境下不被淘汰,这就是我的回答。

另外如果你的知识体系里面过多的是“How to”,而很少有“Why”、“How to be better”这样的知识,会很容易被淘汰。

4.Windows Phone 7不管是CE 6.0 based还是7.0 based,所以我猜测在API机制方面变化应该是不大的。差异比较大的是CE 5.0向6.0跨越的时候。

5.在这里感谢马宁、肖翔等对我的指导和帮助,刚进部门时我就问马宁:“你们微软的员工是不是都是这么平易近人呢?”。恩,我没有遇到过比微软员工还优秀的同事了。我老喜欢向他们问些很“幼稚”的问题,但是他们不会让你觉得你的问题“幼稚”。

6.我的PS居然跟正文差不多长了,我果然废话连篇。