VC++ process and multi process management method

  • 2020-04-02 02:54:55
  • OfStack

This article illustrates the VC++ process and multi - process management methods, to share for your reference. Specific methods are analyzed as follows:

Abstract : this paper mainly introduces the multi - process management technology in multi - task management.

keywords : vc + + 6.0; Process; Environmental variables; The child process

process

A process is the next instance of a running application that is loaded into memory by the current operating system. Each process is composed of the kernel object and address space, the kernel object can let the system in its storing statistical information about the process and make the system can be a way to manage process, while including address space by all program module of code and data as well as thread stack, heap allocation space dynamically allocated space, etc. A process is merely an entity, it cannot do anything on its own, and it must have at least one thread running in its environment that is responsible for executing code in the process's address space. When a process starts, a thread is started at the same time. This thread is called the main thread or the execution thread, so the thread can continue to create child threads. If the main thread exits, the process is no longer possible, and the system will automatically undo the process and free up its address space.
Each image of an executable or dynamically linked library file loaded into the process's address space is assigned a globally unique instance handle (Hinstance) associated with it. The instance handle is actually a base memory address that records where the process is loaded. The process's instance handle is passed in the program entry function WinMain () through the first parameter, HINSTANCE hinstExe, whose actual value is the address of the base address space used by the process. For programs linked by VC++ linker, the default base address space address is 0x00400000, do not change this value if it is not necessary. In the program, the basic address space used by the specified module can be obtained by the GetModuleHandle () function.

Child process creation

The process is created by the CreateProcess () function, which starts and runs a new program by creating a new process and a main thread running in its address space. Specifically, when the CreateProcess () function is executed, the operating system first creates a process kernel object, initializes the count to 1, and immediately creates a virtual address space for the new process. The code and data from the executable or any other necessary dynamically linked library files are then loaded into the address space. When the main thread is created, it is also the first responsibility of the system to create a threaded kernel object and initialize it to 1. Finally, the main thread is started and the process's entry function WinMain () is executed to complete the creation of the process and the execution thread.
The prototype of the CreateProcess () function is declared as follows:

BOOL CreateProcess(
 LPCTSTR lpApplicationName, //Executable module name
 LPTSTR lpCommandLine, //Command line string
 LPSECURITY_ATTRIBUTES lpProcessAttributes, //Process security properties
 LPSECURITY_ATTRIBUTES lpThreadAttributes, //Thread safety attribute
 BOOL bInheritHandles, //Handle inheritance flag
 DWORD dwCreationFlags, //Create a flag
 LPVOID lpEnvironment, //Pointer to the new environment block
 LPCTSTR lpCurrentDirectory, //Pointer to the current directory name
 LPSTARTUPINFO lpStartupInfo, //
pointer to the start information structure  LPPROCESS_INFORMATION lpProcessInformation //Pointer to the process information structure
);

In the process of program design, a specific function module can be realized by different forms such as function or thread. For the same process, these functions and threads all exist in the same address space, and most of them only process some data related to them when executing. If the algorithm has some kind of error, it may destroy some other important contents in the same address space with it, which will cause serious consequences. To protect the contents of the address space, consider putting the operations that need to access the data in the address space into the address space of another process and only allowing it to access the relevant data in the address space of the original process. Specifically, a child process can be created in the process through the CreateProcess () function, and the child process can only access the relevant data in the address space of the parent process in the entire process, so as to protect all the data in the address space of the parent process that is irrelevant to the task of the current child process. In this case, the function of the child process is similar to that of functions and threads, which can be regarded as a process of the parent process during the run. To do this, the parent process needs to master the start, execution, and exit of the child process. The following code illustrates this process:
//Temporary variable 
CString sCommandLine;
char cWindowsDirectory[MAX_PATH];
char cCommandLine[MAX_PATH];
DWORD dwExitCode;
PROCESS_INFORMATION pi;
STARTUPINFO si = {sizeof(si)};
//You get the Windows directory
GetWindowsDirectory(cWindowsDirectory, MAX_PATH);
//The command line to start the notepad program
sCommandLine = CString(cWindowsDirectory) + "//NotePad.exe";
::strcpy(cCommandLine, sCommandLine);
//Start notepad as a child process
BOOL ret = CreateProcess(NULL, cCommandLine, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);
if (ret) {
 //Close the main thread handle of the child process
 CloseHandle(pi.hThread);
 //Wait for the child process to exit
 WaitForSingleObject(pi.hProcess, INFINITE);
 //Gets the exit code of the child process
 GetExitCodeProcess(pi.hProcess, &dwExitCode);
 //Close child process handle
 CloseHandle(pi.hProcess);
}

