Microsoft Windows CE 3.0 Technical Articles  

Using the Kernel Tracker More Effectively

Important:
This is retired content. This content is outdated and is no longer being maintained. It is provided as a courtesy for individuals who are still using these technologies. This content may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist.

Paul Lewis
Microsoft Corporation

February 2001

Applies to:

Microsoft Windows CE Platform Builder version 3.0
Microsoft Windows CE Add-In Pack and System Analysis Tools for Platform Builder 3.0
Microsoft eMbedded Visual Tools 3.0

Summary:This article is intended for developers who are familiar with the Windows CE operating system and the Platform Builder development tools. It describes how the Kernel Tracker tool, when used on a profile-enabled kernel, can be used to debug and improve the performance of embedded systems running Windows CE version 3.0. (21 printed pages)

Contents

Introduction
Where the Kernel Tracker is Useful
    Deadlock
    Missed Real-Time Deadline
Data Collection Modes
    Live From Device
    Limited Buffer
    Existing Log File
Interface
Basic Features
    Locating Transitions
    Locating Sequential Events
    Filtering
    Narrowing a Search
    Showing and Hiding Threads and Processes
    Adjusting the Zoom Range
Advanced Features
    Searching for a Memory Leak in an Application
    Resolving a Deadlocked Thread
    Determining the Length of Time Between Events
Applying User-Defined Data Types
    Detecting a Missed Real-Time Deadline
    Emitting a User-Defined Data Type
Appendix A: Using the Code Samples
Appendix B: Dining Philosophers
Appendix C: Priority Inheritance

Introduction

The Kernel Tracker provides a graphical view of the execution of the Windows CE operating system (OS). It shows all processes and threads in the system and when these processes and threads are created, run, stopped, or killed. It also shows when processes and threads are sleeping. This data provides the means for a developer to track system-wide program execution.

The Kernel Tracker is useful in studying activity within your system. It allows you to quickly perform basic tasks such as locating transitions between events and locating sequential events. You may also find the Kernel Tracker helpful in searching for memory leaks, resolving deadlocked threads, and detecting missed real-time deadlines.

In addition to showing the running states of processes and threads, the Kernel Tracker also displays 42 different system events. These system events are mapped onto the thread that was executing at the time they occurred. System interrupts are displayed above the list of processes.

CELog is the underlying mechanism that provides the Kernel Tracker with system data to display. CELog is a library which links to the OS and collects a record of system events as they occur. To tune the tracking of system events, you must pre-set collection zones in the desktop registry. To dynamically change collection zones, call the CeLogSetZonesfunction. Consult the Platform Builder documentation for more information on collection zones. The CELog format may be used in raw form or by a proprietary analyzer or viewer.

Where the Kernel Tracker is Useful

There are many scenarios where the Kernel Tracker may be useful. Such scenarios include deadlock and a missed real-time deadline. These conditions can be near impossible to track down with a traditional application or kernel level debugger but can be diagnosed and fixed in a straightforward manner using the Kernel Tracker.

Deadlock

Deadlock is a situation in which two processes or threads sharing a resource prevent each other from accessing the resource, causing those processes or threads to cease functioning. If all processes executing in a system hold a resource that another requires and all processes are unable to move forward because of this then the entire system has entered a state of deadlock. The Kernel Tracker can generate a log of activity in your system. By examining system activity prior to deadlock, you may discover how to prevent deadlock from occurring. For more information on deadlock, see Appendix B: Dining Philosophers.

Missed Real-Time Deadline

A real-time OS guarantees a certain capability within a specified time constraint. If you have multiple threads or drivers with real-time requirements in a system, you could encounter a condition where one lower priority driver is not able to meet its deadline because an interrupt with an equal or higher priority is always preempting it. The Kernel Tracker allows you to see the thread and process interactions and can help you understand what prevents the thread or driver from reaching its real-time deadline.

A plausible example of an application that has a real-time deadline is a media player that must deliver 30 frames per second to meet its minimum quality level. If a thread preempts the media player, the media player could miss the delivery of several frames each second. The Kernel Tracker shows the specific thread that is taking over execution from the media player and allows you to tune the thread's priority so that the missed deadline no longer occurs.

