• Windows进程简介


    1.简介

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

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

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

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

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

    1. DWORD GetCurrentDirectory(
    2. [in] DWORD nBufferLength,
    3. [out] LPTSTR lpBuffer
    4. );

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

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

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

    3.创建进程

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

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

    1. BOOL CreateProcessW(
    2. [in, optional] LPCWSTR lpApplicationName,
    3. [in, out, optional] LPWSTR lpCommandLine,
    4. [in, optional] LPSECURITY_ATTRIBUTES lpProcessAttributes,
    5. [in, optional] LPSECURITY_ATTRIBUTES lpThreadAttributes,
    6. [in] BOOL bInheritHandles,
    7. [in] DWORD dwCreationFlags,
    8. [in, optional] LPVOID lpEnvironment,
    9. [in, optional] LPCWSTR lpCurrentDirectory,
    10. [in] LPSTARTUPINFOW lpStartupInfo,
    11. [out] LPPROCESS_INFORMATION lpProcessInformation
    12. );

    lpApplicationName:可执行文件的名称。

    • lpApplicationName参数可以为NULL。在这种情况下,模块名必须是lpCommandLine字符串中以空格分隔的第一个标记;当CreateProcess解析lpCommandLine 字符串时,它会检查字符串中的第一个标记,并假记此标记为我们想运行的可执行文件的名称,如果可执行文件的名称没有扩展名,默认为.exe。并且如果文件名不包含一个完整的路径,CreateProcess还会按以下顺序来搜索可执行文件:
    1. (1)主调进程.exe文件所在的目录
    2. (2)主调进程的当前目录
    3. (3)windows系统目录,即GetSystemDirectory返回的System32子文件夹
    4. (4)windows目录
    5. (5)PATH环境变量中列出的目录
    • 如果不是NULL,该参数必须包含文件扩展名,不假定默认扩展名。

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

    1. STARTUPINFO si = {sizeof(si)} ;
    2. PROCESS_INFORMATION pi ;
    3. CreateProcess(NULL,TEXT("NOTEPAD"),NULL,NULL,FALSE,0,NULL,NULL,&si,&pi) ;
    • 解决问题的最佳方式是把常量字符串放在一个临时缓冲区:
      1. STARTUPINFO si = {sizeof(si)} ;
      2. PROCESS_INFORMATION pi ;
      3. TCHAR szCommandLine[] = TEXT("NOTEPAD") ;
      4. 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结构的指针,该结构接收关于新进程的标识信息。

    示例:创建一个子进程。

    1. #include
    2. #include
    3. #include
    4. void _tmain( int argc, TCHAR *argv[] )
    5. {
    6. STARTUPINFO si;
    7. PROCESS_INFORMATION pi;
    8. ZeroMemory( &si, sizeof(si) );
    9. si.cb = sizeof(si);
    10. ZeroMemory( &pi, sizeof(pi) );
    11. if( argc != 2 )
    12. {
    13. printf("Usage: %s [cmdline]\n", argv[0]);
    14. return;
    15. }
    16. // Start the child process.
    17. if( !CreateProcess( NULL, // No module name (use command line)
    18. argv[1], // Command line
    19. NULL, // Process handle not inheritable
    20. NULL, // Thread handle not inheritable
    21. FALSE, // Set handle inheritance to FALSE
    22. 0, // No creation flags
    23. NULL, // Use parent's environment block
    24. NULL, // Use parent's starting directory
    25. &si, // Pointer to STARTUPINFO structure
    26. &pi ) // Pointer to PROCESS_INFORMATION structure
    27. )
    28. {
    29. printf( "CreateProcess failed (%d).\n", GetLastError() );
    30. return;
    31. }
    32. // Wait until child process exits.
    33. WaitForSingleObject( pi.hProcess, INFINITE );
    34. // Close process and thread handles.
    35. CloseHandle( pi.hProcess );
    36. CloseHandle( pi.hThread );
    37. }

    4.终止进程

    有以下4中方式终止:

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

    4.1使用ExitProcess函数

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

    1. class Test {
    2. public:
    3. Test() { printf("Construct \r\n"); }
    4. ~Test() { printf("Destruct \r\n"); }
    5. };
    6. Test g_test;
    7. int main()
    8. {
    9. Test test;
    10. ExitProcess(0);
    11. }

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

     4.2使用TerminateProcess函数

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

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

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

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

  • 相关阅读:
    C语言之指针、结构体、动态内存分配
    减法聚类(Subtractive Clustering)算法实践
    备战秋招--redis篇
    Element ui 快速入门(基础知识点)
    github连接失败Host key verification failed.解决方案
    擎创技术流 | ClickHouse实用工具—ckman教程(4)
    【OpenCV】使用OpenCV调用手机摄像头
    SpringBoot系列之动态生成cron表达式执行定时程序
    AVR单片机开发1——IO口的输入和输出
    AI实战 | 手把手带你打造校园生活助手
  • 原文地址:https://blog.csdn.net/wzz953200463/article/details/126292967