Programming Microsoft Windows Ce Net 3Rd [Electronic resources] نسخه متنی

اینجــــا یک کتابخانه دیجیتالی است

با بیش از 100000 منبع الکترونیکی رایگان به زبان فارسی ، عربی و انگلیسی

Programming Microsoft Windows Ce Net 3Rd [Electronic resources] - نسخه متنی

| نمايش فراداده ، افزودن یک نقد و بررسی
افزودن به کتابخانه شخصی
ارسال به دوستان
جستجو در متن کتاب
بیشتر
تنظیمات قلم

فونت

اندازه قلم

+ - پیش فرض

حالت نمایش

روز نیمروز شب
جستجو در لغت نامه
بیشتر
لیست موضوعات
توضیحات
افزودن یادداشت جدید






Threads


A thread is, fundamentally, Chapter 22 for details.

The stack size of the main thread of a process is set by the linker. (The linker switch for setting the stack size in Microsoft eMbedded C++ is /stack.) Secondary threads are created by default with the same stack size as the primary thread, but the default can be overridden when the thread is created.


The System Scheduler


Windows CE schedules threads in a preemptive manner. Threads run for a quantum, or time slice. After that time, if the thread hasn't already relinquished its time slice and if the thread isn't a run-to-completion thread, it's suspended and another thread is scheduled to run. Windows CE chooses which thread to run based on a priority scheme. Threads of a higher priority are scheduled before threads of lower priority.

The rules for how Windows CE allocates time among the threads are quite different from Windows XP. Windows CE processes don't have a priority class. Under Windows XP, threads derive their priority based on the priority class of their parent processes. A Windows XP process with a higher-priority class has threads that run at a higher priority than threads in a process with a lower-priority class. Threads within a process can refine their priority within a process by setting their relative thread priority.

Because Windows CE has no priority classes, all processes are treated as peers. Individual threads can have different priorities, but the process that the thread runs within doesn't influence those priorities. Also, unlike some of the desktop versions of Windows, the foreground thread in Windows CE doesn't get a boost in priority.

When Windows CE was first developed, the scheduler supported eight priority levels. Starting with Windows CE 3.0, that number was increased to 256 priority levels. However, most applications still use the original (now lowest) eight priority levels. The upper 248 levels are typically used by device drivers or other system-level threads. This doesn't mean that an application can't use the higher levels, but accessing them requires different API calls, and the application must be a "trusted" application. I'll talk more about security and the concept of trusted vs. untrusted applications later in the chapter.

The lowest eight priority levels are listed below:



THREAD_PRIORITY_TIME_CRITICALIndicates 3 points above normal priority



THREAD_PRIORITY_HIGHEST Indicates 2 points above normal priority



THREAD_PRIORITY_ABOVE_NORMALIndicates 1 point above normal priority



THREAD_PRIORITY_NORMAL Indicates normal priority. All threads are created with this priority



THREAD_PRIORITY_BELOW_NORMAL Indicates 1 point below normal priority



THREAD_PRIORITY_LOWEST Indicates 2 points below normal priority



THREAD_PRIORITY_ABOVE_IDLEIndicates 3 points below normal priority



THREAD_PRIORITY_IDLEIndicates 4 points below normal priority



All higher-priority threads run before lower-priority threads. This means that before a thread set to run at a particular priority can be scheduled, all threads that have a higher priority must be blocked. A blocked thread is one that's waiting on some system resource or synchronization object before it can continue. Threads of equal priority are scheduled in a round-robin fashion. Once a thread has voluntarily given up its time slice, is blocked, or has completed its time slice, all other threads of the same priority are allowed to run before the original thread is allowed to continue. If a thread of higher priority is unblocked and a thread of lower priority is currently running, the lower-priority thread is immediately suspended and the higher-priority thread is scheduled. Lower-priority threads can never preempt a higher-priority thread.

