子进程

创建进程

https://docs.microsoft.com/en-us/windows/desktop/procthread/creating-processes

CreateProcess函数创建一个新进程,该进程独立于创建进程运行。但是,为简单起见,该关系称为父子关系。 以下代码演示了如何创建进程。

如果CreateProcess成功,它将返回一个PROCESS_INFORMATION结构,其中包含新进程及其主线程的句柄和标识符。 尽管您指定了安全描述符,其可以限制访问权限,但创建的线程和进程句柄具有完全访问权限。 当您不再需要这些句柄时,请使用CloseHandle函数关闭它们。

您还可以使用CreateProcessAsUser或CreateProcessWithLogonW函数创建进程。 这允许您指定进程将在其中执行的用户帐户的安全上下文。

通过STARTUPINFO 设置窗口属性

父进程可以指定与其子进程的主窗口关联的属性。 CreateProcess函数将指向STARTUPINFO结构的指针作为其参数之一。使用此结构的成员指定子进程主窗口的特征。 dwFlags成员包含一个位域,用于确定使用该结构的其他成员。这允许您为窗口属性的任何子集指定值。系统使用您未指定的属性的默认值。 dwFlags成员还可以强制在新进程初始化期间显示反馈光标。

对于GUI进程,STARTUPINFO结构指定在新进程第一次调用CreateWindow和ShowWindow函数以创建和显示重叠窗口时使用的默认值。可以指定以下默认值:

1. CreateWindow创建的窗口的宽度和高度(以像素为单位)。

2. CreateWindow创建的窗口的屏幕坐标中的位置。

3. ShowWindow的nCmdShow参数。

对于控制台进程,仅在创建新控制台时使用STARTUPINFO结构指定窗口属性(使用带有CREATE_NEW_CONSOLE的CreateProcess或使用AllocConsole函数)。 STARTUPINFO结构可用于指定以下控制台窗口属性:

1. 新控制台窗口的大小,在字符单元格中。

2. 屏幕坐标中新控制台窗口的位置。

3. 新控制台屏幕缓冲区的大小(以字符为单位)。

4. 新控制台屏幕缓冲区的文本和背景颜色属性。

5. 新控制台窗口的标题。

创建子进程并重定向输入和输出

https://docs.microsoft.com/en-us/windows/desktop/procthread/creating-a-child-process-with-redirected-input-and-output

本主题中的示例演示如何使用控制台进程中的CreateProcess函数创建子进程。它还演示了一种使用匿名管道重定向子进程的标准输入和输出句柄的技术。请注意,命名管道也可用于重定向进程I / O.

CreatePipe函数使用SECURITY_ATTRIBUTES结构为两个管道的读取和写入端创建可继承的句柄。一个管道的读取端用作子进程的标准输入,另一个管道的写入端是子进程的标准输出。这些管道句柄在STARTUPINFO结构中指定,这使它们成为子进程继承的标准句柄。

父进程使用这两个管道的相反端写入子进程的输入并从子进程的输出中读取。如STARTUPINFO结构中所指定的,这些句柄也是可继承的。但是,不得继承这些句柄。因此,在创建子进程之前,父进程使用SetHandleInformation函数来确保不能继承子进程的标准输入的写句柄和子进程的标准输入的读句柄。有关更多信息,请参阅管道。以下是父进程的代码。它需要一个命令行参数:文本文件的名称。

进程句柄和ID

当CreateProcess函数创建新进程时,将返回新进程及其主线程的句柄。这些句柄是使用完全访问权限创建的,并且 - 受安全访问检查限制 - 可以在接受线程或进程句柄的任何函数中使用。这些句柄可以由子进程继承,具体取决于创建它们时指定的继承标志。句柄在关闭之前有效,即使在它们所代表的进程或线程已终止之后也是如此。

CreateProcess函数还返回一个标识符,该标识符唯一标识整个系统中的进程。进程可以使用GetCurrentProcessId函数来获取其自己的进程标识符(也称为进程ID或PID)。标识符从创建进程到终止进程之间有效。进程可以使用Process32First函数来获取其父进程的进程标识符。

