zoukankan      html  css  js  c++  java
  • Windows程序设计(1)——Win32运行原理(二)

    3 创建进程

    3.1 进程和线程

    进程通常被定义为一个存在运行的程序的实例。进程是一个正在运行的程序,它拥有自己的虚拟地址空间,拥有自己的代码、数据和其他系统资源。一个进程也包含一个或者多个运行在此进程内的线程。

    程序是指一连串的静态的指令,而进程是一个容器,它包含了一系列运行在这个程序实例上下文中的线程使用的资源。

    每个进程至少拥有一个在它的地址空间中运行的线程。操作系统创建进程后,会创建一个线程执行进程中的代码。通常把这个线程称为该进程的主线程。

    3.2 应用程序的启动过程

    应用程序必须有一个入口函数,它在程序开始运行的时候被调用。如:

    int main(int argc, char* argv[]);

    操作系统事实上并不是真正的调用main函数,而是去调用C/C++运行期启动函数,此函数会初始化C/C++运行期库。它会保证在用户的代码执行之前所有的全局的或者静态的C++对象能够被正确的创建,执行这些对象的构造函数中的代码。在控制台应用程序中,C/C++运行期启动函数会调用程序入口函数main。

    在Win32程序的启动过程中。应用程序的启动过程就是进程的创建过程,操作系统是通过调用CreateProcess函数来创建新的进程的。当一个线程调用CreateProcess函数的时候,系统会创建一个进程内核对象,其使用计数被初始化为1。此进程内核对象不是进程本身,仅仅是一个系统用来管理这个进程的小的数据结构。系统然后会为新的进程创建一个虚拟地址空间,加载应用程序运行时所需要的代码和数据。

    系统接着会为新进程创建一个主线程,这个主线程通过执行C/C++运行期启动代码开始运行,C/C++运行期启动代码又会调用main函数。如果系统成功创建了新的进程和其主线程,CreateProcess函数会返回TRUE,否则返回FALSE。

    一般将创建进程称为父进程,被创建的进程称为子进程。系统在创建进程时会为新进程指定一个STARTUPINFO类型的变量,这个结构包含了父进程传递给子进程的一些显示信息。

    typedef struct _STARTUPINFO { 
        DWORD   cb;                // 本结构长度,总是应该设为为sizeof(STARTUPINFO)
        LPTSTR  lpReserved;        // 保留字段
        LPTSTR  lpDesktop;         // 指定桌面名称
        LPTSTR  lpTitle;           // 操控台应用程序使用,指定控制台窗口标题
        DWORD   dwX;               // 指定新创建窗口的位置坐标和大小信息
        DWORD   dwY; 
        DWORD   dwXSize; 
        DWORD   dwYSize; 
        DWORD   dwXCountChars;     // 控制台应用程序使用,指定控制台窗口的大小
        DWORD   dwYCountChars; 
        DWORD   dwFillAttribute;   // 控制台应用程序使用,指定控制台窗口的前/背景色
        DWORD   dwFlags;           // 标志,标识本结构中哪些成员的值是有效的
        WORD    wShowWindow;       // 窗口显示方式
        WORD    cbReserved2;       // 保留
        LPBYTE  lpReserved2;       // 保留
        HANDLE  hStdInput;         // 控制台应用程序使用,内个标准句柄
        HANDLE  hStdOutput; 
        HANDLE  hStdError; 
    } STARTUPINFO, *LPSTARTUPINFO; 

    一个进程可能调用GetStartupInfo函数来取得父进程创建自己时使用的STARTUPINFO结构。事实上,Windows系统就是通过调用这个函数来取得当前进程的创建信息的。

    VOID GetStartupInfo(
      LPSTARTUPINFO lpStartupInfo   // startup information
    );

    定义一个STARTUPINFO结构的对象以后,总要在使用此对象将对象的cb成员初始化为STARTUPINFO结构的大小,如:

    STARTUPINFO si = {sizeof(si)};
    ::GetStartupInfo(&si);

    3.3 CreateProcess函数

    CreateProcess函数创建一个新的进程和该进程的主线程。函数原型如下:

    BOOL CreateProcess(
      LPCTSTR lpApplicationName,                 // 可执行文件/模块的名称
      LPTSTR lpCommandLine,                      // 指定传递给执行模块的参数
      LPSECURITY_ATTRIBUTES lpProcessAttributes, // 进程安全性,值为NULL表示使用默认安全属性
      LPSECURITY_ATTRIBUTES lpThreadAttributes,  // 线程安全性,值为NULL表示使用默认安全属性
      BOOL bInheritHandles,                      // 指定当前进程中的可继承句柄是否可被新进程继承
      DWORD dwCreationFlags,                     // 指定新进程的优先级和其他创建标志
      LPVOID lpEnvironment,                      // 指定新进程使用的环境变量
      LPCTSTR lpCurrentDirectory,                // 指定新进程使用的当前目录
      LPSTARTUPINFO lpStartupInfo,               // 指定新进程中主窗口位置、大小和标准句柄等
      LPPROCESS_INFORMATION lpProcessInformation // 返回新建进程的标志信息,如ID和句柄
    );

    这里很多的变量类型都与平时使用的不太一样,它一般有如下的定义:

    typedef unsigned long   DWORD;
    typedef int             BOOL;
    typedef unsigned short  WORD;
    typedef unsigned char   BYTE;
    typedef float           FLOAT;
    typedef void far        *LPVOID;
    typedef int             INT;
    typedef unsigned int    UINT;

    lpApplicationNamelpCommandLine参数指定了新的进程将要使用的可执行文件的名称和传递给新进程的参数,如果lpApplicationName为空,那么将lpCommandLine中第一个空白分隔之前的字符串做为模块名。如下启动记事本:

    STARTUPINFO si = {sizeof(si)};
    PROCESS_INFORMATION pi;
    char *szCommandLine = "notepad";
    ::CreateProcess(NULL, szCommandLine, NULL, NULL,
            FALSE, NULL, NULL, NULL, &si, &pi);

    如果lpCommandLine参数中的第一个单词没有后缀,则.exe后缀将被添加进来。CreateProcess函数将会按照以下路径去搜索可执行文件:

    • 调用进程的可执行文件所在的目录。
    • 调用进程的当前目录。
    • Windows的系统目录(system32目录)。
    • Windows目录。
    • 在名称为PATH的环境变量中列出的目录。

    如果文件名中包含了目录,系统会直接在这个目录中查找可执行文件。可以给启动的进程传递参数,如下参数,将使用记事本打开主进程当前目录下的ReadMe.txt文件:

    char* szCommandLine = "notepad ReadMe.txt";

    lpProcessInformation参数是一个指向PROCESS_INFORMATION结构的指针。CreateProcess函数在返回之前会初始化此结构成员。结构定义如下:

    typedef struct _PROCESS_INFORMATION {
        HANDLE hProcess;    // 新建进程的内核句柄
        HANDLE hThread;     // 新建进程中主线程的内核句柄
        DWORD  dwProcessId; // 新建进程的ID
        DWORD  dwThreadId;  // 新建进程的主线程的ID
    } PROCESS_INFORMATION;

    创建一个新的进程促使系统创建一个进程内核对象和一个线程内核对象。在创建它们的时候,系统将每个对象的使用计数初始化为1。然后在CreateProcess返回之前,这个函数打开此进程内核对象和线程内核对象的句柄,并将它们的值传给上述结构的hProcesshThread成员。此时这些内核对象的使用计数为2。因此父进程中必须有线程调用CloseHandle关闭这两个内核对象的句柄,否则子进程已经终止了,该进程的进程内核对象和主线程的内核对象也不会被释放。

    进程和线程都分被分配一个唯一的ID号,且二者不会相同。但该ID会被重新利用,也就是说当一个ID的进程终止后,后续的进程或者线程是有可能再次使用该ID的。

    3.4 实例

    如下实例打开系统的记事本。

    #include <stdio.h>
    #include <windows.h>
    
    int main(int argc, char* argv[])
    {
        TCHAR szCommandLine[] = "notepad";
        STARTUPINFO si = {sizeof(si)};
        PROCESS_INFORMATION pi;
    
        BOOL bRet = ::CreateProcess(
            NULL,          // 该参数留空
            szCommandLine, // 命令行参数
            NULL,          // 默认进程安全性
            NULL,          // 默认线程安全性
            FALSE,         // 指定当前进程内的句柄不被子进程继承
            NULL,          // 不指定优先级和创建参数
            NULL,          // 使用本进程的环境变量
            NULL,          // 使用本进程的驱动器和目录
            &si,
            &pi);
        if (bRet)
        {
            // 关闭不使用的句柄
            ::CloseHandle(pi.hThread);
            ::CloseHandle(pi.hProcess);
            printf("Process ID: %d
    ", pi.dwProcessId);
            printf("Thread ID: %d
    ", pi.dwThreadId);
        }
        getchar();
        return 0;
    }
    

    运行结果如:

    CreateProcess

  • 相关阅读:
    2021.3.16
    2021.3.15
    通过指定的URL获取返回图片的BASE64编码
    Redis系统学习之缓存穿透,缓存击穿,缓存雪崩的概念及其解决方案
    Redis系统学习之其他高可用模型
    Redis系统学习之哨兵模式
    Redis系统学习之主从复制
    Redis系统学习之发布订阅
    Redis系统学习之持久化(AOF)
    Redis系统学习之持久化(RDB)
  • 原文地址:https://www.cnblogs.com/furzoom/p/7710238.html
Copyright © 2011-2022 走看看