Data Collection Modes

The Kernel Tracker provides three modes you can use to view data collected from a device. The Live From Device and Limited Buffer modes collect data from a device as it is being run, while the third mode allows you to open and examine an existing log file to compare against current activity.

Live From Device

This is the default mode for the Kernel Tracker. In this mode, as long as there is a valid connection to the device, data will automatically appear in the Kernel Tracker. This is especially useful for watching the initial boot sequence of the OS. To guarantee that you receive the boot sequence, launch the Kernel Tracker before you download the OS image or start your device.

Limited Buffer

This mode allows you to specifically limit the quantity of data stored by the Kernel Tracker by defining the buffer size. When the amount of data stored within the buffer reaches the predefined limit, the newest events begin to replace the oldest events. The size of the buffer may range from 1 megabyte (MB) to 100 MB.

Limited Buffer mode may prove helpful in capturing the events occurring in your system prior to the point when it locked up. In many cases, a buffer size of 10 MB to 20 MB will provide you with a few hours of events leading up to a crash. Defining the size of the buffer is not the same as defining the duration of time over which events are logged because the buffer fills at a variable rate related to level of activity in your system. A 10-MB buffer in one system might fill after five hours of execution while a 10-MB buffer in a more active system might fill after a half hour. Approximately the same number of events will have occurred within both systems in the time required to fill the buffer.

Existing Log File

After you capture log data using the Live From Device or Limited Buffer mode, you have the option to save the data to a log file. The log file may be opened later to compare with a more recent run. You may have another user send you a log file so that you may examine events on his or her system at a particular time.

Interface

The System Pane is the center pane of the Kernel Tracker interface. The System Pane displays events and the state of interrupts, processes, and threads in the system. The Process Pane is the left pane of the Kernel Tracker interface. The Process Pane displays a list of interrupts, processes, and threads. The System Pane automatically synchronizes with the Process Pane, displaying relevant data as you expand and collapse threads in a process. To display information on a particular event in the System Pane, hover the pointer over the event. To display the same information in the status bar, left-click on the event.

The Legend Pane is the right pane of the Kernel Tracker interface. The Legend Pane displays the symbols used to represent each thread and process state and each event.

The Kernel Tracker follows a document model similar to Microsoft Word 2000 and other applications with which you may be familiar. Like Word, the Kernel Tracker has a cursor. When you collect data or open a log file, the Kernel Tracker presents you with a view of the system at a specific point in time. The vertical red line in the System Pane is the cursor, which represents your current position in the log file.

The position of the cursor is important because searches start at the cursor's current location. The default location of the cursor is the earliest, or leftmost, point in the log file. The cursor remains at its current location until you move it to a new location. To move the cursor to a new location, click on the new location in the System Pane or select a specific event.

Basic Features

Locating Transitions

When you examine a log file, you may wish to locate transitions between threads and processes. To step through a series of thread and process transitions, choose Next Running Threador Previous Running Threadfrom the Viewmenu or press the RIGHT ARROW key or LEFT ARROW key, respectively.

Locating Sequential Events

The Kernel Tracker allows you to step from one system event to the next. A system event is an event that occurs within the system, for example, a mutual exclusion object (mutex), critical section, or system-wide interrupt. In the Kernel Tracker, most system events appear on the thread or process running at the time that the event occurred. Interrupts appear on separate timelines above all processes and threads.

The buttons for Find Next Eventand Find Previous Eventare similar to the Next Process/Threadbutton. The search starts from the current position of the cursor and moves forward when you click Find Next Eventand backward when you click Find Previous Event. You may use the shortcut keys CTRL+RIGHT ARROW for Find Next Eventand CTRL+LEFT ARROW for Find Previous Event.

By default, all events in the threads and processes are searched. Interrupts are also found in this search. Memory events are excluded by default due to the number that can be present in the system. Use the filtering feature to show or hide events.

Tip   Check the information related to a given event carefully. It may not be immediately apparent which threads the event affected. For example, a Set Thread Priorityevent on one thread could set the priority on another thread in the same process. To ascertain which thread is affected, check the affected thread handle.

Filtering

To display the Event Filterdialog box, click the Event Filterbutton on the toolbar or choose Event Filterfrom the Viewmenu. The Event Filterdialog box shows four categories of events, each on a separate tab. You may select specific events to filter or choose Select Allor Select Noneto select or clear an entire category of events. When you clear an event, you will no longer see that event appear in your system log. Searches for events will find only those still selected.

Filtering events may be useful if you are looking for specific classes of events such as Enter Critical Sectionand Leave Critical Section. After filtering out other events you can quickly find when these events occur.

Narrowing a Search

To further narrow your search, display the Find Eventdialog box by choosing Find Eventfrom the Editmenu. The Find Eventdialog box allows you to choose which process and thread you wish to search upon and designate whether you are interested in a specific type of event. You also have the ability to search for an identifier that is either a handle or a name. After you execute a search, subsequent searches will use the same criteria until you change it.

Showing and Hiding Threads and Processes

After the system executes for a period of time, there may be a large number of threads under certain processes. If you are only interested in one or two processes or threads, you may wish to hide the others. To hide a thread, right-click the name of the thread in the Process Pane and choose Hide Thread. This will place the thread under the Hidden Threadsprocess at the bottom of the Process Pane. Similarly, to eliminate all threads from a process, effectively hiding the process, right-click the process name and choose Hide All Threads in Process.

You may expand and browse the threads placed under Hidden Threads, but the threads are no longer in the context of their parent process.

Tip   If there are a number of dead threads in a process and you wish to view only certain threads in that process as they continue to interact, hide all threads in the process and then under the Hidden Threadsprocess right-click the name of the thread(s) you wish to view and choose Show Thread. This is much quicker than individually hiding each of the dead threads.

Adjusting the Zoom Range

The Kernel Tracker offers the ability to select a zoom range. The Zoom Range feature is limited to values between 1 millisecond (ms) and 10000 ms (10 seconds). To find a cluster of activity within the system, you might select a zoom range of 500 ms or 1000 ms. To examine a section that you find interesting, reduce the zoom range to study the activity on an event-by-event basis. When you reduce the zoom range, the contents at the center of the System Pane are enlarged. The Kernel Tracker will not zoom on a selected event unless the event is in the center of the System Pane when the zoom range is reduced.

Advanced Features

Searching for a Memory Leak in an Application

To search for a memory leak within an application, disable all system events then enable all memory-related events. Use the Find Eventdialog box to filter your search so that the Kernel Tracker searches for events within that particular application process. Walk through the events in the application process and verify that the Allocate Heapevents are associated with an appropriate number of Free Heapevents. Do the same for Allocate Virtual Memoryand Free Virtual Memoryevents.

If the application appears to have a memory leak, determine the function in which memory allocations occur and when the corresponding memory is freed. Remember that this is a simplistic method for examining memory usage. Microsoft recommends that you use a resource tracker or bounds checker.

Resolving a Deadlocked Thread

A thread may become deadlocked while waiting to enter a critical section or mutex. It is important to know what thread entered that critical section or mutex prior to the deadlock.

Find where the thread becomes deadlocked. It is likely that the thread executes until a Wait for Multiple Objects, Enter Critical Section, or Create Mutexevent occurs on it. After that point the thread either does not execute again or waits longer than is appropriate for the application.

