Windows核心编程:进程

作者:shenzi

链接:http://blog.csdn.net/shenzi

Windows核心编程:进程

1.进程
    1.1进程概念
         程序是一系列静态指令,而进程是由执行程序实例的线程使用的一系列资源的容器(container)。从最高抽象层上来说,Windows进程的组成如下:
  • 专用的虚拟地址空间(包括程序代码,数据,堆,栈等);
  • 可执行程序,它定义初始代码和数据,并映射到进程的虚拟地址空间;
  • 各种内核对象的开放句柄列表,它们对所有线程都是可访问的;
  • 标识用户并成为“访问令牌”的安全环境、安全组和同进程相关的特权;
  • 称为“进程ID”的唯一标示符;
  • 至少一个执行线程;


                                             图1:进程环境                                                                                                  图2:进程地址空间

图3:进程相关数据结构

        1.2Microsoft C/C++创建应用程序
        Microsoft Visual Studio集成开发环境会设置各种链接器开关,是链接器将子系统的正确类型嵌入最终生成的可执行文件中。对于CUI (Console User Interface,控制台用户界面)程序,这个链接器开关是/SUBSYSTEM:CONSOLE ,对于GUI (Graphical User Interface,图形用户界面)程序,则是/SUBSYSTEM:WINDOWS
        Windows应用程序必须有一个入口点函数,应用程序开始运行时,这个函数会被调用。C/C++开发人员可以使用一下两种入口点函数:
        int WINAPI _tWinMain(
        HINSTANCE hInstanceExe,
        HINSTANCE,
        PTSTR pszCmdLine,
        int nCmdShow);
        
int _tmain(
            int argc,
            TCHAR *argv[],
            TCHAR *envp[]);
     具体的符号拒绝与我们是否要使用Unicode字符串。操作系统实际并不调用我们所写的入口点函数。相反,它会调用由C/C++运行库实现并在链接时使用 -entry:命令行选项来设置一个C/C++运行时启动函数。该函数初始化C/C++运行库,确保在我们的代码开始执行之前,声明的任何全局和静态 C++对象都被正确地构造。

应用程序类型

入口点函数

嵌入可执行文件的启动函数

处理ANSI字符和字符串的GUI应用程序

_tWinMain (WinMain)

WinMainCRTStartup

处理Unicode字符和字符串的GUI应用程序

_tWinMain (wWinMain)

wWinMainCRTStartup

处理ANSI字符和字符串的CUI应用程序

_tmain (Main)

mainCRTStartup

处理Unicode字符和字符串的CUI应用程序

_tmain (Wmain)

wmainCRTStart

                        表1:应用程序类型和相应的入口点函数

      链接器根据/SUBSYSTEM链接器开关,选择相应的C/C++运行库启动函数。如果指定 /SUBSYSTEM:CONSOLE ,链接器会寻找main或wmain,并选择相应 C/C++运行时启动函数 如果没有找到这两个函数,链接器将返回“unresolved external symbol”(无法解析的外部符号)错误。 /SUBSYSTEM:WINDOWS 的情况类似。
      如果我们移除 /SUBSYSTEM 链接器开关 ,链接器将自动判断应该将应用程序设备哪一个子系统,链接器会检查代码中包括4个函数中的哪一个(WinMain,wWinMain,main,wmain),并据此推算可执行文件应该是哪个子系统,以及应该在可执行文件中嵌入哪个C/C++启动函数。
      所以我们在创建一个新项目时如果错误的选择了项目的类型,我们可以更改
/SUBSYSTEM: 开关,或则直接删除 /SUBSYSTEM: 开关,让链接器自动判断应该将应用程序设为哪个子系统。
      C/C++运行库启动函数所做的事情基本都是一样的,区别在于它们要处理的是ANSI字符串,还是Unicode字符串;以及在初始化C运行库之后它们调用的是哪一个入口点函数。启动函数将做了以下一些工作:

  • 获取指向新进程的完整命令行的一个指针;
  • 获取指向新进程的环境变量的一个指针;
  • 初始化C/C++运行库的全局变量。如果定义了StdLib.h,我们的代码将可以访问这些变量;
  • 初始化C/C++运行库内存分配函数(malloc和calloc)和其它底层I/O例程使用的堆;
  • 调用所有全局和静态C++类对象的构造函数;   

      完成所有这些初始化工作之后,C/C++启动函数将会调用相应的应用程序的入口点函数 (WinMain,wWinMain,main,wmain)
      入口点函数返回后,启动函数将调用C运行库函数exit, 向其传递返回值(nMainRetVal)。
      exit 函数执行以下任务:

  • 调用_onexit 函数调用所注册的任何一个函数; 
  • 调用所有全局和静态C++对象的析构函数;
  • DEBUG 生成中,如果设置了_CRTDBG_LEAK_CHECK_DF 标志,就通过调用_CrtDumpMemoryLeaks 来生成内存泄露报告; 
  • 调用操作系统的ExitProcess 函数,向其传入nMainRetVal。这回导致操作系统“杀死”我们的进程,并设置它的推出代码。    

    1.2.1进程实例句柄
    加载到进程地址空间的每一个可执行文件或者DLL文件都被赋予了一个独一无二的实例句柄。可执行文件的实例被当做(w)WinMain函数的第一个参数hInstanceExe传入。在需要加载资源的函数调用中,一般都要提供此句柄的值。许多应用程序都会将
hInstanceExe参数保存在一个全局变量中。
    有的函数需要一个HMODULE类型的参数,HMODULEHINSTANCE 完全是一回事。(16位Windows遗留问题。)
    实例句柄实际值是一个内存基地址。
hInstanceExe实际是可执行文件的映像加载到进程地址空间中的位置。使用Microsoft链接器的/BASE:address链接器开关,可以更改要将应用程序加载到哪个基地址。
    为了知道一个可执行文件或DLL文件被加载到进程地址空间的什么位置,可以使用
GetModuleFileName
函数返回一个句柄/基地址:

        HMODULE GetModuleHandle(PCTSTR pszModule);
   
GetModuleFileName传入NULL,返回主调进程的可执行文件的基地址。
    1.2.2进程前一个实例的句柄
    C/C++运行库启动代码总是向(w)WinMain的hPrevInstance参数传递NULL,又是
16位Windows遗留问题 ,绝对不要在自己的代码中引用这个参数:
    
int WINAPI _tWinMain(

    HINSTANCE hInstanceExe,
    HINSTANCE,
    PSTR pszCmdLine,
    int nCmdShow);

    1.2.3进程的命令行
    系统在创建一个新进程时,会传一个命令行给它。这个命令行几乎总是非空的;至少,用于创建新进程的可执行文件的名称是命令上的第一个标记(token)。
    C运行库的启动代码开始执行一个GUI应用程序时,会调用Windows函数
GetCommandLine() 来获取进程的完整命令行,忽略可执行文件的名称,然后将指向命令行剩余部分的一个指针传给WinMain的pszCmdLine参数。

      将命令行分解成单独的标记:
    PWSTR* CommandLineToArgvW(
    PWSTR pszCmdLine,
    int* pNumArgs);

    1.2.4进程的环境变量
    每个进程都有一个与它关联的环境块,这是在进程地址空间内分配的一块内存。
    1.3CreateProcess函数
   
BOOL CreateProcess(

    PCTSTR pszApplicationName, //可执行文件的名称
    PTSTR pszCommandLine,       //传给新进程的命令行字符串
    PSECURITY_ATTRIBUTES psaProcess, //进程对象安全属性
    PSECURITY_ATTRIBUTES psaThread, //线程对象安全属性
    BOOL bInheritHandles, //指定继承性
    DWORD fdwCreate, //标识影响新进程创建方式的标志
    PVOID pvEnvironment, //指向新进程要使用的环境字符串
    PCTSTR pszCurDir, //允许父进程指定子进程的当前驱动器和目录
    PSTARTUPINFO psiStartInfo,//指向一个STARTUPINFO结构或STARTUPINFOEX结构
    PPROCESS_INFORMATION ppiProcInfo);
//指向一个 PROCESS_INFORMATION 结构
    CreateProcess 创建进程的主要阶段:

  • 打开将在进程中被执行的映像文件(.EXE);
  • 创建Windows内核进程对象;
  • 创建初始线程(堆栈,环境和内核线程对象);
  • 向Win32子系统通知新进程以便它设置新的进程和线程;
  • 启动初始线程的执行(除非CREATE-SUSPENDED标记被指定);
  • 在新进程和线程的环境中,完成地址空间的初始化(例如加载所需DLL)并开始程序的执行;   


图4:进程创建主要阶段


    1.4终止进程
    进程可以通过以下4种方式终止:

  • 主线程的入口点函数返回(强烈推荐的方式);
  • 进程中的一个线程调用ExitProcess函数(要避免这种方式);
  • 另一个进程中的县城调用TerminateProcess函数(要避免这种方式);
  • 进程中的所有线程都“自然死亡”(这种情况几乎从来不会发生);   

    1.4.1主线程的入口点函数返回
    设计一个应用程序时,应该保证只有在主线程的入口点函数返回之后,这个应用程序的进程才终止。只有这样才能保证主线程的所有资源都被正确清理:

  • 该线程创建的任何C++对象都将由这些对象的析构函数正确销毁;
  • 操作系统将正确释放线程栈使用的内存;
  • 系统将进程的退出代码(在进程内核对象中维护)设为入口点函数的返回值;  
  • 系统递减进程内核对象的使用计数;

    1.4.2ExitProcess函数
    进程中的某个线程调用
ExitProcess 函数来终止进程:
   
VOID ExitProcess(UINT fuExitCode);

    该函数将终止进程,并将进程的退出代码设为
fuExitCode。 ExitProcess 不会返回值,因为进程已经终止了。 详见
    1.4.3 TerminateProcess函数
    调用 TerminateProcess 也可以终止一个进程:
    BOOL TerminateProcess(
    HANDLE hProcess,
    UINT fuExitCode);

    此函数与
ExitProcess 的一个明显区别是:任何线程都可以调用 TerminateProcess 来终止另一个进程或者它自己的进程。 详见
    1.4.4当进程中的所有线程终止时
    当一个进程中的所有县城都终止了,操作系统就认为没有任何理由再保持进程的地址空间,就会终止这个进程。

    1.4.5当进程终止运行时

     一个进程终止时,系统会一次执行以下操作:

  • 终止进程中遗留的任何线程;
  • 释放进程分配的所有USER对象和GDI对象,关闭所有内核对象(如果使用计数减为0,销毁内核对象);
  • 进程的退出代码从STILE_ACTIVE变为传给ExitProcess或TerminateProcess函数的代码;
  • 进程内核对象的状态变为已触发状态;
  • 进程内核对象的使用计数递减1;









你可能感兴趣的:(windows,Microsoft,dll,interface,attributes,winapi)