如果您有进程标识符,则可以通过调用OpenProcess函数来获取进程句柄。 OpenProcess使您可以指定句柄的访问权限以及是否可以继承。进程可以使用GetCurrentProcess函数来检索其自己的进程对象的伪句柄。此伪句柄仅对调用进程有效;它不能被继承或复制以供其他进程使用。要获得该过程的真实句柄,请调用DuplicateHandle函数。

继承枚举

所有用户都具有对系统中进程列表的读访问权,并且有许多不同的函数可以枚举活动进程。您应该使用的功能取决于所需的平台等因素。以下函数用于枚举进程。

EnumProcesses,检索系统中每个进程对象的进程标识符。

Process32First,检索有关系统快照中遇到的第一个进程的信息。 Process32Next检索有关系统快照中记录的下一个进程的信息。

WTSEnumerateProcesses检索有关指定终端服务器上的活动进程的信息。

toolhelp函数和EnumProcesses枚举所有进程。要列出在特定用户帐户中运行的进程,请使用WTSEnumerateProcesses并过滤用户SID。您可以筛选会话ID以隐藏在其他终端服务器会话中运行的进程。

您还可以通过使用TokenUser调用OpenProcess,OpenProcessToken和GetTokenInformation,按用户帐户筛选进程,而不管枚举函数如何,除非已授予访问权限,否则无法打开受安全描述符保护的进程。

获得额外的进程信息

获取有关进程的信息有多种功能。其中一些函数只能用于调用进程,因为它们不会将进程句柄作为参数。您可以使用带有进程句柄的函数来获取有关其他进程的信息。

1. 要获取当前进程的命令行字符串,请使用GetCommandLine函数。

2. 要检索在创建当前进程时指定的STARTUPINFO结构,请使用GetStartupInfo函数。

3. 要从可执行标头获取版本信息,请使用GetProcessVersion函数。

4. 要获取包含进程代码的可执行文件的完整路径和文件名,请使用GetModuleFileName函数。

5. 要获取正在使用的图形用户界面(GUI)对象的句柄数,请使用GetGuiResources函数。

6. 要确定是否正在调试进程,请使用IsDebuggerPresent函数。

7. 要检索进程执行的所有I / O操作的记帐信息,请使用GetProcessIoCounters函数。

继承

子进程可以从其父进程继承多个属性和资源。您还可以阻止子进程从其父进程继承属性。可以继承以下内容:C

1. reateFile函数返回的打开句柄。这包括文件句柄,控制台输入缓冲区,控制台屏幕缓冲区,命名管道,串行通信设备和邮件槽。

2. 打开进程,线程,互斥锁,事件,信号量,命名管道,匿名管道和文件映射对象的句柄。它们分别由CreateProcess,CreateThread,CreateMutex,CreateEvent,CreateSemaphore,CreateNamedPipe,CreatePipe和CreateFileMapping函数返回。

3. 环境变量。

4. 当前目录。

5. 控制台,除非进程已分离或创建了新控制台。子控制台进程还可以继承父级的标准句柄,以及访问输入缓冲区和活动屏幕缓冲区。

6. 错误模式,由SetErrorMode函数设置。

7. 处理器亲和掩码。

8. 与job的关联。

子进程不继承以下内容:

1. 优先级。

2. LocalAlloc,GlobalAlloc,HeapCreate和HeapAlloc返回的句柄。伪句柄,如GetCurrentProcess或GetCurrentThread函数返回的句柄。这些句柄仅对调用进程有效。

3. LoadLibrary函数返回的DLL模块句柄。

4. GDI或USER处理,例如HBITMAP或HMENU。

继承句柄

子进程可以继承其父进程的一些句柄,但不能继承其他句柄。要使句柄继承,您必须做两件事:

1. 指定在创建,打开或复制句柄时继承句柄。创建函数通常使用SECURITY_ATTRIBUTES结构的bInheritHandle成员来实现此目的。 DuplicateHandle使用bInheritHandles参数。通过在调用CreateProcess函数时将bInheritHandles参数设置为TRUE,指定要继承可继承句柄。此外,要继承标准输入,标准输出和标准错误句柄,STARTUPINFO结构的dwFlags成员必须包含STARTF_USESTDHANDLES。