This code first through CreateProcess () to create Windows' own "notepad" program as a child process, child process started after the parent process through the WaitForSingleObject () function to wait for the end of its execution, in the child process does not exit before the parent process is always in a blocking state, where the child process is similar to the function in a single thread. Once the child process exits, the pi.hprocess object that the WaitForSingleObject () function is waiting for is notified, and the parent process can continue, if necessary, by GetExitCodeProcess () to get the child's exit code.
In contrast, more often than not, the parent process does not exchange any data or communicate with the child process after the child process is started, and the execution success of the child process created by the parent process has nothing to do with the parent process. Many large software in the design also used this kind of thinking, will some functions through the independent application program to complete, when the need to perform an operation as long as through the main program to start the corresponding child process can, the specific processing work by the child process to complete. For example, the above code only needs to remove the wait on the child process handle pi.hprocess:
BOOL ret = CreateProcess(NULL, cCommandLine, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);
if (ret) {
 //Close the main thread handle of the child process
 CloseHandle(pi.hThread);
 //Close child process handle
 CloseHandle(pi.hProcess);
}

You can set the priority of child processes when they are created using the dwCreationFlags parameter. The previous example code USES the default priority when creating a child process. If you want to set the priority to high, you can modify it as follows:
BOOL ret = CreateProcess(NULL, cCommandLine, NULL, NULL, FALSE, HIGH_PRIORITY_CLASS, NULL, NULL, &si, &pi);

If the priority is not specifically set when the process is created, it can be set dynamically through the SetPriorityClass () function, which requires the handle of the process to be manipulated and the priority identifier as the entry parameter. The function prototype is:
BOOL SetPriorityClass(HANDLE hProcess, DWORD dwPriorityClass);

For example code with no priority set earlier, the parent process can dynamically change its priority setting after the child process starts:
SetPriorityClass(pi.hProcess, HIGH_PRIORITY_CLASS);

Or the child process can change the priority setting after it starts. It is important to note that the process handle should be set as the handle of the child process itself, which can be obtained by the GetCurrentProcess () function:
HANDLE hProcess = GetCurrentProcess();
SetPriorityClass(hProcess, HIGH_PRIORITY_CLASS);

Mutually exclusive running of processes

Normally, the running of one process does not affect other processes that are running. But some programs with special requirements, such as exclusive use of a serial port or other hardware devices, require that other programs attempting to use the port device not be allowed to run while their processes are running, and such programs are generally not allowed to run multiple instances of the same program. This leads to the problem of mutual exclusion of processes.
The core idea of implementing process mutex is simple: when a process starts, it first checks to see if an instance of the process already exists on the current system, and if it does not, it successfully creates and sets a flag that identifies the existence of the instance. When a process is created later, it is known by the tag that the instance already exists, thus ensuring that only one instance of the process can exist in the system. It can be realized by many methods such as memory-mapped file, named event quantity, named mutex and global Shared variable. The following two representative methods, named mutex and global Shared variable, are introduced:

//Create a mutex 
HANDLE m_hMutex = CreateMutex(NULL, FALSE, "Sample07");
//Check for error code
if (GetLastError() == ERROR_ALREADY_EXISTS) {
 //If an existing mutex exists, release the handle and reset the mutex
 CloseHandle(m_hMutex);
 m_hMutex = NULL;
 //The program exits
 return FALSE;
}

The above code demonstrates the use of a named mutex in a process mutex. At the heart of the code is the creation of a named mutex by CreateMutex (). The CreateMutex () function is used to create a mutex object, named or unknown, with the function prototype:
HANDLE CreateMutex(
 LPSECURITY_ATTRIBUTES lpMutexAttributes, //Pointer to the security property
 BOOL bInitialOwner, //Initializes the owner of the mutex
 LPCTSTR lpName //Pointer to the mutex name
);

If the function executes successfully, it returns a handle to a mutex object. If a mutex with the same name already exists before CreateMutex () executes, the function returns a handle to the existing mutex, and the error code ERROR_ALREADY_EXIST is available through GetLastError (). As you can see, CreateMutex () is mutually exclusive to processes by detecting the error code ERROR_ALREADY_EXIST.

The method of using global Shared variables is mainly implemented by compiler in MFC framework program. Create a new section with the #pragma data_seg precompile instruction where a variable can be defined using the volatile keyword and must be initialized. The Volatile keyword specifies that variables can be accessed by external processes. Finally, in order for the variable to work in the process mutex, it is also set to a Shared variable, allowing read and write access. This can be notified to the compiler by #pragma comment precompiled instruction. The following is a list of process mutexes that use global variables:

#pragma data_seg("Shared") 
int volatile g_lAppInstance =0;
#pragma data_seg()
#pragma comment(linker,"/section:Shared,RWS")
...
if(++g_lAppInstance>1)
return FALSE;

The purpose of this code is to add 1 to the global Shared variable g_nAppInstancd when the process starts, and if the value is found to be greater than 1, return FALSE to notify the process to end. In particular, in order for the above two pieces of code to be truly mutually exclusive to the process, they must be placed at the entry code of the application, where the InitInstance () initialization function of the application class begins.

The end of the process

A process simply provides an address space and a kernel object, and its execution is represented by the main thread in its address space. When the main thread's entry point function returns, the process ends. This process termination method is the normal exit of the process, all thread resources in the process can be cleared correctly. In addition to the normal rollout of this process, you sometimes need to use code in your program to force the end of this process or another process. The ExitProcess () function can be used in a thread in a process and will immediately terminate the process. The prototype of the ExitProcess () function is:
VOID ExitProcess (UINT uExitCode);

Its parameter uExitCode sets the exit code for the process. This function is mandatory, and the process is terminated after execution, so any code that follows it cannot be executed. Although the ExitProcess () function can terminate a process while notifying the dynamically linked library associated with it, the execution of the ExitProcess () function can be a security concern. For example, if you had requested a chunk of memory with the new operator before the program called the ExitProcess () function, you would have a memory leak because the ExitProcess () function was forced to release it through the delete operator. Because the ExitProcess () function is mandatory and insecure, it is important to note when using it.

ExitProcess () can only force the exit of this process, and TerminateProcess () is used to force the end of other processes in one process. Unlike ExitProcess (), after the TerminateProcess () function is executed, the terminated process is not notified of the exit of the program. In other words, a process that is terminated cannot finish its exit before ending its run. Therefore, TerminateProcess () is usually considered to be used to force a process to terminate only if no other method can force the process to exit. The function prototype for TerminateProcess () is shown below:

BOOL TerminateProcess(HANDLE hProcess, UINT uExitCode);

The parameters hProcess and uExitCode are process handle and exit code, respectively. If the process is terminated, the handle can be obtained through GetCurrentProcess (). TerminateProcess () is executed asynchronously, and there is no way to determine whether the terminated process has actually exited after the call returns. If the TerminateProcess () process is interested in this detail, it can wait for the process to actually end via WaitForSingleObject ().

summary

Multi - process is an important part of multi - task management. The basic concepts and main technologies such as the creation and termination of child processes, mutual exclusion between processes are introduced in detail. Through this article, the reader should be able to have a preliminary understanding of multi-process management.

Hope that this article described for everyone's VC++ programming help.


Related articles: