各位前辈好!鄙人是软件工程专业大三在读的学生。最近在操作系统的实验课上遇到了一个自己不知道该怎么解决的问题,且我自己也不知道怎么描述才能把问题清楚的展现,希望各位前辈能看完我的调试过程,给我一些建议。
问题起源
操作系统课程的实验指导书版本很旧,由于学校机房的设备限制,只能让我们通过简单的Windows API编程来体会操作系统内部的过程。
实验目的
通过对事件的了解,加深对线程同步的理解。
实验环境
系统版本:Windows 10
处理器:Inter(R) Core(TM) i7-6700 CPU @ 3.40GHz 3.40 GHz
安装内存:8.00GB
系统类型:64位操作系统
安装软件:Visual Studio2019
实验源代码
// event项目
# include <windows.h>
# include <iostream>
// 以下是句柄事件。实际中很可能使用共享的包含文件来进行通讯
static LPCTSTR g_szContinueEvent = "w2kdg.EventDemo.event.Continue";
// 本方法只是创建了一个进程的副本,以子进程模式 (由命令行指定) 工作
BOOL CreateChild()
{
// 提取当前可执行文件的文件名
TCHAR szFilename[MAX_PATH];
::GetModuleFileName(NULL, szFilename, MAX_PATH);
// 格式化用于子进程的命令行,指明它是一个EXE文件和子进程
TCHAR szCmdLine[MAX_PATH];
::sprintf(szCmdLine, "\"%s\"child", szFilename);
// 子进程的启动信息结构
STARTUPINFO si;
::ZeroMemory(reinterpret_cast<void*>(&si), sizeof(si));
si.cb = sizeof(si); // 必须是本结构的大小
// 返回的子进程的进程信息结构
PROCESS_INFORMATION pi;
// 使用同一可执行文件和告诉它是一个子进程的命令行创建进程
BOOL bCreateOK = ::CreateProcess(
szFilename, // 生成的可执行文件名
szCmdLine, // 指示其行为与子进程一样的标志
NULL, // 子进程句柄的安全性
NULL, // 子线程句柄的安全性
FALSE, // 不继承句柄
0, // 特殊的创建标志
NULL, // 新环境
NULL, // 当前目录
&si, // 启动信息结构
&pi); // 返回的进程信息结构
// 释放对子进程的引用
if (bCreateOK)
{
::CloseHandle(pi.hProcess);
::CloseHandle(pi.hThread);
}
return(bCreateOK);
}
// 下面的方法创建一个事件和一个子进程,然后等待子进程在返回前向事件发出信号
void WaitForChild()
{
// create a new event object for the child process
// to use when releasing this process
HANDLE hEventContinue = ::CreateEvent(
NULL, // 缺省的安全性,子进程将具有访问权限
TRUE, // 手工重置事件
FALSE, // 初始时是非接受信号状态
g_szContinueEvent); // 事件名称
if (hEventContinue != NULL)
{
std::cout << "event created " << std::endl;
// 创建子进程
if (::CreateChild())
{
std::cout << " chlid created" << std::endl;
// 等待,直到子进程发出信号
std::cout << "Parent waiting on child." << std::endl;
::WaitForSingleObject(hEventContinue, INFINITE);
::Sleep(1500); // 删去这句试试
std::cout << "parent received the envent signaling from child" << std::endl;
}
// 清除句柄
::CloseHandle(hEventContinue);
hEventContinue = INVALID_HANDLE_VALUE;
}
}
// 以下方法在子进程模式下被调用,其功能只是向父进程发出终止信号
void SignalParent()
{
// 尝试打开句柄
std::cout << "child process begining......" << std::endl;
HANDLE hEventContinue = ::OpenEvent(
EVENT_MODIFY_STATE, // 所要求的最小访问权限
FALSE, // 不是可继承的句柄
g_szContinueEvent); // 事件名称
if (hEventContinue != NULL)
{
::SetEvent(hEventContinue);
std::cout << "event signaled" << std::endl;
}
// 清除句柄
::CloseHandle(hEventContinue);
hEventContinue = INVALID_HANDLE_VALUE;
}
int main(int argc, char* argv[])
{
// 检查父进程或是子进程是否启动
if (argc > 1 && ::strcmp(argv[1], "child") == 0)
{
// 向父进程创建的事件发出信号
::SignalParent();
}
else
{
// 创建一个事件并等待子进程发出信号
::WaitForChild();
::Sleep(1500);
std::cout << "Parent released." << std::endl;
}
return 0;
}
程序的大致流程是:创建一个事件->创建子进程->创建信号对象->父进程接受信号
初次运行
我使用Visual Studio2019来编译并运行源程序,发现程序一执行就陷入死锁(如下所示)。
调试过程与问题发现
于是,我开始一步步调试代码。在main函数的else分支的第一个语句处设置了断点(如下图)。
逐语句执行至WaitForChild函数(如下图)。
但是执行到CreaeteChild()
函数中的
BOOL bCreateOK = ::CreateProcess(
szFilename, // 生成的可执行文件名
szCmdLine, // 指示其行为与子进程一样的标志
NULL, // 子进程句柄的安全性
NULL, // 子线程句柄的安全性
FALSE, // 不继承句柄
0, // 特殊的创建标志
NULL, // 新环境
NULL, // 当前目录
&si, // 启动信息结构
&pi); // 返回的进程信息结构
控制台窗口开始循环出现
event created
child created
Parent waiting on child.
可是根据代码的顺序,
child created
Parent waiting on child.
应该是要在执行完CreateChild()
函数后,才会打印,可当前调试的语句还在CreateChild()
函数中,尚未返回。
继续执行,跳出CreateChild()
后,控制台窗口仍然循环出现
event created
child created
Parent waiting on child.
执行至::WaitForSingleObject(hEventContinue, INFINITE);
由于不能收到信号,::WaitForSingleObject(hEventContinue, INFINITE);
陷入死锁,不能正常返回。以至于后面的语句都无法执行下去。
问题重述
请问各位前辈,我如何才能让父进程接收到子进程的消息,并且得到正常执行顺序的结果呢?