Chapter 6 1. Composition of threads:
● One is the kernel object of the thread, which is used by the operating system to manage the thread. The system also uses the kernel object to store the thread statistics.
● A thread stack is used to maintain all function parameters and local variables required for thread execution.
2. Thread entry functions:
DWORD winapi threadfunc (pvoid pvparam)
When using threads, pay attention
■ By default, the main thread's entry function must be named main, wmain, winmain, or wwinmain (unless we use/entry: the linker option to specify another function as its entry function ). the difference is that the thread function can be named at will. in fact, if an application contains multiple thread functions, you must specify different names for them. Otherwise, the compiler/Linker will assume that you have created multiple implementations of a function.
■ Because the main thread's entry point function has string parameters, the ANSI/Unicode version is provided for us to choose from: Main/wmain, winmain, and wwinmain. On the contrary, we do not have to worry about ANSI/Unicode.
■ A thread function must return a value that will become the exit code of the thread. This is similar to the C ++/C Runtime library policy: the exit code of the main thread becomes the exit code of the process.
■ Use function parameters and local variables as much as possible for thread functions (actually including all functions. when static variables and global variables are used, multiple threads can access these variables, which may destroy the contents saved in the variables. in fact, from the perspective of resource utilization, static variables and global variables occupy the memory when the entire object exists.
Call createthread to create a thread kernel object. this thread kernel object is not a thread itself, but a small data structure that the operating system uses to manage threads. the kernel object can be considered as a small data structure composed of thread statistics. this is the same as the relationship between processes and process kernel objects. in addition, we try to use _ beginthreadex to replace createthread when writing code (or use a similar function from the compiler provider to replace it ).
3. Thread-created functions:
Handle createthread (
Psecurity_attributes PSA, // Security Attribute
DWORD chstacksize, // stack size
Pthread_start_routine pfnstartaddr, // The starting address of the thread to execute the Function
Pvoid pvparam, // The parameter passed to the start address
DWORD dwcreateflags, // create a flag
Pdword pdwthreadid) // thread ID
Parameters:
◆ PSA: If you want to use the default security attribute, null is passed. if you want all sub-processes to inherit the handle of this thread object, you must specify a security_attribute structure and initialize the binherithandle member of this structure to true.
◆ Cbstacksize: Specifies the address space that a thread can use for its thread stack. each thread has its own stack. for this parameter, CreateProcess uses a value stored in the executable file. it can be set in the compiler. if the input value is not 0, the function will reserve space for the thread stack and allocate all the storage space required for it. If the input value is 0, the function will reserve a region, the storage space is allocated based on the storage volume specified by the/stack linker switch.
◆ Pfnstartaddr and pvparam: Address and parameter of the thread function to be executed by the new thread. pvparam can be a numeric value or a pointer pointing to a data structure (including additional information such as this.
◆ Dwcreateflags: If it is 0, scheduling can be performed immediately after creation. if it is creatre_suincluded, the system will create and initialize the thread, but it will pause the running of the thread, so that it will not be able to schedule.
◆ Pdwthreadid: used to store the ID allocated by the system to the new thread.
4. Terminate the thread
■ The thread can terminate the operation in the following four ways:
● Thread function return (this is strongly recommended)
● The thread "kills" itself by calling the exitthread function.
● The terminatethread function is called by a thread in the same process or another process.
● Processes containing threads terminate the operation
■ Return of the thread function: This ensures that all the correct application cleanup operations are executed.
● All the C ++ objects created in the thread functions are correctly destroyed through their destructor.
● The operating system correctly releases the memory used by the thread Stack
● The operating system sets the exit code of the thread as the return value of the thread function.
● Kernel Object count of the system decreasing thread
■ Exitthread function:
Void exitthread (DWORD dwexitcode );
This function terminates the running of the thread and causes the operating system to clear all system resources of the thread. However, your C/C ++ resources (objects, etc.) will not be destroyed.
You can also use the dwexitcode parameter of exitthread to tell the system to set the exit code of the thread. The function itself does not return a value (because the thread has been terminated ).
Note: end thread functions must be used together: cratethread corresponds to exitthread; _ beginthreadex corresponds to _ endthread .)
■ Terminatethread function:
Bool terminatethread (
Handle hthread,
DWORD dwexitcode)
Unlike exitthread, terminatethread can kill any thread.
Note: The terminatethread function is asynchronous. That is, the call may have a "delay". To confirm that the thread has been terminated, you can call waitforsingleobject or a similar function and pass the thread handle to it.
■ Exitthread and terminatethread:
If a thread is terminated by returning or calling the exitthread function, the stack of the thread will also be destroyed (but the object will not ). however, if terminatethread is called, the system will not destroy the stack of the thread unless the process that owns the thread stops running. in addition, if exitthread is used, the DLL will be notified when the thread is terminated, but terminatethread will not.
■ Exitprocess and terminateprocess can also be used to terminate the running of threads. the difference is that these functions will terminate all threads in the terminated processes. (We call terminatethread for every remaining thread ). this means that the destructor of the C ++ object will not be called. data will not be written back to the disk.
■ When the thread stops running, the following events occur:
● All user object handles owned by the thread will be released. in Windows, most objects are owned by processes that contain "Threads for creating these objects. however, a thread has two user objects: window and hook. when a process stops running, the system will automatically destroy any windows created or installed by threads. and Uninstall any hooks created or installed by the thread. other objects are destroyed only when the thread-owning process is terminated.
● The exit code of the thread changes from still_active to the Code passed to exitthread and terminatethread.
● The state of the thread kernel object changes to the trigger state.
● If the last active thread in the process is used, the system determines that the process has terminated.
● The usage count of the thread Kernel Object decreases by 1
When a thread stops running, its associated thread object will not be automatically released unless all the unended references to this object are closed.
■ You can use getexitcodethread to check whether the thread indicated by the hthread has been terminated. If yes, you can determine the exit code:
Bool getexitcodethread (handle hthread,
Pdword pdwexitcode) // set the thread to still_active if the thread has not been terminated.
If the call is successful, true is returned.
5. Thread creation and initialization diagram:
Thread Insider: A Call to the createthread function causes the system to create a thread kernel object. the initial count of this object is 2. (unless the thread is terminated and the handle returned from createthread is closed, the thread kernel object will not be destroyed.) other attributes of the thread kernel object are also initialized: The pause count is 1, the exit code is set to still_active (0x103), and the object is set to not triggered.
Once a kernel object is created, the system allocates memory for the thread stack to use. then the system writes the two values to the top of the new thread stack (the thread stack is always built from the high memory address to the memory address ). the first value of the writing thread stack is the pvparam parameter passed to the createthread function, followed by the pfnstartaddr value passed to the createthread function below it.
Each thread has its own set of CPU registers (that is, the thread context ). it reflects the status of the CPU register of the thread when the thread was last executed. the CPU registers of the thread are all stored in a context structure (it is stored in the kernel object of the thread itself ).
The instruction pointer register (IP) and the stack pointer register (SP) are the two most important registers in the thread context. the memory of the two addresses is located in the address space of the thread's process. when the kernel object of the thread is initialized, the stack pointer register (SP) of the context structure is set to the address of pfnstartaddr in the thread battle. the instruction pointer register is set to the address of the rtluserthreadstart function (this function is NTDLL..
The basic operations are as follows:
Void rtluserthreadstart (pthread_start_routine pfnstartaddr, pvoid pvparam)
// These two parameters are explicitly written to the thread stack by the system. (Some parameters are passed using the CPU register)
{
_ Try {
Exitthread (pfnstartaddr) (pvparam ));
}
_ Random t (unhandleexceptionfilter (getexceptioninformation ())){
Exitprocess (getexceptioncode ());
}
}
After the thread is fully initialized, the system will check whether the create_suincluded mark has been passed to the createthread function. if this flag is not passed, the system will decrease the number of pending threads to 0; then the thread can be scheduled to a processor for execution. then, the system loads the value stored in the thread context in the actual CPU register.
Because the instruction pointer of the new thread is set to rtluserthreadstart, this function is actually where the thread starts to execute.
The new thread executes the rtluserthreadstart function to perform the following tasks:
● A structured exception handling (seh) frame will be set around the thread function, so that if the thread encounters an exception, the system will get the default processing.
● The system calls the thread function and passes the pvparam parameter passed to the createthread function to it.
● When a thread function returns, call exitthread to pass the return value of your thread function to him. The thread Kernel Object count is decreased and the thread stops.
● If the thread generates an unhandled exception, the seh frame of the function will handle this exception.
Note: Within rtluserthreadstart, the thread will call exitthread or exitprocess. This means that the thread will never exit this function; it will always die inside it.
When the main thread of a process is initialized, its command pointer will be set to the same undocumented function rtluserthreadstart. when the function starts to run, it will call the startup code of the C/C ++ Runtime Library. The latter will initialize and then call your _ tmain or _ twinmain function, when your entry point function returns, the startup code of C ++/C will call exitprocess. therefore, he will not return to the rtluserthreadstart function.
6. to ensure the normal operation of C/C ++ multi-threaded applications, you must create a data structure and associate it with each thread that uses the C/C ++ Runtime library function. then, when calling the C/C ++ Runtime library function, those functions must know how to find the function blocks of the main thread to avoid affecting other threads.
To ensure thread security, do not call createthread. Instead, call the C/C ++ runtime function _ beginthreadex:
Unsigned long _ beginthreadex (
Void * security,
Unsigned stack_size,
Unsigned (* start_address) (void *),
Void Arglist,
Unsigned initflag,
Unsigned * threadaddr );
Force conversion is required during use.
_ Beginthreadex:
1. _ beginthreadex (...)
2 .{
3. _ ptiddata PTD;
4. if (PTD = (_ ptiddata) _ calloc_crt (1, sizeof (struct _ tiddata) = NULL) // allocate memory (allocated from the heap of the C/C ++ Runtime Library
5. // This structure contains pvparam and pfnstartaddr.
6 .{
7. // other operations
8 .}
9.
10. // initialize the object
11. PTD-> _ initaddr = (void *) pfnstartaddr;
12. PTD-> _ initarg = pvparam;
13. PTD-> _ thandle = (uintptr_t) (-1 );
14.
15. uintptr_t thdl = (uintptr_t) createthread (lpsecurity_attributes) PSA,
16. cbstacksize,
17. _ threadstartex,
18. (pvoid) PTD, dwcrateflags,
19. pdwthreadid );
20. // This actually involves how to associate the PTD structure with the thread.
21 .}
22.
23. _ threadstartex (void * PTD)
24 .{
25. tlssetvalue (_ tlsindex, PTD); // this function can be understood as setting variables into processes
26. // if you want to obtain it, you can use tlsgetvalue ()
27. _ callthreadstartex ();
28 .}
29.
30. _ callthreadstartex ()
31 .{
32. PTD = getptd (); // get PTD
33.
34. _ Try {
35. _ endthreadex (unsigned (winapi *) (void *) (ptiddata) PTD)-> _ initaddr) (_ ptiddata) PTD) -> _ initarg ));
36 .}
37. _ partition T (_ xceptfileter (getexceptioncode (), getexceptioninformation ()))
38 .{
39. _ exit (getexceptioncode ());
40 .}
41 .}
42.
43. _ endthread (unsigned retcode)
44 .{
45. PTD = _ getptd_noexit ();
46.
47. If (PTD! = NULL)
48 .{
49. _ freeptd (); // release the memory
50 .}
51.
52. exitprocess (retcode );
53 .}
As we expected, the startup code of the C/C ++ Runtime Library allocates and initializes a data block for the main thread of the application. in this way, the main thread can safely call any C/C ++ Runtime Library Function (Similarly, data blocks will be released when returned ). in addition, the startup Code sets the correct structured exception handling code so that the main thread can successfully call the signal function of the C/C ++ Runtime Library.
Conclusion: Use _ beginthreadex instead of createthread to create a thread. It should not be two functions: _ beginthread and _ endthread (it will close the handle with respect to _ endthreadex.
7. Several common functions:
Handle getcurrentprocess ();
Handle getcurrentthread ();
These two functions return the process Kernel Object of the main thread and the pseudo handle (pseudo handle) of the thread kernel object, that is, the handle pointing to the current thread (who calls the function, then points to WHO ).
It does not create a new handle in the handle table of the main process. it does not affect the use count of process kernel objects or thread kernel objects. (If closehanle is called and a pseudo handle is passed in, this operation will be ignored and false will be returned. At the same time, getlasterror will return error_invalid_handle ).
Two corresponding functions:
DWORD getcurrentprocessid ()
DWORD getcurrentthreadid ()
To convert a pseudo handle to a real handle, you can call duplicatehandle.
Bool duplicatehandle (
Handle hsourceprocesshandle, // handle to source process
Handle hsourcehandle, // handle to duplicate
Handle htargetprocesshandle, // handle to target process
Lphandle lptargethandle, // duplicate handle
DWORD dwdesiredaccess, // requested access
Bool binherithandle, // handle inheritance Option
DWORD dwoptions) // optional actions
Parameters:
Hsourceprocesshandle: The Kernel Handle of the source process (that is, the Process Handle responsible for passing the kernel object handle)
Hsourcehandle: kernel object handle to be passed (thread)
Htargetprocesshandle: Kernel Handle of the target process
Lptargethandle: Address for receiving the kernel object handle (a handle is declared first)
Dwdesiredaccess: Specifies the access mask used by the targethandle handle (this mask is an item in the handle table)
Binherithandle: whether to inherit
Dwoptions: When duplicate_same_access is set, it indicates that all the flags of the source kernel object are the same. In this case, wdesiredaccess can be 0.
It should be noted that after the conversion is complete, the kernel uses the count already plus 1, and the closehandle should be called to decrease immediately.