System performance is critical for user productivity. It directly influences the user's perception of a device. In fact, it is not uncommon for users to judge the usefulness of a device based on the performance of the system and the look and feel of the user interface. By providing too complex of an interface, you can confuse users and open your device to potential security risks or unexpected user manipulations. By using the incorrect APIs, or incorrect applications architecture in a multithreaded environment, you may significantly impact performance. Performance optimization and system customization are real challenges for firmware providers. This chapter discusses the tools and highlights best practices to achieve optimal system response times on target devices.
Exam objectives in this chapter:
■ Monitoring and optimizing system performance
■ Implementing system applications
■ Programming with threads and thread synchronization objects
■ Implementing exception handling in drivers and applications
■ Supporting power management at the system level
To complete the lessons in this chapter, you must have the following:
■ A thorough understanding of real-time systems design concepts, such as scheduler functionality in an operating system, interrupts, and timers.
■ Basic knowledge of multithreaded programming, including synchronization objects.
■ A development computer with Microsoft® Visual Studio® 2005 Service Pack 1 and Platform Builder for Microsoft Windows® Embedded CE 6.0 installed.
Performance monitoring and optimization are important tasks in the development of small-footprint devices. The need for optimized system performance remains critical because of an ever-growing number of increasingly complex applications and the requirement for intuitive and therefore resource-intensive user interfaces. Performance optimization requires firmware architects and software developers to constrain resource consumption within their system components and applications so that other components and applications can use the available resources. Whether developing device drivers or user applications, optimized processing algorithms can help to save processor cycles, and efficient data structures can preserve memory. Tools exist at all system levels to identify performance issues within and between drivers, applications, and other components.
After this lesson, you will be able to:
■ Identify the latency of an interrupt service routine (ISR).
■ Improve the performance of a Windows Embedded CE system.
■ Log and analyze system performance information.
Estimated lesson time: 20 minutes.
Drivers, applications, and OEM adaptation layer (OAL) code impact system and realtime performance. Although Windows Embedded CE may be used in real-time and non-real-time configurations, it is important to note that using non-real-time components and applications can decrease system performance in a real-time operating system (OS) configuration. For example, you should keep in mind that demand paging, device input/output (I/O), and power management are not designed for real-time devices. Use these features carefully.
Demand paging facilitates memory sharing between multiple processes on devices with limited RAM capacity. When demand paging is enabled, Windows Embedded CE discards and removes memory pages from active processes under low-memory conditions. However, to keep the code of all active processes in memory, disable demand paging for the entire operating system or for a specific module, such as a dynamic-link library (DLL) or device driver.
You can disable demand paging by using the following methods:
■ Operating system Edit the Config.bib file and set the ROMFLAGS option in the CONFIG section.
■ DLLs Use the LoadDriver function instead of the LoadLibrary function to load the DLL into memory.
■ Device drivers Add the DEVFLAGS_LOADLIBRARY flag to the Flags registry entry for the driver. This flag causes Device Manager to use the LoadLibrary function instead of the LoadDriver function to load the driver.
Windows Embedded CE allocates and uses memory as usual, but does not discard it automatically when you disable demand paging.
The system timer is a hardware timer that generates system ticks at a frequency of one tick per millisecond. The system scheduler uses this timer to determine which threads should run at what time on the system. A thread is the smallest executable unit within a process that is allocated processor time to execute instructions in the operating system. You can stop a thread for an amount of time by using the Sleep function. The minimum value that you can pass to the Sleep function is 1 (Sleep(1)), which stops the thread for approximately 1 millisecond. However, the sleep time is not exactly 1 millisecond because the sleep time includes the current system timer tick plus the remainder of the previous tick. The sleep time is also linked to the priority of the thread. The thread priority determines the order in which the operating system schedules the threads to run on the processor. For those reasons, you should not use the Sleep function if you need accurate timers for real-time applications. Use dedicated timers with interrupts or multimedia timers for real-time purposes.
Power management can affect system performance. When the processor enters the Idle power state, any interrupt generated by a peripheral or the system scheduler causes the processor to exit this state, restore the previous context, and invoke the scheduler. Power context switching is a time-consuming process. For detailed information about the power management features of Windows Embedded CE, see the section "Power Management" in the Windows Embedded CE 6.0 documentation available on the Microsoft MSDN® website at http://msdn2.microsoft.com/en-us/library/aa923906.aspx.
The kernel allocates and manages system memory for heaps, processes, critical sections, mutexes, events, and semaphores. Yet, the kernel does not completely free the system memory when releasing these kernel objects. Instead, the kernel holds on to the system memory to reuse it for the next allocation. Because it is faster to reuse allocated memory, the kernel initializes the system memory pool during the startup process and allocates further memory only if no more memory is available in the pool. System performance can decrease depending how processes use virtual memory, heap objects, and the stack.
When calling system APIs, or Graphical Windows Event System (GWES) APIs, be aware that some APIs rely on non-real-time features, such as for window drawing. Forwarding calls to non-real-time APIs may dramatically decrease system performance. Consequently, you should make sure that your APIs in real-time applications are real-time compliant. Other APIs, such as ones used for accessing a file system or hardware, can have an impact on performance because these APIs may use blocking mechanisms, such as mutexes or critical sections, to protect resources.
Non-real-time APIs can have a measurable impact on real-time performances and, unfortunately, the Win32® API documentation provides little detail on real-time issues. Practical experience and performance testing can help you choose the right functions.
Windows Embedded CE includes many performance monitoring and troubleshooting tools that can be used to measure the impact of Win32 APIs on system performance. These tools are helpful when identifying inefficient memory use, such as an application not releasing the system memory it allocates.
The following Windows Embedded CE tools are particularly useful to measure the real-time performance of your system components and applications:
■ ILTiming Measures Interrupt Service Routine (ISR) and Interrupt Service Thread (IST) latencies.
■ OSBench Measures system performance by tracking the time the kernel spends managing kernel objects.
■ Remote Performance Monitor Measures system performance, including memory usage, network throughput, and other aspects.
The ILTiming tool is particularly useful for Original Equipment Manufacturers (OEMs) who want to measure ISR and IST latencies. Specifically, ILTiming enables you to measure the time it takes to invoke an ISR after an interrupt occurred (ISR latency) and the time between when the ISR exits and the IST actually starts (IST latency). This tool uses a system hardware tick timer by default, but it is also possible to use alternative timers (high-performance counters).
Not all hardware platforms provide the required timer support for the ILTiming tool.
The ILTiming tool relies on the OALTimerIntrHandler function in the OAL to implement the ISR for managing the system tick interrupt. The timer interrupt handler stores the current time and returns a SYSINTR_TIMING interrupt event, which an ILTiming application thread waits to receive. This thread is the IST. The time elapsed between the reception of the interrupt in the ISR and the reception of the SYSINTR_TIMING event in the IST is the IST latency that the ILTiming tool measures.
You can find the ILTiming tool's source code in the %_WINCEROOT%\Public\Common\Oak\Utils folder on your development computer if you have installed Microsoft Platform Builder for Windows Embedded CE 6.0 R2. The ILTiming tool supports several command-line parameters that you can use to set the IST priority and type according to the following syntax:
iltiming [-i0] [-i1] [-i2] [-i3] [-i4] [-p priority] [-ni] [-t interval] [-n interrupt] [-all] [-o file_name] [-h]
Table 3-1 describes the individual ILTiming command-line parameters in more detail.
Table 3-1 ILTiming parameters
| Command-Line Parameter | Description |
|---|---|
| -i0 | No idle thread. This is equivalent to using the -ni parameter. |
| -i1 | One thread spinning without performing any actual processing. |
| -i2 | One thread spinning, calling SetThreadPriority (THREAD_PRIORITY_IDLE). |
| -i3 | Two threads alternating SetEvent and WaitForSingleObject with a 10-second timeout. |
| -i4 | Two threads alternating SetEvent and WaitForSingleObject with an infinite timeout. |
| -i5 | One thread spinning, calling either VirtualAlloc (64 KB), VirtualFree, or both. Designed to flush the cache and the translation look-aside buffer (TLB). |
| -p priority | Specifies the IST priority (zero through 255). The default setting is zero for highest priority. |
| -ni | Specifies no idle priority thread. The default setting is equal to the number of idle priority thread spins. This is equivalent to using the -i0 parameter. |
| -t interval | Specifies the SYSINTR_TIMING timing interval, with clock ticks in milliseconds. The default setting is five. |
| -n interrupt | Specifies the number of interrupts. Using this parameter you can specify how long the test will run. The default setting is 10. |
| -all | Specifies to output all data. The default setting is to output the summary only. |
| -o file_name | Specifies to output to file. The default setting is to output to the debugger message window. |
ILTiming may create idle threads (command-line parameters: -i1, -i2, -i3, and -i4) to generate activity on the system. This enables the kernel to be in a non-preemptive kernel call that must be finished before handling the IST. It can be useful to enable idle threads in background tasks.
The OSBench tool can help you measure system performance by identifying the time that the kernel spends managing kernel objects. Based on the scheduler, OSBench collects timing measurements by means of scheduler performance-timing tests. A scheduler performance-timing test measures how much time basic kernel operations, such as thread synchronization, require.
OSBench enables you to track timing information for the following kernel operations:
■ Acquiring or releasing a critical section.
■ Waiting for or signaling an event.
■ Creating a semaphore or mutex.
■ Yielding a thread.
■ Calling system APIs.
To identify performance issues in different system configurations, use OSBench in conjunction with a stress test suite, such as the Microsoft Windows CE Test Kit (CETK).
The OSBench tool supports several command-line parameters that you can use according to the following syntax to collect timing samples for kernel operations:
osbench [-all] [-t test_case] [-list] [-v] [-n number] [-m address] [-o file_name] [-h]
Table 3-2 describes the individual OSBench command-line parameters in more detail.
Table 3-2 OSBench parameters
| Command-Line Parameter | Description |
|---|---|
| -all | Run all tests (default: run only those specified by -t option): |
| TestId 0: CriticalSections. | |
| TestId 1: Event set-wakeup. | |
| TestId 2: Semaphore release-acquire. | |
| TestId 3: Mutex. | |
| TestId 4: Voluntary yield. | |
| TestId 5: PSL API call overhead. | |
| TestId 6: Interlocked API's (decrement, increment, testexchange, exchange). | |
| -t test_case | ID of test to run (need separate -t for each test). |
| -list | List test ID's with descriptions. |
| -v | Verbose: show extra measurement details. |
| -n number | Number of samples per test (default=100). |
| -m address | Virtual address to write marker values to (default= |
| -o file_name | Output to comma-separated values (CSV) file (default: output only to debug). |
Check out the OSBench source code to identify the test content. You can find the source code at the following locations:
■ %_WINCEROOT%\Public\Common\Oak\Utils\Osbench
■ %_WINCEROOT%\Public\Common\Oak\Utils\Ob_load
Test results are by default sent to the debug output, but can be redirected to a CSV file.
The OSBench tool uses system timers. The OAL must therefore support the QueryPerformanceCounter and QueryPerformanceFrequency functions initialized in the OEMInit function.
The Remote Performance Monitor application can track the real-time performance of the operating system as well as memory usage, network latencies, and other elements. Each system element is associated with a set of indicators that provide information on usage, queue length, and delays. Remote Performance Monitor can analyze log files generated on a target device.
As the name suggests, the Remote Performance Monitor application is a remote tool. The application monitors devices both under development and out in the field, as long as you have a way to connect to the device and deploy the application.
The Remote Performance Monitor monitors the following objects:
■ Remote Access Server (RAS).
■ Internet Control Message Protocol (ICMP).
■ Transport Control Protocol (TCP).
■ Internet Protocol (IP).
■ User Datagram Protocol (UDP).
■ Memory.
■ Battery.
■ System.
■ Process.
■ Thread.
This list is extended by implementing your own Remote Performance Monitor extension DLL. For sample code, look in the %COMMONPROGRAMFILES%\Microsoft Shared\Windows CE Tools\Platman\Sdk\WCE600\Samples\CEPerf folder.
Similar to the Performance tool on a Windows workstation, Remote Performance Monitor can create performance charts, configure alerts triggered at specified thresholds, write raw log files, and compile performance reports based on the performance objects available on the target device. Figure 3-1 shows a performance chart example.
Figure 3-1 A performance chart in Remote Performance Monitor
ILTiming tool, OSBench, and Remote Performance Monitor cover most performance monitoring needs. However, some cases may require other methods of gathering system performance information. For example, if you want to obtain exact interrupt latency timings, or if your hardware platform does not provide the required timer support for the ILTiming tool, you must use hardware-based performance measuring methods based on the General Purpose Input/Output (GPIO) interface of the processor and a waveform generator.
By using a waveform generator on a GPIO, it is possible to generate interrupts that are handled through ISRs and ISTs. These ISRs and ISTs then use another GPIO to generate a waveform in response to the received interrupt. The time elapsed between the two waveforms — the input waveform from the generator and the output waveform from the ISR or IST — is the latency time of the interrupt.
Windows Embedded CE provides many tools that can be employed in a development environment to measure the system performance and validate real-time device performance. The ILTiming tool is useful for measuring interrupt latencies. The OSBench tool enables you to analyze how the kernel manages system objects. Remote Performance Monitor provides the means to gather performance and statistical data in charts, logs, as well as report on devices under development and out in the field. Remote Performance Monitor has the ability to generate alerts based on configurable performance thresholds. Beyond the capabilities of these tools, you have the option to use hardware monitoring for latency and performance-measurement purposes.
As discussed in Chapter 1 "Customizing the Operating System Design", Windows Embedded CE acts as a componentized operating system and a development platform for a wide variety of small-footprint devices. These range from devices with restricted access for dedicated tasks, such as mission-critical industrial controllers, to open platforms offering access to the complete operating system, including all settings and applications, such as personal digital assistant (PDA). However, practically all Windows Embedded CE devices require system applications to provide an interface to the user.
After this lesson, you will be able to:
■ Launch an application at startup.
■ Replace the default shell.
■ Customize the shell.
Estimated lesson time: 25 minutes.
Developers distinguish between system applications and user applications to emphasize that these applications have different purposes. In the context of Windows Embedded CE devices, the term system application generally refers to an application that provides an interface between the user and the system. In contrast, a user application is a program that provides an interface between the user and application- specific logic and data. Like user applications, system applications can implement a graphical or command-line interface, but system applications are typically started automatically as part of the operating system.
You can configure applications to start automatically as part of the Windows Embedded CE initialization process. This feature can be set in several ways, depending on whether you want to run the applications before or after Windows Embedded CE loads the shell user interface (UI). One method is to manipulate several registry settings that control the application startup behavior. Another common method is to place a shortcut to the application in the Startup folder so that the standard shell can start the application.
The Windows Embedded CE registry includes several registry entries to start operating system components and applications at startup time, such as Device Manager and Graphical Windows Event System (GWES). These registry entries are located under the HKEY_LOCAL_MACHINE\INIT registry key, as illustrated in Figure 3-2. You can create additional entries at this location to run your own applications included in the run-time image without having to load and run these applications manually on your target device. Among other things, automatically starting an application can facilitate debugging activities during software development.
Figure 3-2 The HKEY_LOCAL_MACHINE\INIT registry key
Table 3-3 lists three examples of registry entries to start typical Windows Embedded CE components when the run-time image starts.
Table 3-3 Startup registry parameter examples
| Location | HKEY_LOCAL_MACHINE\INIT | ||
|---|---|---|---|
| Component | Device Manager | GWES | Explorer |
| Binary | Launch20="Device.dll" | Launch30="Gwes.dll" | Launch50="Explorer.exe" |
| Dependencies | Depend20=hex:0a,00 | Depend30=hex:14,00 | Depend50=hex:14,00,1e,00 |
| Description | The LaunchXX registry entry specifies the binary file of the application and the DependXX registry entry defines the dependencies between applications. |
If you look at the Launch50 registry entry in Table 3-3, you can see that the Windows Embedded CE standard shell (Explorer.exe), will not run until process 0x14 (20) and process 0x1E (30) have started successfully, which happen to be Device Manager and GWES. The hexadecimal values in the DependXX entry refer to decimal launch numbers XX, specified in the name of the LaunchXX entries.
Implementing the SignalStarted API helps the kernel manage process dependencies between all applications registered under the HKEY_LOCAL_MACHINE\INIT registry key. The application can then use the SignalStarted function to inform the kernel that the application has started and initialization is complete, as illustrated in the following code snippet.
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPTSTR lpCmdLine, int nCmdShow) {
// Perform initialization here...
// Initialization complete,
// call SignalStarted...
SignalStarted(_wtol(lpCmdLine));
// Perform application work and eventually exit.
return 0;
}
Dependency handling is straightforward. The kernel determines the launch number from the Launch registry entry, uses it as a sequence identifier, and passes it as a startup parameter in lpCmdLine to the WinMain entry point. The application performs any required initialization work and then informs the kernel that it has finished this part by calling the SignalStarted function. The call to the _wtol function in the SignalStarted code line performs a conversion of the launch number from a string to a long integer value because the SignalStarted function expects a DWORD parameter. For example, Device Manager must pass a SignalStarted value of 20 and GWES must pass a value of 30 back to the kernel for the kernel to start Explorer.exe.
If you are using the standard shell on your target device, you can drop the application or a shortcut to the application into the Windows\Startup folder of the device. Explorer.exe examines this folder and starts all found applications.
Only use the Windows\Startup folder if your target device runs the Windows Embedded CE standard shell. If you are not using the standard shell, then create a custom launch application for the same purpose and initiate it at start time based on entries under the HKEY_LOCAL_MACHINE\INIT registry key. For sample code that demonstrates how to examine the Startup folder and launch the applications, find the Explorer.cpp file in the %_WINCEROOT%\Public\Shell\OAK\HPC\Explorer\Main folder. Look for a function called StartupProcessFolder and use it as a starting point for your own implementation.
The Windows Embedded CE standard shell can handle executable and shortcut files. Windows Embedded CE shortcut files differ from the shortcut files of Windows XP, but provide similar functionality. CE shortcut files are text files with an .lnk file-name extension. They contain the command-line parameters for the linked target according to the following syntax:
nn# command [optional parameters]
The placeholder nn stands for the number of characters followed by a pound sign (#), and the actual command, such as 27#\Windows\iexplore.exe -home to start Internet Explorer® and open the home page. After creating and adding the desired .lnk file to the run-time image, edit the Platform.dat or Project.dat file to map the .lnk file to the Startup folder, similar to the following .dat file entry:
Directory("\Windows\Startup"):-File("Home Page.lnk", "\Windows\homepage.lnk")
Chapter 2 covers these configuration tasks in more detail.
The key advantage of the Startup folder is that the applications placed in this folder do not need to implement the SignalStarted API to inform the kernel that the initialization and start process completed successfully. However, this also implies that the operating system cannot manage dependencies between applications or enforce a specific startup sequence. The operating system starts all applications in the Startup folder concurrently.
Another interesting option to start applications automatically is to leverage the services host process (Services.exe). Although Windows Embedded CE does not include a full-featured Service Control Manager (SCM), it does include built-in services and also comes with a sample service called Svcstart that can be used to start applications.
Svcstart is particularly useful for applications with dependencies on system components and services that are not immediately available after the startup process finishes. For example, it might take a few seconds to obtain an Internet Protocol (IP) address from a Dynamic Host Configuration Protocol (DHCP) server for a network interface card (NIC) or initialize a file system. To accommodate these scenarios, the Svcstart service supports a Delay parameter that specifies the time to wait before starting an application. You can find the Svcstart sample code in the %_WINCEROOT%\Public\Servers\SDK\Samples\Services\Svcstart folder. Compile the sample code into Svcstart.dll, add this DLL to your run-time image, and then run the sysgen -p servers svcstart command to register the Svcstart service with the operating system. Load it by using Services.exe.
Table 3-4 lists the registry settings that the Svcstart service supports to start applications.
Table 3-4 Svcstart registry parameters
| Location | HKEY_LOCAL_MACHINE\Software\Microsoft\Svcstart\1 |
|---|---|
| Application Path | @="iexplore.exe" |
| Command-line Parameters | Args="-home" |
| Delay Time | Delay=dword:4000 |
| Description | Starts the application with the specified command-line parameters after a delay time defined in milliseconds. See the Svcstart.cpp file for more details. |
By default, Platform Builder provides three shells to implement the interface between the target device and the user: the command processor shell, the standard shell, and a thin client shell. Each shell supports different features to interact with the target device.
The command processor shell provides console input and output with a limited set of commands. This shell is available for both display-enabled devices and headless devices without keyboard and display screen. For display-enabled devices, include the Console Window component (Cmd.exe) so that the command processor shell can handle input and output through a command-prompt window. Headless devices, on the other hand, typically use a serial port for input and output.
Table 3-5 lists registry settings that you must configure on the target device to use a serial port in conjunction with the command processor shell.
Table 3-5 Console registry parameters
| Location | HKEY_LOCAL_MACHINE\Drivers\Console | |
|---|---|---|
| Registry Entry | OutputTo | COMSpeed |
| Type | REG_DWORD | REG_DWORD |
| Default Value | None | 19600 |
| Description | Defines which serial port the command processor shell uses for input and output. | Specifies the data transfer rate of the serial port in bits per second (bps). |
| ■ Setting this value to -1 will redirect input and output to a debug port. | ||
| ■ Setting this value to zero specifies no redirection. | ||
| ■ Setting this value to a number greater than zero and less than 10 will redirect input and output to a serial port. |
The standard shell provides a graphical user interface (GUI) similar to the Windows XP desktop. The primary purpose of the standard shell is to start and run user applications on the target device. This shell includes a desktop with Start menu and taskbar that enables the user to switch between applications, from one window to another. The standard shell also includes a system notification area to display additional information, such as the status of network interfaces and the current system time.
Windows Embedded CE Standard Shell is a required catalog item if you select the Enterprise Terminal design template when creating an OS design project in Visual Studio by using the OS Design Wizard. If you want to clone and customize this shell, you can find the source code in the %_WINCEROOT\Public\Shell\OAK\HPC folder. Chapter 1 explains how to clone catalog items and add them to an OS design.
The thin client shell, also called the Windows-based Terminal (WBT) shell in the product documentation, is a GUI shell for thin-client devices that do not run user applications locally. You can add Internet Explorer to a thin-client OS design, yet all other user applications must run on a Terminal server in the network. The thin client shell uses the Remote Desktop Protocol (RDP) to connect to the server and display the remote Windows desktop. By default, the thin client shell displays the remote desktop in full-screen mode.
You can also implement your own shell by cloning and customizing the Windows Task Manager (TaskMan) shell application. The source code in the %_WINCEROOT%\Public\Wceshellfe\Oak\Taskman folder is a good starting point.
The Control Panel is a special repository for central access to system and application configuration tools. The product documentation refers to these configuration tools as applets, to indicate the fact that they are embedded in the Control Panel. Each applet serves a specific and targeted purpose and does not depend on other applets. You can customize the content of the Control Panel by adding your own applets or by removing existing Control Panel applets included with Windows Embedded CE.
The Control Panel is a configuration system that relies on the following three key components:
■ Front-End (Control.exe) This application displays the user interface and facilitates starting Control Panel applets.
■ Host Application (Ctlpnl.exe) This application loads and runs the Control Panel applets.
■ Applets These are the individual configuration tools, implemented in form of .cpl files listed with icon and name in the Control Panel user interface.
For details regarding the implementation of the Windows Embedded CE Control Panel, check out the source code in the %_WINCEROOT%\Public\Wceshellfe\Oak\Ctlpnl folder. You can clone the Control Panel code and customize it to implement your own Control Panel version
As mentioned, a Control Panel applet is a configuration tool for a system component or user application implemented in form of a .cpl file and located in the Windows folder on the target device. Essentially, a .cpl file is a DLL that implements the CPlApplet API. A single .cpl file can contain multiple Control Panel applications, yet a single applet cannot span multiple .cpl files. Because all .cpl files implement the CPlApplet API, it is a straightforward process for Control.exe to obtain detailed info about the implemented applets at startup in order to display the set of available applets in the user interface. Control.exe only needs to enumerate all .cpl files in the Windows folder and to call the CPlApplet function in each file.
According to the DLL nature and CPlApplet API requirements, .cpl files must implement the following two public entry points:
■ BOOL APIENTRY DllMain(HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) Used to initialize the DLL. The system calls DllMain to load the DLL. The DLL returns true if initialization succeeded or false if initialization failed.
■ LONG CALLBACK CPlApplet(HWND hwndCPL, UINT message, LPARAM lParam1, LPARAM lParam2) A callback function that serves as the entry point for the Control Panel to perform actions on the applet.
You must export the DllMain and CPlApplet entry points so that the Control Panel application can access these functions. Non-exported functions are private to the DLL. Make sure the function definitions are in export "C" { } blocks to export the C interface.
The Control Panel calls the CPlApplet function to initialize the applet, obtain information, provide information about user actions, and to unload the applet. The applet must support several Control Panel messages, listed in Table 3-6, to implement a fully functional CPlApplet interface:
Table 3-6 Control Panel messages
| Control Panel Message | Description |
|---|---|
| CPL_INIT | The Control Panel sends this message to perform global initialization of the applet. Memory initialization is a typical task performed at this step. |
| CPL_GETCOUNT | The Control Panel sends this message to determine the number of Control Panel applications implemented in the .cpl file. |
| CPL_NEWINQUIRE | The Control Panel sends this message for all the Control Panel applications specified by CPL_GETCOUNT. At this step each Control Panel application must return a NEWCPLINFO structure to specify the icon and title to display in the Control Panel user interface. |
| CPL_DBLCLK | The Control Panel sends this message when the user double-clicks on an icon of the applet in the Control Panel user interface. |
| CPL_STOP | The Control Panel sends this message once for each instance specified by CPL_GETCOUNT. |
| CPL_EXIT | The Control Panel sends this message once for the applet before the system releases the DLL. |
Store the NEWCPLINFO information for each Control Panel application that you implement in a Control Panel applet in a resource embedded in the .cpl file. This facilitates the localization of icons, names, and applet descriptions returned in response to CPL_NEWINQUIRE messages.
To build a Control Panel applet and generate the corresponding .cpl file, find the source code folder of the applet subproject and add the following CPL build directive on a new line at the end of the Sources file:
CPL=1
You also must add the path to the Control Panel header file to the Include Directories entry on the C/C++ tab in the applet subproject settings in Visual Studio, as illustrated in Figure 3-3:
$(_PROJECTROOT)\CESysgen\Oak\Inc
Figure 3-3 Include Directories entry for a Control Panel applet
Many Windows Embedded CE devices, such as medical monitoring devices, automated teller machines (ATM), or industrial control systems are dedicated to a single task. The standard graphical shell is not useful for these devices. Removing the standard shell restricts access to the Control Panel configuration settings and also protects users from starting additional applications. The result is a device in kiosk mode that opens an application according to the special purpose of the target device directly with no shell access.
Kiosk applications for Windows Embedded CE are developed in native code or managed code. The only requirement is to start this application in place of the standard shell (Explorer.exe). The system then starts a black shell, meaning no shell application is running on the device. You only need to configure the registry entries under the HKEY_LOCAL_MACHINE\Init key to implement this configuration. As mentioned earlier in this chapter, the LaunchXX entry for Explorer.exe is Launch50. Replace Explorer.exe with your custom kiosk application and consider the job completed, as shown in Table 3-7. Keep in mind that your custom kiosk application must implement the SignalStarted API for the kernel to manage the application dependencies correctly.
Table 3-7 Startup registry parameter examples
| Location | HKEY_LOCAL_MACHINE\INIT |
|---|---|
| Component | Custom Kiosk Application |
| Binary | Launch50="myKioskApp.exe" |
| Dependencies | Depend50=hex:14,00,1e,00 |
| Description | To enable kiosk mode replace the Launch50 entry for Explorer.exe in the device registry with an entry that points to a custom kiosk application. |
To run a managed application in place of the standard shell, include the binary file in the runtime image and edit the .bib file that belongs to the managed application. Specifically, you must define binary files in a FILES section for the system to load the application inside the Common Language Runtime (CLR).
Windows Embedded CE is a componentized operating system with a broad palette of items and customizable features. One such feature enables you to configure automatic launching of applications at start time, which is particularly useful for installation and configuration tools. You can also customize the Control Panel by adding your own applets, implemented in custom .cpl files, which are DLLs that adhere to the CPlApplet API so that the Control Panel can call into the applets. For special-purpose devices, such as ATMs, ticket machines, medical monitoring devices, airport check-in terminals, or industrial control systems, you can further customize the user environment by replacing the standard shell with your kiosk application. You do not need to customize the code base or start process of the Windows Embedded CE operating system. Enabling kiosk mode is merely a task of replacing the default Launch50 registry entry with a custom Launch50 entry that points to your standard or managed code application.
Windows Embedded CE is a multithreaded operating system. The processing model differs from UNIX-based embedded operating systems because processes can include multiple threads. You need to know how to manage, schedule, and synchronize these threads within a single process and between processes in order to implement and debug multithreaded applications and drivers and to achieve optimal system performance on your target devices.
After this lesson, you will be able to:
■ Create and stop a thread.
■ Manage thread priorities.
■ Synchronize multiple threads.
■ Debug thread synchronization issues.
Estimated lesson time: 45 minutes.
A process is a single instance of an application. It has a processing context, which can include a virtual address space, executable code, open handles to system objects, a security context, a unique process identifier, and environment variables. It also has a primary thread of execution. A thread is the basic unit of execution managed by the scheduler. In a Windows process, a thread can create additional threads. There is no hard-coded maximum number of threads per process. The maximum number depends on available memory resources because every thread uses memory and the physical memory is limited on the platform. The maximum number of processes on Windows Embedded CE is limited to 32,000.
Windows Embedded CE supports preemptive multitasking to run multiple threads from various processes simultaneously. Windows Embedded CE performs thread scheduling based on priority. Each thread on the system has a priority ranging from zero to 255. Priority zero is the highest priority. The scheduler maintains a priority list and selects the thread to run next according to the thread priority in a round-robin fashion. Threads of the same priority run sequentially in a random order. It is important to note that thread scheduling relies on a time-slice algorithm. Each thread can only run for a limited amount of time. The maximum possible time slice that a thread can run is called the quantum. Once the quantum has elapsed, the scheduler suspends the thread and resumes the next thread in the list.
Applications can set the quantum on a thread-by-thread basis to adapt thread scheduling according to application needs. However, changing the quantum for a thread does not affect threads with a higher priority because the schedule selects threads with higher priority to run first. The scheduler even suspends lower-priority threads within their time slice if a higher-priority thread becomes available to run.
Windows Embedded CE includes several process management functions as part of the core Win32 API. Three important functions are listed in Table 3-8 that are useful for creating and ending processes.
Table 3-8 Process management functions
| Function | Description |
|---|---|
| CreateProcess | Starts a new process. |
| ExitProcess | Ends a process with cleanup and unloading DLLs. |
| TerminateProcess | Terminates a process without cleanup or unloading DLLs. |
For more information about process management functions and complete API documentation, see the Core OS Reference for Windows Mobile® 6 and Windows Embedded CE 6.0, available on the Microsoft MSDN website at http://msdn2.microsoft.com/en-us/library/aa910709.aspx.
Each process has at least one thread called the primary thread. This is the main thread of the process, which means that exiting or terminating this thread also ends the process. The primary thread can also create additional threads, such as worker threads, to perform parallel calculations or accomplish other processing tasks. These additional threads can create more threads if necessary by using the core Win32 API. Table 3-9 lists the most important functions to use in applications that work with threads on Windows Embedded CE.
Table 3-9 Thread management functions
| Function | Description |
|---|---|
| CreateThread | Creates a new thread. |
| ExitThread | Ends a thread. |
| TerminateThread | Stops a specified thread without running cleanup or other code. Use this function only in extreme cases because terminating a thread can leave memory objects behind and cause memory leaks. |
| GetExitCodeThread | Returns the thread exit code. |
| CeSetThreadPriority | Sets the thread priority. |
| CeGetThreadPriority | Gets the current thread priority. |
| SuspendThread | Suspends a thread. |
| ResumeThread | Resumes a suspended thread. |
| Sleep | Suspends a thread for a specified amount of time. |
| SleepTillTick | Suspends a thread until the next system tick. |
For more information about thread management functions and complete API documentation, see the Core OS Reference for Windows Mobile 6 and Windows Embedded CE 6.0, available on the Microsoft MSDN website at http://msdn2.microsoft.com/en-us/library/aa910709.aspx.
The CreateThread function used to create a new thread expects several parameters that control how the system creates the thread and the instructions that the thread runs. Although it is possible to set most of these parameters to null or zero, it is necessary to provide at least a pointer to an application-defined function that the thread is supposed to execute. This function typically defines the core processing instructions for the thread, although you can also call other functions from within this function. It is important to pass the core function as a static reference to CreateThread because the linker must be able to determine the core function's starting address at compile time. Passing a non-static function pointer does not work.
The following code listing is copied from the Explorer.cpp file that you can find in the %_WINCEROOT%\Public\Shell\OAK\HPC\Explorer\Main folder. It illustrates how to create a thread.
void DoStartupTasks() {
HANDLE hThread = NULL;
// Spin off the thread which registers and watches the font dirs
hThread = CreateThread(NULL, NULL, FontThread, NULL, 0, NULL);
if (hThread) {
CloseHandle(hThread);
}
// Launch all applications in the startup folder
ProcessStartupFolder();
}
This code specifies FontThread as the new thread's core function. It immediately closes the returned thread handle because the current thread does not need it. The new thread runs parallel to the current thread and implicitly exits upon returning from the core function. This is the preferred way to exit threads because it enables C++ function cleanup to occur. It is not necessary to explicitly call ExitThread.
However, it is possible to explicitly call the ExitThread function within a thread routine to end processing without reaching the end of the core function. ExitThread invokes the entry point of all attached DLLs with a value indicating that the current thread is detaching, and then deallocates the current thread's stack to terminate the current thread. The application process exits if the current thread happens to be the primary thread. Because ExitThread acts on the current thread, it is not necessary to specify a thread handle. However, you must pass a numeric exit code, which other threads can retrieve by using the GetExitCodeThread function. This process is useful to identify errors and reasons for the thread exiting. If ExitThread is not explicitly called, the exit code corresponds to the return value of the thread function. If GetExitCodeThread returns the value STILL_ACTIVE, the thread is still active and running.
Although you should avoid it, there can be rare situations that leave you no other way to terminate a thread except for calling the TerminateThread function. A malfunctioning thread destroying file records might require this function. Formatting a file system might need you to call TerminateThread in debugging sessions while your code is still under development. You need to pass the handle to the thread to be terminated and an exit code, which you can retrieve later by using the GetExitCodeThread function. Calling the TerminateThread function should never be part of normal processing. It leaves the thread stack and attached DLLs behind, abandons critical sections and mutexes owned by the terminated thread, and leads to memory leaks and instability. Do not use TerminateThread as part of the process shutdown procedure. Threads within the process can exit implicitly or explicitly by using the ExitThread function.
Each thread has a priority value ranging from zero to 255, which determines how the system schedules the thread to run in relationship to all other threads within the process and between processes. On Windows Embedded CE, the core Win32 API includes four thread management functions that set the priority of a thread as follows.
■ Base priority levels Use the SetThreadPriority and SetThreadPriority functions to manage the thread priority at levels compatible with early versions of Windows Embedded CE (zero through seven).
■ All priority levels Use the CeSetThreadPriority and CeGetThreadPriority functions to manage the thread priority at all levels (zero through 255).
The base priority levels zero through seven of earlier versions of Windows Embedded CE are now mapped to the eight lowest priority levels 248 through 255 of the CeSetThreadPriority function.
It is important to keep in mind that thread priorities define a relationship between threads. Assigning a high thread priority can be detrimental to the system if other important threads run with lower priority. You might achieve better application behavior by using a lower priority value. Performance testing with different priority values is a reliable manner of identifying the best priority level for a thread in an application or driver. However, testing 256 different priority values is not efficient. Choose an appropriate priority range for your threads according to the purpose of your driver or application as listed in Table 3-10.
Table 3-10 Thread priority ranges
| Range | Description |
|---|---|
| zero through 96 | Reserved for real-time drivers. |
| 97 through 152 | Used by default device drivers. |
| 153 through 247 | Reserved for real-time below drivers. |
| 248 through 255 | Maps to non-real-time priorities for applications. |
It can help system performance to delay certain conditional tasks that depend on time-consuming initialization routines or other factors. After all, it is not efficient to enter a loop and check 10,000 times if a required component is finally ready for use. A better approach is to put the worker thread to sleep for an appropriate amount of time, such as 10 milliseconds, check the state of the dependencies after that time, and go back to sleep for another 10 milliseconds or continue processing when conditions permit. Use the Sleep function from within the thread itself to suspend and resume a thread. You can also use the SuspendThread and ResumeThread functions to control a thread through another thread.
The Sleep function accepts a numeric value that specifies the sleep interval in milliseconds. It is important to remember that the actual sleep interval will likely exceed this value. The Sleep function relinquishes the remainder of the current thread's quantum and the scheduler will not give this thread another time slice until the specified interval has passed and there are no other threads with higher priority. For example, the function call
sleep(0) does not imply a sleep interval of zero milliseconds. Instead, sleep(0) relinquishes the remainder of the current quantum to other threads. The current thread will only continue to run if the scheduler has no other threads with the same or higher priority on the thread list.
Similar to the
sleep(0) call, the SleepTillTick function relinquishes the remainder of the current thread's quantum and suspends the thread until the next system tick. This is useful if you want to synchronize a task on a system tick basis.
The WaitForSingleObject or WaitForMultipleObjects functions suspend a thread until another thread or a synchronization object is signaled. For example, a thread can wait for another thread to exit without having to enter a loop with repeated Sleep and GetExitCodeThread calls if the WaitForSingleObject function is enabled instead. This approach results in a better use of resources and improves code readability. It is possible to pass a timeout value in milliseconds to the WaitForSingleObject or WaitForMultipleObjects functions.
The following code snippet illustrates how to create a thread in suspended mode, specify a thread function and parameters, change the thread priority, resume the thread, and wait for the thread to finish its processing and exit. In the last step, the following code snippet demonstrates how to check the error code returned from the thread function.
// Structure used to pass parameters to the thread.
typedef struct {
BOOL bStop;
} THREAD_PARAM_T
// Thread function
DWORD WINAPI ThreadProc(LPVOID lpParameter) {
// Perform thread actions...
// Exit the thread.
return ERROR_SUCCESS;
}
BOOL bRet = FALSE;
THREAD_PARAM_T threadParams;
threadParams.bStop = FALSE;
DWORD dwExitCodeValue = 0;
// Create the thread in suspended mode.
HANDLE hThread = CreateThread(NULL, 0, ThreadProc,
(LPVOID) &threadParams, CREATE_SUSPENDED, NULL);
if (hThread == NULL) {
// Manage the error...
} else {
// Change the Thread priority.
CeSetThreadPriority(hThread, 200);
// Resume the thread, the new thread will run now.
ResumeThread(hThread);
// Perform parallel actions with the current thread...
// Wait until the new thread exits.
WaitForSingleObject(hThread, INFINITE);
// Get the thread exit code
// to identify the reason for the thread exiting
// and potentially detect errors
// if the return value is an error code value.
bRet = GetExitCodeThread(hThread, &dwExitCodeValue);
if (bRet && (ERROR_SUCCESS == dwExitCodeValue)) {
// Thread exited without errors.
} else {
// Thread exited with an error.
}
// Don't forget to close the thread handle
CloseHandle(hThread);
}
The real art of multithreaded programming lies in avoiding deadlocks, protecting access to resources, and ensuring thread synchronization. Windows Embedded CE provides several kernel objects to synchronize resource access for threads in drivers or applications, such as critical sections, mutexes, semaphores, events, and interlocks functions. Yet, the choice of the object depends on the task that you want to accomplish.
Critical sections are objects that synchronize threads and guard access to resources within a single process. A critical section cannot be shared between processes. To access a resource protected by a critical section, a thread calls the EnterCriticalSection function. This function blocks the thread until the critical section is available.
In some situations, blocking the thread execution might not be efficient. For example, if you want to use an optional resource that might never be available, calling the EnterCriticalSection function blocks your thread and consumes kernel resources without performing any processing on the optional resource. It is more efficient in this case to use a critical section without blocking by calling the TryEnterCriticalSection function. This function attempts to grab the critical section and returns immediately if the critical section cannot be used. The thread can then continue along an alternative code path, such as to prompt the user for input or to plug in a missing device.
Having obtained the critical section object through EnterCriticalSection or TryEnterCriticalSection, the thread enjoys exclusive access to the resource. No other thread can access this resource until the current thread calls the LeaveCriticalSection function to release the critical section object. Among other things, this mechanism highlights why you should not use the TerminateThread function to terminate threads. TerminateThread does not perform cleanup. If the terminated thread owned a critical section, the protected resource becomes unusable until the user restarts the application.
Table 3-11 lists the most important functions that you can use to work with critical section objects for thread synchronization purposes.
Table 3-11 Critical Section API
| Function | Description |
|---|---|
| InitializeCriticalSection | Create and initialize a critical section object. |
| DeleteCriticalSection | Destroy a critical section object. |
| EnterCriticalSection | Grab a critical section object. |
| TryEnterCriticalSection | Try to grab a critical section object. |
| LeaveCriticalSection | Release a critical section object. |
Whereas critical sections are limited to a single process, mutexes can coordinate mutually exclusive access to resources shared between multiple processes. A mutex is a kernel object that facilitates inter-process synchronization. Call the CreateMutex function to create a mutex. The creating thread can specify a name for the mutex object at creation time, although it is possible to create an unnamed mutex. Threads in other processes can also call CreateMutex and specify the same name. However, these subsequent calls do not create new kernel objects, but instead return a handle to the existing mutex. At this point, the threads in the separate processes can use the mutex object to synchronize access to the protected shared resource.
The state of a mutex object is signaled when no thread owns it and non-signaled when one thread has ownership. A thread must use one of the wait functions, WaitForSingleObject or WaitForMultipleObjects, to request ownership. You can specify a timeout value to resume thread processing along an alternative code path if the mutex does not become available during the wait interval. On the other hand, if the mutex becomes available and ownership is granted to the current thread, do not forget to call ReleaseMutex for each time that the mutex satisfied a wait in order to release the mutex object for other threads. This is important because a thread can call a wait function multiple times, such as in a loop, without blocking its own execution. The system does not block the owning thread to avoid a deadlock situation, but the thread must still call ReleaseMutex as many times as the wait function to release the mutex.
Table 3-12 lists the most important functions that you can use to work with mutex objects for thread synchronization purposes.
Table 3-12 Mutex API
| Function | Description |
|---|---|
| CreateMutex | Create and initialize a named or unnamed mutex object. To protect resources shared between processes, you must use named mutex objects. |
| CloseHandle | Closes a mutex handle and deletes the reference to the mutex object. All references to the mutex must be deleted individually before the kernel deletes the mutex object. |
| WaitForSingleObject | Waits to be granted ownership of a single mutex object. |
| WaitForMultipleObjects | Waits to be granted ownership for a single or multiple mutex objects. |
| ReleaseMutex | Releases a mutex object. |
Apart from kernel objects that enable you to provide mutually exclusive access to resources within a process and between processes, Windows Embedded CE also provides semaphore objects that enable concurrent access to a resource by one or multiple threads. These semaphore objects maintain a counter between zero and a maximum value to control the number of threads accessing the resource. The maximum value amount is specified in the CreateSemaphore function call.
The semaphore counter limits the number of threads that can access the synchronization object concurrently. The system will keep decrementing the counter every time a thread completes a wait for the semaphore object until the counter reaches zero and enters the nonsignaled state. The counter cannot decrement past zero. No further thread can gain access to the resource until an owning thread releases the semaphore by calling the ReleaseSemaphore function, which increments the counter by a specified value and again switches the semaphore object back into signaled state.
Similar to mutexes, multiple processes can open handles of the same semaphore object to access resources shared between processes. The first call to the CreateSemaphore function creates the semaphore object with a specified name. You can also construct unnamed semaphores, but these objects are not available for interprocess synchronization. Subsequent calls to the CreateSemaphore function with the same semaphore name do not create new objects, but open a new handle of the same semaphore.
Table 3-13 lists the most important functions that work with semaphore objects for thread synchronization purposes.
Table 3-13 Semaphore API
| Function | Description |
|---|---|
| CreateSemaphore | Creates and initializes a named or unnamed semaphore object with a counter value. Use named semaphore objects to protect resources shared between processes. |
| CloseHandle | Closes a semaphore handle and deletes the reference to the semaphore object. All references to the semaphore must be closed individually before the kernel deletes the semaphore object. |
| WaitForSingleObject | Waits to be granted ownership of a single semaphore object. |
| WaitForMultipleObjects | Waits to be granted ownership for a single or multiple semaphore objects. |
| ReleaseSemaphore | Releases a semaphore object. |
The Event object is another kernel object that synchronizes threads. This object enables applications to signal other threads when a task is finished or when data is available to potential readers. Each event has signaled/non-signaled state information used by the API to identify the state of the event. Two types of events, manual events and auto-reset events, are created according to the behavior expected by the event.
The creating thread specifies a name for the event object at creation time, although it is also possible to create an unnamed event. It is possible for threads in other processes to call CreateMutex and specify the same name, but these subsequent calls do not create new kernel objects.
Table 3-14 lists the most important functions for event objects for thread synchronization purposes.
Table 3-14 Event API
| Function | Description |
|---|---|
| CreateEvent | Creates and initializes a named or unnamed event object. |
| SetEvent | Signal an event (see below). |
| PulseEvent | Pulse and signal the event (see below). |
| ResetEvent | Reset a signaled event. |
| WaitForSingleObject | Waits for an event to be signaled. |
| WaitForMultipleObjects | Waits to be signaled by a single or multiple event objects. |
| CloseHandle | Releases an Event object. |
The behavior of the events API is different according to the type of events. When you use SetEvent on a manual event object, the event will stay signaled until ResetEvent is explicitly called. Auto-reset events only stay signaled until a single waiting thread is released. At most, one waiting thread is released when using the PulseEvent function on auto-reset events before it immediately transitions back to the non-signaled state. In the case of manual threads, all waiting threads are released and immediately transition back to a non-signaled state.
In multithread environments, threads can be interrupted at any time and resumed later by the scheduler. Portions of code or applications resources can be protected using semaphores, events, or critical sections. In some applications, it could be too time consuming to use those kinds of system objects to protect only one line of code like this:
// Increment variable
dwMyVariable = dwMyVariable + 1;
The sample source code above in C is one single instruction, but in assembly it could be more than that. In this particular example, the thread can be suspended in the middle of the operation and resumed later, but errors can potentially be encountered in the case of another thread using the same variable. The operation is not atomic. Fortunately, it is possible in Windows Embedded CE 6.0 R2 to increment, decrement, and add values in multithreading-safe, atomic operations without using synchronization objects. This is done by using interlocked functions.
Table 3-15 lists the most important interlocked functions that are used to atomically manipulate variables.
Table 3-15 Interlock API
| Function | Description |
|---|---|
| InterlockedIncrement | Increment the value of a 32 bit variable. |
| InterlockedDecrement | Decrement the value of a 32 bit variable. |
| InterlockedExchangeAdd | Perform atomic addition on a value. |
Multithreaded programming enables you to structure your software solutions based on separate code execution units for user interface interaction and background tasks. It is an advanced development technique that requires careful implementation of thread synchronization mechanisms. Deadlocks can happen, especially when using multiple synchronization objects in loops and subroutines. For example, thread One owns mutex A and waits for mutex B before releasing A, while thread Two waits for mutex A before releasing mutex B. Neither thread can continue in this situation because each depends on a resource being released by the other. These situations are hard to locate and troubleshoot, particularly when threads from multiple processes are accessing shared resources. The Remote Kernel Tracker tool identifies how threads are scheduled on the system and enables you to locate deadlocks.
The Remote Kernel Tracker tool enables you to monitor all processes, threads, thread interactions, and other system activities on a target device. This tool relies on the CeLog event-tracking system to log kernel and other system events in a file named Celog.clg in the %_FLATRELEASEDIR% directory. System events are classified by zone. The CeLog event-tracking system can be configured to focus on a specific zone for data logging.
If you have Kernel Independent Transport Layer (KITL) enabled on the target device, the Remote Kernel Tracker visualizes the CeLog data and analyzes the interactions between threads and processes. This is illustrated in Figure 3-4. While KITL sends the data directly to the Remote Kernel Tracker tool, it is also possible to analyze collected data offline.
Figure 3-4 The Remote Kernel Tracker tool
For more information about CeLog event tracking and CeLog event filtering, see the section "CeLog Event Tracking Overview" in the Windows Embedded CE 6.0 documentation available on the Microsoft MSDN website at http://msdn2.microsoft.com/en-us/library/aa935693.aspx.
Windows Embedded CE is a multithreaded operating system that provides several process management functions to create processes and threads, assign thread priorities ranging from zero through 255, suspend threads, and resume threads. The Sleep function is useful in suspending a thread for a specified period of time, but the WaitForSingleObject or WaitForMultipleObjects functions can also be used to suspend a thread until another thread or a synchronization object is signaled. Processes and threads are ended in two ways: with and without cleanup. As a general rule, always use ExitProcess and ExitThread to give the system a chance to perform cleanup. Use TerminateProcess and TerminateThread only if you have absolutely no other choice.
When working with multiple threads, it is beneficial to implement thread synchronization in order to coordinate access to shared resources within and between processes. Windows Embedded CE provides several kernel objects for this purpose, specifically critical sections, mutexes, and semaphores. Critical sections guard access to resources within a single process. Mutexes coordinate mutually exclusive access to resources shared among multiple processes. Semaphores implement concurrent access by multiple threads to resources within a process and between processes. Events are used to notify the other threads, and interlocked functions for the manipulation of variables in a thread-safe atomic way. If you happen to encounter thread synchronization problems during the development phase, such as deadlocks, use the CeLog event-tracking system and Remote Kernel Tracker to analyze the thread interactions on the target device.
To pass the certification exam, make sure you understand how to use the various synchronization objects in Windows Embedded CE 6.0 R2.
Target devices running Windows Embedded CE include exceptions as part of system and application processing. The challenge is to respond to exceptions in the appropriate manner. Handling exceptions correctly ensures a stable operating system and positive user experience. For example, instead of unexpectedly terminating a video application, you might find it more useful to prompt the user to connect a Universal Serial Bus (USB) camera if the camera is currently disconnected. However, you should not use exception handling as a universal solution. Unexpected application behavior can be the result of malicious code tampering with executables, DLLs, memory structures, and data. In this case, terminating the malfunctioning component or application is the best course of action to protect the data and the system.
After this lesson, you will be able to:
■ Understand the reason for exceptions.
■ Catch and throw exceptions.
Estimated lesson time: 30 minutes.
Exceptions are events resulting from error conditions. These conditions can arise when the processor, operating system, and applications are executing instructions outside the normal flow of control in kernel mode and user mode. By catching and handling exceptions, you can increase the robustness of your applications and ensure a positive user experience. Strictly speaking, however, you are not required to implement exception handlers in your code because structured exception handling is an integral part of Windows Embedded CE.
The operating system catches all exceptions and forwards them to the application processes that caused the events. If a process does not handle its exception event, the system forwards the exception to a postmortem debugger and eventually terminates the process in an effort to protect the system from malfunctioning hardware or software. Dr. Watson is a common postmortem debugger that creates a memory dump file for Windows Embedded CE.
Exception handling is also the basis for kernel debugging. When you enable kernel debugging in an operating system design, Platform Builder includes the kernel debugging stub (KdStub) in the run-time image to enable components that raise exceptions to break into the debugger. Now you can analyze the situation, step through the code, resume processing, or terminate the application process manually. However, you need a KITL connection to a development workstation in order to interact with the target device. Without a KITL connection, the debugger ignores the exception and lets the application continue to run so that the operating system can use another exception handler as if no debugger was active. If the application does not handle the exception, then the operating system gives the kernel debugger a second chance to perform postmortem debugging. In this context, it is often called just in time (JIT) debugging. The debugger must now accept the exception and waits for a KITL connection to become available for the debug output. Windows Embedded CE waits until you establish the KITL connection and start debugging the target device. Developer documentation often uses the terms first-chance exception and second-chance exception because the kernel debugger has two chances to handle an exception in this scenario, but they are, in fact, referring to the same exception event. For more information about debugging and system testing, read Chapter 5, "Debugging and Testing the System."
Windows Embedded CE uses the same structured exception handling (SEH) approach for all hardware and software exceptions. The central processing unit (CPU) can raise hardware exceptions in response to invalid instruction sequences, such as division by zero or an access violation caused by an attempt to access an invalid memory address. Drivers, system applications, and user applications, on the other hand, can raise software exceptions to invoke the operating system's SEH mechanisms by using the RaiseException function. For example, you can raise an exception if a required device is not accessible (such as a USB camera or a database connection), if the user specified an invalid command-line parameter, or for any other reason that requires you to run special instructions outside the normal code path. You can specify several parameters in the RaiseException function call to specify information that describes the exception. This specification can then be used in the filter expression of an exception handler.
Windows Embedded CE supports frame-based structured exception handling. It is possible to enclose a sensitive sequence of code in braces ({}) and mark it with the __try keyword to indicate that any exceptions during the execution of this code should invoke an exception handler that follows in a section marked by using the __except keyword. The C/C++ compiler included in Microsoft Visual Studio supports these keywords and compiles the code blocks with additional instructions that enable the system either to restore the machine state and continue thread execution at the point at which the exception occurred, or to transfer control to an exception handler and continue thread execution in the call stack frame in which the exception handler is located.
The following code fragment illustrates how to use the __try and __except keywords for structured exception handling:
__try {
// Place guarded code here.
} __except (filter-expression) {
// Place exception-handler code here.
}
The __except keyword supports a filter expression, which can be a simple expression or a filter function. The filter expression can evaluate to one of the following values:
■ EXCEPTION_CONTINUE_EXECUTION The system assumes that the exception is resolved and continues thread execution at the point at which the exception occurred. Filter functions typically return this value after handling the exception to continue processing as normal.
■ EXCEPTION_CONTINUE_SEARCH The system continues its search for an appropriate exception handler.
■ EXCEPTION_EXECUTE_HANDLER The system thread execution continues sequentially from the exception handler rather than from the point of the exception.
Exception handling is an extension of the C language, but it is natively supported in C+ + .
Windows Embedded CE supports termination handling. As a Microsoft extension to the C and C++ languages, it enables you to guarantee that the system always runs a certain block of code not matter how the flow of control leaves the guarded code block. This code section is called a termination handler, and is used to perform cleanup tasks even if an exception or some other error occurs in the guarded code. For example, you can use a termination handler to close thread handles that are no longer needed.
The following code fragment illustrates how to use the __try and __finally keywords for structured exception handling:
__try {
// Place guarded code here.
} __finally {
// Place termination code here.
}
Termination handling supports the __leave keyword within the guarded section. This keyword ends thread execution at the current position in the guarded section and resumes thread execution at the first statement in the termination handler without unwinding the call stack.
A single __try block cannot have both an exception handler and a termination handler. If you must use both __except and __finally, use an outer try-except statement and an inner try-finally statement.
Dynamic memory allocation is an allocation technique that relies on structured exception handling to minimize the total number of committed memory pages on the system. This is particularly useful if you must perform large memory allocations. Precommitting an entire allocation can cause the system to run out of committable pages and result in virtual memory allocation failures.
The dynamic memory allocation technique is as follows:
1. Call VirtualAlloc with a base address of NULL to reserve a block of memory. The system reserves this memory block without committing the pages.
2. Try to access a memory page. This raises an exception because you cannot read from or write to a non-committed page. This illegal operation results in a page fault exception. Figure 3-5 and Figure 3-6 show the outcome of an unhandled page fault in an application called PageFault.exe.
3. Implement an exception handler based on a filter function. Commit a page in the filter function from the reserved region. If successful, return EXCEPTION_CONTINUE_EXECUTION to continue thread execution in the __try block at the point where the exception occurred. If the page allocation failed, return EXCEPTION_EXECUTE_HANDLER to invoke the exception handler in the __except block and release the entire region of reserved and committed pages.
Figure 3-5 An unhandled page fault exception from a user's perspective
Figure 3-6 An unhandled page fault exception's debug output over KITL in Visual Studio 2005
The following code snippet illustrates the dynamic memory allocation technique based on page fault exception handling:
#define PAGESTOTAL 42 // Max. number of pages
LPTSTR lpPage; // Page to commit
DWORD dwPageSize; // Page size, in bytes
INT ExceptionFilter(DWORD dwCode) {
LPVOID lpvPage;
if (EXCEPTION_ACCESS_VIOLATION != dwCode) {
// This is an unexpected exception!
// Do not return EXCEPTION_EXECUTE_HANDLER
// to handle this in the application process.
// Instead, let the operating system handle it.
return EXCEPTION_CONTINUE_SEARCH;
}
// Allocate page for read/write access.
lpvPage = VirtualAlloc((LPVOID) lpPage,
dwPageSize, MEM_COMMIT, PAGE_READWRITE);
if (NULL == lpvPage) {
// Continue thread execution
// in __except block.
return EXCEPTION_EXECUTE_HANDLER;
}
// Set lpPage to the next page.
lpPage = (LPTSTR) ((PCHAR) lpPage + dwPageSize);
// Continue thread execution in __try block.
return EXCEPTION_CONTINUE_EXECUTION;
}
VOID DynamicVirtualAlloc() {
LPVOID lpvMem;
LPTSTR lpPtr;
DWORD i;
BOOL bRet;
// Get page size on computer.
SYSTEM_INFO sSysInfo;
GetSystemInfo(&sSysInfo);
dwPageSize = sSysInfo.dwPageSize;
// Reserve memory pages without committing.
lpvMem = VirtualAlloc(NULL, PAGESTOTAL*dwPageSize,
MEM_RESERVE, PAGE_NOACCESS);
lpPtr = lpPage = (LPTSTR) lpvMem;
// Use structured exception handling when accessing the pages.
for (i=0; i < PAGESTOTAL*dwPageSize; i++) {
__try {
// Write to memory.
lpPtr[i] = 'x';
} __except (ExceptionFilter(GetExceptionCode())) {
// Filter function unsuccessful. Abort mission.
ExitProcess( GetLastError() );
}
}
// Release the memory.
bRet = VirtualFree(lpvMem, 0, MEM_RELEASE);
}
Windows Embedded CE supports exception handling and termination handling natively. Exceptions in the processor, operating system, and applications are raised in response to improper instruction sequences, attempts to access an unavailable memory address, inaccessible device resources, invalid parameters, or any other operation that requires special processing, such as dynamic memory allocations. You can use try-except statements to react to error conditions outside the normal flow of control and try-finally statements to run code no matter how the flow of control leaves the guarded __try code block.
Exception handling supports filtering expressions and filtering functions, which enable you to control how you respond to raised events. It is not advisable to catch all exceptions because unexpected application behavior can be the result of malicious code. Only handle those exceptions that need to be dealt with directly for reliable and robust application behavior. The operating system can forward any unhandled exceptions to a postmortem debugger to create a memory dump and terminate the application.
To pass the certification exam, make sure you understand how to use exception handling and termination handling in Windows Embedded CE 6.0 R2.
Power management is essential for Windows Embedded CE devices. By lowering power consumption, you extend battery life and ensure a long-lasting, positive user experience. This is the ultimate goal of power management on portable devices. Stationary devices also benefit from power management. Regardless of equipment size, you can reduce operating costs, heat dissipation, mechanical wear and tear, and noise levels if you switch the device into a low-power state after a period of inactivity. And of course, implementing effective power-management features helps to lessen the burden on our environment.
After this lesson, you will be able to:
■ Enable power management on a target device.
■ Implement power-management features in applications.
Estimated lesson time: 40 minutes.
On Windows Embedded CE, Power Manager (PM.dll) is a kernel component that integrates with the Device Manager (Device.exe) to implement power management features. Essentially, Power Manager acts as a mediator between the kernel, OEM adaptation layer (OAL), and drivers for peripheral devices and applications. By separating the kernel and OAL from drivers and applications, drivers and applications can manage their own power state separately from the system state. Drivers and applications interface with Power Manager to receive notifications about power events and to perform power management functions. Power Manager has the ability to set the system power state in response to events and timers, control driver power states, and respond to OAL events that require a power state change, such as when the battery power state is critical.
Power Manager exposes a notification interface, an application interface, and a device interface according to its tasks. The notification interface enables applications to receive information about power management events, such as when the system state or a device power state changes. In response to these events, power management-enabled applications use the application interface to interact with Power Manager to communicate their power management requirements or change the system power state. The device interface, on the other hand, provides a mechanism to control the power level of device drivers. Power Manager can set device power states separately from the system power state. Similar to applications, device drivers may use the driver interface to communicate their power requirements back to Power Manager. The important point is that Power Manager and the Power Manager APIs centralize power management on Windows Embedded CE, as illustrated in Figure 3-7.
Figure 3-7 Power Manager and power management interaction
Windows Embedded CE comes with source code for Power Manager, which is found in the %_WINCEROOT%\Public\Common\Oak\Drivers\Pm folder on your development computer. Customizing this code provides personalized power handling mechanisms on a target device. For example, an Original Equipment Manufacturer (OEM) can implement additional logic to shut down special components before calling the PowerOffSystem function. See Chapter 1, "Customizing the Operating System Design" for techniques to clone and customize standard Windows Embedded CE components.
Applications and device drivers are able to use the DevicePowerNotify function to control the power state of peripheral devices. For instance, you can call DevicePowerNotify to inform Power Manager that you want to change the power level of a backlight driver, such as BLK1:. Power Manager expects you to specify the desired power state at the one of the following five different power levels, according to the hardware device capabilities:
■ D0 Full On; the device is fully functional.
■ D1 Low On; the device is functional, but the performance is reduced.
■ D2 Standby; the device is partially powered and will wake up on requests.
■ D3 Sleep; the device is partially powered. In this state the device still has power and can raise interrupts that will wake up the CPU (device-initiated wakeup).
■ D4 Off; device has no power. The device should not consume any significant power in this state.
The device power states (D0 through D4) are guidelines to help OEMs implement power management functions on their platforms. Power Manager does not impose restrictions on device power consumption, responsiveness, or capabilities in any of the states. As a general rule, higher-numbered states should consume less power than lower numbered states and power states D0 and D1 should be for devices perceived as operational from the perspective of the user. Device drivers that manage the power level of a physical device with fewer granularities can implement a subset of the power states. DO is the only required power state.
In addition to sending power-state change notifications to device drivers in response to application and device driver requests, Power Manager can also transition the power state of the entire system in response to hardware-related events and software requests. Hardware events enable Power Manager to respond to low and critical battery levels and transitions from battery power to AC power. Software requests enable applications to request a change of the system power state in a call to Power Manager's SetSystemPowerState function.
The default Power Manager implementation supports the following four system power states:
■ On The system is fully operational and on full power.
■ UserIdle The user is passively using the device. There was no user input for a configurable period of time.
■ SystemIdle The user is not using the device. There was no system activity for a configurable period of time.
■ Suspend The device is powered down, but supports device-initiated wakeup.
It is important to keep in mind that system power states depend on the requirements and capabilities of the target device. OEMs can define their own or additional system power states, such as InCradle and OutOfCradle. Windows Embedded CE does not impose a limit on the number of system power states that can be defined, but all system power states eventually translate into one of the device power states, mentioned earlier in this lesson.
Figure 3-8 illustrates the relationship between the default system power states and the device power states.
Figure 3-8 Default system power states and associated device power states
System state transitions are based on activity timers and corresponding events. If a user is not using the device, a timer eventually expires and raises an inactivity event, which in turn causes Power Manager to transition the system into Suspend power state. When the user returns and interacts with the system again by providing input, an activity event occurs causing Power Manager to transition the system back into an On power state. However, this simplified model does not take into account prolonged periods of user activity without input, such as a user watching a video clip on a personal digital assistant (PDA). This simplified model also does not take into account target devices without any direct user input methods, as in the case of display panels. To support these scenarios, the default Power Manager implementation distinguishes between user activity and system activity and accordingly transitions the system power state, as illustrated in Figure 3-9.
Figure 3-9 Activity timers, events, and system power state transitions
To configure system-activity and user-activity timeouts, use the Power Control Panel applet. You can also implement additional timers and set their timeouts by editing the registry directly. Windows Embedded CE does not limit the number of timers you can create. At startup, Power Manager reads the registry keys, enumerates the activity timers, and creates the associated events. Table 3-16 lists the registry settings for the SystemActivity timer. OEMs can add similar registry keys and configure these values for additional timers.
Table 3-16 Registry settings for activity timers
| Location | HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Power\ActivityTimers\SystemActivity | |
|---|---|---|
| Entry | Timeout | WakeSources |
| Type | REG_DWORD | REG_MULTI_SZ |
| Value | A (10 minutes) | 0x20 |
| Description | The Timeout registry entry defines the timer threshold in minutes. | The WakeSources registry entry is optional and defines a list of identifiers for possible wake sources. During device-initiated wakeup, Power Manager uses the IOCTL_HAL_GET_WAKE_SOURCE input and output control (IOCTL) code to determine the wake source and sets associated activity timers to active. |
Defining activity timers causes the Power Manager to construct a set of named events for resetting the timer and for obtaining activity status. For more information, see the section "Activity Timers" in the Windows Embedded CE 6.0 Documentation available on the Microsoft MSDN website at http://msdn2.microsoft.com/en-us/library/aa923909.aspx.
As mentioned earlier in this lesson, Power Manager exposes three interfaces to enable applications and drives for power management: notification interface, driver interface, and application interface.
The notification interface provides two functions that applications can use to register and deregister for power notifications through message queues, as listed in Table 3-17. It is important to note that power notifications are multicast messages, which means that Power Manager sends these notification messages only to registered processes. In this way, power management-enabled applications can seamlessly coexist on Windows Embedded CE with applications that do not implement the Power Management API.
Table 3-17 Power Management notification interface
| Function | Description |
|---|---|
| RequestPowerNotifications | Registers an application process with Power Manager to receive power notifications. Power Manager then sends the following notification messages: |
| ■ PBT_RESUME The system resumes from Suspend state. | |
| ■ PBT_POWERSTATUSCHANGE The system transitions between AC power and battery power. | |
| ■ PBT_TRANSITION The system changes to a new power state. | |
| ■ PBT_POWERINFOCHANGE The battery status changes. This message is only valid if a battery driver is loaded. | |
| StopPowerNotifications | Unregisters an application process so it no longer receives power notifications. |
The following sample code illustrates how to use power notifications:
// Size of a POWER_BROADCAST message.
DWORD cbPowerMsgSize =
sizeof POWER_BROADCAST + (MAX_PATH * sizeof TCHAR);
// Initialize a MSGQUEUEOPTIONS structure.
MSGQUEUEOPTIONS mqo;
mqo.dwSize = sizeof(MSGQUEUEOPTIONS);
mqo.dwFlags = MSGQUEUE_NOPRECOMMIT;
mqo.dwMaxMessages = 4;
mqo.cbMaxMessage = cbPowerMsgSize;
mqo.bReadAccess = TRUE;
//Create a message queue to receive power notifications.
HANDLE hPowerMsgQ = CreateMsgQueue(NULL, &mqo);
if (NULL == hPowerMsgQ) {
RETAILMSG(1, (L"CreateMsgQueue failed: %x\n", GetLastError()));
return ERROR;
}
// Request power notifications.
HANDLE hPowerNotifications = RequestPowerNotifications(hPowerMsgQ,
PBT_TRANSITION | PBT_RESUME | PBT_POWERINFOCHANGE);
// Wait for a power notification or for the app to exit.
while(WaitForSingleObject(hPowerMsgQ, FALSE, INFINITE) == WAIT_OBJECT_0) {
DWORD cbRead;
DWORD dwFlags;
POWER_BROADCAST *ppb = (POWER_BROADCAST*) new BYTE[cbPowerMsgSize];
// Loop through in case there is more than 1 msg.
while(ReadMsgQueue(hPowerMsgQ, ppb, cbPowerMsgSize, &cbRead, 0, &dwFlags)) {
// Perform action according to the message type.
}
}
In order to integrate with the Power Manager, device drivers must support a set of I/O controls (IOCTLs). Power Manager uses these to query device-specific power capabilities as well as to set and change the device's power state, as illustrated in Figure 3-10. Based on the Power Manager IOCTLs, the device driver should put the hardware device into a corresponding power configuration.
Figure 3-10 Power Manager and device driver interaction
Power Manager uses the following IOCTLs to interact with device drivers:
■ IOCTL_POWER_CAPABILITIES Power Manager checks the power management capabilities of the device driver. The returned information should reflect the capabilities of the hardware and the driver managing the hardware device. The driver must return only supported Dx states.
■ IOCTL_POWER_SET Power Manager forces the driver to switch to a specified Dx state. The driver must perform the power transition.
■ IOCTL_POWER_QUERY Power Manger checks to see if the driver is able to change the state of the device.
■ IOCTL_POWER_GET Power Manager wants to determine the current power state of the device.
■ IOCTL_REGISTER_POWER_RELATIONSHIP Power Manager notifies a parent driver to register all child devices that it controls. Power Manager sends this IOCTL only to devices that include the POWER_CAP_PARENT flag in the Flags member of the POWER_CAPABILITIES structure.
To ensure reliable power management, device drivers should not change their own internal power state without the involvement of Power Manager. If a driver requires a power state transition, the driver should use the DevicePowerNotify function to request the power state change. The driver can then change its internal power state when Power Manager sends a power state change request back to the driver.
The application interface provides functions that applications can use to manage the power state of the system and of individual devices through Power Manager. Table 3-18 summarizes these power management functions.
Table 3-18 Application interface
| Function | Description |
|---|---|
| GetSystemPowerState | Retrieves the current system power state. |
| SetSystemPowerState | Requests a power state change. When switching to Suspend mode, the function will return after the resume because suspend is transparent to the system. After the resume, you can analyze the notification message to identify that the system resumed from suspend. |
| SetPowerRequirement | Requests a minimal power state for a device. |
| ReleasePowerRequirement | Releases a power requirement previously set with the SetPowerRequirement function and restores the original device power state. |
| GetDevicePower | Retrieves the current power state of a specified device. |
| SetDevicePower | Requests a power state change for a device. |
As illustrated in Figure 3-8, Power Manager associates system power states with device power states to keep system and devices synchronized. Unless configured otherwise, Power Manager enforces the following default system-state-to-device-state mappings: On = D0, UserIdle = D1, SystemIdle = D2, and Suspend = D3. Overriding this association for individual devices and device classes can be accomplished by means of explicit registry settings.
The default Power Manager implementation maintains system-state-to-device-state mappings in the registry under the HKEY_LOCAL_MACHINE\System\CurrentControlSet\State key. Each system power state corresponds to a separate subkey and you can create additional subkeys for OEM-specific power states.
Table 3-19 shows a sample configuration for the system power state On. This configuration causes Power Manager to switch all devices, except the backlight driver BLK1: driver, into the D0 device power state. The backlight driver BLK1: can only go to the D2 state.
Table 3-19 Default and driver-specific power state definitions for system power state On
| Location | HKEY_LOCAL_MACHINE\System\CurrentControlSet\State\On | ||
|---|---|---|---|
| Entry | Flags | Default | BKL1: |
| Type | REG_DWORD | REG_DWORD | REG_DWORD |
| Value | 0x00010000 (POWER_STATE_ON) | 0 (D0) | 2 (D2) |
| Description | Identifies the system power state associated with this registry key. For a list of possible flags, see the Pm.h header file in the Public\Common\Sdk\Inc folder. | Sets the power state for drivers by default to the D0 state when the system power state is On. | Sets the backlight driver BLK1: to the D2 state when the system power state is On. |
Defining device power state for multiple system power states individually can be a tedious task. Power Manager facilitates the configuration by supporting device classes based on IClass values, which can be used to define the power management rules. The following three default class definitions are found under the HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Power\Interfaces registry key.
■ {A3292B7-920C-486b-B0E6-92A702A99B35} Generic power management-enabled devices.
■ {8DD679CE-8AB4-43c8-A14A-EA4963FAA715} Power-management-enabled block devices.
■ {98C5250D-C29A-4985-AE5F-AFE5367E5006} Power-management-enabled Network Driver Interface Specification (NDIS) miniport drivers.
Table 3-20 shows a sample configuration for the NDIS device class, which specifies that NDIS drivers only go as high as the D4 state.
Table 3-20 Sample power state definition for NDIS device class
| Location | HKEY_LOCAL_MACHINE\SystemCurrentControlSet\Control\Power\State\On\{98C5250D-C29A-4985-AE5F-AFE5367E5006} |
|---|---|
| Entry | Default |
| Type | REG_DWORD |
| Value | 4 (D4) |
| Description | Sets the device power state for NDIS drivers to the D4 state when the system power state is On. |
In addition to power-management-enabled applications and device drivers, the kernel also contributes to power management. The kernel calls the OEMIdle function, part of the OAL, when no threads are ready to run. This action switches the processor into idle state, which includes saving the current context, placing the memory into a refresh state, and stopping the clock. The processor idle state reduces power consumption to the lowest possible level while retaining the ability to return from the idle state quickly.
It is important to keep in mind that the OEMIdle function does not involve Power Manager. The kernel calls the OEMIdle function directly and it is up to the OAL to switch the hardware into an appropriate idle or sleep state. The kernel passes a DWORD value (dwReschedTime) to OEMIdle to indicate the maximum period of idle time. When this time passes or the maximum delay supported by the hardware timer is reached, the processor switches back to non-idle mode, the previous state is restored, and the scheduler is invoked. If there still is no thread ready to run, the kernel immediately calls OEMIdle again. Driver events, as in the response to user input via keyboard or stylus, may occur at any time and cause the system to stop idling before the system timer starts.
The scheduler is, by default, based on a static timer and system ticks at a one-millisecond frequency. However, the system can optimize power consumption by using dynamic timers and by setting the system timer to the next timeout identified by using the scheduler table content. The processor will then not switch back out of idle mode with every tick. Instead, the processor switches only to non-idle mode after the timeout defined by dwReschedTime expires or an interrupt occurres.
Windows Embedded CE 6.0 R2 provides a default Power Manager implementation with a set of power-management APIs that you can use to manage the power state of the system and its devices. It also provides the OEMIdle function, which it executes when the system does not have any threads scheduled in order to provide original equipment manufacturers (OEMs) a chance to put the system into a low power idle state for a specified period of time.
Power Manager is a kernel component that exposes a notification interface, an application interface, and a device interface. It acts as a mediator between kernel and OAL on one side and device drivers and applications on the other side. Applications and device drivers can use the DevicePowerNotify function to control the power state of peripheral devices at five different power levels. Device power states may also associate with default and custom system power states to keep system and devices synchronized. Based on activity times and corresponding events, Power Manger can automatically perform system state transitions. The four default system power states are On, UserIdle, SystemIdle, and Suspend. Customizations for system-state-to- device-state mapping take place in the registry settings of individual devices and device classes.
In addition to Power Manager, the kernel supports power management by means of the OEMIdle function. Switching the processor into idle state reduces power consumption to the lowest possible level while retaining the ability to return from the idle state quickly. The processor will return to non-idle state periodically or when interrupts occur, as when it responds to user input or when a device requests access to memory for data transfer.
You can significantly reduce the power consumption of a device if you implement power management properly using Power Manager and OEMIdle, thereby increasing battery life, decreasing operating costs, and extending device lifetime.
In this lab, you develop a kiosk application and configure a target device to run this application instead of the standard shell. You then extend this application to run multiple threads in parallel in the application process and analyze the thread execution by using the Remote Kernel Tracker tool. Subsequently, you enable this application for power management.
To help you successfully master the procedures presented in this lab, see the document "Detailed Step-by-Step Instructions for Lab 3" in the companion material for this book.
1. Using the New Project Wizard, create a new WCE Console Application named HelloWorld. Use the Typical Hello_World Application option.
2. Before the _tmain function, implement a thread function named ThreadProc:
DWORD WINAPI ThreadProc( LPVOID lpParameter) {
RETAILMSG(1,(TEXT("Thread started")));
// Suspend Thread execution for 3 seconds
Sleep(3000);
RETAILMSG(1,(TEXT("Thread Ended")));
// Return code of the thread 0,
// usually used to indicate no errors.
return 0;
}
3. By using the CreateThread function, start a thread:
HANDLE hThread = CreateThread( NULL, 0, ThreadProc, NULL, 0, NULL);
4. Check the returned value of CreateThread to verify that the thread was created successfully.
5. Wait for the thread to reach the end of the thread function and exit:
WaitForSingleObject(hThread, INFINITE);
6. Build the run-time image and download it to the target device.
7. Launch Remote Kernel Tracker and analyze how threads are managed on the system.
8. Start the HelloWorld application and follow the thread execution in the Remote Kernel Tracker window, as illustrated in Figure 3–11.
Figure 3-11 Tracking thread execution in Remote Kernel Tracker tool
1. Continue to use the HelloWorld application in Visual Studio.
2. Generate power-management notifications in more frequent intervals by going into the subproject registry settings and setting the registry entry for the UserIdle timeout in AC power mode (ACUserIdle) to five seconds:
[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Power\Timeouts]
"ACUserIdle"=dword:5 ; in seconds
3. In the ThreadProc function, create a message queue object:
// Size of a POWER_BROADCAST message.
DWORD cbPowerMsgSize =
sizeof POWER_BROADCAST + (MAX_PATH * sizeof TCHAR);
// Initialize our MSGQUEUEOPTIONS structure.
MSGQUEUEOPTIONS mqo;
mqo.dwSize = sizeof(MSGQUEUEOPTIONS);
mqo.dwFlags = MSGQUEUE_NOPRECOMMIT;
mqo.dwMaxMessages = 4;
mqo.cbMaxMessage = cbPowerMsgSize;
mqo.bReadAccess = TRUE;
//Create a message queue to receive power notifications.
HANDLE hPowerMsgQ = CreateMsgQueue(NULL, &mqo);
if (NULL == hPowerMsgQ) {
RETAILMSG(1, (L"CreateMsgQueue failed: %x\n", GetLastError()));
return -1;
}
4. Request to receive notifications from Power Manager and check the received messages:
// Request power notifications
HANDLE hPowerNotifications = RequestPowerNotifications(hPowerMsgQ,
PBT_TRANSITION | PBT_RESUME | PBT_POWERINFOCHANGE);
DWORD dwCounter = 20;
// Wait for a power notification or for the app to exit
while(dwCounter-- &&
WaitForSinglObject(hPowerMsgQ, INFINITE) == WAIT_OBJECT_0) {
DWORD cbRead;
DWORD dwFlags;
POWER_BROADCAST *ppb =
(POWER_BROADCAST*) new BYTE[cbPowerMsgSize];
// loop through in case there is more than 1 msg.
while(ReadMsgQueue(hPowerMsgQ, ppb, cbPowerMsgSize,
&cbRead, 0, &dwFlags)) {
switch(ppb->Message) {
case PBT_TRANSITION: {
RETAILMSG(1,(L"Notification: PBT_TRANSITION\n"));
if(ppb->Length) {
RETAILMSG(1,(L"SystemPowerState: %s\n", ppb->SystemPowerState));
}
break;
}
case PBT_RESUME: {
RETAILMSG(1,(L"Notification: PBT_RESUME\n"));
break;
}
case PBT_POWERINFOCHANGE: {
RETAILMSG(1,(L"Notification: PBT_POWERINFOCHANGE\n"));
break;
}
default:
break;
}
}
delete[] ppb;
}
5. Build the application and rebuild the run-time image.
6. Start the run-time image.
7. You generate user activity by moving the mouse cursor. After five seconds of inactivity, Power Manager should notify the application, as illustrated in Figure 3-12.
Figure 3-12 Received Power Management notifications
1. Create a WCE Application named Subproject_Shell using the Subproject Wizard. Use the Typical Hello_World Application option.
2. Before the first LoadString line, add a SignalStarted instruction.
// Initialization complete,
// call SignalStarted...
SignalStarted(_wtol(lpCmdLine));
3. Build the application.
4. Add a registry key in the subproject .reg file to launch the application at startup. Add the following lines, which create the corresponding Launch99 and Depend99 entries:
[HKEY_LOCAL_MACHINE\INIT]
"Launch99"="Subproject_Shell.exe"
"Depend99"=hex:14,00,1e,00
5. Build and start the run-time image.
6. Verify that the Subproject_Shell application starts automatically.
7. Replace the reference to Explorer.exe in the Launch50 registry key with a reference to the Subproject_Shell application, as follows:
[HKEY_LOCAL_MACHINE\INIT]
"Launch50"="Subproject_Shell.exe"
"Depend50"=hex:14,00,1e,00
8. Build and start the run-time image.
9. Verify that the target device runs the Subproject_Shell application in place of the standard shell, as illustrated in Figure 3-13.
Figure 3-13 Replacing the standard shell with a Subproject_Shell application
Windows Embedded CE provides a variety of tools, features, and APIs that you can use to ensure optimal system performance and power consumption on your target devices. Performance tools, such as ILTiming, OSBench, and Remote Performance Monitor, can help identify performance issues within and between drivers, applications, and OAL code, such as deadlocks or other issues related to thread synchronization. The Remote Kernel Tracker enables you to examine process and thread execution in great detail, while relying on structured exception handling, which Windows Embedded CE supports natively.
Windows Embedded CE is a componentized operating system. You can include or exclude optional components and even replace the standard shell with a custom application. Replacing the standard shell with an application configured to start automatically lays the groundwork for enabling a kiosk configuration. Windows Embedded CE runs with a black shell in a kiosk configuration, meaning that the user cannot start or switch to other applications on your device.
Regardless of the shell, you can implement power management functions in your device drivers and applications to control energy usage. The default Power Manager implementation covers the typical needs, but OEMs with special requirements add custom logic. The Power Manager source code is included with Windows Embedded CE. The power management framework is flexible and supports any number of custom system power states that can map device power states by means of registry settings.
Do you know what these key terms mean? You can check your answers by looking up the terms in the glossary at the end of the book.
■ ILTiming
■ Kiosk Mode
■ Synchronization Objects
■ Power Manager
■ RequestDeviceNotifications
Complete the following tasks to help you successfully master the exam objectives presented in this chapter:
Use Iltiming and OSBench on the emulator device to examine the emulated ARMV4 processor's performance.
Customize the look and feel of the target device by using Task Manager, included in Windows Embedded CE with source code, to replace the shell.
Use critical section objects in a multithreaded application to protect access to a global variable. Complete the following tasks:
1. Create two threads in the main code of the applications and in the thread functions wait two seconds (Sleep(2000)) and three seconds (Sleep(3000)) in an infinite loop. The primary thread of the application should wait until both threads exit by using the WaitForMultipleObjects function.
2. Create a global variable and access it from both threads. One thread should write to the variable and the other thread should read the variable. By accessing the variable before and after the first Sleep and displaying the values, you should be able visualize concurrent access.
3. Protect access to the variable by using a CriticalSection object shared between both threads. Grab the critical section at the beginnings of the loops and release it at the ends of the loops. Compare the results with the previous output.