|< Windows Share Programming 2 | Main | Windows Process & Threads: C Run-Time 2 >| Site Index | Download |
MODULE R
PROCESSES AND THREADS: C RUN-TIME
Part 1: Story And Program Examples
My Training Period: hours
Note:
Program examples if any, compiled using Visual C++ .Net (Visual studio .Net 2003). It is low-level programming and the .Net used is Unmanaged (/clr is not set: Project menu → your_project_name Properties… sub menu → Configuration Properties folder → General subfolder → Used Managed Extension setting set to No). This also applied to other program examples in other Modules of tenouk.com Tutorial that mentioned “compiled using Visual C++ .Net”. Other settings are default. Machine’s OS is standalone Win Xp SP2. Program examples have been tested for Non Destructive Test :o). All information recomposed for Win 2000 (NT5.0) above.
WARNING
Wrongly modified and run the program examples presented here might collapse your Windows machine or disturb its integrity. So for your Windows machine safety, make sure you fulfill the following conditions:
Abilities
▪ Able to understand the basic of Windows process and thread concepts.
▪ Able to appreciate the using of C, C++ and MFC to implement process and thread.
▪ Able to understand and manipulate process and thread functions mainly the exec() and spawn() family from C Run-Time Library.
▪ Able to understand the basic of multithreading.
▪ Able to understand, execute, modify and develop a process and thread programs.
|
|
C Run-Time Threads and Multithread Applications: Story & Program Examples
Introduction
In this Module we will try to learn about process and thread in Windows based on using Microsoft C Run-Time library. Later on, more understandable about process and thread using Win32 APIs will be discussed thoroughly in another Module. Just grasp some idea here or you can skip this Module and directly jump to the other Module of process and thread using win32 APIs. Keep in mind that most of the functions/routines used in Microsoft C are similar to the Standard C except the function names prefixed by underscore ( _ ). A thread is basically a path of execution through a program. It is also the smallest unit of execution that Win32 schedules. Basically a thread consists of a stack, the state of the CPU registers, and an entry in the execution list of the system scheduler. Each thread shares all of the process's resources. A process consists of one or more threads and the code, data, and other resources of a program in memory. Typical program resources are open files, semaphores, and dynamically allocated memory. A program executes when the system scheduler gives one of its threads execution control. The scheduler determines which threads should run and when they should run. Threads of lower priority may have to wait while higher priority threads complete their tasks. On multiprocessor machines, the scheduler can move individual threads to different processors to "balance" the CPU load. Each thread in a process operates independently. Unless you make them visible to each other, the threads execute individually and are unaware of the other threads in a process. Threads sharing common resources, however, must coordinate their work by using semaphores or another method of interprocess communication.
Multithreading with C and Win32
Microsoft Visual C++ provides support for creating multithread applications with 32-bit versions of Microsoft Windows: Windows XP, Windows 2000, Windows NT, Windows Me, and Windows 98. You should consider using more than one thread if your application needs to manage multiple activities, such as simultaneous keyboard and mouse input. One thread can process keyboard input while a second thread filters mouse activities. A third thread can update the display screen based on data from the mouse and keyboard threads. At the same time, other threads can access disk files or get data from a communications port. With Visual C++, there are two ways to program with multiple threads:
We will concentrate on using Microsoft C in this Module and after that Win32 APIs. MFC will not be discussed because there is a lot of information about it out there furthermore Tenouk doesn’t like GUI :o)
C Run-Time Libraries Compatibility
The Microsoft C run-time library supports American National Standards Institute (ANSI) C and UNIX C. In this documentation, references to UNIX include XENIX, other UNIX-like systems, and the POSIX subsystem in Windows 98, Windows ME, Windows NT, Windows 2000, and Windows XP. The description of each run-time library routine in MSDN documentation includes a compatibility section for the following targets:
All C run-time library routines that included in Visual C++/. Net also compatible with the Win32 API. In this Module and that follows we will concentrate using C run-time library and Win32 API will be discussed in next Module.
|
||||||||||||||||
Writing a Multithreaded Win32 Program
When you write a program with multiple threads, you must coordinate their behavior and use of the program's resources. You must also make sure that each thread receives its own stack.
Each thread has its own stack and its own copy of the CPU registers. Other resources, such as files, static data, and heap memory, are shared by all threads in the process. Threads using these common resources must be synchronized. Win32 provides several ways to synchronize resources, including semaphores, critical sections, events, and mutexes. You will learn the detail of these things in next Module.
When multiple threads are accessing static data, your program must provide for possible resource conflicts. Consider a program where one thread updates a static data structure containing (x, y) coordinates for items to be displayed by another thread. If the update thread alters the x coordinate and is preempted before it can change the y coordinate, the display thread may be scheduled before the y coordinate is updated. The item would be displayed at the wrong location. You can avoid this problem by using semaphores to control access to the structure.
A mutex (short for mutual exclusion) is a way of communicating among threads or processes that are executing asynchronously of one another. This communication is usually used to coordinate the activities of multiple threads or processes, typically by controlling access to a shared resource by "locking" and "unlocking" the resource. For example, to solve the (x, y) coordinate update problem, the update thread would set a mutex indicating that the data structure is in use before performing the update. It would clear the mutex after both coordinates had been processed. The display thread must wait for the mutex to be clear before updating the display. This process of waiting for a mutex is often called "blocking" on a mutex because the process is blocked and cannot continue until the mutex clears.
All of an application's default stack space is allocated to the first thread of execution, which is known as thread 1. As a result, you must specify how much memory to allocate for a separate stack for each additional thread your program needs. The operating system will allocate additional stack space for the thread, if necessary, but you must specify a default value.
Threads that make calls to the C run-time library or to the Win32 API must allow sufficient stack space for the library and API functions they call. The C printf() function for example, requires more than 500 bytes of stack space, and you should have 2K of stack space available when calling Win32 API routines. Because each thread has its own stack, you can avoid potential collisions over data items by using as little static data as possible. Design your program to use automatic stack variables for all data that can be private to a thread.
We try to get some brief idea about multithreading in MFC in this section and the detail will not be discussed here. The Microsoft Foundation Class Library (MFC) provides support for multithreaded applications. A "process" is an executing instance of an application. For example, when you double-click the Notepad icon, you start a process that runs Notepad. Again, a "thread" is a path of execution within a process. When you start Notepad, the operating system creates a process and begins executing the primary thread of that process. When this thread terminates, so does the process. This primary thread is supplied to the operating system by the startup code in the form of a function address. Usually, it is the address of the main() or WinMain() function that is supplied. You can create additional threads in your application if you wish. You may want to do this to handle background or maintenance tasks when you don't want the user to wait for them to complete. All threads in MFC applications are represented by CWinThread() objects. In most situations, you don't even have to explicitly create these objects; instead call the framework helper function AfxBeginThread(), which creates the CWinThread() object for you. MFC distinguishes two types of threads:
User-interface threads are commonly used to handle user input and respond to events and messages generated by the user. Worker threads are commonly used to complete tasks, such as recalculation, that do not require user input. The Win32 API does not distinguish between types of threads; it just needs to know the thread's starting address so it can begin to execute the thread. MFC handles user-interface threads specially by supplying a message pump for events in the user interface. CWinApp is an example of a user-interface thread object, as it derives from CWinThread and handles events and messages generated by the user.
Special attention should be given to situations where more than one thread may require access to the same object. Writing and debugging multithreaded programming is inherently a complicated and tricky undertaking, as you must ensure that objects are not accessed by more than one thread at a time.
As an example, if one thread is suspended by the Win32 scheduler while executing the printf() function, one of the program's other threads might start executing. If the second thread also calls printf(), data might be corrupted. To avoid this situation, access to static data used by the function must be restricted to one thread at a time. You do not need to serialize access to stack-based (automatic) variables because each thread has a different stack. Therefore, a function that uses only automatic (stack) variables is reentrant. The standard C run-time libraries, such as LIBC, have a limited number of reentrant functions. A multithread program needing to use C run-time library functions that are normally not reentrant should be built with the multithread library LIBCMT.LIB.
The support library LIBCMT.LIB is a reentrant library for creating multithread programs. The MSVCRT.LIB library, which calls code in the shared MSVCRT70.DLL, is also reentrant. When your application calls functions in these libraries, the following rules may apply:
▪ All library calls must use the C (__cdecl) calling convention; programs compiled using other calling conventions (such as __fastcall or __stdcall) must use the standard include files for the run-time library functions they call.
▪ Variables passed to library functions must be passed by value or cast to a pointer.
Programs built with LIBCMT.LIB do not share C run-time library code or data with any dynamic-link libraries they call.
If you build a multithread program without using LIBCMT.LIB, you must do the following:
▪ Use the standard C libraries and limit library calls to the set of reentrant functions.
▪ Use the Win32 API thread management functions, such as CreateThread().
▪ Provide your own synchronization for functions that are not reentrant by using Win32 services such as semaphores and the EnterCriticalSection() and LeaveCriticalSection() functions.
Warning: The multithread library LIBCMT.LIB includes the _beginthread() and _endthread() functions. The _beginthread() function performs initialization without which many C run-time functions will fail. You must use _beginthread() instead of CreateThread() in C programs built with LIBCMT.LIB if you intend to call C run-time functions.
To build a multithread application that uses the C run-time libraries, you must tell the compiler to use a special version of the libraries (LIBCMT.LIB). To select these libraries:
|
First open the project's Property Pages dialog box (View menu) and click the C/C++ folder. Select the Code Generation page. From the Runtime Library drop-down box, select Multi-threaded. Click OK to return to editing.
|
From the command line, the Multithread Library compiler option (/MT) is the best way to build a multithread program with LIBCMT.LIB. This option, which is automatically set when you specify a multithreaded application when creating a new project, embeds the LIBCMT library name in the object file.
Use the process-control routines to start, stop, and manage processes from within a program. Use the environment-control routines to get and change information about the operating-system environment.
C Run-time: Process and Environment Control Functions
The following Table lists the process and environment functions that can be used. Take note that most of the functions also available in Standard C with the same function name except prefixed by the underscore ( _ ).
|
Routine |
Use |
|
abort() |
Abort process without flushing buffers or calling functions registered by atexit() and _onexit(). |
|
assert() |
Test for logic error |
|
_ASSERT, _ASSERTE macros |
Similar to assert(), but only available in the debug versions of the run-time libraries. |
|
atexit() |
Schedule routines for execution at program termination. |
|
_beginthread(), _beginthreadex() |
Create a new thread on a Windows operating system process. |
|
_cexit() |
Perform exit termination procedures (such as flushing buffers), then return control to calling program without terminating process. |
|
_c_exit() |
Perform _exit() termination procedures, then return control to calling program without terminating process. |
|
_cwait() |
Wait until another process terminates. |
|
_endthread(), _endthreadex() |
Terminate a Windows operating system thread. |
|
_execl(), _wexecl() |
Execute new process with argument list. |
|
_execle(), _wexecle() |
Execute new process with argument list and given environment. |
|
_execlp(), _wexeclp() |
Execute new process using PATH variable and argument list. |
|
_execlpe(), _wexeclpe() |
Execute new process using PATH variable, given environment, and argument list. |
|
_execv(), _wexecv() |
Execute new process with argument array. |
|
_execve(), _wexecve() |
Execute new process with argument array and given environment. |
|
_execvp(), _wexecvp() |
Execute new process using PATH variable and argument array. |
|
_execvpe(), _wexecvpe() |
Execute new process using PATH variable, given environment, and argument array. |
|
exit() |
Call functions registered by atexit() and _onexit(), flush all buffers, close all open files, and terminate process. |
|
_exit() |
Terminate process immediately without calling atexit() or _onexit() or flushing buffers. |
|
getenv(), _wgetenv() |
Get value of environment variable. |
|
_getpid() |
Get process ID number. |
|
longjmp() |
Restore saved stack environment; use it to execute a non-local goto. |
|
_onexit() |
Schedule routines for execution at program termination; use for compatibility with Microsoft C/C++ version 7.0 and earlier. |
|
_pclose() |
Wait for new command processor and close stream on associated pipe. |
|
perror(), _wperror() |
Print error message. |
|
_pipe() |
Create pipe for reading and writing. |
|
_popen(), _wpopen() |
Create pipe and execute command. |
|
_putenv(), _wputenv() |
Add or change value of environment variable. |
|
raise() |
Send signal to calling process. |
|
setjmp() |
Save stack environment; use to execute non-local goto. |
|
signal() |
Handle interrupt signal. |
|
_spawnl(), _wspawnl() |
Create and execute new process with specified argument list. |
|
_spawnle(), _wspawnle() |
Create and execute new process with specified argument list and environment. |
|
_spawnlp(), _wspawnlp() |
Create and execute new process using PATH variable and specified argument list. |
|
_spawnlpe(), _wspawnlpe() |
Create and execute new process using PATH variable, specified environment, and argument list. |
|
_spawnv(), _wspawnv() |
Create and execute new process with specified argument array. |
|
_spawnve(), _wspawnve() |
Create and execute new process with specified environment and argument array. |
|
_spawnvp(), _wspawnvp() |
Create and execute new process using PATH variable and specified argument array. |
|
_spawnvpe(), _wspawnvpe() |
Create and execute new process using PATH variable, specified environment, and argument array. |
|
system(), _wsystem() |
Execute operating-system command. |
|
Table 2 |
|
In Windows 98/Me and Windows NT/2000/XP, the spawned process is equivalent to the spawning process. Any process can use _cwait() to wait for any other process for which the process ID is known. The difference between the _exec() and _spawn() families is that a _spawn() function can return control from the new process to the calling process. In a _spawn() function, both the calling process and the new process are present in memory unless _P_OVERLAY is specified. In an _exec() function, the new process overlays the calling process, so control cannot return to the calling process unless an error occurs in the attempt to start execution of the new process. The differences among the functions in the _exec() family, as well as among those in the _spawn() family, involve the method of locating the file to be executed as the new process, the form in which arguments are passed to the new process, and the method of setting the environment, as shown in the following table. Use a function that passes an argument list when the number of arguments is constant or is known at compile time. Use a function that passes a pointer to an array containing the arguments when the number of arguments is to be determined at run time. The information in the following table also applies to the wide-character counterparts of the _spawn() and _exec() functions.
_exec(), _wexec() Family
Each function in this family loads and executes a new process. The _w is for wide-character version.
|
_exec(), _wexec() Family |
|
|
_execl(), _wexecl() |
_execv(), _wexecv() |
|
_execle(), _wexecle() |
_execve(), _wexecve() |
|
_execlp(), _wexeclp() |
_execvp(), _wexecvp() |
|
_execlpe(), _wexeclpe() |
_execvpe(), _wexecvpe() |
|
Table 3 |
|
The letter(s) at the end of the function name determine the variation.
|
_exec function suffix |
Description |
|
e |
envp, array of pointers to environment settings, is passed to new process. |
|
l |
Command-line arguments are passed individually to _exec() function. Typically used when number of parameters to new process is known in advance. |
|
p |
PATH environment variable is used to find file to execute. |
|
v |
argv, array of pointers to command-line arguments, is passed to _exec(). Typically used when number of parameters to new process is variable. |
|
Table 4 |
|
Each of the _exec() functions loads and executes a new process. All _exec() functions use the same operating-system function. The _exec() functions automatically handle multibyte-character string arguments as appropriate, recognizing multibyte-character sequences according to the multibyte code page currently in use. The _wexec() functions are wide-character versions of the _exec() functions. The _wexec() functions behave identically to their _exec() family counterparts except that they do not handle multibyte-character strings. The following Table lists the needed information in order to use the _exec() functions family.
|
Information |
Description |
|
The function |
_exec() family. |
|
The use |
Load and execute new child processes. |
|
The prototype |
intptr_t _execl(const char *cmdname, const char *arg0, ... const char *argn, NULL); intptr_t _wexecl(const wchar_t *cmdname, const wchar_t *arg0, ... const wchar_t *argn, NULL);
intptr_t _execle(const char *cmdname, const char *arg0, ... const char *argn, NULL, const char *const *envp); intptr_t _wexecle(const wchar_t *cmdname, const wchar_t *arg0, ... const wchar_t *argn, NULL, const char *const *envp);
intptr_t _execlp(const char *cmdname, const char *arg0, ... const char *argn, NULL); intptr_t _wexeclp(const wchar_t *cmdname, const wchar_t *arg0, ... const wchar_t *argn, NULL);
intptr_t _execlpe(const char *cmdname, const char *arg0, ... const char *argn, NULL, const char *const *envp); intptr_t _wexeclpe(const wchar_t *cmdname, const wchar_t *arg0, ... const wchar_t *argn, NULL, const wchar_t *const *envp);
intptr_t _execv(const char *cmdname, const char *const *argv); intptr_t _wexecv(const wchar_t *cmdname, const wchar_t *const *argv);
intptr_t _execve(const char *cmdname, const char *const *argv, const char *const *envp); intptr_t _wexecve(const wchar_t *cmdname, const wchar_t *const *argv, const wchar_t *const *envp);
intptr_t _execvp(const char *cmdname, const char *const *argv); intptr_t _wexecvp(const wchar_t *cmdname, const wchar_t *const *argv);
intptr_t _execvpe(const char *cmdname, const char *const *argv, const char *const *envp); intptr_t _wexecvpe(const wchar_t *cmdname, const wchar_t *const *argv, const wchar_t *const *envp); |
|
Example |
|
|
The parameters |
cmdname - Path of file to be executed. arg0, ... argn - List of pointers to parameters. envp - Array of pointers to environment settings. argv - Array of pointers to parameters. |
|
The return value |
See below. |
|
The header file |
_execl(), _execle(), _execlp(), _execlpe(), _execv(), _execve(), _execvp(), _execvpe() - <process.h>. _wexecl(), _wexecle(), _wexeclp(), _wexeclpe(), _wexecv(), _wexecve(), _wexecvp(), _wexecvpe() - <process.h> or <wchar.h> |
|
Remarks |
Each of these functions loads and executes a new process, passing each command-line argument as a separate parameter. |
|
Table 5: _exec() function family information. |
|
Return Value
If successful, these functions do not return to the calling process. A return value of –1 indicates an error, in which case the errno global variable is set.
|
errno value |
Description |
|
E2BIG |
The space required for the arguments and environment settings exceeds 32 K. |
|
EACCES |
The specified file has a locking or sharing violation. |
|
EMFILE |
Too many files open (the specified file must be opened to determine whether it is executable). |
|
ENOENT |
File or path not found. |
|
ENOEXEC |
The specified file is not executable or has an invalid executable-file format. |
|
ENOMEM |
Not enough memory is available to execute the new process; or the available memory has been corrupted; or an invalid block exists, indicating that the calling process was not allocated properly. |
|
Table 6 |
|
Generic-Text Function Mappings
The following Table lists the generic-tex