Windows 进程管理

1、实验目的

(1)学会使用 VC 编写基本的 Win32 Consol Application(控制台应用程序)。
(2)通过创建进程、观察正在运行的进程和终止进程的程序设计和调试操作,进一步熟悉操作系统的进程概念,理解 Windows 进程的“一生”。
(3)通过阅读和分析实验程序,学习创建进程、观察进程、终止进程以及父子进程同步的基本程序设计方法。

2、背景知识

Windows 所创建的每个进程都从调用 CreateProcess() API 函数开始,该函数的任务是在对象管理器子系统内初始化进程对象。每一进程都以调用ExitProcess() 或TerminateProcess() API 函数终止。通常应用程序的框架负责调用 ExitProcess() 函数。对于 C++ 运行库来说,这一调用发生在应用程序的 main() 函数返回之后。

(1) 创建进程
CreateProcess() 调用的核心参数是可执行文件运行时的文件名及其命令行。表 1-1 详细地列出了每个参数的类型和名称。

表 1-1 CreateProcess() 函数的参数
这里写图片描述

可以指定第一个参数,即应用程序的名称,其中包括相对于当前进程的当前目录的全路径或者利用搜索方法找到的路径;lpCommandLine 参数允许调用者向新应用程序发送数据;接下来的三个参数与进程和它的主线程以及返回的指向该对象的句柄的安全性有关。
然后是标志参数,用以在 dwCreationFlags 参数中指明系统应该给予新进程什么行为。经常使用的标志是 CREATE_SUSPNDED,告诉主线程立刻暂停。当准备好时,应该使用 ResumeThread() API 来启动进程。另一个常用的标志是 CREATE_NEW_CONSOLE,告诉新进程启动自己的控制台窗口, 而不是利用父窗口。这一参数还允许设置进程的优先级,用以向系统指明,相对于系统中所有其他的活动进程来说,给此进程多少 CPU 时间。
接着是 CreateProcess() 函数调用所需要的三个通常使用缺省值的参数。第一个参数是

lpEnvironment 参数,指明为新进程提供的环境;第二个参数是 lpCurrentDirectory,可用于向主创进程发送与缺省目录不同的新进程使用的特殊的当前目录;第三个参数是 STARTUPINFO 数据结构所必需的,用于在必要时指明新应用程序的主窗口的外观。
CreateProcess() 的最后一个参数是用于新进程对象及其主线程的句柄和 ID 的返回值缓冲区。以 PROCESS_INFORMATION 结构中返回的句柄调用 CloseHandle() API 函数是重要的,因为如果不将这些句柄关闭的话,有可能危及主创进程终止之前的任何未释放的资源。

3、实验内容和步骤

(1)编写基本的 Win32 Consol Application
步骤 1:登录进入 Windows 系统,启动 VC++ 6.0。
步骤 2:在“FILE”菜单中单击“NEW”子菜单,在“projects”选项卡中选择“Win32 Consol
Application”,然后在“Project name”处输入工程名,在“Location” 处输入工程目录。创建一个新的控制台应用程序工程。
步骤 3:在“FILE”菜单中单击“NEW”子菜单,在“Files”选项卡中选择“C++ Source File”, 然后在“File” 处输入 C/C++源程序的文件名。
步骤 4:将清单 1-1 所示的程序清单复制到新创建的 C/C++源程序中。编译成可执行文件。步骤 5:在“开始”菜单中单击“程序”-“附件”-“命令提示符”命令,进入 Windows“命
令提示符”窗口,然后进入工程目录中的 debug 子目录,执行编译好的可执行程序,列出运行结果
(如果运行不成功,则可能的原因是什么?)

(2) 创建进程
本实验显示了创建子进程的基本框架。该程序只是再一次地启动自身,显示它的系统进程 ID
和它在进程列表中的位置。
步骤 1:创建一个“Win32 Consol Application”工程,然后拷贝清单 1-2 中的程序,编译成可执行文件。
步骤 2:在“命令提示符”窗口运行步骤 1 中生成的可执行文件,列出运行结果。按下ctrl+alt+del,调用 windows 的任务管理器,记录进程相关的行为属性。
步骤 3:在“命令提示符”窗口加入参数重新运行生成的可执行文件,列出运行结果。按下ctrl+alt+del,调用 windows 的任务管理器,记录进程相关的行为属性。
步骤 4:修改清单 1-2 中的程序,将 nClone 的定义和初始化方法按程序注释中的修改方法进行修改,编译成可执行文件(执行前请先保存已经完成的工作)。再按步骤 2 中的方式运行,看看结