An exception to the scheduling rules happens if a low-priority thread owns a resource that a higher-priority thread is waiting on. In this case, the low-priority thread is temporarily given the higher-priority thread's priority to avoid a problem known as priority inversion, so that it can quickly accomplish its task and free the needed resource.

While it might seem that lower-priority threads never get a chance to run in this scheme, it works out that threads are almost always blocked, waiting on something to free up before they can be scheduled. Threads are always created at THREAD_PRIORITY_NORMAL, so, unless they proactively change their priority level, a thread is usually at an equal priority to most of the other threads in the system. Even at the normal priority level, threads are almost always blocked. For example, an application's primary thread is typically blocked waiting on messages. Other threads should be designed to block on one of the many synchronization objects available to a Windows CE application.

Never Do This!


What's not supported by the arrangement I just described, or by any other thread-based scheme, is code like the following:

while (bFlag == FALSE) {
// Do nothing, and spin.
}
// Now do something.

This kind of code isn't just bad manners; because it wastes CPU power, it's a death sentence to a battery-powered Windows CE device. To understand why this is important, I need to digress into a quick lesson on Windows CE power management.

Windows CE is designed so that when all threads are blocked, which happens over 90 percent of the time, it calls down to the OEM Abstraction Layer (the equivalent of the BIOS on an MS-DOS machine) to enter a low-power waiting state. Typically, this low-power state means that the CPU is halted; that is, it simply stops executing instructions. Because the CPU isn't executing any instructions, no power-consuming reads and writes of memory are performed by the CPU. At this point, the only power necessary for the system is to maintain the contents of the RAM and light the display. This low-power mode can reduce power consumption by up to 99 percent of what is required when a thread is running in a well-designed system.