To locate a process using the resource required by your deadlocked thread

  1. Display the Event Propertiesdialog box by double-clicking the icon corresponding to the event that occurs when the thread becomes deadlocked.

    The Event Propertiesdialog box shows a handle associated with the event. For example, the Event Propertiesdialog box might show the following information for an Enter Critical Sectionevent:

    Enter Critical Section: 0:00:38.024452

    hCS = 0x81E8A508

    hOwnerThread = 0xA1E31C2E

  2. To copy the handle from the Event Propertiesdialog box, highlight the handle, right-click it, and then choose Copy. In this example, you might copy the text 0x81E8A508, which is the value of hCS.
  3. From the Editmenu, choose Find Event.

    The Find Eventdialog box provides a means of searching for an identifier that is either a handle or a name.

  4. Choose ID is a Handle.
  5. Paste the text you copied from the Event Propertiesdialog box into the IDtext box. Choose the Previousbutton to search prior events and find which system process is using that resource and preventing the thread from using it.

    Now that you know which process is using the resource that your thread requires, tune your system to avoid conflict for the resource. Appendix B: Dining Philosopherscontains a code sample that illustrates deadlock.

    Determining the Length of Time Between Events

    The Kernel Tracker provides a mechanism for measuring the length of time between a pair of events.

    To measure the length of time between events

    1. Scroll to the first event in the pair of events.
    2. Right-click the ruler above the event, and then choose Set Time Marker 1from the menu.

      A marker icon appears on the ruler.

    3. Scroll to the next event.
    4. Right-click the ruler above the event, and then choose Set Time Marker 2from the menu.

      The length of time between the marked events is automatically computed and displayed as Marker Time Deltain the status bar in the lower-right corner of the Kernel Tracker window.

      To move a marker to a new location

      • Right-click the ruler in the new location, and then choose Set Time Marker 1or Set Time Marker 2from the menu.

        To remove a marker

        • Right-click the ruler, and then choose Delete Time Marker 1or Delete Time Marker 2from the menu.

          Applying User-Defined Data Types

          Detecting a Missed Real-Time Deadline

          The Kernel Tracker allows you to browse an entire system. However, it can be difficult to determine when a condition like a missed real-time deadline occurs. To determine if such a condition occurs and, if so, what causes it to occur, you may modify the application or driver to help you recognize the condition.

          You may wrap the section of code that is time-critical to determine exactly how long it takes to execute. If the application exceeds a real-time deadline then the wrapper emits a user-defined data type that is stored in the CELog (.clg) file. There are nine basic data types that can be emitted by an application.

          After you appropriately modify the application and generate a new .clg file, use the Find Eventdialog box to search specifically for the user-defined data type emitted by the wrapper. This takes you immediately to the points, if any, where the application misses a real-time deadline. You may then analyze what happens in the system to cause the missed deadline to occur.

          Emitting a User-Defined Data Type

          This illustration uses the priority inheritance sample from Appendix C: Priority Inheritance. Please refer to Appendix C for the complete sample. The goal of this illustration is to verify that the function LowPrioritymeets a real-time deadline when it calls the function CalculatePrimes. If the function fails to meet the real-time deadline, it emits user-defined data to the log and includes the length of time by which the deadline is missed.

          The CELogDatafunction enters user-defined data into the .clg file.

          void CeLogData ( BOOL 
          fTimeStamp, WORD 
          wID, PVOID 
          pData, WORD 
          wLen, DWORD 
          dwZoneUser, DWORD 
          dwZoneCE, DWORD 
          wFlag, DWORD 
          fFlagged);

          The parameter list results in the following code.

          CeLogData (TRUE, CELID_RAW_LONG, &delta,
          (WORD) (1 * sizeof(LONG)), 0, CELZONE_MISC, 0, FALSE);

          Please see the Platform Builder documentation for a complete list of valid event identifiers and descriptions of other parameters.

          The following code example shows the LowPriorityfunction after wrapping the CalculatePrimesfunction. The deadlinevariable is user defined since the real-time deadline varies from system to system and processor to processor. You may wish to set the Ifblock to True to ensure that the user-defined event appears in the log file.

          #define DEADLINE 5 DWORD WINAPI
          LowPriority(HANDLE h) { DWORD time1, time2; ULONG delta = 0; time1
          = 0; time2 = 0; WaitForSingleObject(hTheLock, INFINITE);
          ResumeThread(h); time1 = GetTickCount(); CalculatePrimes(1000000);
          time2 = GetTickCount(); /* You want to compare the time that was
          spent in the CalculatePrimes() function. If it took longer than
          DEADLINE, then you want to generate a user-defined event so you
          know that you have missed the real-time deadline. */ delta = (time2
          – time1); if(delta >= DEADLINE) { CeLogData (TRUE,
          CELID_RAW_LONG, &delta, (WORD) (1 * sizeof(LONG)), 0,
          CELZONE_MISC, 0, FALSE); } ReleaseMutex(hTheLock); printf("Low pri
          done\n"); return 0; }

          Appendix A: Using the Code Samples

          Before using the code samples, you must create or use an existing platform. See the Platform Builder documentation for information on creating a platform.

          To use a code sample

          1. From the Filemenu, choose New.
          2. Select WCE Applicationon the Projectstab.
          3. Type a name for the application in the Project namebox. Choose OK.
          4. Choose An empty project. Choose Finish.
          5. Choose OK.
          6. From the Viewmenu, choose Workspace,and then choose Projectto switch to Project View.
          7. From the Filemenu, choose New.
          8. Select C++ Source Fileon the Filestab.
          9. Type a name for the source file in the File namebox. Select the Add to projectcheck box, and then choose OK.
          10. Paste the source code from the sample into the empty window.
          11. From the Filemenu, choose New.
          12. Select C/C++ Header Fileon the Filestab.
          13. Type a name for the header file in the File namebox. Select the Add to projectcheck box, and then choose OK.
          14. Paste the header code from the sample into the empty window.
          15. From the Filemenu, choose Save All.

            Appendix B: Dining Philosophers

            The following code sample shows what deadlock between five threads in a single process looks like when it occurs in your system.

            This is a classic problem found in many OS textbooks. The analogy is that there are five philosophers who are going to eat Chinese food. Each philosopher has a plate, and on either side of each philosopher's plate is a chopstick. Each philosopher needs both chopsticks to eat before the philosopher can sit back and ponder the mysteries of the universe. After the philosopher ponders for a while, the philosopher becomes hungry and attempts to eat again. In other words, at any given time the philosopher must be in a state of eating, sleeping, or waiting for a resource.

            The complication arises because there are five philosophers and five chopsticks. Given this, no more than two philosophers may eat at the same time. In the sample below, deadlock occurs when a philosopher grabs one chopstick and will not relinquish it until getting a second chopstick. If each philosopher picks up the chopstick on the left side of the plate at the same time and waits for the chopstick on the right side of the plate, the entire group enters a state of deadlock.

            In this sample, there are a number of algorithms that can be used to prevent deadlock from occurring. The simplest is the algorithm that dictates that if the philosopher cannot gain both chopsticks, the philosopher releases any chopsticks in possession and can later try to gain both.

            Philosopher.cpp

            // philosopher.cpp: Defines the entry point for
            the console application. #include "stdafx.h" #include
            <stdio.h> #define NUM_PHILOSOPHERS 5 #define NUM_CHOPSTICKS
            NUM_PHILOSOPHERS DWORD WINAPI Philosopher(LPVOID lpPhilosopherID);
            int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,
            LPTSTR lpCmdLine, int nCmdShow) { HANDLE
            arrChopsticks[NUM_CHOPSTICKS]; HANDLE
            arrPhilosophers[NUM_PHILOSOPHERS]; int i; for(i = 0; i <
            NUM_CHOPSTICKS; i++) { TCHAR szName[50]; _stprintf(szName,
            L"Chopstick%d", i); arrChopsticks[i] = CreateMutex(NULL, FALSE,
            szName); if(arrChopsticks[i] == NULL) { return 0; } } for(i = 0; i
            < NUM_PHILOSOPHERS; i++) { arrPhilosophers[i] =
            CreateThread(NULL, 0, Philosopher, (void*)i, 0, NULL);
            if(arrPhilosophers[i] == NULL) { return 0; } } for(i = 0; i <
            NUM_PHILOSOPHERS; i++) { WaitForSingleObject(arrPhilosophers[i],
            INFINITE); CloseHandle(arrPhilosophers[i]); } for(i = 0; i <
            NUM_CHOPSTICKS; i++) { CloseHandle(arrChopsticks[i]); } return 0; }
            void PonderNothingness(){ Sleep(1000); } void Eat(HANDLE hLeft,
            HANDLE hRight) { WaitForSingleObject(hRight, INFINITE);
            WaitForSingleObject(hLeft, INFINITE); Sleep(500);
            ReleaseMutex(hLeft); ReleaseMutex(hRight); } void GreedyEat(HANDLE
            hLeft, HANDLE hRight) { WaitForSingleObject(hRight, INFINITE);
            WaitForSingleObject(hLeft, INFINITE); Sleep(INFINITE);
            ReleaseMutex(hLeft); ReleaseMutex(hRight); } DWORD WINAPI
            Philosopher(LPVOID lpPhilosopherID) { UINT nID =
            (UINT)lpPhilosopherID; HANDLE hChopstickRight, hChopstickLeft;
            TCHAR szNameRight[50], szNameLeft[50]; _stprintf(szNameRight,
            L"Chopstick%d", nID); _stprintf(szNameLeft, L"Chopstick%d", (nID +
            1) % NUM_CHOPSTICKS); hChopstickRight = CreateMutex(NULL, FALSE,
            szNameRight); hChopstickLeft = CreateMutex(NULL, FALSE,
            szNameLeft); while(1) { PonderNothingness(); if(nID != 1) {
            Eat(hChopstickLeft, hChopstickRight); } else {
            GreedyEat(hChopstickLeft, hChopstickRight); } } return 0; }

            Stdafx.h

            // stdafx.h : This is the include file for
            standard system include files // or project specific include files
            that are used frequently, but // are changed infrequently. #if
            !defined(AFX_STDAFX_H__B26DF3B3_31A2_11D4_9B68_00105AC728C4__INCLUDED_)
            #define
            AFX_STDAFX_H__B26DF3B3_31A2_11D4_9B68_00105AC728C4__INCLUDED_ #if
            _MSC_VER > 1000 #pragma once #endif // _MSC_VER > 1000 //
            Exclude rarely-used information from Windows headers. #define
            WIN32_LEAN_AND_MEAN #include <windows.h> #include
            <stdio.h> // Reference additional headers your program
            requires. //{{AFX_INSERT_LOCATION}} // Microsoft Visual C++ will
            insert additional declarations immediately above the previous line.
            #endif //
            !defined(AFX_STDAFX_H__B26DF3B3_31A2_11D4_9B68_00105AC728C4__INCLUDED_)

            Appendix C: Priority Inheritance

            This code sample shows priority inheritance. Higher priority threads are waiting for a resource held by a lower priority thread. Because the higher priority threads are blocked, the lower priority thread inherits their higher priority so that it can run and release its resource more quickly.

            Priority_inheritance.cpp

            // priority_inheritance.cpp: Defines the entry
            point for the // console application. #include "stdafx.h" #include
            <math.h> #include <celog.h> // This handle points to a
            mutex called "TheLock". HANDLE hTheLock; // Define three real-time
            thread priorities to use. // 240 = lowest, 0 = highest. const UINT
            LOW_PRIORITY = 240; const UINT MEDIUM_PRIORITY = 50; const UINT
            HIGH_PRIORITY = 0; // A way to associate each thread with its
            priority type. const UINT LOW = 0; const UINT MED = 1; const UINT
            HIGH = 2; // Function prototypes DWORD WINAPI LowPriority(HANDLE
            h); DWORD WINAPI MedPriority(HANDLE h); DWORD WINAPI
            HighPriority(LPVOID lp); // Work functions called by each thread.
            void CalculatePrimes(UINT n); BOOL IsPrime(UINT n); int WINAPI
            WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR
            lpCmdLine, int nCmdShow) { // Create three handles for the threads.
            HANDLE phThreads[3]; // Create the mutex titled "TheLock". hTheLock
            = CreateMutex(NULL, FALSE, _T("TheLock")); // Create the three
            threads in a suspended state: phThreads[HIGH] = CreateThread(NULL,
            0, HighPriority, NULL, CREATE_SUSPENDED, NULL); phThreads[MED] =
            CreateThread(NULL, 0, MedPriority, phThreads[HIGH],
            CREATE_SUSPENDED, NULL); phThreads[LOW] = CreateThread(NULL, 0,
            LowPriority, phThreads[MED], CREATE_SUSPENDED, NULL); // Set the
            priorities. CeSetThreadPriority(phThreads[LOW], LOW_PRIORITY);
            CeSetThreadPriority(phThreads[MED], MEDIUM_PRIORITY);
            CeSetThreadPriority(phThreads[HIGH], HIGH_PRIORITY); // Start with
            the lowest priority thread that is running first.
            ResumeThread(phThreads[LOW]); WaitForSingleObject(phThreads[LOW],
            INFINITE); WaitForSingleObject(phThreads[MED], INFINITE);
            WaitForSingleObject(phThreads[HIGH], INFINITE);
            CloseHandle(hTheLock); return 0; } // This is a low priority thread
            where you want to detect any // missed real-time deadlines. #define
            DEADLINE 5 DWORD WINAPI LowPriority(HANDLE h) { DWORD time1, time2;
            ULONG delta = 0; time1 = 0; time2 = 0;
            WaitForSingleObject(hTheLock, INFINITE); ResumeThread(h); time1 =
            GetTickCount(); CalculatePrimes(1000000); time2 = GetTickCount();
            /* Compare the time that was spent in the CalculatePrimes()
            function. If it took longer than DEADLINE, you want to generate a
            user-defined event so you know that you have missed the real-time
            deadline. */ delta = (time2 – time1); if(delta >= DEADLINE) {
            CeLogData (TRUE, CELID_RAW_LONG, &delta, (WORD) (1 *
            sizeof(LONG)), 0, CELZONE_MISC, 0, FALSE); } // To log raw data,
            call CeLogData or CeLogDataFlagged directly. // For example: // //
            CeLogData(TRUE, CELID_RAW_LONG, &ImyData, // (WORD) (iMyDataLen
            * sizeof(LONG)), 1, CELZONE_MISC); ReleaseMutex(hTheLock);
            printf("Low pri done\n"); return 0; } DWORD WINAPI
            MedPriority(HANDLE h) { CalculatePrimes(10000); ResumeThread(h);
            CalculatePrimes(10000); printf("Med pri done\n"); return 0; } DWORD
            WINAPI HighPriority(LPVOID lp) { WaitForSingleObject(hTheLock,
            INFINITE); CalculatePrimes(10000); ReleaseMutex(hTheLock);
            printf("High pri done\n"); return 0; } void CalculatePrimes(UINT n)
            { UINT i; for(i = 0; i <= n; i++) { IsPrime(n); } } BOOL
            IsPrime(UINT n) { UINT nUpperBound = 0; UINT i; if(n <= 1) {
            return FALSE; } if(n == 2) { return TRUE; } nUpperBound =
            (UINT)sqrt((double)n); for(i = 2; i <= nUpperBound; i++) { if(n
            % i == 0) { return FALSE; } } return TRUE; }

            Stdafx.h

            // stdafx.h : This is the include file for
            standard system include files, // or project-specific include files
            that are used frequently, but // are changed infrequently. #if
            !defined(AFX_STDAFX_H__2FA1E7D2_46C4_11D4_9B70_00105AC728C4__INCLUDED_)
            #define
            AFX_STDAFX_H__2FA1E7D2_46C4_11D4_9B70_00105AC728C4__INCLUDED_ #if
            _MSC_VER > 1000 #pragma once #endif // _MSC_VER > 1000 //
            Exclude rarely-used information from Windows headers. #define
            WIN32_LEAN_AND_MEAN #include <windows.h> #include
            <stdio.h> // Reference additional headers that your program
            requires. //{{AFX_INSERT_LOCATION}} // Microsoft Visual C++ will
            insert additional declarations immediately above the previous line.
            #endif //
            !defined(AFX_STDAFX_H__2FA1E7D2_46C4_11D4_9B70_00105AC728C4__INCLUDED_)