理铭's profile阿闷的共享空间PhotosBlogListsMore ![]() | Help |
|
阿闷的共享空间June 22 《代码之美(Beautiful Code)》这本书有33位作者! 每位作者都贡献了一章内容:
Jon Bentley:久负盛名的《Programming Pearls》(《编程珠玑》)的作者。在斯坦福获得学士学位,在北卡罗莱纳获得硕士和博士学位。继而在卡内基梅隆执教6年。贝尔实验室前研究员,西点军校和普林斯顿的访问教授。
Brian Kernighan:C语言圣经K&R C(《C程序设计语言》)和《程序设计实践》两本不朽著作的作者,他的书被翻译成近30种不同的语言。
Charles Petzold:经典的《Windows程序设计》影响了整整一代程序员,被奉为Windows编程圣经。而他的另一本经典著作《编码的奥秘》则另辟蹊径,由浅入深地将计算机最深层的奥秘娓娓道来。
Tim Bray:XML创始人之一。
Yukihiro "Matz" Matsumoto:Ruby之父。
Douglas C. Schmidt:著名的C++跨平台开源框架ACE的设计者,《C++网络编程》卷I,卷II的作者。
Jeff Dean:天才架构师,Google大型并发编程框架Map/Reduce作者。
Diomidis Spinellis:两届Jolt大奖获主,分别以《Code Reading》和《Code Quality》获2004 和2007年的Jolt大奖。
Simon Peyton Jones:Haskell语言核心人物之一,并领导设计了著名的Haskell编译器GHC。 Douglas Crockford:JSON发明者,Javascript领域大牛,写了广为流传的《Javascript,世界上最被误解的语言》。
Bryan Cantrill:著名的DTrace的作者之一;之前是Sun杰出工程师,主要工作领域为Solaris内核开发...
Greg Kroah-Hartman:目前的Linux内核维护者,经典的《Linux Device Drivers》的作者。
Andreas Zeller:大名鼎鼎的GNU DDD可视化调试器的作者,著作《Why Programs Fail》获得2006年Jolt生产效率大奖。
Sanjay Ghemawat:大规模分布式文件系统Google FileSystem(GFS)的主要作者(GFS是Google的基石之一),同时也是Google Map/Reduce以及Google BigTable的作者之一。
不读《代码之美(Beautiful Code)》的10大理由
10. 危及感情:爱不释手,容易专注,老婆/女友会怀疑我有外遇。 9. 影响心情:从此我总会发现很多不美的代码。 8. 剥夺乐趣:编程的乐趣有很大一部分在于不停地追求完美。 7. 备受压榨:工作效率大大提高,老板只会给我更多工作来做。 6. 各有所爱:我就喜欢通过海啃书山来了解大师的精髓。 5. 分裂社区:只能让读过本书的人先牛起来,在开发界造成严重的"菜-牛"差距。 4. 没有需求:最优秀的开发者根本不需要最优秀的书。 3. 诱发犯罪:不管我买几本,都会被同事偷走甚至抢走。 2. 传染性强:一旦我买了,其他同事肯定也会买。 1. 无知者无畏:不读本书的最大理由其实只有一个,那就是你没有读过。 April 17 Making parts of Windows CE Device Driver Code Non-PageableMaking parts of Windows CE Device Driver Code Non-PageablePosted by Wes Barcalow Following on to Sue’s previous posts describing the paging pool and memory management, I wanted to talk about how drivers can be made pageable for additional virtual memory savings. Windows CE has features to allow for more data and code to be used on a device than the available RAM. It does this by ‘paging’ resources into RAM from fixed or read-only storage (ROM/Flash), and discarding pages if the overall amount of RAM available in the system becomes too low. In systems where code cannot execute directly from ROM, this paging is the only available way to use storage to offset RAM usage. This is the case for NAND Flash, which is more perfomant and of lower cost than NOR Flash (which does allow XIP or eXecute In Place). Some code and data in the system is read (‘paged’) into RAM and ‘locked’ there – it is marked as non-pageable after it is loaded. This code and data must be available, namely when the storage it was retrieved from is no longer available. For example, to achieve the best power saving on entry to a low-power or deep-idle mode, it is preferable to turn off the power to a NAND Flash chip. Applications are typically pageable, since the operating system completely stops the threads of applications before entering a low power mode. At this point, since the application's code will not be executed and it's data cannot be accessed, such code and data can be ‘paged out’ and is not needed. For device drivers things are slightly different. Most drivers written for Windows CE / Windows Mobile are by default loaded non-pageable by device manager. This means that no matter how big the driver is, it takes up all the RAM it wants to once it is loaded – none of it can be paged out. In the case of user mode drivers, udevice.exe loads the driver instead of device manager, but it too uses the same criteria for choosing between pageable and non-pageable modes. With an increase of functionality or flexibility in a driver comes an increase in size. A camera driver that supports many formats or many features may be very large. However, if the camera is not used for a long time, then the RAM resources taken up by it is not being put to efficient use. It makes sense to make this type of driver pageable by default instead. To make a driver pageable, these steps have to be taken. 1) Tell device manager you want the driver to be pageable. 2) Tell the kernel that pageable mode is allowed. 3) Identify and flag code that is needed to be non-pageable. The last step is slightly more complicated than the first two steps. Even though you may have a large driver, you may still need portions of it to be non-pageable. The most important parts of a Windows CE / Windows Mobile driver that cannot be paged out are functions that execute when the file system is not in operation. If you do not have such functions in your driver then you do not have to worry about making them non-pageable. Marking a driver as pageable needs to happen in two steps; the first is with a registry setting for that driver. It may or may not already have a “Flags” registry entry. To enable the driver to be paged ensure there is a registry value named “Flags” of type DWORD entry and that in its value the DEVFLAGS_LOADLIBRARY bit is set (0x02). If there are other flag bits set, simply logical ‘or’ this with what is already there. Here is an example of what a GPIO driver registry setting might look like in platform.reg: [HKEY_LOCAL_MACHINE\Drivers\BuiltIn\GPIO] "Dll"="gpio.dll" "Flags"=dword:10002 ;Trusted caller only & pageable ... The second step for marking a driver pageable is ensuring the ‘M’ flag of the binary image builder file (BIB file) is not set. The purpose of the ‘M’ flag is to inform the kernel not to demand page the driver, thus forcing the driver to be completely loaded into RAM. Here is an example of what a GPIO bib file entry might look like that allows the driver to be loaded in a pageable mode by the kernel: msm7x00_gpio.dll $(_FLATRELEASEDIR)\gpio.dll NK SH
Notice the flags at the end of the statement, there is no ‘M’ flag. A user wishing to force the driver into a non-pageable mode would use “SHM” instead of “SH”. Or alternatively, a user wishing to force the driver into a non-pageable mode would clear the DEVFLAGS_LOADLIBRARY bit in the registry. Either approach is valid. It is also worth pointing out that a trusted user can potentially change the registry after run time, thus changing a driver from non-pageable to pageable and back again. The bib file flag, however, is built into the image and cannot be overridden. Both are viewed as equally secure as only a trusted caller can change the registry, though the bib file flag provides a predictable pageable status when loading the driver. The final, more complicated step from above is to identify and isolate code that can’t be pageable. As mentioned above, this is code that runs in single threaded mode where the file system cannot page in or out code and data. The most well-known examples of this are: - XXX_PowerUp - XXX_PowerDown - Interrupt Service Threads and Interrupt Service Routines (ISTs and/or ISRs) that may execute while the file system is inactive. - Read-Only constants that are accessed by these functions. - Any supporting code called by these functions. - All code associated with the file system path, as it is responsible for bringing in new pages. Once the code is identified, it should be wrapped in compiler #pragma statements to inform the linker about the properties of the code. Below is an example of making xxx_PowerUp and xxx_PowerDown non-pageable. #pragma comment(linker, "/section:.no_page,ER!P") #pragma code_seg(push, ".no_page") XXX_PowerDown() { //Perform single-threaded power off logic }
XXX_PowerUp() { //Perform single-threaded power on logic } UtilityFuncOne() { // Non-Paged utility function that can be called by // both page and non-paged code } #pragma code_seg(pop)
UtilityFuncTwo() { // Paged utility function that can only be called by other // paged code. }
This sample code shows the XXX_PowerDown and XXX_PowerUp code being marked as pageable. This will allow the processor to access this code in RAM while the file system is not in operation (during suspend and resume operations). UtilityFuncOne is also in the non-paged section of code, thus making it safe to call from within XXX_PowerUp/Down. However the UtilityFuncTwo code is outside of the non-paged area, and therefore pageable and at risk of not being available if the processor were to try to access it while performing suspend / resume operations. To test for drivers marked as pageable that are critical to suspend, resume, and shutdown code paths the registry key PageOutAllModules can be used to instruct the kernel to page out all code. This can be used to find drivers that use pageable code when calling XXX_PowerUp and XXX_PowerDown API’s while the file system is inactive. By generating page faults, problematic drivers can be identified more easily. Below is what the registry key looks like: [HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Power] "PageOutAllModules"=dword:1
Set this registry key and force the system to suspend, resume, or shutdown. The OS will then page out all code marked as pageable and proceed with suspend / resume / shutdown operation. If a critical driver is improperly marked as pageable then this process will generate a page fault and device will die. This technique will help ensure that all drivers a properly marked as pageable/non-pageable when preparing to release the device to market. By making your driver pageable you can decrease the load on the system for resources while a component or feature is not being used. It is important to take care as outlined above to make sure some important parts of your driver can still function even though in general the bulk of it is ‘paged’.
How does Windows Embedded CE 6.0 Start?How does Windows Embedded CE 6.0 Start?Posted by Kurt Kennett, Senior Development Lead, Windows CE OS Core Operating system code, as one of my colleague developers recently realized, is “just code”. It’s not voodoo and it does not exist on a higher plane of knowledge. In fact, an operating system kernel is usually remarkably well structured and well designed in comparison to other pieces of software. When you think about it, it has to be. More than one person needs to understand and maintain a core set of code that must work and must support debugging of all other software that runs upon it. People move on, and change jobs to look for new challenges to keep learning. If only one person understood the way an operating system worked, then there is a huge amount of risk. One of the most interesting facets to Operating Systems that I’ve followed in my career is how they start. Initialization is the last step in design, but at the same time it uncovers the most fundamental bedrock of the principles used. You start with literally nothing but a CPU which can execute instructions (sometimes not even with memory to use), and must take a platform from that point to a fully functioning system - one that not only utilizes available hardware, but abstracts it to a common understanding. What I’m going to do in this article is discuss the details of how the Windows CE 6.0 kernel starts, and the association of the ‘Microsoft’ kernel code with the code that comes from an Original Equipment Manufacturer (OEM). It is hoped that by relating this understanding more people will have a better idea of the ‘hows’ and ‘whys’ of the Microsoft design. To start, let’s quickly review how the operating system software is built. The Microsoft tool chain emits .EXE and/or .DLL program files. Files of both these types of extension are “Portable Executable” format, or “PE” format. They are practically identical in every aspect:
There is nothing extraordinary about the operating system kernel program – it is compiled using the standard compiler and with a minimal set of definitions for that compiler. An EXE file is produced (called NK.EXE). It does not link to any external library or DLL – it can’t. When this code starts there is nothing in the system, or even a system for that matter. Since the EXE is in a known format (PE COFF), you can determine the entry point from looking at the EXE header. This means we know where to set the CPU’s instruction pointer to so that the program can start. One additional property is that a PE file can be arranged so that it may “execute in place”. This means that if the file data is placed at a particular virtual address, no changes need to be made to the program code in the file in order for it to address other code and data at the correct addresses. For example, I can tell the Microsoft linker program to place the kernel program file at the virtual address 0x80000000. Then references to code (function entry points) will be placed in the EXE file such that other code can jump to them by address. If function foo() is at address 0x80001000 and inside its body it calls a function bar() which sits at address 0x80005000, there will be an instruction stored directly in the program code for ‘foo()’ that calls to 0x80005000. The dotted lines are just the delineation of function code start or end.
If the EXE program file for the kernel could not sit at 0x80000000 and had to be moved, the ‘bar()’ function would move with it and the call instruction in ‘foo()’ would have to be changed to have the correct, new address. Otherwise it would call to the wrong place:
You can see in the example above that if the kernel EXE file that is designed to be placed at 0x80000000 is loaded at 0x80050000 instead, the instructions in the program will be incorrect. The process of changing an EXE or DLL program file after it has been loaded to reflect the actual load address is called “fixing up”. Records are placed in a standard EXE file which allow the program file to be fixed up. However, until the fixup process is done the addresses of functions in the EXE will be incorrect. To get around this, Windows CE kernel EXE files are fixed up beforehand to be loaded at a specific address. A program called ROMIMAGE actually pre-processes the kernel EXE file and some DLLs that are used and fixes them up when it builds the operating system image file (NK.BIN). To recap, we get a fixed-up EXE file which is called NK.EXE which contains portions of the operating system kernel. This EXE has an entry point defined in it, the same as every other COFF EXE or DLL. For execution to start, the bootloader for the system is supposed to put the image file at the right address, find this EXE entry point and jump to it. The bootloader is a separate discussion, and its startup and execution is very platform-specific. For the context of this article, we will simply assume that a bootloader places the OS image file into memory at a specific address. We will see below how the bootloader can find the NK.EXE file within the image and then find its entry point.
The NK.EXE is only part of the Windows Embedded CE 6.0 kernel – it comprises the OEM Adaptation Layer (OAL) and boilerplate code to start the system. The main portion of the operating system kernel that does all the process, thread and memory functionality lives in a Microsoft-supplied DLL called ‘kernel.dll’. This is a DLL which is also ‘fixed up’ by the ROMIMAGE program to live at a specific virtual address in memory. So this means there are at least two executable modules that we need to know the location and the entry point of. The entry point address is stored inside the EXE or DLL file, but what about the location of the EXE and DLL files inside the image? Windows CE images have an important structure set up by ROMIMAGE that is placed into the image file, called the “Table Of Contents”, or TOC. This TOC holds pointers and metadata for the operating system image file. Somewhere near the beginning of the image file a marker is placed – the bytes “CECE” (0x44424442). Right after this marker is placed an offset to the TOC. This allows a bootloader or other program looking at the file to be able to find information about the image. In addition to this offset value that is prefixed by a marker, the OAL must define a public symbol called ‘pTOC’ (exported using ‘C’ naming conventions), which ROMIMAGE can find and fill in with the virtual address of the TOC when it prepares the image file. When compiled, the pTOC variable in the NK.EXE must have the value 0xFFFFFFFF. When it prepares the NK.BIN OS system image, ROMIMAGE does the following (in addition to other tasks):
This way, when the NK.EXE starts it can reference this variable to know where the TOC is. Using the TOC, the program can find all the other pieces of the operating system image. ROMIMAGE uses the configuration .BIB files to know where the image is supposed to go and where RAM is. There are two important parts of the CONFIG.BIB file – the RAMIMAGE and the RAM lines. Here is an example from the Device Emulator’s CONFIG.BIB: NK 0x80070000 0x02000000 RAMIMAGE These entries tell ROMIMAGE what to do. It knows to place the OS image file at 0x80070000, and that it can start using read/write memory at 0x82070000. With this information it can place modules such as NK.EXE and KERNEL.DLL into virtual memory, and then build a TOC and put that into the image as well. To help the kernel start, the TOC also contains information on where RAM is. A more detailed look at what is in memory when the image file has been placed is shown below:
In order for the actual operating system to start, the bootloader needs to:
The really interesting stuff happens once the NK.EXE program is started. In broad strokes, it has its own tasks to perform:
We will walk through these activities in detail to better understand them. Some parts of the startup process are CPU-type-specific. For instance, the ARM CPU and the X86 CPU have different virtual memory management hardware and mapping structures. However, to keep things consistent a general process is maintained. Whenever possible I will attempt to call out any operations specific to an architecture. When the NK.EXE starts, there are a few prerequisites of the system:
An additional prerequisite can be satisfied before NK.EXE starts, or can be done in the very beginning of NK.EXE execution: 4. RAM should be writeable without any supplemental configuration (for example, of a memory controller). These assumptions allow the NK.EXE startup code to do what is necessary to bring any particular system up, and not have to worry about some things being done and others not being done. Point (3) above may be counterintuitive, but since the kernel must be entirely self-contained, it does not make sense for it to rely on the bootloader to configure virtual memory properly before it starts. This ‘decouples’ the OS from whatever bootloader is used to start it. When it starts executing instructions in physical address mode, the first action taken by NK.EXE is to calculate the physical address of the OEMAddressTable symbol. This is a table that is built into the kernel that defines the static (unchanging) default regions of virtual memory. NK.EXE knows:
Using this information, a simple calculation tells it the physical address of OEMAddressTable: NK::PhysicalBase + (NK::Virtual OEMAddressTable – NK::Virtual Base) è NK Physical OEMAddressTable The OEMAddressTable has triads of DWORDS making up a line in a table, with the following format:
From the information found in this table, the NK.EXE program can set up the virtual memory mapping tables for the Memory Management Unit (MMU) to function. Where the MMU-formatted mapping tables are kept and what they look like is platform-specific – the OEMAddressTable is a simplistic format that works for any architecture. Virtual memory is set up using the data in the OEMAddressTable and enabled, and then the NK.EXE transitions to the virtual address where it can execute code. One thing to note at this point is that anything that is supposed to be in RAM that needs to be pre-initialized (set to zero or some other known value) is not yet available. RAM is still a clean slate and can have any contents whatsoever. The initialization values in the image file (the .data sections of NK.EXE and other modules) for read/write data must be copied from the image to actual RAM addresses before they can be properly used. How does the NK.EXE know what to copy or where to place things in virtual RAM for these modules? The TOC. The TOC not only lists the start addresses of all modules in the image, but it also describes RAM and where the read/write portions of each module are to be located so that the kernel can work with them. Pieces of the OS image that need to be copied to RAM are called “copy entries”. Before the NK.EXE can access its own read/write variables, it needs to copy the copy entries to RAM. This begs the question – the pTOC is a variable, isn’t it? How could the NK.EXE know where the pTOC is if it hasn’t been set up? The answer is that the pTOC is a read-only variable – only ROMIMAGE writes to it when the image file is created. The storage for pTOC is not located in RAM, and does not need to be copied before its value can be used. The function inside NK.EXE that copies all the copy entries described by the pTOC to RAM is typically called “KernelRelocate()”. It is a simple process of going through a simple table of structures and copying ranges of virtual memory from one place to another. Once it is finished all NK.EXE variables can be read from or written to just like any other program.
At this point we have a working program, just like any other program for decades past. It executes instructions, can call functions, and can read and write memory locations. There are no threads, no processes, and no operating system constructs, but everything is placed in a known location and can be accessed to let us do the rest of the startup of the higher-level systems.
Virtual Memory allows a tremendous amount of flexibility. Windows CE reserves a few regions of the virtual address range for its own private use inside the OS kernel. There are several ranges of 4k ‘pages’ of virtual memory that are set aside in the highest address ranges, from about 0xFFFE0000 upwards. The kernel maps some physical memory into this range to store its ‘global’ dynamic data. Some of this memory can be used for memory mapping tables for an architecture-specific MMU. Some is reserved for the kernel-mode and interrupt stacks. Most importantly, at least one of the 4k pages is reserved specifically as a ‘Kernel Data Page’. This page contains a plethora of data fields which is specific to a version of the kernel. The NK.EXE sets up the location and initial contents of this page directly.
Three important values stored in the structure by NK.EXE:
The first two pieces of information are placed in the Kernel Data Page so that any code that knows the address of the Page can find what is in the OS image and the basic layout of virtual memory. The last piece of information is specifically used so that the NK.EXE contents can be used once control has been passed to KERNEL.DLL. In general, the contents of the reserved portion of virtual memory looks like:
Now that the Kernel data page has been initialized and virtual memory is active, we can jump into the Microsoft KERNEL.DLL executable’s entry point. Remember, we can find the KERNEL.DLL file in the image by using the TOC, and then we can scan for the entry point of the module. Even though NK.EXE knows where it is going to put the kernel data page in virtual memory beforehand, the KERNEL.DLL cannot assume its location. Therefore, we pass the virtual address of the kernel data page to the entry point of KERNEL.DLL. Although the Microsoft code can call back into the NK.EXE function addresses, control is never fully restored to the NK.EXE program.
After the jump, we are now executing Microsoft kernel code. The code at the entry point is given the address of the Kernel Data Page, and through its fields the TOC to know anything it needs to know about the OS image. The kernel does some basic setup of its own and sets some critical data fields for its own use into the Kernel Data Page.
The KERNEL.DLL has a static table of functions and data, called “NKGlobals”, which is built into its DLL simply as a static data structure. Since the KERNEL.DLL is fixed up by ROMIMAGE to run from a particular virtual address, the function pointers in the NKGlobals will be correct when the KERNEL.DLL code starts to run. Some of the functions pointed to by this structure are ones like SetLastError() and NKwvsprintfW(). These are routines that the NK.EXE is allowed to call directly. However, it is important to note that at this point the NK.EXE does not know where these functions are in KERNEL.DLL – it still needs to be told where this table of functions and data is inside KERNEL.DLL .
The KERNEL.DLL passes the address of “NKGlobals” back to NK.EXE in a function call to OEMInitGlobals(), the address of which was left in the Kernel Data Page. So, in essence the function call graph looks like this:
As shown above, the OEMInitGlobals() function stores a pointer to the NKGlobals structure that resides in KERNEL.DLL. After it stores this pointer, NK.EXE can use it to find the addresses of the KERNEL.DLL functions it is allowed to call.
OEMInitGlobals also passes back (via function return value) a pointer to its own structure, called “OEMGlobals”. This structure is critical to the kernel to get access to all the functionality that is platform-specific that is inside NK.EXE. The KERNEL.DLL module is constructed so that it will run on any processor belonging to a certain architecture (X86, ARM, etc). The NK.EXE is the abstraction of a specific species of the architecture (such as XSCALE or OMAP processor) and the platform that supports that architecture. The OEMGlobals structure is comprised of function pointers and data just like NKGlobals. Some of its members include:
These function pointers point to the legacy OEM functions like OEMInitDebugSerial and OEMIoctl that live inside NK.EXE. Many other functions are listed so that KERNEL.DLL can do what is necessary for a particular platform. The functions are fairly self-explanatory in name and are well documented on MSDN.
Once the call to OEMInitGlobals() completes, the KERNEL.DLL has everything it needs to do architecture-generic and platform-specific processing. It knows where memory is and how it is laid out virtually, as well as the location of every module in the image. The NK.EXE also has a pointer to a table of functions it can call. In essence, the two code modules have executed a manual ‘handshake’ by executing a simplistic method of manual dynamic linking.
Everything up to this point that NK.EXE and KERNEL.DLL have done has been done without any processes or threads, and without any kernel services running. To bring the rest of the system up, the KERNEL.DLL has to do three things:
The architecture-specific setup is done first by a call to a KERNEL.DLL function called <architecture>Setup. On an ARM platform this would be called ARMSetup(). On an X86 platform this would be called X86Setup(). The actions taken by the architecture-specific code are numerous, but they all execute in a single-threaded context with no processes running. The actions taken here include but are not limited to:
The one other thing this architecture-specific code does is set up the Interlocked API code so that NK.EXE knows where it is and can call it. This is a bit of an aside, but I will explain in detail because it is a critically important piece of the OS.
Even at the most basic level, Windows CE needs to coordinate actions among different threads of execution – even some that run inside the kernel, outside the scope of any specific process. The mechanism used to do this with the highest amount of efficiency is the Interlocked API. The API consists of a handful of functions, the most important of which is InterlockedCompareExchange(). The purpose of this function is to:
These four steps are meant to execute atomically, and they form the basis of coordination between different threads. That is, there should be no interruption between each of (1), (2), (3) and (4). The only way to guarantee this on some of today’s processors where the operation is not available directly in hardware is to ensure interrupts are disabled. Herein lies a problem, since user-mode processes do not have sufficient privilege to disable interrupts, and it would be very inefficient to have to do a system call to the kernel and disable interrupts every time two threads wanted to coordinate with each other.
To be efficient, there is one single place in the entire system where the InterlockedCompareExchange() happens. The code for the four steps above is placed in the Kernel Data Page, at a particular location that is well known. Then the NK.EXE and KERNEL.DLL (and any process which has the Kernel Data Page mapped) can call the code, and the instructions all occur in the same place. This is done so that the API is restartable. What does this mean? Why do we do this?
Thread switches in an operating system can happen for three reasons:
The second two cases are really the same – an interrupt occurs that ultimately causes a thread switch. Since an interrupt can occur between any of the steps (1) to (4) and potentially switch out the thread, the operation we needed to be atomic might not be – some other thread might run in between (2) and (3), for example.
To ensure that the instructions (1) to (4) occur atomically, every time there is an interrupt a simple bounds check is made to see if the CPU was currently executing somewhere in (1) to (4). If the interrupt occurred when the CPU was executing after (1) and before (4), then the instruction pointer for the current thread is reset to point to instruction (1), so that the operation may be retried. In order for the interrupt code to be able to check if the CPU was executing in between (1) and (4), the code for it must be in a single known location. That location is inside the Kernel Data Page.
Once the Interlocked API code has been copied to the Kernel Data Page, the NK.EXE knows where it is and can coordinate actions with KERNEL.DLL when multiple threads become active – ultimately by using the Interlocked API.
Back onto our main discussion, the next step in the KERNEL.DLL startup is the architecture-neutral setup. One of the first architectural-neutral things to set up is to see if the OS image includes a KITL.DLL to allow communication with and debugging of the OS kernel.
KITL stands for “Kernel Independent Transport Layer”. This is basically a mechanism by which data ‘packets’ specific to the Windows CE system can be passed between the kernel of the device and Platform Builder running on the desktop. Usually, the portions of KITL which are implemented in NK.EXE purely revolve around the encoding for transport and the transport of the data packets. A Board Support Package (BSP) does not have to know anything about the data being sent and received between the device and the desktop – it just has to facilitate the correct transmission and reception. Mechanisms for transport of the KITL packets include but are not limited to RS232 Serial, Ethernet, and USB. A full description of KITL is beyond the scope of this blog article.
Other actions that happen during the architecture-neutral setup include:
When the architecture-neutral portions have been completed, we can do the platform-specific setup. This code lives in NK.EXE since it is OEM and board specific. To initialize this part, the kernel calls into OEMInit() through the function pointer that is in the OEMGlobals structure. OEMInit does board-specific initialization, and can do one other important thing – start KITL.
If KITL is built into the NK.EXE, then its functions are directly accessible from NK.EXE. If KITL is in a DLL, then that DLL will have been loaded by the kernel at the beginning of the architecture-neutral setup, as shown above. In either event, the OEMInit() function can call a Kernel IO control saying that KITL should be started. Based on whether the KITL.DLL was found or not, the kernel knows what to do.
Upon return from OEMInit(), the kernel is ready to start processes and threads to run. It synchronizes its cache, and then enters the processor architecture’s service mode if it is not already running in it. Then it does any one-time inits that do not require a current thread. These actions include:
After all single-threaded initialization is done, the kernel is ready to schedule the first thread. This first thread is called “SystemStartupFunc()”, and lives in KERNEL.DLL. To start the thread, the kernel specifies that there is no current thread to switch from, sets the first thread as the only one available to run, then calls into the thread scheduler code. The scheduler code takes a look at all available threads and chooses the next one to run. At this point in startup we only have one thread that has been manually set up to run, so that one is the one that is switched to.
The SystemStartupFunc() function begins execution by flushing the system cache, then does things that require a ‘current’ thread to be running in order to happen. These actions include:
The SystemStartupFunc() will call one more OEM function before it completes initialization – it will call the OEMIoctl() function through the function pointer in the OEMGlobals, with an argument ‘OEM_HAL_POSTINIT’. This tells the NK.EXE that all system startup has completed and we are about to schedule threads and processes.
Upon exit from this first call to OEMIoctl(), the SystemStartupFunc() initializes the system message queue, any watchdogs, and then creates and starts the threads for the power manager and file system. Thus, the rest of the higher-level parts of the operating system begin to execute here. The last operation taken by the SystemStartupFunc() is to create another thread which executes the function “RunAppsAtStartup()”. This function creates the first user processes.
We are now at the point where the kernel, power manager, and file system are all executing, and applications can begin to get executed that have been described to run in the system registry.
This concludes the blog entry on how Windows Embedded CE 6.0 starts. The internals of Windows CE are quite interesting and very well structured, and the startup process described above gives insight into the most critical system components. In the future I hope to publish other articles on the internals of the system registry, the file system, and the device and power managers. Adding and removing KITL drivers in x86 BSPsAdding and removing KITL drivers in x86 BSPs
Overview Today I want to chat about what it takes to support a new Ethernet chip for download and KITL debugging on an x86 PC-based platform. We'll start by talking about how Ethernet drivers are represented in the x86 KITL structure, then we'll walk through (in a detailed, step-by-step fashion) adding a new driver to your bootloader and OS image. This article is valid for both CE5.0 and CE6.0.
This articles assumes you have some knowledge of what KITL (Kernel Independent Transport Layer) is, as well as basic understanding of what a bootloader does.
Architecture First, let's take a look at the architecture of x86 KITL. At the lowest level for our discussion, there is the driver code that supports our NIC (network interface card). This code implements the functions in the OAL_KITL_ETH_DRIVER structure, as described in platform\common\src\inc\oal_kitl.h. Things like GetFrame, SendFrame, Init, and InitDMABuffer will all be implemented here. There are working samples of this code in platform\common\src\common\ethdrv.
Because x86 supports a PCI bus, there may be multiple supported chips; the CEPC platform, for example, supports the RTL8139 chip as well as NE2000 compatible chips, among others. Each NIC we support will have a separate driver library that implements these functions. The OAL_KITL_ETH_DRIVER structure that represents our NIC driver plugs into a larger structure to allow us to support multiple drivers. This larger structure lists all of the drivers the platform supports. It is found in platform\common\src\x86\common\kitl\kitldrv_x86.c (1), and its type is SUPPORTED_NIC.
If we look more closely at the SUPPORTED_NIC structure we'll see several fields that distinguish one NIC driver from another. We can see the code in platform\common\src\x86\inc\x86kitl.h: // // Ethernet debug controller vendor and PCI information. // typedef struct _SUPPORTED_NIC // NIC vendor ID { USHORT wVenId; // PCI Vendor ID USHORT wDevId; // PCI Device ID DWORD dwUpperMAC; // 1st 3 bytes of mac address UCHAR Type; // adapter type UCHAR szAbbrev[3]; // Vendor name abbreviation const OAL_KITL_ETH_DRIVER *pDriver; // corresponding driver } SUPPORTED_NIC, *PSUPPORTED_NIC;
The comments are pretty self-explanatory. The only one that's a little ambiguous is the UCHAR Type, which is just a CE-specific type that you can pick from the list in public\common\oak\inc\halether.h - EDBG_ADAPTER_NE2000, for example. The wVenId and Type fields in the SUPPORTED_NIC structure will identify the NIC to the bootloader and KITL as supported.
How it Fits Together The SUPPORTED_NIC structure is compiled into oal_kitl_x86.lib, which our bootloader and KITL will link with. The bootloader and KITL will also link with static libraries for each NIC driver, such as rtl8139dbg.lib, ne2kdbg.lib. You can see examples in the SOURCES files in platform\CEPC\src\bootloader\eboot and platform\CEPC\src\kitl (2).
With all of this in place, we'll run the bootloader at device boot time and it will enumerate the devices on the PCI bus. It will read the PCI Config space and find network-class devices that have a PCI Vendor ID that matches one in the SUPPORTED_NIC list. If it can't find such a device, it will check the Adapter Type in the SUPPORTED_NIC list and match it against the default adapter type that's compiled into the bootloader. Once it finds a match, it will use the driver to download the OS image and then begin execution at the OS level.
Once downloaded, the OS will perform some basic initialization. Then it will go through the same matching process to discover a supported network card for KITL. If it finds a match, it will use that driver and NIC for the KITL connection.
Walkthrough Now that we understand how it all works, let's consider what is needed to add our own driver to this structure. We'll use CE6.0 as a basis since the architecture is the same in 5.0 and 6.0; see the footnotes for filenames / paths that have changed slightly from CE5.0.
Our overall approach will be to first create a driver library that supports the new NIC. Then we will create an x86 KITL library that includes this driver in the list.
We can use one the existing drivers from platform\common\src\common\ethdrv as a baseline for creating our driver. We’ll copy this driver into our BSP and then modify it there.
Step 1) Copy the platform\common\src\common\ethdrv\rtl8139\... to a directory in your own platform (for example platform\MyBSP\src\kitl\ethdrv\MyNIC).
Add your new directory to the DIRS file in the parent directory so it gets compiled when we build. In this new directory, change the filename of rtl8139.c to MyNIC.c. Open the SOURCES file and change the TARGETNAME to bsp_ethdrv_mynic, and change the SOURCES target to MyNIC.c.
Step 2) Replace the copied driver functions with functions that support your NIC.
This is the hardware-specific code that initializes your NIC, allows it to send and receive frames, etc. Note that the function names will change but the signatures should match the OAL_KITL_ETH_DRIVER structure. Once we have the code written, we’ll create a header with some prototypes so the functions can be referenced by the bootloader and KITL.
Step 3) In your platform\MyBSP\src\inc directory, make a header file, MyNIC.h that prototypes the MyNIC.c functions.
You can copy the RTL8139 prototypes from platform\common\src\inc\oal_ethdrv.h, and then modify them to match your MyNIC.c function names.
The next hurdle we need to get over is the fact that our driver list is compiled and linked in an "off-limits" directory. We can't modify the platform\common implementation for a number of reasons - if there is ever a QFE in this code, our modifications would conflict with it. Secondly, if we add our driver to the common list, the driver will be expected by all x86 BSPs that use the common library. So, we need to take the common list and make it our own. The easiest way to do this is to simply clone it.
Step 4) Copy platform\common\src\x86\kitl\... to a directory in your own platform (for example platform\MyBSP\src\kitl\x86kitllib).
Add your new directory to the DIRS file in the parent directory so it gets compiled when we build. Now that we have a copy of the supported NIC code, we need to add our driver to the list. We can also remove drivers that we don't want to support, thus reducing the size of our bootloader and OS. Before we can add our driver, though, we need to define an OAL_KITL_ETH_DRIVER structure that describes it.
Step 5) In your new kitldrv_x86.c, define an OAL_KITL_ETH_DRIVER structure for your NIC using the prototypes from your MyNIC.h file.
Step 6) Edit the SUPPORTED_NIC structure in kitldrv_x86.c, adding our driver to the list and removing any unwanted drivers.
If you don't know the PCI Vendor ID or Device ID of your NIC, you can boot your BSP without the driver in the list and examine the serial debug messages from the bootloader.
Now our NIC is in the supported list. We still need to spruce up our bootloader and KITL SOURCES files so that we're linking with our custom library as opposed to the original in platform\common.
Step 7) Edit the SOURCES file in .\base and .\baseboot (3) subdirectories of our cloned directory. Change the TARGETNAMEs from oal_kitl_x86.lib[s] to different names, such as mybsp_kitl_x86.lib and mybsp_kitl_x86_boot.lib.
Step 8) Edit the SOURCES files in your bootloader directory and kitl (4), and add the mybsp_kitl_x86_boot and mybsp_kitl_x86 libraries, respectively, to the TARGETLIBS for each SOURCES file. Note that the location of these libraries is going to be in $(_TARGETPLATROOT)\lib instead of $(_PLATCOMMONLIB).
Now we’ve pointed our bootloader and KITL implementations to link with our new driver list. The last step is to add the driver library that supports our NIC.
Step 9) Edit the SOURCES files in your bootloader directory and kitl (4), and add bsp_ethdrv_myNIC.lib to the TARGETLIBS for each SOURCES file.
Now you can rebuild your BSP and the resultant bootloader and OS image will support your new NIC!
I think you might agree that this architecture isn't the most flexible; that's something Microsoft will look at for future versions of CE.
(1) [In CE5.0, the filename is just kitldrv.c,] (2) [In CE5.0, the driver libraries are linked with the OAL and the kernel in platform\CEPC\src\kernel\kernkitl] (3) [In CE5.0, there are no subdirectories, so you only need to change a single SOURCES file.] (4) [In CE5.0, this would be the kernkitl directory] April 09 太牛了,这是一种什么样的国际主义精神啊,呵呵[作者: 新长城 于:2008-04-03 08:11:33] 看了最近的新闻,对欧洲人彻底失望了。人家的态度是:西藏事件真的假的,who cares?德国之声更露骨:中国人就算没错,西藏人更没错,就算是武装起义,也是应该的。 看到这儿俺就明白了,什么支持藏独?全是假的,真支持的话没这个干的。就是讨厌你,想恶心你。你凑上去解释也没用,不信就是不信,证据再多也没用。 俺现在大彻大悟,最近正在认真学习东普鲁士,也就是east prussia的历史。碰到德国人的话,就问问他们这事咋办?被人家俄罗斯人给殖民了,我们中国人都替你着急。碰到法国人就问问尼斯,科西嘉什么时候还给意大利,碰到意大利人就问问什么时候能把这俩地方要回来,再问问他们怎么考虑西西里独立的问题。英国人就啥也不说了,碰到英格兰的就谴责他们压制苏格兰,爱尔兰和威尔士,碰到剩下这三地方的就同情他们,问他们为啥不敢反抗。鬼子如果说他活得挺好,不觉得被压制俺就更加同情他,倍有爱心。 这几天一直在号召德国人要回东普鲁士,心情很舒畅。终于知道李察基尔这小子是啥感觉,虽然是损人不利己,但总觉得自己很有理想,很崇高,而且在精神上俯瞰被欺辱的德国人,鄙视抢占他们土地的俄罗斯人,立陶宛人等。 遗憾的就是youtube这烂地方经常删帖子,俺煽风点火的那些帖子存在往往不到几分钟,真他*的服了,这得多少人在线管理阿。 列个俺熟悉的表 1:科西嘉岛,Corsica及其wiki链接链接出处 介绍:18世纪以前一直是意大利领土,后来归属法国。 2:尼斯(nice),16世纪前一直是意大利领土,后来反复争夺,19世纪后彻底归属法国。 3:东普鲁士(east prussia) 这个成分很复杂,传统德国人聚集区,历史政治面貌可以参考wiki,链接出处该地区到19世纪末还是以德国人为主,现在基本没有德国人了,也不归他们统治。 4:加泰罗尼亚(catalonia),喜爱巴萨的球迷都知道,西班牙的传统独立倾向很强的区域。 5:西西里岛(sicily),这个地方历史上没有任何定论,大家轮流坐庄,19世纪归属意大利。因此西西里人到现在独立情绪还是很激烈。 6.直布罗陀,Gibraltar,英国的殖民地,西班牙一直想搞回来就是搞不回来。 7:英国的地名大家都清楚,剩下的阿尔萨斯洛林大家可以多研究,还有一些很不错的民族争议地区欢迎大家补充,我熟悉的主要是这些。 8:特里亚斯特(Trieste),夜月空山提供: 1382年到1918年一直都是奥地利的一部分,那里还有希茜公主的行宫。曾经非常繁荣,并入意大利以后衰落下去。 因为工作原因曾去那里呆过一段时间,两个印象特别深,一,风景灰常优美,半山半水的美丽古城。二,不少老人以及郊区的人还能说德语,郊区以及农村的人还有很多说斯洛文尼亚语,我住的那个旅馆老板娘一句英语都不会,而我除了Bon jorno不懂半句意大利语,但很快我就发现用德语可以和她流畅的交流… 在1382 年到1919年之间,的里雅斯特属于奥地利的一部分。1920年,的里雅斯特和整个弗留利-威尼斯朱利亚被移交给意大利。但这次合并剥夺了该市传统的腹地,使其重要性大为下降。1922年10月30日,贝尼托·墨索里尼的法西斯执政,斯洛文尼亚裔(占人口的25%)开始受到镇压,达到顶点 on 4月13日 1920年,意大利民族主义团体烧毁Narodni dom (National House),的里雅斯特斯洛文尼亚人的文化中心。 9.此外三个地方,夜月空山提供 o比利时,弗莱芒人vs瓦隆人 [ 夜月空山 ] 于:2008-04-03 15:58:41 罗马尼亚——特兰西瓦尼亚,围绕这个地方罗马尼亚和匈牙利成了死敌,以至于二战时同为德国盟军的他们自己打起来倒比跟苏联打热闹。 摩尔多瓦——德涅斯特河沿岸共和国。 Belgium——Flemish vs Walloon Romania——Transivania(就是出吸血鬼那地儿) , with Hungary Moldova——Transnistria(Pridnestrovian Moldavian Republic 看人下菜单 [ 四方城 ] 于:2008-04-03 15:22:53 英国: +北爱尔兰 +苏格兰 +威尔士 法国 +布列塔尼(Bretagne/Breizh) +科西嘉(Corsica) +尼斯(Nice) 德国 +东普鲁士*(East Prussia),首府哥尼斯堡。现在分属: ++波兰,主要城市:什切青(Szczecin,德语原名斯德丁Stettin),格但斯克(Gdansk,德语原名但泽Danzig) —— 这些地方还可以给波兰挖坑 ++俄罗斯,主要城市:加里宁格勒(Kaliningrad,德语原名哥尼斯堡Konigsberg) ++立陶宛,主要城市:克莱佩达(Klaipeda,德语原名Memel) 意大利 +西西里(Sicily/Sicilia),首府巴勒莫(Palermo) 西班牙 +巴斯克(Basque/Vasco/Euskadi),首府毕尔巴鄂(Bilbao) +加泰隆尼亚(Catalonia),首府巴塞罗那(Barcelona) +直布罗陀*(Gibraltar) * 是被占领土 波兰历史就是一部被瓜分史,对付他们很容易。捷克领土有啥问题没有?最近跳得也很欢。 附作者感言: 说道理不管用,讲不明白的[新长城 于:2008-04-03 08:29:25 他们要的是一种道德上的优越感,我们也可以嘛。 一定要多关心他们,例如每次和意大利人聊起来科西嘉的时候,他们总是逃避或者痛苦,或者装出无所谓的样子。哈哈,很好玩 俺喜爱科西嘉是因为科西嘉和我们这疙瘩比较有渊源[新长城 于:2008-04-03 08:35:18 没事讲讲这些很好的,往他们伤口撒点盐,最后表个态:俺支持勤劳善良美丽大方的意大利人,虚伪的法国人俺最讨厌。意大利鬼子感激的眼眶都湿润了 我没少拿直布罗陀恶心西班牙人[新长城 于:2008-04-03 08:43:01 曾经和西班牙人住一块,这帮鸟人很无聊,整天party。不过还算比较友好。每当他们流露出对我们缺少人权的自由的时候,俺就很清纯的问:直布罗陀怎么回事啊? 西班牙人倒也聪明:英国抢的呀,你们的香港不也一样吗? 俺继续问:香港回来了,直布罗陀什么时候回来啊。 西班牙人就开始沉默,然后傻乐 这几天一直在号召德国人要回东普鲁士,心情很舒畅。终于知道李察基尔这小子是啥感觉,虽然是损人不利己,但总觉得自己很有理想,很崇高,而且在精神上俯瞰被欺辱的德国人,鄙视抢占他们土地的俄罗斯人,立陶宛人等。 哈哈,昨天刚和一个西班牙帅哥聊天,那家伙一直说自己是加泰罗尼亚人,不是西班牙人,满口的悲愤 东普鲁士还好 装作茫然无知的问他们“德意志的精神首都柯尼斯堡早就想去旅游了,就是地图上怎么也找不到……”,不过这话只和纯粹较劲的德国SB说。 一般的同学什么的还是耐心和他们解释 很早以前就用科西嘉和法国美女掐过了;哦还有用塞浦路斯和希腊掐,最狠的是用伊斯坦布尔掐,他们总会纠正我那个地名叫君士坦丁堡,那才是欧洲人心中永远的痛啊。 土国三件事,君堡、库尔德、亚美尼亚屠杀 西班牙,直布罗陀,巴斯克 法国 科西嘉 意大利 的里雅斯特 希腊 塞浦路斯,小亚细亚东海岸 德国 东普鲁士,苏台德区 英国 北爱 加国 魁北克 北欧有什么破事? 哦,大斯堪的那维亚主义 hoi2里面,北欧就是大斯一个国家。。。 July 11 强烈推荐一个很酷的场景编辑器 - freeworld3d!它的主页,大家可以自己去看看 http://www.freeworld3d.org/
功能相当强大,可以做地形,摆mesh,摆路,放water,种草(billboard)等等,而且是所见即所得。可惜评估版本很多功能都没有。。。详细功能可以下载一个评估版本,然后选择 help-》tutorials 看。 种草
![]() 摆路
摆路
![]() ![]()
June 08 关于Ogre3D在MFC中的问题把Ogre3D放到MFC的视图中去其实很简单,但是还是有一个小问题说不定要花上一点时间去解决:OIS创建输入对象时抛出异常。这个问题当时花了我一个上午才解决。今天在gamers论坛上看到有人在问,然后就给他回复了一下。
May 20 EmbeddedWizard最新版本4.4支持Cursor Events(touch screen, mouse...)啦世界上最先进的嵌入式GUI开发工具EmbeddedWizard, 现在新出了一个4.4版本,我download回来试了一下,发现已经支持touch screen & mouse了. 已经是 perfect 的EmbeddedWizard, 以后会不会还有激动人心的新特性呢
preview some simple GUI examples designed by EW :
May 14 Memory marshalling in Windows CEMemory marshalling in Windows CE by Sue Loh Although the subject is memory access, this presentation is primarily about drivers, how drivers used to work in CE5 and how they will work in CE6. That's because it's most urgent for BSP and driver developers to understand how their code is going to have to change. But these explanations also cover system servers: how the implementations of APIs and services work. Drivers and servers work the same way. Let's begin with some quick definitions related to passing a pointer from client to server. Each term will be covered in more detail as we go.
An embedded pointer is a pointer that's passed to an API by storing it inside a pointer parameter, or nested inside another embedded pointer. For example, while the pMyStruct parameter to the following DeviceIoControl() call is a pointer parameter, the pEmbedded pointer that is stored inside MyStruct is an embedded pointer. Pointers that are passed by other means, for example by storing them inside shared memory or by using SetEventData() to attach them to an event, end up having all the same properties as embedded pointers and so should be treated as such. Access checking is verifying that the caller of an API has enough privilege to access a buffer that it passed to the API. (Access checking is not limited to memory, but in this case I'm only defining it with regard to memory.) The reason access checking is necessary is to prevent malicious applications from being able to induce driver code to perform actions on their behalf. Drivers have a lot of privilege, and can access a lot of system data. Applications can not. If a malicious application could cause a driver to read or write system memory on its behalf, then that driver is essentially granting the malicious application access to data it should not. Proper access checking inside the driver can protect system memory. In CE5:
Pointer mapping or marshalling is the preparation of a pointer that a driver can use to access a caller's buffer. Drivers run inside a different process than the application which calls them. The virtual memory space of every process is, by default, protected against access by other processes. A driver must do some work in order to access a buffer inside another process' memory. In CE6, each process has its own unique address space. Marshalling memory cannot be as simple as a pointer transformation. Either the memory must be copied from one process to another (duplication) or a new virtual address must be allocated in the driver process and pointed at the same physical memory the caller was using (aliasing). Either way, resources are allocated inside the driver process, and must be freed when the driver is done with them. The following pictures show a marshalled version of the caller's buffer being created inside the kernel (for kernel-mode drivers) or udevice.exe (for user-mode drivers.) The CE6 marshalling is also more formalized about declaring whether the buffer is in-only, in/out or out-only. Based on these settings, the marshalling helpers will ensure that copy-in and copy-out happen at the appropriate times. They are also used for access checking, for example a user-mode application cannot pass a shared heap address (which is read-only to applications) as an in/out or out-only parameter. To explain what drivers must do to marshal memory, it is simpler to examine synchronous and asynchronous accesses separately. First, for synchronous access:
Asynchronous accesses are more complicated. In CE5, Additional work must be done to access the caller's memory on a different thread. Each process "slot" was protected from access by other processes. Each thread had a its own set of "permissions" to access the various process slots. As the caller's thread jumped into the driver, it carried with it permission to access its owner process slot. So accesses to caller's memory would succeed as long as they were done on that thread. Other threads would first have to obtain permission to access to the other process slot. In CE6, like CE5, additional work must be done to access the caller's memory on a different thread. The reasons are different, and not as easy to explain. The way memory is marshalled differs between kernel mode and user mode, and differs between pointer parameters and embedded pointers. The only way to guarantee that the driver code is going to work properly in all modes is to prepare buffers for asynchronous access before accessing them on another thread. For asynchronous access, pointer parameters and embedded pointers are handled the same way. Assuming that we start with a buffer that is already mapped or marshalled for synchronous access, the steps a driver must take in order to access it asynchronously are:
Other details for production quality drivers You may say that the following two topics, secure copy and exception handling, are not part of memory marshalling. But they are required in today's world for safely receiving memory from other processes, and I believe that any discussion of memory passing is not complete without covering them. There is a security risk a lot of developers are not aware of: callers can modify the buffers they pass, while a driver is still using it. The caller application could have a secondary thread which manipulates the data in a buffer while the primary thread is inside a driver call. Malicious applications could manipulate embedded pointers to get access to memory they shouldn't, or cause buffer overruns by manipulating buffer sizes, or cause other problems like exceptions and leaks. To prevent against this class of attacks, drivers must make a copy of the caller's data, called a secure copy, to prevent the caller from modifying it asynchronously. For my first example of an attack that can be prevented using secure copies, imagine that the caller passes an embedded pointer to a driver. The driver uses MapCallerPtr (in CE5) or CeOpenCallerBuffer (in CE6) to access check the pointer and map/marshal it for use. If the driver continues to store that pointer into the caller's buffer, the caller could later manipulate it to point at other memory, and the driver would access the wrong memory. Drivers must make copies of the pointers they receive from callers to prevent asynchronous modification. Similarly, drivers must make copies of buffer size values they get from callers. So, always copy embedded pointers to a local variable. This is easily accomplished as part of mapping/marshalling since you have to call MapCallerPtr or CeOpenCallerBuffer anyway. Never store the mapped/marshalled pointer back to the caller's buffer. Never use the pointer in the caller's buffer after it has been mapped/marshalled. Treat buffer size and length variables with the same caution, so that callers cannot manipulate sizes any more than they can manipulate pointers. My second example of why secure copy is necessary involves file names. The CreateFile API, which takes a file name, validates that the caller is allowed to access that file. Suppose CreateFile read the file name, checked access, then used the file name to open the file when the access check passed. If the caller passes the name of a file it can access, then asynchronously changes it to a file name the caller is NOT supposed to be able to access, then there is a small window of time in which the caller could trick CreateFile into opening a file it's not supposed to. Perhaps it would only be able to get access 1 percent of the tries, but a hacker program could keep trying and trying until the trick worked. It only has to work once in order to compromise system security. The way to protect against this type of attack is that CreateFile must make a copy of the filename, in memory that the caller cannot access, before validating the caller's access to that file. (By the way, the OS already does a secure-copy of the file name before passing it to a driver's CreateFile in CE6, this is just a thought experiment.) You should make a copy of any data that requires validation, to prevent asynchronous modification after the validation is done. Making a secure copy can be as simple as copying a buffer or pointer into a stack variable. Or you could make a temporary heap allocation to copy the caller's data into. You will notice that CeOpenCallerBuffer has a ForceDuplicate parameter you can use to guarantee that you get a secure copy of an embedded buffer. We've also created a CeAllocDuplicateBuffer helper function that you can choose to use. (It is basically a heap alloc, with memcpy as necessary for copy-in or copy-out.) It does not matter how you make the secure copy, as long as you do something to protect the data you take from callers. Similar to secure copy is how drivers must use exception handling to protect their access of caller memory. It is important to note that, even if a caller has access to an address, that address may not refer to valid memory. An application can pass a pointer to a user-mode address that was never allocated. Or it could asynchronously free the buffer. So, drivers should always surround user buffer accesses with try/except blocks, and clean up resources during __except or __finally. For example, make sure to free memory that was allocated during the call, and release any critical sections, before returning to the caller. In Summary As you can see, passing memory between processes is a complicated matter. But don't despair. There are relatively simple rules governing drivers, as covered in the following table.
... and remember, always use try/except so you can clean up properly if you get exceptions on caller memory! One other tip: CE6 has some helper C++ classes to simplify your usage of these APIs. In public\common\oak\inc\marshal.hpp you will find:
... and always use try/except! Copyright (c) 2006 Microsoft Corp. All rights reserved. Reproduced by WindowsForDevices.com with permission. This article was originally published on the Windows CE Base Team Blog, here. About the author: Sue Loh has been a developer on the Windows CE team for just over six years, spending the past couple of years working on system performance and tools for diagnosing performance problems, such as the kernel profiler and CeLog. She's also worked on the file system, registry and databases (CEDB), and from time to time has dabbled in parts of the kernel. CE6 drivers: what you need to knowCE6 drivers: what you need to know by Sue Loh Many, in fact most, device drivers will need modifications in order to run on CE6. While binary compatibility (being able to run the exact same driver without a rebuild) is not likely, we do expect it to be easy to port almost all drivers. That was our goal once we realized many drivers would have to change. The primary reasons that drivers will need change are:
That will take care of most of the porting work. The other thing to look for is UI functionality. If your driver has any UI, you won't be able to run it in the kernel. And most CE6 drivers will run in the kernel. Even if your driver will run in user mode, we recommend using the kernel UI handling to maximize portability between user and kernel mode. In CE6, drivers that require UI should break that UI functionality out into a companion user-mode DLL. Move all the resources, shell calls, etc. into the new DLL. Then use the new CeCallUserProc API to call into the user-mode helper.
This is something like a combination of LoadLibrary / GetProcAddress with an IOCTL call. When a kernel-mode driver calls this API, we'll load the DLL inside an instance of udevice.exe. When a user-mode driver calls this API, the DLL will load in-proc inside the same instance of udevice.exe that the user-mode driver is running in. So drivers that use this API can run in kernel or user mode without change. The one big difference between CeCallUserProc and an IOCTL is that CeCallUserProc does NOT allow embedded pointers. All arguments must be stored inside the single "in" buffer passed to CeCallUserProc, and return data must be stored in the single "out" buffer. The problem is, if kernel code calls user code, user code cannot use CeOpenCallerBuffer or any other method to get the contents of kernel memory. We never allow user-mode code to access kernel-mode memory. And, while you are modifying your drivers to use the new marshalling helpers and CeCallUserProc, you might as well check to see if it needs to do any secure-copy or exception handling it never did before -- as I outlined in the marshalling article. Remember, now that drivers run in the kernel, you must be more careful than ever to preserve the security and stability of the system. User-Mode Drivers As we've already mentioned, CE6 now supports running drivers inside a user-mode driver host, udevice.exe. User-mode drivers work pretty much the same as kernel-mode drivers: an application calls ActivateDevice(Ex) and DeactivateDevice on the driver. The device manager will check registry settings to see if the driver is supposed to be loaded in user mode. You can also use registry settings to specify an instance "ID" of udevice.exe to use, if you want multiple user-mode drivers to load into the same process. For example, there is one user-mode driver group with ID 3. Multiple drivers load into this group. If you look inside the CE6 %_WINCEROOT%\public\common\oak\files\common.reg (an unprocessed version of what you get in your release directory), you'll see how this group is created and a few drivers that belong to it.
If you don't specify a process group, your driver will be launched inside a unique instance of udevice.exe. The device manager creates a reflector service object to help the user-mode driver do its job. The reflector service launches udevice.exe, mounts the specified volume and registers the file system volume APIs for communicating with the driver. Communication between applications and the user mode driver pass through the reflector, which helps with buffer marshalling. The reflector also assists the user-mode driver with operations that user-mode code is not normally allowed to make, like mapping physical memory; more on this later. It is our goal that drivers should be as close to 100% portable between kernel and user mode as possible. However, kernel code will always be more privileged than user code will be. Taking advantage of the increased kernel capabilities will make your kernel-mode driver impossible to port to user mode. What are some of the incompatibilities you need to know about? As I explained in the marshalling article, user-mode drivers cannot write back pointer parameters asynchronously. I take it a step further and say that user-mode drivers cannot operate on caller memory asynchronously. That you're better off keeping such drivers in kernel mode for now, or restructuring their communication with the caller so that nothing is asynchronous. Another detail you should know about is that user-mode drivers cannot receive embedded pointers from the kernel. This is exactly the same as saying that CeCallUserProc cannot support embedded pointers. If you're writing a driver that talks to kernel-mode drivers, and those kernel-mode drivers pass you embedded pointers, then your driver may have no choice but to run in kernel mode. If you can reorganize the communication between drivers, you may be able to "flatten" the structure so that, like CeCallUserProc, all the data is stored directly in the IN and OUT buffers instead of referenced via embedded pointers. There are some APIs which used to require trust that now are (mostly) blocked against use in user mode. One notable example is VirtualCopy, and its wrapper function MmMapIoSpace. Most user-mode code cannot call VirtualCopy. User-mode drivers can, with a little help from the reflector. The reflector can call VirtualCopy on behalf of a user-mode driver, but it will not do so unless it knows the driver is allowed to use the addresses it's copying. Under each driver setup entry in the registry, there are IOBase and IOLen keys that we use to mark physical memory. When your driver calls VirtualCopy, the reflector will check these values to make sure your driver is allowed to access the physical address. For example, the serial driver might specify a physical address like this:
If you have just one buffer to copy, use DWORD values. Use multi-strings to specify multiple base addresses and sizes.
Since only privileged applications can write to this part of the registry, the registry keys should protect against unprivileged code trying to gain access to these addresses. Notable APIs that user-mode code cannot call:
OEMs can choose to expose additional OAL IOCTLs and APIs to user mode by building a kernel-mode driver that provides these services -- by essentially writing their own version of a reflector. There is a kernel-mode driver, the oalioctl driver, that OEMs can extend to this end. Anyone who's not an OEM would have to write their own kernel-mode driver to do it. But be warned! Using oalioctl or writing new kernel-mode drivers to expose this functionality is essentially opening up a security gap that we (Microsoft) sought to close. Personally I advise against it. Writing CE5 drivers to be compatible with CE6 I would like to mention that Steve Maillet, one of our eMVPs, had a good suggestion: you can set up abstractions which combine the CE5 and CE6 driver needs, so that all you have to do is reimplement the abstraction layer in order to port from CE5 to CE6. He even presented his abstraction layer at this year's MEDC (Mobile & Embedded DevCon, 2006). I don't know if he's interested in giving it out widely, but you could contact him at EmbeddedFusion, or steal his idea and implement your own layer. Juggs Ravalia did a Channel 9 interview on the topic of drivers in CE6 -- if you don't like my explanation, maybe you'll like his better. He knows much more about our user mode driver framework than I do. Many thanks to Juggs for reviewing this article and my marshalling article. Copyright (c) 2006 Microsoft Corp. All rights reserved. Reproduced by WindowsForDevices.com with permission. This article was originally published on the Windows CE Base Team Blog, here. About the author: Sue Loh has been a developer on the Windows CE team for just over six years, spending the past couple of years working on system performance and tools for diagnosing performance problems, such as the kernel profiler and CeLog. She's also worked on the file system, registry and databases (CEDB), and from time to time has dabbled in parts of the kernel. Understanding Windows CE 6.0's kernel modeUnderstanding Windows CE 6.0's kernel mode by Sue Loh In Windows CE 5.0 and earlier, "kernel mode" is an access level attached to a thread. If a thread is "in kernel mode" it can access kernel address space. You could call SetKMode to put your thread into or out of kernel mode, whenever you wanted (it required trust, of course). Most system APIs were not implemented by the kernel (nk.exe), so calling an API didn抰 put your thread into kernel mode. Unless you called an API that was implemented by nk.exe, in which case your thread would temporarily enter kernel mode for the duration of the call. In Windows CE 6.0 the implementation is actually the same, except that the SetKMode API is no longer supported. You can't put threads into or out of kernel mode at a time of your choice. However, most system APIs are implemented by modules that are now loaded into the kernel process, so calling an API usually puts your thread into kernel mode. While the implementation is the same, the result is effectively different meanings. In CE 5.0, "kernel mode" was a property a thread could acquire or release on demand. In CE 6.0, the rule is fairly simple: your thread is in kernel mode while executing code inside the kernel process, and not in kernel mode when executing code inside a user process. We use the term loosely, like talking about "kernel mode drivers," "kernel mode code" and "kernel mode addresses." Whenever people use these phrases they're trying to talk about code and addresses that are only accessible to the kernel process, that are at addresses above 0x80000000. (Side note, you also have to be clear about whether you're talking about everything inside the kernel process vs. only the kernel module, kernel.dll.) The one remaining gotcha in the CE 6.0 rule is callbacks. If a user application passes a function pointer to kernel mode code, and the kernel mode code calls the function pointer directly, then the thread is STILL in kernel mode (remember it is still a property of the thread, just not so obviously) while executing user mode code. And that is very bad for security. Because the user mode code could do anything; it could access kernel addresses or call kernel-only APIs. If you write a driver that takes a function pointer from the caller and later calls it, make sure your driver only does so by using the new CEDDK function, CeDriverPerformCallback. That way your thread jumps back to the user process properly before calling the function, so that the function call can't do anything that the user process itself couldn't do already. Or even better -- find a different way to implement what you're doing. Ask yourself this: if an attacker passed me an evil function pointer, could they get me to do something bad on their behalf? If you don't know the answer, don't take function pointers from your callers. You might wonder about function pointers and memory marshalling. "Marshalling" only applies to passing data buffers between processes -- not to passing function pointers between processes. You can't "marshal" a function so that you can safely call it from kernel code. The CeDriverPerformCallback function is the only way to shed the privileges that a kernel mode driver has, for the duration of the call. I suppose you could consider that a form of "marshalling" but I don't. Kernel mode vs. "most privileged processor mode" Andrew Tuck, one of our support engineers, pointed out another detail that can lead to confusion. (Thanks, Andrew!) Often (at least in Windows environments, if not all OSes), the most privileged processor mode is referred to as "kernel mode." This usage of the term refers to when the CPU is operating with additional privileges, such as the ability to call special instructions that aren't normally legal. For example interrupt handling often happens in this CPU "kernel mode." This terminology is unrelated to the "kernel mode" concept I've been talking about in this article. When a Windows CE thread is in "kernel mode" it is not running in the most privileged processor mode. Only very restricted parts of the CE kernel and OAL run in that mode. The Windows CE "kernel mode" thread property controls whether the page tables for kernel address space are mapped, and some other things. (For example, in CE 5.0 it controlled whether the thread could make fast-path API calls, like I described in the API call post I wrote a while back.) Copyright (c) 2006 Microsoft Corp. All rights reserved. Reproduced by WindowsForDevices.com with permission. This article was originally published on the Windows CE Base Team Blog, here. About the author: Sue Loh has been a developer on the Windows CE team for just over six years, spending the past couple of years working on system performance and tools for diagnosing performance problems, such as the kernel profiler and CeLog. She's also worked on the file system, registry and databases (CEDB), and from time to time has dabbled in parts of the kernel. Differences between Windows CE 5.0 and Windows CE 6.0Differences between Windows CE 5.0 and Windows CE 6.0 by K. Ashok Babu Introduction There have been lot of questions regarding differences between Windows CE 5.0 and Windows CE 6.0, and we thought it would be useful to Windows CE developers and OEMs (original equipment manufacturers) to know more about these changes. This article is the first in a series of articles on this subject. The major changes in CE 6.0 are:
The existing implementation of CE 5.0 supports only 32 MB per process and is based on the Slot implementation. Every process apart from its native slot (Slot 2 ?33) used Slot 0 while running. For example, as mentioned in Figure 1, if filesys.exe running from Slot 2 has to run, then it has to run from Slot 0. So, if a.exe belonging to Slot 24 is running from Slot 0 apart from its native slot 24, all other processes also occupy the respective virtual address space. This actually leads to a waste in Virtual address space. A quick question would be, therefore, why was such an architecture arrived at when it was known that it is not scalable. The answer to this is beyond the scope of this article, and shall be discussed separately in future articles. Figure 1 Windows CE 6.0 has moved toward more of a desktop OS format. Each process can now occupy up to 1 GB of address space, and the number of processes can be up to 32K. Microsoft confirms that it has tested up to 2600 processes running simultaneously. The process switching function is more like desktop like process switching; for example, every time a process switch happens, the entire TLB is flushed, data and instruction cache is invalidated, and fresh page tables are created (if the process is new). Compared to Windows CE 5.0, this should take more time to switch a process. Figure 2 illustrates the architecture in Windows CE 6.0. Figure 2 You can see that Windows CE allows a user process to go up to 1 GB. The other 1 GB is allocated to DLLs, shared memory, and Kernel shared heap. The shared memory is for backward compatibility with CE 5.0 for sharing of files across processes. The DLLs, which were earlier restricted to 32MB of space (Slot 1), are now allowed to have 512MB of virtual address space starting from 0x40000000. An key aspect of this new architecture is that one process cannot view another process data directly, unlike in Windows CE 5.0. A process has to go through the kernel API to get data from other processes. Device driver architecture changes CE 6.0 implements both kernel mode device drivers and user mode device drivers. The use of kernel mode drivers provides enhanced security and robustness. OEMs can prevent drivers from gaining accesses to kernel resources by third party drivers, and hence can offer more security to his installation. OEMs can ship products with kernel mode drivers for all of the peripherals they supply, and for add on peripherals they can allow third parties to load kernel drivers only if they are signed by them, otherwise the drivers are restricted to run in user mode. However, user mode drivers are restricted to use Virtual Copy for only the memory space defined in the registry. In contrast, Windows CE 5.0 allowed user mode drivers to Virtual Copy any memory region other than the ones mentioned in the registry for them. In CE 6.0, Filesys.exe, device.exe, and GWES.exe -- which were earlier part of user mode -- have been moved in to kernel mode. Calls to SetKMode and setting process permissions, so that other processes can be accessed, are not possible due to the reasons mentioned above. Consequently, drivers that use these calls have to be rewritten. System call performance In CE 5.0, system calls from a user application to a service process such as GWES.exe was tedious. A trap signals the event of the system call to the kernel. The kernel then switches the process to the service executable GWES.exe. Once GWES.exe returns, the kernel switches the process back to the user application. In CE 6.0, since GWES.exe is part of the kernel, there is no process switching and the user application goes to its application. This is very similar to what takes place in the desktop architecture. The flow chart shown in Figure 3 illustrates that. Figure 3 Conclusion With the release of CE 6.0, Microsoft has added lot more technologies, and has also renamed the OS from "Windows CE" to "Windows Embedded CE." Of significance, CE 6.0 boasts many security feature enhancements and performance improvements, such as moving filesys.exe, device.exe and GWES.exe into the kernel. OEMs have also gained an OAL update feature that will be provided by Microsoft from time to time, so that when there are bug fixes, they won't need rebuild the images and can just update the OAL on the fly. The next few articles in this series will explore new technologies in Windows CE 6.0 and also how to port a BSP from CE 5.0 to CE 6.0. About the author: Ashok Babu is the Program Manager at e-con Systems in Chennai, India. His software specialities include developing Windows device drivers and designing Video codecs, and he also has hands-on experience in both Windows CE and Windows Mobile. He holds a Bachelor of Engineering, and his hobbies include music, soccer, and debugging. You can reach him by email, at "ashok at e-consystems dot com." How Windows CE Bus Drivers WorkHow Windows CE Bus Drivers Work by David Liao Abstract A bus driver is designed for controlling and configuring a specific Bus. It also configures and controls hardware on the bus and loads and unloads hardware drivers called client drivers. It also carries out bus request from its client drivers. A bus driver has two basic functions from software perspective. One is serving its client driver. Another is configuring, loading, and controlling its client drivers. Microsoft provides a bus driver for most common buses, such as PCI, PCCARD, and the "Root" Bus. What a Bus Driver does A Bus Driver is responsible for Hardware Configuration, Hardware Power Control, Bus address translation, and loading and unloading upper-level client drivers. Loading and Unloading upper-level client drivers Most upper-level client drivers are loaded by a bus driver calling ActivateDevice(Ex). ActivateDevice(Ex) loads a client driver according to the contents of a registry entry that is passed in by the caller. This means you need the registry in order to load the driver. A Bus driver has to either use an existing registry entry or create an instance registry entry according to a template. For PNP drivers, the bus driver configures the hardware and sets up instance registry entries according to PNP information. Then, it loads a client driver by calling ActivateDevice(Ex) and specifying the new instance registry entries. For non-PNP drivers and intermediate drivers, usually the registry is set up manually. It could be Microsoft, an OEM, or a third party who provides the bus driver. The bus driver normally still uses the registry to load those drivers. A Bus driver can also be loaded by a "parent" bus driver, creating a "tree" of buses. The exception is the "Root" Bus Driver, or trunk of the tree. The Root Bus Driver is loaded by Device Manager using a registry path value found at a specific registry location:
"RootKey"="Drivers\BuiltIn" The default value is set up by Microsoft. However, it can be changed by an OEM to allow the Root Bus Driver to enumerate drivers in some other registry path. The above example indicates that the Root Bus driver is located at
Important: Device Manager makes the Root Bus Driver run TWICE in a system with a two-phase boot. In Boot Phase One, the Device Manager deletes the Drivers "Active" Key holding any stale driver instances (i.e. HKLM\drivers\Active), then loads the Root Bus Driver with the fixed Bus name "BuiltInPhase1". If this system has a two-phase boot, after the system "Phase 2" Event is signaled, the Device Manager loads the Root Bus Driver again. The second time the Device Manager uses a bus name found in the registry. Is it possible that a driver can be loaded twice in two phase boot system? The answer is yes. If a driver is included in HIVE registry and its entries do not include the DWORD "Flags" entry specifying DEVFLAGS_BOOTPHASE_1 (0x00001000), it will be loaded twice. It is important to take this into consideration when specifying the registry entries for a Bus Driver. In theory, some Bus drivers should be loaded twice. Bus Driver Access and the CEDDK Bus Functions A client driver is capable of using its Bus Driver to:
There are many reasons to use a Bus Driver. One important reason is to make a Client Driver become bus-agnostic. This means a driver is capable of moving to user mode, or of working on different bus-architectures with the same binary. Some examples of bus-agnostic drivers are COM16550.dll and NE2000.dll. These can work as client drivers of the PCI Bus, Native Bus and PCMCIA bus with the same binary. Bus Access Functions In order to get access to its parent Bus Driver, a client driver has to call the CreateBusAccessHandle() function to get a bus driver access handle. This function is usually called by a client driver during its initialization (XXX_Init) because it requires that the client driver pass the "Active Registry Path" as an argument. CloseBusAccessHandle() is used for closing the access handle returned by calling CreateBusAccessHandle(). Usually the CloseBusAccessHandle() function is called before exiting XXX_Deinit. Please refer to MSDN online documentation for detailed information on these functions. After a Bus Access Handle is created, a Client can use any function which uses this handle as an argument. CEDDK Functions for Bus Driver operations are provided as wrappers -- they pack the corresponding function parameters and call the Bus Driver with an operation-specific IO Control Code (IOCTL). Microsoft strongly recommends that driver writers use the CEDDK functions instead of calling the Bus Driver directly with the IO Control Code. The parameters for the Bus IO Control Code may change from release to release, but the CEDDK functions (as formal APIs) signatures will not. Listed below are the CEDDK functions and their corresponding IO Control Codes:
SetDevicePowerState() and GetDevicePowerState() are used by a client driver to request that its parent Bus Driver put the client into a certain power state. Although some client drivers do the actual power state change by themselves, but it is the Bus driver's responsibility for putting its client drivers to their effective power states based upon the requests. Before a Bus Driver loads a client driver, it should put client driver's hardware into Power State D0. After the Bus driver unloads the client drivers, it should put the corresponding hardware into Power State D4. TranslateBusAddr() and TranslateSystemAddr() are used to translate between physical CPU addresses and subordinate Bus addresses. To do so, TranslateBusAddr() should translate the target address from a Subordinate Bus to a Parent Bus. These calls should propagate up all the way to the Root Bus driver. Then Root Bus driver then calls the OAL function HalTranslateBusAddress() to acquire the final CPU-relative physical address. TranslateSystemAddr() works similarly, but in the other direction. The OAL uses HalTranslateSystemAddress() to translate from a CPU address to a Bus address. So, multi-layer bus address translation only can be resolved by TranslateBusAddr() and TranslateSystemAddr(). Before doing a virtual or static system memory mapping function for a driver, the BusTransBusAddrToVirtual() and BusTransBusAddrToStatic() functions use TranslateBusAddr() to translate a Bus address to a CPU(system) address. IO Control Codes that are not supported by CEDDK CEDDK provides the BusIoControl() and BusChildIoControl() functions to client drivers. Client drivers can use these two functions to issue a Bus IO Control call directly. The difference with these function is that BusChildIoControl() is used to issue an IO Control call related to client driver which is making the call. The IOCTL_BUS_ACTIVATE_CHILD and IOCTL_BUS_DEACTIVATE_CHILD control codes are standard IO Control codes for which no wrapper function is implemented in the CEDDK. These IO Control codes can be called by any application if it opens a handle to the bus driver. The two Io Control Codes are used to activate or deactivate a client driver. Note: For legacy reasons not all client drivers can be deactivated. How a driver's bus name is assigned A Client Driver's bus name is assigned by its parent Bus Driver. The Bus Name that Bus Driver assigns follows a simple four-part pattern: busname _ bus# _ device# _ function#![]() Usually, "busname" is assigned to a Bus Driver according to the value of its "BusName" registry entry under the Bus Driver device's registry key. This is value is pre-assigned either by Microsoft or by the OEM that is using the Bus Driver. The "bus#", "device#" and "function#" are set to the "BusNumber", "DeviceNumber" and "FunctionNumber" registry entry values under the device key for the Client Driver. These registry value are created by the PnP Bus Driver. For non-PnP Bus drivers (for example, the Root Bus Driver), the "bus#" is assigned by the Bus Driver's device registry key. The "device#" and "function#" are then assigned automatically by the Bus driver. Because the "busname" has to be unique in the system, an appropriate name must be chosen when the system has multiple bus drivers. The following is a list of well-known bus driver names:
Power Manageable Drivers and Bus Drivers Power IO Control Codes There are two different types of Power Manageable Drivers. The first type is a driver which is under the control of the system Power Manager (PM). The second type is a driver which manages its own power. Drivers which are under the control of the system Power Manager have to support the following IO Control Codes:
Role the Bus Driver plays in Power Management of a Client Driver As we described previously, the Client's Bus Driver is responsible for changing the hardware power state. Therefore, it would seem that the best solution would be for the Power Manager to call the Bus Driver directly to have it manage power. In fact, this does not happen. There are two reasons why not:
The Power Control commands from the System Power Manager are sent to the client driver. They are propagated to the client's Bus Driver via an Bus IO Control Code. The process is:
There can be confusion and fear resulting from the driver being simultaneously commanded by I/O functions (IoControl/Read/Write), and the Power Manager IO Control Code. When simultaneous requests happen, something must be done so that the Client driver still performs its functions correct.y. Use of a few CEDDK functions can help resolve the situation by following a general pattern: This pseudocode gives a general reference for how this is done. The released example for how to use the set of CEDDK function can be found public\common\oak\drivers\serial\serpddcm\cserpdd.cpp. Bus Driver Library Microsoft provides a Bus Driver Library to allow a user to write a Bus Driver. CEDDK functions pass Bus Io Control Codes to a Bus Driver. There are two different IO Control Codes. The first is targeted at the Bus Driver, the second is targeted at one client driver to control Configuration, Power State and Bus Address translation: The DefaultBusDriver and DeviceFolder classes are abstract templates for implementation of a Bus Driver. A Bus Driver should inherit from these two basic classes and modify their virtual functions according to specific needs. The Bus Library does not provide instantiable DefaultBusDriver and DeviceFolder classes. You must subclass these abstract bases and fill in implementation details. There are some default methods in the DefaultBusDriver implementation to manage a DeviceFolder-based class. There are a few functions that the Bus Library provides: DefaultBusDriver Class
DeviceFolder
PCI Bus Driver The PCI Bus Driver is required to Configure the PCI Bus, Assign Resources, Search for Drivers according to a template, and set Instant Device Loading Registry keys. The details of this implementation are not described here, but the functions used to support Bus IoControl calls from external clients are detailed. In order to support a Bus IO Control call from a Client Driver the PCI Bus Driver implements the following subclasses of the ones defined in the Bus Driver Library: These classes override their bases in the following ways:
Root Bus Driver The default Root Bus Driver is called "BusEnum". This default only implements a simple BusEnum class which inherits its behavior from the DefaultBusDriver class in the Bus Driver library. It does not modify the default DeviceFolder implementation. Therefore, it does not support the SetPowerState() call to a device folder (default implementation is just a dummy).
For the Root Bus to support Power State Setting for Client Driver requests, the SetPowerState() Driver Folder function has to be implemented. The F-Sample (OMAP850) has an example of a platform-specific Root Bus Driver to show it how it can be done. Copyright (c) 2007 Microsoft Corp. All rights reserved. Reproduced by WindowsForDevices.com with permission. This article was originally published on the Windows CE Base Team Blog, here. About the author: David Liao has been a software developer for over 20 years, and has worked for Microsoft for the past nine years. During his time at Microsoft, he has specialized in Windows CE device drivers, including the Mode Driver Framework, SD Bus driver re-design, USB 2.0 EHCI stack, PC Card stack, and Windows CE Bus Driver Architect, and BSPs (board support packages), including the design of USB Function Controller driver, USB OTG, PCI, and others. Prior to joining Microsoft, Liao worked for Xerox, Teklogix, and other companies as a DSP Software Developer. May 11 [转]FLASH驱动开发需要注意的几个问题 -开发FLASH驱动除了要实现FMD_***的一系列函数之外,有几下几点需要格外注意:
1. FMD_GetInfo函数中需要正确地指明FLASH的类型,NOR或NAND,如果是NOR,则SECTOR SIZE只能是512.
pFlashInfo->dwNumBlocks 应设置成FLASH盘BLOCK的个数,
pFlashInfo->wDataBytesPerSector = 应该为PAGE的大小,
2.读写函数中需要访问的FlashInfo信息应该存放在FLASH的SPARE区,即数据区之外的地方,WINCE5.0系统中,有时上层不会将这些信息与真实的数据一起写入,而且有时候只会写这种数据. 但在某些系统或某些FLASH中不能将数据单独存入SPARE区,在这种情况下,可以将这些信息存放在一些专门的BLOCK中,或者不保存这些数据,在这种情况下,上层会不时地调用GetBlockStatus,别的似乎没有什么问题。
3.NAND的SPARE区一般用来保存ECC值,除此之外还会保存DISK INFO,而有些硬件会在写入数据时将数据区与SPARE区一起写入,所以得注意不要将原来SPARE区的数据覆盖掉。
DiskInfo中表示BLOCK是否已坏的那个成员,其值为-1时表示此BLOCK正常,其它值表示BLOCK已坏. 4.注意ECC只有在写入数据时才会产生(不论是由硬件还是软件生成),所以,刚把一个BLOCK擦除之后计算其ECC一定会有错,不应该在此时做ECC较验.
5.有一个问题,拷贝文件当FLASH快满时,驱动程序会死掉(Exception or dead lock like),不知还有谁遇到过? [转]WINCE的键盘驱动程序开发的注意事项 -WINCE中标准的键盘驱动程序接口可以参考PS2键盘的驱动程序,但那个接口比较复杂,对于了解流接口的人来说,实现一个流接口的驱动程序应该是一个更好的选择.我们只需要实现一个流接口驱动程序,发生中断以后读到键盘的扫描码,将其用MapVirtualKey转化成虚键,再调用keybd_event函数将些虚键发送出去即可。只是我们需要注意记录CTRL,ALT等特殊键的状态。 注意: 1,某些键的扫描码有两个值,以0XE0或0XE1开始,注意正确处理。 2,一个PS2键盘不需要初始化就可以工作,但我们可以发送RESET命令再读其返回值来判断键盘是否已经连接。 3,必须加上kbdmous.dll,这个模块,我们的键盘驱动才能正常工作,通常只需要加上NOP Keyboard/Mouse English,再加上相应的注册表设置就行。 4,系统中只能有一个标准接口的键盘驱动,即kbdmouse.dll,所以如果我们有更多的键盘硬件需要驱动,就需要把其它的做成流接口的,最多将一个写成标准的键盘驱动,当然也可以把所有的都写成流接口驱动,再加上NOP Keyboard/Mouse English。 [转]开发串口驱动程序转自:http://winceblog.blogspot.com/2007/02/blog-post.html 串口驱动程序的样例在public\common\oak\drivers\serial下,其中COM_MDD2是MDD部分, SERCARD是PC卡MODEM设备的驱动.ISR16550是16550的ISR代码.SERPDDCM,是PDD的公用部分,OO16550是16550的PDD,一般我们可以以这个代码为基础根据需要开发我们自己的代码.可以参考OO16550中的代码,实现其中各个函数即可.
串口在硬件上很简单,只有九根线, 但其驱动程序却极其复杂.幸好WINCE提供了MDD,封装了与硬件无关的部分代码,而且从CE5.0开始又新加了SERPDDCM,提供了PDD的公用代码,将PDD又封装了一层.如此一来就极大的减少了我们开发的代码量.只需要参照OO16550的代码即可.需要注意的问题如下:
<!--[if !supportLists]-->1) <!--[endif]-->InitXmit与InitRecieve用来初始化发送器与接收器,它们在端口被打开,关闭和RESET时被调用。当输入参数为TRUE时启动硬件,输入参数为FALSE时关闭硬件。 <!--[if !supportLists]-->2) <!--[endif]-->CancelXmit与CancelRecieve,这两个函数在应用程序调用PurgeCom时被调用。只需要清除FIFO即可,不能将硬件停用。 <!--[if !supportLists]-->3) <!--[endif]-->其它比较重要的函数就是发送与接收的函数,与传输的稳定性有直接关系。 <!--[if !supportLists]-->4) <!--[endif]-->别的就是设置各种参数的函数,包括像停止位,奇偶效验位等,比较简单。 8.有一点需要注意:硬件对寄存器的读写顺序也许有要求,必须注意。比如,如果硬件已经启动之后就不能更改波特率,我们在改波特率时就要先将硬件停用,修改波特率之后再启用。
9.电源管理. 我们可以在调用InitRecieve及InitXmit函数中分别启动或停止接收及发送模块。如果发送和接收都已经禁用,就可以将整个UART硬件停用,用以节电。 10.注意InitXmit函数中,在停用发送模块时,需要等到所有数据都发送出去以后再停用发送模块。否则在低速率时驱动程序刚将数据写入FIFO,COM_Write函数返回,上层即关掉串口句柄,引起发送模块被停用,FIFO中没有发送出去的数据就再也无法发送出去。我在38400及以下的波特率上都测到过这个现象。 11.CETK。因为CETK需要两个WINCE设备,它们需要一根交叉线来连接,注意检查交叉线的连法:2与3互连,这样就能通过数据传输相关的测试。7与8互连,硬件流控就没有问题。其它的几个与MODEM信号相关的引线也要正确连接。 [转]多个设备共享同一个硬件中断硬件中断线总是有限的,我们可能需要在已有的系统上做一些扩展,比如将串口扩展成好几个,有些硬件本身就设计成多个设备共享一条中断线,比如我的系统中两个串口就共享同一个CPU中断,任何一个串口发生中断以后都会触发CPU的同一条中断线,需要判断别的寄存器来确定是哪个串口发生了什么中断。
我们可以在OAL中分析各个中断源,然后返回不同的SYSINTR值,但这种做法扩展性不好。例如,OAL中设值某个中断源最多会产生三个SYSINTR,但以后扩展成了四个设备,有一个设备就无法正常工作了。 WINCE引入了可装载中断处理例程的概念。即在需要与别的设备共享中断的驱动程序中加载一个ISR,一般使用WINCE提供的GIISR即成满足需求。将其安装到内核。OAL中发生中断时调用NKCallIntChain来得到SYSINTR,这个函数会引起系统逐个调用在该IRQ上加载的所有可装载的ISR,当某个ISR认为这个中断是由它引发的时就返回其SYSINTR,否则就返回SYSINTR_CHAIN,系统就会接着调用其它的ISR,甚至所有的ISR都被调用或者有一个ISR返回了正确的SYSINTR。 驱动程序中的调用办法如下(CE帮助文档): if (InstallIsr) { // Install ISR handler g_IsrHandle = LoadIntChainHandler(IsrDll, IsrHandler, (BYTE)Irq); if (!g_IsrHandle) { DEBUGMSG(ZONE_ERROR, (L"WAVEDEV: Couldn't install ISR handler\r\n")); } else { GIISR_INFO Info; PVOID PhysAddr; DWORD inIoSpace = 1; // io space PHYSICAL_ADDRESS PortAddress = {ulIoBase, 0};
if (!TransBusAddrToStatic(PCIBus, 0, PortAddress, ulIoLen, &inIoSpace, &PhysAddr)) { DEBUGMSG(ZONE_ERROR, (L"WAVEDEV: Failed TransBusAddrToStatic\r\n")); return FALSE; }
DEBUGMSG(ZONE_PDD, (L"WAVEDEV: Installed ISR handler, Dll = '%s', Handler = '%s', Irq = %d, PhysAddr = 0x%x\r\n", IsrDll, IsrHandler, Irq, PhysAddr));
// Set up ISR handler Info.SysIntr = ulSysIntr; Info.CheckPort = TRUE; Info.PortIsIO = TRUE; Info.UseMaskReg = FALSE; Info.PortAddr = (DWORD)PhysAddr + ES1371_dSTATUS_OFF; Info.PortSize = sizeof(DWORD); Info.Mask = ES1371_INTSTAT_PENDING;
if (!KernelLibIoControl(g_IsrHandle, IOCTL_GIISR_INFO, &Info, sizeof(Info), NULL, 0, NULL)) { DEBUGMSG(ZONE_ERROR, (L"WAVEDEV: KernelLibIoControl call failed.\r\n")); } } }
这里需要注意一下,因为ISR在内核态运行,Info.PortAddr必须是系统最原始的虚拟地址,即没有用VirtualCopy映射过的,从OEMAddressTable中计算出来的虚拟地址。在这个例子中用TransBusAddrToStatic函数可以直接把物理地址转换成这种地址。而MmMapIoSpace得到是在当前程序空间中的地址,不能使用。而且GIIR要被加载到内核空间,所以在加入到OS包中时需要加上K标志,否则LoadIntChainHandler函数会失败。 [转]WINCE下USBFN驱动程序的一些概念转自:http://winceblog.blogspot.com/2007/03/winceusbfn.html USBFN,即USB客户端驱动,用来将一个WINCE设备模拟成一定的USB设备,让主机端(如PC)访问。目前WINCE提供的USB客户端有存储设备,串口设备,及RNDIS网络接口设备。
存储设备用来将WINCE设备上的存储空间,例如FLASH,当作一块存储介质给主机访问,即将WINCE设备模拟成一个U盘。 串口设备将设备与主机的USB连线模拟成串口,WINCE和主机端都认为它们之前连接上了一根串口线,它们之间可以做串口通信,典型的应用是用来实现WINCE与PC机的同步连接。 RNDIS设备使两端认为它们之间建立了网络连接,通过注册表设置可以让主机通过WINCE设备上网或者使WINCE设备通过主机上网。
WINCE已经提供了以上三种设备的驱动程序,在同一时刻只能使用一个设备。而我们需要做的只是提供USBFN总线控制器的驱动程序。USBFN系统各个模块的关系如下: USBFN总路线控制器作为一个总线驱动程序,被设备管理器加载,根据注册表设置加载相应的客户驱动程序,即存储设备,串口设备或者RNDIS设备。客户驱动程序即启动USBFN,引发主机配置设备,配置完成以后即可开始工作。 而USBFN总路线控制器驱动的MDD部分WINCE本身已经提供,PDD只需初始化硬件设备,提供传输即可。MDD在初始化时调用UfnPdd_Init函数得到PDD层的函数表,之后会根据需要调用各个函数。PDD还需要提供IST,用以处理各个中断。需要注意的是USBFN有一个与其它设备不同之处,它的注册表需要这样一个设置: "BusIoctl"=dword:2a0048,用以让系统加载完设备之后调用值为0x2a0048的IOCTL代码去完成初始化,其定义为IOCTL_BUS_POSTINIT。 [转]开发DMA驱动使用DMA的好处就是它不需要CPU的干预而直接服务外设,这样CPU就可以去处理别的事务,从而提高系统的效率,对于慢速设备,如UART,其作用只是降低CPU的使用率,但对于高速设备,如硬盘,它不只是降低CPU的使用率,而且能大大提高硬件设备的吞吐量。因为对于这种设备,CPU直接供应数据的速度太低。
因CPU只能一个总线周期最多存取一次总线,而且对于ARM,它不能把内存中A地址的值直接搬到B地址。它只能先把A地址的值搬到一个寄存器,然后再从这个寄存器搬到B地址。也就是说,对于ARM,要花费两个总线周期才能将A地址的值送到B地址。而DMA就不同了,一般系统中的DMA都有突发(Burst)传输的能力,在这种模式下,DMA能一次传输几个甚至几十个字节的数据,所以使用DMA能使设备的吞吐能力大为增强。 使用DMA时我们必须要注意如下事实: <!--[if !supportLists]-->1. <!--[endif]-->DMA使用物理地址,程序是使用虚拟地址的,所以配置DMA时必须将虚拟地址转化成物理地址。 <!--[if !supportLists]-->2. <!--[endif]-->因为程序使用虚拟地址,而且一般使用CACHED地址,所以虚拟地址中的内容与其物理地址上的内容不一定一致辞,所以在启动DMA传输之前一定要将该地址的CACHE刷新,即写入内存。 <!--[if !supportLists]-->3. <!--[endif]-->OS并不能保证每次分配到的内在空间在物理上是连续的。尤其是在系统使用过一段时间而又分配了一块比较大的内存时。 所以每次都需要判断地址是不是连续的,如果不连续就需要把这段内存分成几段让DMA完成传输。 [转]ARM开发过程中最最需要注意的问题平时大家接触最多的可能是X86平台,在这种系统上写程序几乎不需要考虑太多问题,但ARM上就不一样了,最常见也最容易被忽略的问题可能就是字节的对齐,即使像我这样有六七年程序开发经验的才手也时常难于提防,最近就有一个BUG,花了一天时间最终发现是对齐引发的,在此与大家分享,但愿大家能够注意到。 我在EBOOT中读取存在HARD DISK上的nk.bin文件,从而从HARD DISK上LOAD WINCE系统,在这个过程中总是有check sum错误,但从ethernet下载时不会有错,所以问题应该还是在我加的这部分代码上,而且同样的代码在PC上能正常运行。经过检查代码的逻辑关系是正确的。接着我在出错时将那些数据全部用调试信息打出来,发现从文件开始算起第4096个字节被丢掉了,而其它的字节都是对的。初步判断是对齐引发的问题,所以去查每一个BUFFER,最终发现是在读取硬盘数据时BUFFERR并没有按双字节对齐,而硬盘以16BIT读取数据,而引发了错误。 实际上,这类问题在ARM系统上很常见,让人防不胜防,以下是我的一些例子。 1,解析数据流时应该时刻注意。如果需要把一个数据流(BUFFER)转化成结构进行取值,就应该把这个结构定义为按字节存取.考虑如下结构: struct a{ char a; short b; long c; }; 如果某个数据流中包含这样的结构,而且我们要直接将数据流的指针转化成该结构的指针,然后直接取结构成员的值,我们就应该将这个结构定义成按字节访问,即将其夹在语句 #pragma pack(push,1) ... #pragma pack(pop) 之中。如果我们不这样做,编译器会将成员b的地址对齐到short指针的地址,即在a之后加上一个char即8位的成员,将C对齐到LONG,即在B之后再加一个char成员。如此一来,成员B和成员C就得不到正确的值了。 如果我们定义一个普通的结构用来存放一些数据,则不用定义成按字节存取,编译器会加上一些占位成员,但并不会影响程序的运行。从这个意义上讲,在ARM中,将结构成员定义成CHAR和SHORT来节约内存是没有意义的。 一个典型的例子就文件系统的驱动程序,文件是以一些已经定义好的结构存放在存储介质上的,它们被读取到一个BUFFER中,而具体取某个文件、目录结构时,我们会将地址转化成结构而读取其中的值。 2,访问外设时。 例如,磁盘驱动通常以16BIT的方式存取数据,即每次存取两个字节,这样就要求传给它的BUFFER是双字节对齐的,驱动程序应该至上层传来的指针做出正确的处理以保证数据的正确性。 3.有时,我们没有将数据流指针转化为结构指针取值,但如果我们读取的是双字节或者是四字节的数据,同样需要注意对齐的问题,例如,如果从一个BUFFER的偏移10处读取一个四字节值,则实际得到的值是偏移8处的 地址上的DWORD值。 [转]HIVE registry转自:http://winceblog.blogspot.com/2006/12/hive-registry.htmlHIVE registry is useful and easy to use feature, to enable it, we need first add the HIVE registry feature from CATALOG into the OSDesign file. then add registry as following listed. The following is the setting in my platform using FLASH to store the registry. There're some difference in every version. 1. in CE5.0 and later, DDK_GetWindowInfo can't be called in the flash driver. if it's called, the system crashed in CE5.0 due to a data abort, it can't read the values in CE6. 2. The registry settings in CE5 are the same as CE4.2. But there're some difference with CE6, Please look at the comment in the following paragraph. ; @CESYSGEN IF FILESYS_FSREGHIVE ; HIVE BOOT SECTION ; this line is mandatory for every verion, it indicates the start of HIVE registry setting. [HKEY_LOCAL_MACHINE\init\BootVars] "SystemHive"="\\norflash\\Registry\\system.hv" ; in CE6, it's a full path, but in CE4.2 and 5.0, norflash is not needed. "DefaultUser"="default" "Flags"=dword:3 [HKEY_LOCAL_MACHINE\Drivers\BuiltIn\NORFlash] ;block device driver to store registry "Dll"="flash.dll" "Order"=dword:0 "Prefix"="DSK" "Ioctl"=dword:4 "Profile"="MSFlash" "Flags"=dword:1000 ;must to set to this value "IClass"="{A4E7EDDA-E575-4252-9D6B-4195D48BB865}" [HKEY_LOCAL_MACHINE\System\StorageManager\Profiles\MSFlash] "Name"="Ep93xx NOR Flash" "Folder"="NORFlash" ; the value should be the same as the first word of "systemHive" in CE6 "AutoMount"=dword:1 "AutoPart"=dword:1 "AutoFormat"=dword:1 [HKEY_LOCAL_MACHINE\System\StorageManager\Profiles\MSFlash\FATFS] ;"MountFlags"=dword:2 ;uses this setting in CE4.2 "EnableCache"=dword:0 "MountBootable"=dword:1 ;uses this setting in CE6 ; END HIVE BOOT SECTION ; this line is mandatory for every verion, it indicates the END of HIVE registry setting. ; @CESYSGEN ENDIF FILESYS_FSREGHIVE NOTE HIVE registry can work in NOR flash, but can't work in HARD DISK in WINCE6.0. HIVE is called after hard disk driver is loaded while the file system is still not mounted, so the directory "HARD DISK" still not appears, as a result, HIVE creates a directory named "hard disk", and stores the registry to the directory. but in nor flash, it does not occur. [转]PQOAL格式下的驱动开发与非PQOAL BSP的区别转自:http://winceblog.blogspot.com/2007/01/pqoalpqoal-bsp.htmlPQOAL是WINCE5。0的一个新特性,其最大的特点就是BSP的开发变得更为容易,驱动程序在不同的硬件平台上也更易于移植,当然我们必须了解一些细节才能充分利用这种新特性。 在支持PQOAL格式的BSP中,中断被分成两个级别,IRQ与SYSINTR, IRQ就是每个设备中断线在系统(CPU)中断线上的编号,SYSINTR是从OS的角度看每个设备使用的中断号。OAL层将IRQ与SYSINTR关联,这样,OS只会关心SYSINTR,而不必关心设备具体的IRQ。从而使驱动程序具有更好的移植性,能很容易的移植到不同的硬件平台上去。 在中断发生时,OAL会读出IRQ,将其转化成SYSINTR报告给Kernel,从而使kernel知道是哪个SYSINTR发生了中断,而触发相应的IST,驱动程序调用InterruptDone函数时,OAL将SYSINTR转换成IRQ,从而完成相应的动作。 通常,在PQOAL格式的BSP上运行的驱动程序在初始化阶段需做如下工作, 1,用IOCTL_HAL_REQUEST_SYSINTR代码去调用 KernelIoControl,,从而得到一个SYSINTR,' 2,创建一个事件(Event),用InterruptInitialize将该事件与SYSINTR关联起来, 3,创建一个线程去处理该事件。 所以,在这种新体系中,驱动程序不需要知道自己所用的SYSINTR,只需要知道而且也必须知道IRQ,然后用这个IRQ去申请一个SYSINTR,以后就使用申请来的这个SYSINTR,其它与以前的驱动一样。 May 05 与熊共舞节选1: ——摘自第1章 在克里福德之前,曾有这样一种观点:信念永远不能被放在伦理的灯下接受拷问;只要你愿意,你可以相信任何事。你甚至可以相信绝无可能的事,就像《爱丽丝漫游镜中世界》(Through the Looking Glass)里的白皇后那样。当爱丽丝认为“人不能相信不可能的事”时,这位皇后答道: “我猜你只是缺乏练习……像你这么大的时候,我每天都会用半小时去相信不可能的事。啊,有时我甚至可以在早餐前相信六件不可能的事。” 不过,对于软件项目管理者来说,“在早餐前相信六件不可能的事”似乎并不是一种难以企及的能力——我们总在相信着各种各样不可能的事,例如在极短的时间里、以极低的预算和极高的效率完成项目。 在做这件事时,我们与那位对自己的船充满信心的船主并无太大区别。无疑,作为软件项目的管理者,你肯定曾经做过这样的事。这也许是因为旁人的怂恿,例如你的老板会请求你在圣诞节前完成一个项目,却只给你三个人。当然,你会表示怀疑:这点时间够用吗? “那就是我选你来管理这个项目的原因。”老板信赖地对你说。 事情就这么定下来了:你接受这份工作,接受挑战,也准备接受荣誉……但你首先必须相信这个日程安排,那就是你要付出的代价。终于,你艰难地说:“我能行。”然后开始不断地巩固自己的信念。当然能行,不就是圣诞节吗?为什么不行?别的项目用的时间还要少呢,不是吗?没多久,你就已经充满信心了。也许时间会证明你的信心毫无根据,但至少现在,你非常肯定:你能够按时完成任务。 这时,你应该回想一下威廉·金顿·克里福德的质询。没错,那是你相信的,但你是否有权相信它?凭面前的证据,你是否有权相信那个日程安排? 只相信你有权相信的事,这就是风险管理。说到底,风险管理的核心就是克里福德的“信仰的伦理学”——尽管不确定性的存在使情况愈加复杂,但风险管理要求每个信念必须接受伦理的拷问。它将去除你工作(例如软件项目)中曾经充斥着的自欺欺人。除了“在早餐前相信六件不可能的事”之外,你还可以有另一种选择,一种更为明智的选择。 |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|