Doing a quick back-of-the-envelope calculation, say a Pocket PC is designed to run for 10 hours on a fully charged battery. Given that the system turns itself off after a few minutes of nonuse, this 10 hours translates into weeks of battery life in the device for the user. (I'm basing this calculation on the assumption that the system indeed spends 90 percent or more of its time in its low-power idle state.) Say a poorly written application thread spins on a variable instead of blocking. While this application is running, the system will never enter its low-power state. So, instead of 600 minutes of battery time (10 hours 60 minutes/hour), the system spends 100 percent of its time at full power, resulting in a battery life of slightly over an hour, which means that the battery would be lucky to last a day's normal use. So as you can see, it's good to have the system in its low-power state.

Fortunately, since Windows applications usually spend their time blocked in a call to GetMessage, the system power management works by default. However, if you plan on using multiple threads in your application, you must use synchronization objects to block threads while they're waiting. First let's look at how to create a thread, and then I'll dive into the synchronization tools available to Windows CE programs.


Creating a Thread


You create a thread under Windows CE by calling the function CreateThread, which is a departure from the desktop versions of Windows in which you're never supposed to call this API directly. The reason for this change is that on the desktop, calling CreateThread doesn't give the C runtime library the chance to create thread-unique data structures. So on the desktop, programmers are instructed to use either of the run-time thread creation functions _beginthread or _beginthreadex. These functions provide some thread-specific initialization and then call CreateThread internally.

In Windows CE, however, the runtime is written to be thread safe and doesn't require explicit thread initialization, so calling CreateThread directly is the norm. The function is prototyped as

HANDLE CreateThread (LPSECURITY_ATTRIBUTES lpThreadAttributes, 
DWORD dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter, DWORD dwCreationFlags,
LPDWORD lpThreadId);

As with CreateProcess, Windows CE doesn't support a number of the parameters in CreateThread, so they are set to NULL or 0 as appropriate. For CreateThread, the lpThreadAttributes parameter isn't supported and must be set to NULL. The dwStackSize parameter is used only if the STACK_SIZE_PARAM_IS_A_RESERVATION flag is set in the dwCreationFlags parameter. The size specified in dwStackSize is the maximum size to which the stack can grow. Windows CE doesn't immediately commit the full amount of RAM to the stack when the thread is created. Instead, memory is committed only as necessary as the stack grows.

The third parameter, lpStartAddress, must point to the start of the thread routine. The lpParameter parameter in CreateThread is an application-defined value that's passed to the thread function as its only parameter. You can set the dwCreationFlags parameter to either 0, STACK_SIZE_PARAM_IS_A_RESERVATION, or CREATE_SUSPENDED. If CREATE_SUSPENDED is passed, the thread is created in a suspended state and must be resumed with a call to ResumeThread. The final parameter is a pointer to a DWORD that receives the newly created thread's ID value.

The thread routine should be prototyped this way:

DWORD WINAPI ThreadFunc (LPVOID lpArg);

The only parameter is the lpParameter value, passed unaltered from the call to CreateThread. The parameter can be an integer or a pointer. Make sure, however, that you don't pass a pointer to a stack-based structure that will disappear when the routine that called CreateThread returns.

If CreateThread is successful, it creates the thread and returns the handle to the newly created thread. As with CreateProcess, the handle returned should be closed when you no longer need the handle. Following is a short code fragment that contains a call to start a thread and the thread routine.

//----------------------------------------------------------------------
//
//
HANDLE hThread1;
DWORD dwThread1ID = 0;
int nParameter = 5;
hThread1 = CreateThread (NULL, 0, Thread2, (PVOID)nParameter, 0,
&dwThread1ID);
CloseHandle (hThread1);
//----------------------------------------------------------------------
// Second thread routine
//
DWORD WINAPI Thread2 (PVOID pArg) {
int nParam = (int) pArg;
//
// Do something here.
// .
// .
// .
return 0x15;
}

In this code, the second thread is started with a call to CreateThread. The nParameter value is passed to the second thread as the single parameter to the thread routine. The second thread executes until it terminates, in this case simply by returning from the routine.

A thread can also terminate itself by calling this function:

VOID ExitThread (DWORD dwExitCode);

The only parameter is the exit code that's set for the thread. That thread exit code can be queried by another thread using this function:

BOOL GetExitCodeThread (HANDLE hThread, LPDWORD lpExitCode);

The function takes the handle to the thread (not the thread ID) and returns the exit code of the thread. If the thread is still running, the exit code is STILL_ACTIVE, a constant defined as 0x0103. The exit code is set by a thread using ExitThread or the value returned by the thread procedure. In the preceding code, the thread sets its exit code to 0x15 when it returns.

All threads within a process are terminated when the process terminates. As I said earlier, a process is terminated when its primary thread terminates.


Setting and Querying Thread Priority


Threads are always created at the priority level THREAD_PRIORITY_NORMAL. The thread priority can be changed either by the thread itself or by another thread using one of two functions. The first is

BOOL SetThreadPriority (HANDLE hThread, int nPriority);

The two parameters are the thread handle and the new priority level. The level passed can be one of the constants described previously, ranging from THREAD_PRIORITY_IDLE up to THREAD_PRIORITY_TIME_CRITICAL. You must be extremely careful when you're changing a thread's priority. Remember that threads of a lower priority almost never preempt threads of higher priority. So a simple bumping up of a thread one notch above normal can harm the responsiveness of the rest of the system unless that thread is carefully written.

The other function that sets a thread's priority is

BOOL CeSetThreadPriority (HANDLE hThread, int nPriority);

The difference between this function and SetThreadPriority is that this function sets the thread's priority to any of the 256 priorities. Instead of using predefined constants, nPriority should be set to a value of 0 to 255, with 0 being highest priority and 255 being the lowest.

A word of caution: SetThreadPriority and CeSetThreadPriority use completely different numbering schemes for the nPriority value. For example, to set a thread's priority to 1 above normal, you could call SetThreadPriority with THREAD_PRIORITY_ABOVE_NORMAL or call CeSetThreadPriority with nPriority set to 250 but the constant THREAD_PRIORITY_ABOVE_NORMAL defined as 2, not 250. The rule is that you should use the constants for SetThreadPriority and the numeric values for CeSetThreadPriority. Another difference posed by CeSetThreadPriority is that it's a protected function. For systems that implement Windows CE's module-based security, only trusted modules can call CeSetThreadPriority. To query the priority level of a thread, call this function:

int GetThreadPriority (HANDLE hThread);

This function returns the priority level of the thread. You shouldn't use the hard-coded priority levels. Instead, use constants, such as THREAD_PRIORITY_NORMAL, defined by the system. This ensures that you're using the same numbering scheme that SetThreadPriority uses. For threads that have a priority greater than THREAD_PRIORITY_TIMECRITICAL, this function returns the value THREAD_PRIORITY_TIMECRITICAL.

To query the priority of a thread that might have a higher priority than THREAD_PRIORITY_TIMECRITICAL, call the function

int CeGetThreadPriority (HANDLE hThread);

The value returned by CeGetThreadPriority will be 0 to 255, with 0 being the highest priority possible. Here again, Windows CE uses different numbering schemes for the priority query functions than it does for the priority set functions. For example, for a thread running at normal priority, GetThreadPriority would return THREAD_PRIORITY_NORMAL, which is defined as the value 3. CeGetThreadPriority would return the value 251.


Setting a Thread's Time Quantum


Threads can be individually set with their own time quantum. The time quantum is the maximum amount of time a thread runs before it's preempted by the operating system. By default, the time quantum is set to 100 milliseconds, although for embedded systems, the OEM can change this.[1] For example, some Pocket PC devices use a default quantum of 75 milliseconds, while others use the standard 100-millisecond quantum.

To set the time quantum of a thread, call

int CeSetThreadQuantum (HANDLE hThread, DWORD dwTime);

The first parameter is the handle to the thread. The second parameter is the time, in milliseconds, of the desired quantum. If you set the time quantum to 0, the thread is turned into a "run-to-completion thread." These threads aren't preempted by threads of their own priority. Obviously, threads of higher priorities preempt these threads. CeSetThreadQuantum is a protected function and so can't be called by "untrusted" modules.

You can query a thread's time quantum with the function

int CeGetThreadQuantum (HANDLE hThread);

The first parameter is the handle to the thread. The function returns the current quantum of the thread.


Suspending and Resuming a Thread


You can suspend a thread at any time by calling this function:

DWORD SuspendThread (HANDLE hThread);

The only parameter is the handle to the thread to suspend. The value returned is the suspend count for the thread. Windows maintains a suspend count for each thread. Any thread with a suspend count greater than 0 is suspended. Since SuspendThread increments the suspend count, multiple calls to SuspendThread must be matched with an equal number of calls to ResumeThread before a thread is actually scheduled to run. ResumeCount is prototyped as

DWORD ResumeThread (HANDLE hThread);

Here again, the parameter is the handle to the thread and the return value is the previous suspend count. So if ResumeThread returns 1, the thread is no longer suspended.

At times, a thread simply wants to kill some time. Since I've already explained why simply spinning in a while loop is a very bad thing to do, you need another way to kill time. The best way to do this is to use this function:

void Sleep (DWORD dwMilliseconds);

Sleep suspends the thread for at least the number of milliseconds specified in the dwMilliseconds parameter. Because the scheduler timer in systems based on Windows CE 3.0 and later has a granularity of 1 millisecond, calls to Sleep with very small values are accurate to 1 millisecond. On systems based on earlier versions of Windows CE, the accuracy of Sleep depends on the period of the scheduler timer, which was typically 25 milliseconds. This strategy is entirely valid, and sometimes it's equally valid to pass a 0 to Sleep. When a thread passes a 0 to Sleep, it gives up its time slice but is rescheduled immediately according to the scheduling rules I described previously.

[1] In early versions of Windows CE, a thread’s time quantum was fixed. Typically, the time quantum was set to 25 milliseconds, although this was changeable by the OEM.

/ 169