继承的句柄所引用的对象,子进程与父进程相同。它还具有相同的值和访问权限。因此,当一个进程更改对象的状态时,更改会影响这两个进程。要使用句柄,子进程必须检索句柄值并“知道”它所引用的对象。通常,父进程通过其命令行,环境块或某种形式的进程间通信将此信息传递给子进程。

如果进程具有您不希望由子进程继承的可继承打开句柄,则DuplicateHandle函数很有用。在这种情况下,使用DuplicateHandle打开无法继承的句柄的副本,然后使用CloseHandle函数关闭可继承的句柄。您还可以使用DuplicateHandle函数打开无法继承的句柄的可继承副本。

继承环境变量

默认情况下,子进程继承其父进程的环境变量。但是,CreateProcess使父进程能够指定不同的环境变量块。有关更多信息,请参阅Environment Variables.。

继承当前目录

GetCurrentDirectory函数检索调用进程的当前目录。子进程默认继承其父进程的当前目录。但是,CreateProcess 使父进程能够为子进程指定不同的当前目录。要更改调用进程的当前目录,请使用SetCurrentDirectory 函数。

环境变量

每个进程都有一个包含一组环境变量及其值的环境块。有两种类型的环境变量:用户环境变量(为每个用户设置)和系统环境变量(为每个人设置)。

默认情况下,子进程继承其父进程的环境变量。由命令处理器启动的程序继承命令处理器的环境变量。要为子进程指定不同的环境,请创建一个新的环境块并将指针作为参数传递给CreateProcess函数。命令处理器提供set命令以显示其环境块或创建新的环境变量。您还可以通过从“控制面板”中选择“系统”,选择“高级系统设置”,然后单击“环境变量”来查看或修改环境变量。每个环境块包含以下格式的环境变量:

Var1 = Value1 \ 0

Var2 = Value2 \ 0

Var3 = Value3 \ 0 ...

VarN = ValueN \ 0 \ 0

环境变量的名称不能包含等号(= )。

GetEnvironmentStrings函数返回指向调用进程的环境块的指针。这应该被视为只读块;不要直接修改它。而是使用SetEnvironmentVariable函数来更改环境变量。完成从GetEnvironmentStrings获取的环境块后,调用FreeEnvironmentStrings函数以释放该块。

调用SetEnvironmentVariable对系统环境变量没有影响。若要以编程方式添加或修改系统环境变量,将它们添加到HKEY_LOCAL_MACHINE \ System \ CurrentControlSet \ Control \ Session Manager \ Environment注册表项,然后广播WM_SETTINGCHANGE消息,并将lParam设置为字符串“Environment”。这允许应用程序(如shell)获取更新。

用户定义的环境变量的最大大小为32,767个字符。环境块的大小没有技术限制。但是,取决于用于访问块的机制,存在实际限制。例如,批处理文件无法设置长度超过最大命令行长度的变量。

Windows Server 2003和Windows XP:进程的环境块的最大大小为32,767个字符。从Windows Vista和Windows Server 2008开始,对环境块的大小没有技术限制。

GetEnvironmentVariable函数确定是否在调用进程的环境中定义了指定的变量,如果是,则确定其值是什么。要检索给定用户的环境块副本,请使用CreateEnvironmentBlock函数。要展开环境变量字符串,请使用ExpandEnvironmentStrings函数。

终止一个进程

终止进程具有以下结果:

1. 进程中的任何剩余线程都标记为终止。

2. 进程分配的任何资源都将被释放。

3. 所有内核对象都已关闭。

4. 进程代码将从内存中删除。

5. 进程退出代码已设置。

6. 发信号通知过程对象。

当进程终止时,内核对象的打开句柄会自动关闭,而对象本身会一直存在,直到它们的所有打开句柄都关闭为止。因此,如果另一个进程具有打开的句柄,则在使用它的进程终止后,该对象将保持有效。

GetExitCodeProcess函数返回进程的终止状态。进程正在执行时,其终止状态为STILL_ACTIVE。当进程终止时,其终止状态从STILL_ACTIVE变为进程的退出代码。当进程终止时,进程对象的状态变为信号,释放任何一直等待进程终止的线程。

