Using the Kernel Tracker More Effectively

Windows CE 3.0

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 CeLogSetZones function. 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 Thread or Previous Running Thread from the View menu 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 Event and Find Previous Event are similar to the Next Process/Thread button. The search starts from the current position of the cursor and moves forward when you click Find Next Event and backward when you click Find Previous Event. You may use the shortcut keys CTRL+RIGHT ARROW for Find Next Event and 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 Priority event 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 Filter dialog box, click the Event Filter button on the toolbar or choose Event Filter from the View menu. The Event Filter dialog box shows four categories of events, each on a separate tab. You may select specific events to filter or choose Select All or Select None to 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 Section and 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 Event dialog box by choosing Find Event from the Edit menu. The Find Event dialog 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 Threads process 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 Threads process 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 Event dialog 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 Heap events are associated with an appropriate number of Free Heap events. Do the same for Allocate Virtual Memory and Free Virtual Memory events.

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 Mutex event 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 Properties dialog box by double-clicking the icon corresponding to the event that occurs when the thread becomes deadlocked.

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

    Enter Critical Section: 0:00:38.024452

    hCS = 0x81E8A508

    hOwnerThread = 0xA1E31C2E

  2. To copy the handle from the Event Properties dialog 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 Edit menu, choose Find Event.

    The Find Event dialog 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 Properties dialog box into the ID text box. Choose the Previous button 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 Philosophers contains 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 1 from 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 2 from the menu.

    The length of time between the marked events is automatically computed and displayed as Marker Time Delta in 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 1 or Set Time Marker 2 from the menu.

To remove a marker

  • Right-click the ruler, and then choose Delete Time Marker 1 or Delete Time Marker 2 from 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 Event dialog 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 LowPriority meets 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 CELogData function 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 LowPriority function after wrapping the CalculatePrimes function. The deadline variable is user defined since the real-time deadline varies from system to system and processor to processor. You may wish to set the If block 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 File menu, choose New.
  2. Select WCE Application on the Projects tab.
  3. Type a name for the application in the Project name box. Choose OK.
  4. Choose An empty project. Choose Finish.
  5. Choose OK.
  6. From the View menu, choose Workspace, and then choose Project to switch to Project View.
  7. From the File menu, choose New.
  8. Select C++ Source File on the Files tab.
  9. Type a name for the source file in the File name box. Select the Add to project check box, and then choose OK.
  10. Paste the source code from the sample into the empty window.
  11. From the File menu, choose New.
  12. Select C/C++ Header File on the Files tab.
  13. Type a name for the header file in the File name box. Select the Add to project check box, and then choose OK.
  14. Paste the header code from the sample into the empty window.
  15. From the File menu, 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_)
Show: