1.简介

一般将进程定义为一个正在运行的程序的一个实例,由以下两部分构成。

  • 一个内核对象,操作系统用它来管理进程。内核对象也是系统保存进程统计信息的地方
  • 一个地址空间,其中包含所有可执行文件或DLL模块的代码和数据。此外,它还包含动态内存分配,比如线程堆栈和堆的分配。

进程是惰性的,进程要做任何事,都必须让一个线程在它的上下文中运行,该线程负责执行进程地址空间包含的代码,一个进程可以有多个线程,所有的线程都在进程的地址空间中“同时”执行代码。

  • 每个线程都有它自己的一组CPU寄存器和它自己的堆栈。
  • 每个进程至少需要有一个线程来执行进程地址空间包含的代码。
  • 当系统创建一个进程的时候,会自动为进程创建第一个线程,称为主线程,主线程再创建子线程。

2.获取当前进程所在的驱动器和目录

DWORD GetCurrentDirectory(
  [in]  DWORD  nBufferLength,
  [out] LPTSTR lpBuffer
);

参数1:当前目录字符串的缓冲区长度,缓冲区长度必须包含终止空字符的空间。

参数2:指向接收当前目录字符串的缓冲区的指针。这个以空结尾的字符串指定到当前目录的绝对路径。

返回值:如果函数成功,返回值指定写入缓冲区的字符数,不包括终止的'\0'

3.创建进程

一个线程调用CreateProcess时,系统将创建一个进程内核对象,其初始化计数为1,进程内核对象对象不是进程本身,而是操作系统用来管理这个进程的一个小型数据结构。然后,系统为新进程创建一个虚拟地址空间,并将可执行文件和必要的DLL的代码及数据加载到进程的地址空间。

接着操作系统为新进程的主线程创建一个线程内核对象(其使用计数为1)。和进程内核对象一样,线程内核对象也是一个小型的数据结构,操作系统用它来管理这个线程。这个主线程一开始就会执行C/C++运行时的启动例程,它是由链接器设为应用程序入口的,最终会调用应用程序WinMain,wWinMain函数。如果系统成功创建了新进程和主线程,返回TRUE。

BOOL CreateProcessW(
  [in, optional]      LPCWSTR               lpApplicationName,
  [in, out, optional] LPWSTR                lpCommandLine,
  [in, optional]      LPSECURITY_ATTRIBUTES lpProcessAttributes,
  [in, optional]      LPSECURITY_ATTRIBUTES lpThreadAttributes,
  [in]                BOOL                  bInheritHandles,
  [in]                DWORD                 dwCreationFlags,
  [in, optional]      LPVOID                lpEnvironment,
  [in, optional]      LPCWSTR               lpCurrentDirectory,
  [in]                LPSTARTUPINFOW        lpStartupInfo,
  [out]               LPPROCESS_INFORMATION lpProcessInformation
);

lpApplicationName:可执行文件的名称。

  • lpApplicationName参数可以为NULL。在这种情况下,模块名必须是lpCommandLine字符串中以空格分隔的第一个标记;当CreateProcess解析lpCommandLine 字符串时,它会检查字符串中的第一个标记,并假记此标记为我们想运行的可执行文件的名称,如果可执行文件的名称没有扩展名,默认为.exe。并且如果文件名不包含一个完整的路径,CreateProcess还会按以下顺序来搜索可执行文件:
(1)主调进程.exe文件所在的目录

(2)主调进程的当前目录

(3)windows系统目录,即GetSystemDirectory返回的System32子文件夹

(4)windows目录

(5)PATH环境变量中列出的目录
  • 如果不是NULL,该参数必须包含文件扩展名,不假定默认扩展名。

lpCommandLine:传给新进程的命令行字符串,这个参数类型是LPWSTR,是一个非常量的字符串,CreateProcess实际上会修改我们传给它的命令行字符串。当然在它返回前,它会把这个字符串还原,所以这样的代码是错误的:因为C/C++编译器把“NOTEPAD”字符串放在只读内存中。

