os实验指导书(新)1(2) 下载本文

内容发布更新时间 : 2024/12/22 21:37:45星期一 下面是文章的全部内容请认真阅读。

// 创建传递过来的进程的克隆过程并赋于其ID值 void StartClone(int nCloneID) {

// 提取用于当前可执行文件的文件名 TCHAR szFilename[MAX_PATH] ;

GetModuleFileName(NULL, szFilename, MAX_PATH) ;

// 格式化用于子进程的命令行并通知其EXE文件名和克隆ID TCHAR szCmdLine[MAX_PATH]; sprintf(szCmdLine,\ // 用于子进程的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] , \ }

//第二次修改:nClone=0; // 显示进程位置

16

std :: cout << \ << \ << std :: endl; // 检查是否有创建子进程的需要 const int c_nCloneMax=5; if (nClone < c_nCloneMax) { // 发送新进程的命令行和克隆号 StartClone(++nClone) ; }

// 等待响应键盘输入结束进程 getchar();

return 0; }

清单2-2 父子进程的简单通信及终止进程的示例程序 // procterm项目

# include # include # include

static LPCTSTR g_szMutexName = \

// 创建当前进程的克隆进程的简单方法 void StartClone() {

// 提取当前可执行文件的文件名 TCHAR szFilename[MAX_PATH] ;

GetModuleFileName(NULL, szFilename, MAX_PATH) ;

// 格式化用于子进程的命令行,字符串“child”将作为形参传递给子进程的main函数 TCHAR szCmdLine[MAX_PATH] ;

//实验2-2步骤3:将下句中的字符串child改为别的字符串,重新编译执行,执行前请先保存已经完成的工作 sprintf(szCmdLine, \

// 子进程的启动信息结构 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 ) ; // 返回的进程信息

17

// 释放指向子进程的引用 if (bCreateOK) {

CloseHandle(pi.hProcess) ; CloseHandle(pi.hThread) ; } }

void Parent() {

// 创建“自杀”互斥程序体

HANDLE hMutexSuicide=CreateMutex( NULL, // 缺省的安全性 TRUE, // 最初拥有的 g_szMutexName) ; // 互斥体名称 if (hMutexSuicide != NULL) {

// 创建子进程

std :: cout << \ StartClone() ;

// 指令子进程“杀”掉自身

std :: cout << \ //等待父进程的键盘响应 getchar() ; //释放互斥体的所有权,这个信号会发送给子进程的WaitForSingleObject过程 ReleaseMutex(hMutexSuicide) ;

// 消除句柄

CloseHandle(hMutexSuicide) ; } }

void Child() {

// 打开“自杀”互斥体

HANDLE hMutexSuicide = OpenMutex( SYNCHRONIZE, // 打开用于同步 FALSE, // 不需要向下传递 g_szMutexName) ; // 名称 if (hMutexSuicide != NULL) {

// 报告我们正在等待指令

std :: cout <<\ //子进程进入阻塞状态,等待父进程通过互斥体发来的信号 WaitForSingleObject(hMutexSuicide, INFINITE) ;

//实验2-2步骤5:将上句改为WaitForSingleObject(hMutexSuicide, 0) ,重新编译执行

// 准备好终止,清除句柄

std :: cout << \ CloseHandle(hMutexSuicide) ; } }

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

18

// 决定其行为是父进程还是子进程

if (argc>1 && :: strcmp(argv[1] , \ {

Child() ; } else {

Parent() ; }

return 0; }

19

实验三 进程同步的经典算法

背景知识

Windows 2000提供的常用对象可分成三类:核心应用服务、线程同步和线程间通讯。其中,开发人员可以使用线程同步对象来协调线程和进程的工作,以使其共享信息并执行任务。此类对象包括互锁数据、临界段、事件、互斥体和信号等。

多线程编程中关键的一步是保护所有的共享资源,工具主要有互锁函数、临界段和互斥体等;另一个实质性部分是协调线程使其完成应用程序的任务,为此,可利用内核中的事件对象和信号。

在进程内或进程间实现线程同步的最方便的方法是使用事件对象,这一组内核对象允许一个线程对其受信状态进行直接控制 (见表3-1) 。

而互斥体则是另一个可命名且安全的内核对象,其主要目的是引导对共享资源的访问。拥有单一访问资源的线程创建互斥体,所有想要访问该资源的线程应该在实际执行操作之前获得互斥体,而在访问结束时立即释放互斥体,以允许下一个等待线程获得互斥体,然后接着进行下去。

与事件对象类似,互斥体容易创建、打开、使用并清除。利用CreateMutex() API可创建互斥体,创建时还可以指定一个初始的拥有权标志,通过使用这个标志,只有当线程完成了资源的所有的初始化工作时,才允许创建线程释放互斥体。

表3-1 用于管理事件对象的API API名称 CreateEvent() OpenEvent() SetEvent() ResetEvent() PulseEvent() 描述 在内核中创建一个新的事件对象。此函数允许有安全性设置、手工还是自动重置的标志以及初始时已接受还是未接受信号状态的标志 创建对已经存在的事件对象的引用。此API函数需要名称、继承标志和所需的访问级别 将手工重置事件转化为已接受信号状态 将手工重置事件转化为非接受信号状态 将自动重置事件对象转化为已接受信号状态。当系统释放所有的等待它的线程时此种转化立即发生

为了获得互斥体,首先,想要访问调用的线程可使用OpenMutex() API来获得指向对象的句柄;然后,线程将这个句柄提供给一个等待函数。当内核将互斥体对象发送给等待线程时,就表明该线程获得了互斥体的拥有权。当线程获得拥有权时,线程控制了对共享资源的访问——必须设法尽快地放弃互斥体。放弃共享资源时需要在该对象上调用ReleaseMute() API。然后系统负责将互斥体拥有权传递给下一个等待着的线程 (由到达时间决定顺序) 。

20