当系统终止进程时,它不会终止进程创建的任何子进程。终止进程不会为WH_CBT挂钩过程生成通知。

使用 SetProcessShutdownParameters函数指定系统关闭时进程终止的某些方面,例如当进程应相对于系统中的其他进程终止。

进程如何终止

进程执行直到发生以下事件之一:

1. 进程的任何线程调用ExitProcess函数。请注意,如果进程的主线程返回,则C运行时库(CRT)的某些实现会调用ExitProcess。

2. 该进程的最后一个线程终止。

3. 任何线程使用进程句柄调用TerminateProcess函数。

4. 对于控制台进程,当控制台收到CTRL + C或CTRL + BREAK信号时,默认控制台控制处理程序会调用ExitProcess。

5. 用户关闭系统或注销。

除非其线程处于已知状态,否则不要终止进程。如果线程正在等待内核对象,则在等待完成之前不会终止它。这可能导致应用程序停止响应。

主线程可以通过在导致进程终止之前指示其它线程调用ExitThread来避免终止其他线程。主线程之后仍然可以调用ExitProcess以确保所有线程都被终止。

进程的退出代码是在对ExitProcess或TerminateProcess的调用中指定的值,或者由进程的main或WinMain函数返回的值。如果进程因致命异常而终止,则退出代码是导致终止的异常值。此外,此值用作发生异常时正在执行的所有线程的退出代码。

如果进程由ExitProcess终止,则系统将调用每个附加DLL的入口点函数,其值指示进程正在从DLL中分离。当TerminateProcess终止进程时,不会通知DLL。

如果进程被TerminateProcess终止,则进程的所有线程立即终止,无法运行其他代码。这意味着线程不会在终止处理程序块中执行代码。此外,没有附加的DLL被通知进程正在detach。如果您需要让一个进程终止另一个进程,则以下步骤提供了更好的解决方案:

1. 让两个进程都调用RegisterWindowMessage函数来创建私有消息。

2. 一个进程可以通过使用BroadcastSystemMessage函数广播私有消息来终止另一个进程,如下所示:

DWORD dwRecipients = BSM_APPLICATIONS;
    UINT uMessage = PM_MYMSG;
    WPARAM wParam = 0;
    LPARAM lParam = 0;

    BroadcastSystemMessage( 
        BSF_IGNORECURRENTTASK, // do not send message to this process
        &dwRecipients,         // broadcast only to applications
        uMessage,              // registered private message
        wParam,                // message-specific value
        lParam );              // message-specific value

3. 接收私有消息的进程调用ExitProcess来终止其执行。

ExitProcess,ExitThread,CreateThread,CreateRemoteThread和CreateProcess函数的执行在地址空间中序列化。以下限制适用:

1. 在进程启动和DLL初始化例程期间,可以创建新线程,但在完成进程的DLL初始化之前,它们不会开始执行。

2. 一次只有一个线程可以在DLL初始化或分离例程中。

3. 在没有现在在DLL 初始化,或detach 函数中之前,ExitProcess函数不会返回

进程工作集

程序的工作集是其最近引用的虚拟地址空间中的那些页面的集合。它包括共享和私有数据。共享数据包括包含应用程序执行的所有指令的页面,包括DLL和系统DLL中的指令。随着工作集大小的增加,内存需求也会增加。

进程具有相关的最小工作集大小和最大工作集大小。每次调用CreateProcess时,它都会为进程的保留最小工作集大小。虚拟内存管理器尝试为进程处于活动状态时驻留的最小工作集保留足够的内存,但保留不超过最大大小。

要获取应用程序的工作集的请求的最小和最大大小,请调用GetProcessWorkingSetSize函数。

系统设置默认工作集大小。您还可以使用SetProcessWorkingSetSize函数修改工作集大小。设置这些值并不能保证内存将被保留或驻留。请注意请求过大的最小或最大工作集大小,因为这样做会降低系统性能。

要获取进程的工作集的当前或峰值大小,请使用GetProcessMemoryInfo函数。

发布了93 篇原创文章 · 获赞 13 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/qq_18218335/article/details/84262600
今日推荐