一般的,我们在编写用户态程序(包括普通的应用程序、服务程序、用户态驱动、一些扩展插件)时,每当我们遇到一个不熟悉的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》的进一步补充和深入,把一些迷糊的问题搞清楚点。
我大体罗列了这篇文章涉及的问题:
在文章《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调用情景,比如:
在调用中遇到的Calling back & forward问题,以及如何Mapping指针以及访问其它进程的内存和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居然跟正文差不多长了,我果然废话连篇。