果会有什么不一样。列出行结果。从中你可以得出什么结论?说明 nClone 的作用。 变量的定义和初始化方法(位置)对程序的执行结果有影响吗?为什么?
(3) 父子进程的简单通信及终止进程
步骤 1:创建一个“Win32 Consol Application”工程,然后拷贝清单 1-3 中的程序,编译成可执行文件。
步骤 2:在 VC 的工具栏单击“Execute Program”(执行程序) 按钮,或者按 Ctrl + F5 键,或者在“命令提示符”窗口运行步骤 1 中生成的可执行文件,列出运行结果。
步骤 3:按源程序中注释中的提示,修改源程序 1-3,编译执行(执行前请先保存已经完成的工作),列出运行结果。在程序中加入跟踪语句,或调试运行程序,同时参考 MSDN 中的帮助文件
CreateProcess()的使用方法,理解父子进程如何传递参数。给出程序执行过程的大概描述。
步骤 4:按源程序中注释中的提示,修改源程序 1-3,编译执行,列出运行结果。
步骤 5 : 参考 MSDN 中 的 帮 助 文 件 CreateMutex() 、 OpenMutex() 、 ReleaseMutex() 和
WaitForSingleObject()的使用方法,理解父子进程如何利用互斥体进行同步的。给出父子进程同步过程的一个大概描述。

4、结果分析

  1. 实验内容和步骤
    (1)编写基本的Win32 Console Application
    与实验要求不同的是我用的IDE是CodeBlocks,要求主函数返回类型必须为int,所以要添加int ,正常执行添加return 0;改完之后正常打印“Hello, Win32 Consol Application”。

(2)创建进程
步骤2. 运行结果:
Process ID:3960,Clone ID:0
Process ID:6692,Clone ID:1
Process ID:6484,Clone ID:2
Process ID:4412,Clone ID:3
Process ID:6964,Clone ID:4
Process ID:5336,Clone ID:5

进程属性:

步骤4:
按照注释中的修改位置1改动,运行结果还是创建了5个子进程
按照注释中的修改位置2改动,运行结果就会持续不断的创建进程,没有停止的趋势。

类似这样,无数多个子进程。

分析:
第二次修改后,每次调用StartClone(++nClone);前nClone都为0,所以每次调用完毕后nClone等于1,程序不会运行到最后,也不会结束,只能通过强制终止。

(3)父子进程的简单通信及终止进程
步骤3:
sprintf(szCmdLine, “\”%s\” child”, szFilename) ;
// 子进程的启动信息结构
STARTUPINFO si;
ZeroMemory(&si,sizeof(si)) ;
si.cb = sizeof(si) ; // 应当是此结构的大小
// 返回的用于子进程的进程信息
PROCESS_INFORMATION pi;
// 用同样的可执行文件名和命令行创建进程,并指明它是一个子进程
BOOL bCreateOK=CreateProcess(
szFilename, // 产生的应用程序的名称 (本 EXE 文件)
szCmdLine, // 告诉我们这是一个子进程的标志
NULL, // 用于进程的缺省的安全性
NULL, // 用于线程的缺省安全性
FALSE, // 不继承句柄
CREATE_NEW_CONSOLE, //创建新窗口
NULL, // 新环境
NULL, // 当前目录
&si, // 启动信息结构
&pi ) ; // 返回的进程信息

在利用CreateProcess函数创建进程时,命令行中需要szCmdLine来作为argv的参数创建这个子进程,两个参数是分开的, sprintf()函数中的”\”%s\” child”,%s填入的是可执行文件的路径,child则是创建子进程。两个参数之间必须加上空格才正确。

步骤3:第一次修改后,创建进程的第二个参数不是child了,因此子进程进入的是parent函数,所以程序在不断的创建子进程,没有停止的趋势。
创建进程原理:
BOOL CreateProcess(
LPCWSTR pszImageName, ///可执行模块的绝对路径,也可以是相对路径
LPCWSTR pszCmdLine, /// 指定被运行的模块的命令行
LPSECURITY_ATTRIBUTES psaProcess,
LPSECURITY_ATTRIBUTES psaThread,
BOOL fInheritHandles,
DWORD fdwCreate,
LPVOID pvEnvironment,
LPWSTR pszCurDir,
LPSTARTUPINFOW psiStartInfo,
LPPROCESS_INFORMATION pProcInfo
);
成功创建进程的话会返回0.

