欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Windows API(二)进程

程序员文章站 2022-07-05 12:58:22
...

进程常被定义为应用程序的一个运行实例。在Win32中,每个进程拥有4GB的地址空间。进程是惰性的,Win32进程什么也不执行,它只是拥有4GB的地址空间来存放应用程序所需的二进制代码和数据。

除地址空间外,每个程序还拥有别的资源,比如文件、动态内存分配和线程。这些不同的资源在进程的生命中被创建,当进程终止时,它们也被释放。

每个线程有自己的CPU寄存器组和栈。操作系统为每个独立的线程进行CPU的时间调度,操作系统以轮转方式向线程提供时间片。

1. Win32 应用程序

系统的加载器关心应用程序是基于CUI还是GUI,如果exe文件中指出是基于CUI的应用程序,加载器会自动为该程序生成一个控制台窗口。

1.1 进程的句柄

每个加载进进程地址空间的exe或dll文件都有一个唯一的实例句柄。exe文件的实例是作为WinMain的第一个参数hinstExe传递的,在调用装入资源的函数时通常需要该句柄的值。

1.2 进程的环境变量

每个进程都有相关联的环境块。环境块是分配在进程的地址空间中的一块内存。
一般子进程从它的父进程那里继承了一组完全一样的环境变量。不过父进程可以控制子进程继承哪些变量(通过CreateProcess的参数设置)。
可通过GetEnvironmentVariable和SetEnvironmentVariable获取及设置环境变量。

1.3 进程的错误模式

每个进程都有一组标志来告诉系统该怎样对严重的错误做出反应,进程可以调用SetErrorMode函数该告诉系统如何处理每种错误:

标志 描述
SEM_FAILCRITICALERRORS 0x0001 系统不显示严重错误处理消息框,把错误返回调用的进程
SEM_NOGPFAULTERRORBOX 0x0002 系统不显示普通保护错误消息框,该标志只能被调试应用程序设置来用异常句柄自己处理普通保护(GP)错误
SEM_NOALIGNMENTFAULTEXCEPT 0x0004 系统在找不到文件时不现实消息框
SEM_NOOPENFILEERRORBOX 0x8000 系统自动修正内存排序错误,使它们对应用不可见,该标志对x86或Alpha处理器没有作用

缺省情况,子进程会继承父进程的错误模式。也可以在CreateProcess使设置不继承。

1.4 进程的当前驱动器和目录

可在环境变量中设置

1.5 系统版本

// 这个有点坑,程序员把主次版本颠倒了
DWORD GetVersion(void)

// 用这个
BOOL GetVersionEx(LPOSVERSIONINFOA lpVersionInformation);
	
    printf("%X \n", GetVersion());

    OSVERSIONINFO osv;
    osv.dwOSVersionInfoSize = sizeof(osv);

    BOOL ret = GetVersionEx(&osv);
    if (ret)
    {
        printf("%X - %X - %X - %X - %s\n",
               osv.dwMajorVersion,	// 主版本
               osv.dwMinorVersion,	// 此版本
               osv.dwBuildNumber,	// 编译序号
               osv.dwPlatformId,			//
               osv.szCSDVersion);
    }
    else
    {
        perror("GetVersionEx Error");
    }

2. CreateProcess函数

BOOL CreateProcess(
    _In_opt_ LPCSTR lpApplicationName,
    _Inout_opt_ LPSTR lpCommandLine,
    _In_opt_ LPSECURITY_ATTRIBUTES lpProcessAttributes,
    _In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes,
    _In_ BOOL bInheritHandles,
    _In_ DWORD dwCreationFlags,
    _In_opt_ LPVOID lpEnvironment,
    _In_opt_ LPCSTR lpCurrentDirectory,
    _In_ LPSTARTUPINFOA lpStartupInfo,
    _Out_ LPPROCESS_INFORMATION lpProcessInformation
    );

3. 终止进程

  1. 进程中某个线程调用了ExitProcess函数
  2. 进程中某个线程调用了TerminateProcess函数(可以终结当前进程,也可以杀死其他进程)

在主线程中使用return和ExitProcess及TerminateProcess有什么不同?参考:《Windows下return,exit和ExitProcess的区别和分析- -

进程中的所有线程结束(ExitThread、TerminateThread)后会自动终止进程。

进程结束时,发生了什么:

  • 余下所有线程都被终止
  • 进程分配的内存被释放(线程分配的可能没有被释放),所有内核对象被关闭
  • 进程内核对象状态变成了有信号态(WaitForSingleObject将会得到返回值)
  • 进程的退出码从STILL、ACTIVE变成了由ExitProcess或TerminateProcess传递的代码
  • 进程内核对象的使用计数被-1(进程内核对象不一定会变成0,需要所有引用进程调用CloseHandle或者进程关闭,或者调用GetExitCodeProcess)

4. 子进程

创建一个新线程,并等待结果:

        STARTUPINFO si;
        memset(&si, 0, sizeof(STARTUPINFO)); //初始化si在内存块中的值
        si.cb = sizeof(STARTUPINFO);
        si.dwFlags = STARTF_USESHOWWINDOW;
        si.wShowWindow = TRUE;

        PROCESS_INFORMATION pi;
        ZeroMemory(&pi, sizeof(pi));

		DWORD dwExitCode;

        char exec_file[] = TEXT("kernel_handle_inherit.exe"); //

        if (!CreateProcess(exec_file, //执行模块
                           cmd,       //命令行参数
                           NULL,
                           NULL,
                           TRUE,               //继承父进程的句柄表
                           CREATE_NEW_CONSOLE, // 为新进程创建一个新的控制台窗口
                           NULL,
                           NULL,
                           &si,
                           &pi))
        {
            perror("CreateProcess Error");
            exit(1);
        }
        else
        {
        	//尽早关闭子进程的主线程
            ::CloseHandle(pi.hThread);
            
            // 等待子进程结束
            WaitForSingleObject(pi.hProcess, INFINITE);
            
            // 获取退出码
            GetExitCodeProcess(pi.hProcess,&dwExitCode);
            
            // 关闭子进程,使用计数-1,系统释放进程内核对象
            ::CloseHandle(pi.hProcess);
        }

4.1 运行分离的子进程

不等待子进程关闭:

		PROCESS_INFORMATION pi;
        if (!CreateProcess(,,,&pi))
        {
            perror("CreateProcess Error");
            exit(1);
        }
        else
        {
        	//尽早关闭子进程的主线程
            ::CloseHandle(pi.hThread);
            // 关闭子进程,使用计数-1,不影响系统释放进程内核对象(否则将产生僵尸进程)
            ::CloseHandle(pi.hProcess);
        }