STARTUPINFO si = {sizeof(si)} ;
PROCESS_INFORMATION pi ;
CreateProcess(NULL,TEXT("NOTEPAD"),NULL,NULL,FALSE,0,NULL,NULL,&si,&pi) ;
  • 解决问题的最佳方式是把常量字符串放在一个临时缓冲区:
  • STARTUPINFO si = {sizeof(si)} ;
    PROCESS_INFORMATION pi ;
    TCHAR szCommandLine[] = TEXT("NOTEPAD") ;
    CreateProcess(NULL,szCommandLine,NULL,NULL,FALSE,0,NULL,NULL,&si,&pi) ;

    lpProcessAttributes:指向SECURITY_ATTRIBUTES结构的指针,该结构决定新进程对象的返回句柄是否可以被子进程继承。如果lpProcessAttributes为NULL,句柄不能被继承。

  • lpThreadAttributes:指向SECURITY_ATTRIBUTES结构的指针,该结构决定新线程对象的返回句柄是否可以被子进程继承。如果lpThreadAttributes为NULL,句柄不能被继承。

  • bInheritHandles:如果此参数为TRUE,则调用进程中的每个可继承句柄将被新进程继承。如果参数为FALSE,则不继承句柄。

  • dwCreationFlags:控制优先级类和进程创建的标志。

  • lpEnvironment:指向新进程的环境块的指针。如果该参数为NULL,则新进程使用调用进程的环境。

  • lpCurrentDirectory:进程当前目录的完整路径。如果此参数为NULL,则新进程将具有与调用进程相同的当前驱动器和目录。

  • lpStartupInfo:指向STARTUPINFO或STARTUPINFOEX结构体的指针。

  • lpProcessInformation:指向PROCESS_INFORMATION结构的指针,该结构接收关于新进程的标识信息。

示例:创建一个子进程。

#include <windows.h>
#include <stdio.h>
#include <tchar.h>

void _tmain( int argc, TCHAR *argv[] )
{
    STARTUPINFO si;
    PROCESS_INFORMATION pi;

    ZeroMemory( &si, sizeof(si) );
    si.cb = sizeof(si);
    ZeroMemory( &pi, sizeof(pi) );

    if( argc != 2 )
    {
        printf("Usage: %s [cmdline]\n", argv[0]);
        return;
    }

    // Start the child process.
    if( !CreateProcess( NULL,   // No module name (use command line)
        argv[1],        // Command line
        NULL,           // Process handle not inheritable
        NULL,           // Thread handle not inheritable
        FALSE,          // Set handle inheritance to FALSE
        0,              // No creation flags
        NULL,           // Use parent's environment block
        NULL,           // Use parent's starting directory
        &si,            // Pointer to STARTUPINFO structure
        &pi )           // Pointer to PROCESS_INFORMATION structure
    )
    {
        printf( "CreateProcess failed (%d).\n", GetLastError() );
        return;
    }

    // Wait until child process exits.
    WaitForSingleObject( pi.hProcess, INFINITE );

    // Close process and thread handles.
    CloseHandle( pi.hProcess );
    CloseHandle( pi.hThread );
}

4.终止进程

有以下4中方式终止:

  • 主线程的入口函数返回(强烈推荐使用)
  • 进程中的一个线程调用ExitProcess函数(不推荐使用)
  • 另一个进程中的线程调用TerminateProcess函数(不推荐使用)
  • 进程中的所有线程都“自然死亡”(几乎不会发生)

4.1使用ExitProcess函数

会导致进程或线程直接终止运行,不能正常执行清理工作,示例如下:


class Test {
public:
    Test() { printf("Construct \r\n"); }
    ~Test() { printf("Destruct \r\n"); }
};

Test g_test;

int main()
{
    Test test;
    ExitProcess(0);
}

结果如下图所示:只构造,不析构。

Windows进程简介-LMLPHP

 4.2使用TerminateProcess函数

此函数与ExitProcess有一个明显的区别:任何线程都可以调用TerminalProcess来终止另一个进程或者它自己的进程。

只有在无法通过其他方法来强制进程退出时,才使用TerminateProcess。

虽然进程没有机会执行自己的清理工作,但操作系统会在进程终止之后彻底进行清理,确保不会泄露任何操作系统资源。这意味着使用的所有内存都会被释放,所有打开的文件都会被关闭,所有的内核对象的使用计数都将递减等等。

TerminateProcess是异步的;它启动终止并立即返回。如果需要确定进程已经终止,可以调用带有进程句柄的WaitForSingleObject函数。

08-12 11:26