步骤4:第二次修改后,WaitForSingleObject(hMutexSuicide, INFINITE);INFINITE变成了0,所以会进入临界区,继续执行子进程,最后关闭子进程并关闭句柄。
整个程序要体现的是父子进程通信,父子进程的同步是有PV操作完成的。
P操作:WaitForSingleObject(hMutexSuicide, INFINITE);
V操作:ReleaseMutex(hMutexSuicide)

HANDLE hMutexSuicide = CreateMutex(NULL,TRUE,g_szMutexName);
互斥体实现了“互相排斥”(mutual exclusion)同步的简单形式(所以名为互斥体(mutex))。
互斥体禁止多个线程同时进入受保护的代码“临界区”(critical section)。
因此,在任意时刻,只有一个线程被允许进入这样的代码保护区。任何线程在进入临界区之前,
必须获取(acquire)与此区域相关联的互斥体的所有权。如果已有另一线程拥有了临界区的互斥体,其他线程就不能再进入其中。这些线程必须等待,直到当前的属主线程释放(release)该互斥体。

ReleaseMutex(hMutexSuicide);
一个线程释放了互斥对象的控制权后,如果其他进程在等待互斥对象置位,则等待的线程可以得到该互斥对象,等待函数返回,互斥对象被新的线程所拥有。并且会发送信号给waitforsingleobject

WaitForSingleObject(hMutexSuicide, INFINITE);
WaitForSingleObject函数用来检测hMutexSuicide事件的信号状态,在某一线程中调用该函数时,线程暂时挂起,如果在挂起的dwMilliseconds毫秒内,线程所等待的对象变为有信号状态,则该函数立即返回;如果超时时间已经到达dwMilliseconds毫秒,但hMutexSuicide所指向的对象还没有变成有信号状态,函数照样返回。
参数dwMilliseconds有两个具有特殊意义的值:0和INFINITE。若为0,则该函数立即返回;若为INFINITE,则线程一直为阻塞状态,直到hHandle所指向的对象变为有信号状态时为止。

OpenMutex() 打开已有的互斥体

5、程序清单


清单 1-1  一个简单的 Windows 控制台应用程序
// hello 项目
# include <iostream> void main()
{
std::cout << “Hello, Win32 Consol Application” << std :: endl ;
}

清单 1-2  创建子进程

#include <windows.h>
#include <iostream>
#include <stdio.h>

//  创建传递过来的进程的克隆过程并赋于其 ID 值
void StartClone(int nCloneID)
{
//  提取用于当前可执行文件的文件名
TCHAR szFilename[MAX_PATH] ; GetModuleFileName(NULL, szFilename, MAX_PATH) ;

// 格式化用于子进程的命令行并通知其 EXE 文件名和克隆 ID TCHAR szCmdLine[MAX_PATH];
sprintf(szCmdLine,"\"%s\" %d",szFilename,nCloneID);

//  用于子进程的 STARTUPINFO 结构
STARTUPINFO si;
ZeroMemory(&si , sizeof(si) ) ;
si.cb = sizeof(si) ;    //  必须是本结构的大小

//  返回的用于子进程的进程信息
PROCESS_INFORMATION pi;

//  利用同样的可执行文件和命令行创建进程,并赋于其子进程的性质
BOOL bCreateOK=::CreateProcess(
szFilename, //  产生这个 EXE 的应用程序的名称
szCmdLine,  //  告诉其行为像一个子进程的标志
NULL,   //  缺省的进程安全性
NULL,   //  缺省的线程安全性
FALSE,  //  不继承句柄
CREATE_NEW_CONSOLE, //  使用新的控制台
NULL,   //  新的环境
NULL,   //  当前目录
&si,    //  启动信息
&pi) ;  //  返回的进程信息

//  对子进程释放引用
if (bCreateOK)
{
CloseHandle(pi.hProcess) ; CloseHandle(pi.hThread) ;
}
}

int main(int argc, char* argv[] )
{
//  确定派生出几个进程,及派生进程在进程列表中的位置
int nClone=0;
//修改语句:int nClone;

//第一次修改:nClone=0; if (argc > 1)
{
//  从第二个参数中提取克隆 ID
:: sscanf(argv[1] , "%d" , &nClone) ;
}

//第二次修改:nClone=0;

//  显示进程位置
std :: cout << "Process ID:" << :: GetCurrentProcessId()
<< ", Clone ID:" << nClone
<< std :: endl;
//  检查是否有创建子进程的需要
const int c_nCloneMax=5; if (nClone < c_nCloneMax)
{
//  发送新进程的命令行和克隆号
StartClone(++nClone) ;
}
//  等待响应键盘输入结束进程
getchar(); return 0;
}

清单 1-3  父子进程的简单通信及终止进程的示例程序
// procterm 项目
# include <windows.h>
# include <iostream>
# include <stdio.h>
static LPCTSTR g_szMutexName = "w2kdg.ProcTerm.mutex.Suicide" ;

//  创建当前进程的克隆进程的简单方法
void StartClone()
{
//  提取当前可执行文件的文件名
TCHAR szFilename[MAX_PATH] ; GetModuleFileName(NULL, szFilename, MAX_PATH) ;

//  格式化用于子进程的命令行,字符串“child”将作为形参传递给子进程的 main 函数
TCHAR szCmdLine[MAX_PATH] ;
//实验 1-3 步骤 3:将下句中的字符串 child 改为别的字符串,重新编译执行,执行前请先保存已经完成的工作
sprintf(szCmdLine, "\"%s\"child" , szFilename) ;

//  子进程的启动信息结构
STARTUPINFO si;
ZeroMemory(&si,sizeof(si)) ;
si.cb = sizeof(si) ;    //  应当是此结构的大小

//  返回的用于子进程的进程信息
PROCESS_INFORMATION pi;

//  用同样的可执行文件名和命令行创建进程,并指明它是一个子进程
BOOL bCreateOK=CreateProcess(
szFilename, //  产生的应用程序的名称 (本 EXE 文件)
szCmdLine,  //  告诉我们这是一个子进程的标志
NULL,   //  用于进程的缺省的安全性
NULL,   //  用于线程的缺省安全性
FALSE,  //  不继承句柄 CREATE_NEW_CONSOLE,       //创建新窗口 NULL,   //  新环境
NULL,   //  当前目录
&si,    //  启动信息结构
&pi ) ; //  返回的进程信息

//  释放指向子进程的引用
if (bCreateOK)
{
CloseHandle(pi.hProcess) ; CloseHandle(pi.hThread) ;
}
}

void Parent()
{
//  创建“自杀”互斥程序体
HANDLE hMutexSuicide=CreateMutex(

NULL,   //  缺省的安全性
TRUE,   //  最初拥有的
g_szMutexName) ;    //  互斥体名称
if (hMutexSuicide != NULL)
{
//  创建子进程
std :: cout << "Creating the child process." << std :: endl; StartClone() ;
//  指令子进程“杀”掉自身
std :: cout << "Telling the child process to quit. "<< std :: endl;
//等待父进程的键盘响应getchar() ;
//释放互斥体的所有权,这个信号会发送给子进程的 WaitForSingleObject 过程
ReleaseMutex(hMutexSuicide) ;
//  消除句柄
CloseHandle(hMutexSuicide) ;
}
}

void Child()
{
//  打开“自杀”互斥体
HANDLE hMutexSuicide = OpenMutex( SYNCHRONIZE,  //  打开用于同步
FALSE,  //  不需要向下传递
g_szMutexName) ;    //  名称
if (hMutexSuicide != NULL)
{
//  报告我们正在等待指令
std :: cout <<"Child waiting for suicide instructions. " << std :: endl;

//子进程进入阻塞状态,等待父进程通过互斥体发来的信号WaitForSingleObject(hMutexSuicide, INFINITE) ;
//实验 1-3 步骤 4:将上句改为 WaitForSingleObject(hMutexSuicide, 0) ,重新编译执行

//  准备好终止,清除句柄
std :: cout << "Child quiting." << std :: endl; CloseHandle(hMutexSuicide) ;
}
}

int main(int argc, char* argv[] )
{
//  决定其行为是父进程还是子进程
if (argc>1 && :: strcmp(argv[1] , "child" )== 0)
{


}
else
{

}

Child() ;



Parent() ;

return 0;

}

猜你喜欢

转载自blog.csdn.net/feng_zhiyu/article/details/80806048