操系统原理课内实习二
第一题:遍历当前系统中的所有进程,并能用树状结构显示进程之间的父子关系
题目需求
在Windows操作系统中,每个运行的程序都是一个进程,每个进程都有一个唯一的标识符,叫做进程ID(Process ID)。进程之间也有一定的关系,比如一个进程可以创建另一个进程,这样就形成了父子关系。我们想要编写一个程序,能够遍历当前系统中的所有进程,并能用树状结构显示出它们之间的父子关系。
实现思路
为了实现这个功能,我们需要使用Windows API提供的一些函数和结构体。具体来说,我们需要以下几个步骤:
- 使用CreateToolhelp32Snapshot函数创建一个快照,包含当前系统中所有的进程信息。
- 使用Process32First和Process32Next函数遍历快照中的每个进程,并将它们的信息存储在自定义的ListNode结构体中。ListNode结构体包含以下几个字段:
- th32ProcessID:存储进程ID。
- th32ParentProcessID:存储父进程ID。
- szExeFile:存储可执行文件路径。
- nextList:存储子进程ID集合。
- 使用map容器将每个ListNode结构体按照th32ProcessID作为键值进行存储和查找。这样可以方便地根据任意一个进程ID找到对应的ListNode结构体。
- 使用递归函数showList来打印出树状结构。showList函数接受两个参数:当前要打印的节点ID和当前节点所在深度。showList函数首先检查当前节点是否已经被打印过(使用另一个map容器hashBool记录),如果没有,则按照深度打印出缩进符号和节点信息,然后遍历当前节点的nextList集合,对每个子节点递归调用showList函数。
细节设计
获得系统快照
// 获取进程快照
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (INVALID_HANDLE_VALUE == hSnapshot)
{
return -1;
}
// 遍历快照中的进程信息
PROCESSENTRY32 pi;
pi.dwSize = sizeof(PROCESSENTRY32); //第一次使用必须初始化成员
BOOL bRet = Process32First(hSnapshot, &pi);
hashMap[-1].th32ParentProcessID = -1;
while (bRet)
{
//do something
bRet = Process32Next(hSnapshot, &pi);bRet = Process32Next(hSnapshot, &pi);
}
数据结构
// 定义进程节点结构体
struct ListNode
{
DWORD th32ProcessID; // 进程 ID
DWORD th32ParentProcessID; // 父进程 ID
CHAR szExeFile[MAX_PATH]; // 进程路径
set<DWORD> nextList; // 子进程 ID 集合
};
// 定义进程节点哈希表和访问标记哈希表
map<DWORD, ListNode> hashMap; //用于存储进程号对应的进程节点
map<DWORD, bool> hashBool; //用于标记节点是否出现过
建立树形结构
遍历进程快照,对于每个进程建立进程节点,并将当前进程ID添加到其父进程节点的子进程ID集合中。当父进程不存在或尚未出现时,将-1作为父进程。
while (bRet)
{
if (hashMap.find(pi.th32ProcessID) == hashMap.end())
{
// 新建进程节点
hashMap[pi.th32ProcessID].th32ProcessID = pi.th32ProcessID;
hashMap[pi.th32ProcessID].th32ParentProcessID = pi.th32ParentProcessID;
for (int i = 0; i < MAX_PATH; i++)
{
hashMap[pi.th32ProcessID].szExeFile[i] = pi.szExeFile[i];
}
// 新建父进程节点(如果不存在)
if (hashMap.find(pi.th32ParentProcessID) == hashMap.end())
{
hashMap[pi.th32ParentProcessID].th32ParentProcessID = -1;
hashMap[pi.th32ParentProcessID].nextList.insert(pi.th32ProcessID);
hashMap[pi.th32ParentProcessID].th32ProcessID = pi.th32ParentProcessID;
hashMap[-1].nextList.insert(pi.th32ParentProcessID);
}
// 否则将当前进程节点加入父进程节点的子进程集合
else if(pi.th32ParentProcessID != pi.th32ProcessID)
{
hashMap[pi.th32ParentProcessID].nextList.insert(pi.th32ProcessID);
}
}
else
{
for (int i = 0; i < MAX_PATH; i++)
{
hashMap[pi.th32ProcessID].szExeFile[i] = pi.szExeFile[i];
}
if (hashMap[pi.th32ProcessID].th32ParentProcessID == -1)
{
for (auto i = hashMap[-1].nextList.begin(); i != hashMap[-1].nextList.end(); i++)
{
if (*i == pi.th32ProcessID)
{
hashMap[-1].nextList.erase(i);
}
}
}
hashMap[pi.th32ProcessID].th32ParentProcessID = pi.th32ParentProcessID;
if (hashMap.find(pi.th32ParentProcessID) == hashMap.end())
{
hashMap[pi.th32ParentProcessID].th32ParentProcessID = -1;
hashMap[pi.th32ParentProcessID].nextList.insert(pi.th32ProcessID);
hashMap[pi.th32ParentProcessID].th32ProcessID = pi.th32ParentProcessID;
hashMap[-1].nextList.insert(pi.th32ParentProcessID);
}
else if (pi.th32ParentProcessID != pi.th32ProcessID)
{
hashMap[pi.th32ParentProcessID].nextList.insert(pi.th32ProcessID);
}
}
if (pi.th32ParentProcessID == pi.th32ProcessID)
{
hashMap[pi.th32ProcessID].th32ParentProcessID = -1;
hashMap[-1].nextList.insert(pi.th32ParentProcessID);
}
bRet = Process32Next(hSnapshot, &pi);
}
递归以树形输出进程
main中遍历调用部分
// 遍历哈希表展示进程信息
hashBool[-1] = true;
for (auto i = hashMap[-1].nextList.begin(); i != hashMap[-1].nextList.end(); i++)
{
DWORD ID = *i;
printf("进程ID = %d ,进程路径 = %s\r\n", hashMap[ID].th32ProcessID, hashMap[ID].szExeFile);
for (auto j = hashMap[ID].nextList.begin(); j != hashMap[ID].nextList.end(); j++)
{
showList(*j, 1);
printf("\n");
}
hashBool[ID] = true;
}
递归函数
// 递归展示子进程信息
void showList(DWORD ID, int dep)
{
if (hashBool.find(ID) != hashBool.end()) return;
hashBool[ID] = true;
string s = "";
for (int i = 0; i < dep; i++)
{
s += "\t\t|"; //根据递归深度调整缩进
}
printf("%s\r\n", s.c_str());
printf("%s--", s.c_str());
printf("进程ID = %d ,进程路径 = %s\r\n", hashMap[ID].th32ProcessID, hashMap[ID].szExeFile);
for (auto i = hashMap[ID].nextList.begin(); i != hashMap[ID].nextList.end(); i++)
{
showList(*i, dep + 1);
}
}
实现代码
#include <iostream>
#include <stdio.h>
#include <windows.h>
#include <Tlhelp32.h>
#include <set>
#include <map>
#include <string>
#include <sstream>
using namespace std;
// 定义进程节点结构体
struct ListNode
{
DWORD th32ProcessID; // 进程 ID
DWORD th32ParentProcessID; // 父进程 ID
CHAR szExeFile[MAX_PATH]; // 进程路径
set<DWORD> nextList; // 子进程 ID 集合
};
// 定义进程节点哈希表和访问标记哈希表
map<DWORD, ListNode> hashMap;
map<DWORD, bool> hashBool;
// 递归展示子进程信息
void showList(DWORD ID, int dep)
{
if (hashBool.find(ID) != hashBool.end()) return;
hashBool[ID] = true;
string s = "";
for (int i = 0; i < dep; i++)
{
s += "\t\t|";
}
printf("%s\r\n", s.c_str());
printf("%s--", s.c_str());
printf("进程ID = %d ,进程路径 = %s\r\n", hashMap[ID].th32ProcessID, hashMap[ID].szExeFile);
for (auto i = hashMap[ID].nextList.begin(); i != hashMap[ID].nextList.end(); i++)
{
showList(*i, dep + 1);
}
}
// 主函数
int main(int argc, char* argv[])
{
// 获取进程快照
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (INVALID_HANDLE_VALUE == hSnapshot)
{
return -1;
}
// 遍历快照中的进程信息
PROCESSENTRY32 pi;
pi.dwSize = sizeof(PROCESSENTRY32); //第一次使用必须初始化成员
BOOL bRet = Process32First(hSnapshot, &pi);
hashMap[-1].th32ParentProcessID = -1;
while (bRet)
{
if (hashMap.find(pi.th32ProcessID) == hashMap.end())
{
// 新建进程节点
hashMap[pi.th32ProcessID].th32ProcessID = pi.th32ProcessID;
hashMap[pi.th32ProcessID].th32ParentProcessID = pi.th32ParentProcessID;
for (int i = 0; i < MAX_PATH; i++)
{
hashMap[pi.th32ProcessID].szExeFile[i] = pi.szExeFile[i];
}
// 新建父进程节点(如果不存在)
if (hashMap.find(pi.th32ParentProcessID) == hashMap.end())
{
hashMap[pi.th32ParentProcessID].th32ParentProcessID = -1;
hashMap[pi.th32ParentProcessID].nextList.insert(pi.th32ProcessID);
hashMap[pi.th32ParentProcessID].th32ProcessID = pi.th32ParentProcessID;
hashMap[-1].nextList.insert(pi.th32ParentProcessID);
}
// 否则将当前进程节点加入父进程节点的子进程集合
else if(pi.th32ParentProcessID != pi.th32ProcessID)
{
hashMap[pi.th32ParentProcessID].nextList.insert(pi.th32ProcessID);
}
}
else
{
for (int i = 0; i < MAX_PATH; i++)
{
hashMap[pi.th32ProcessID].szExeFile[i] = pi.szExeFile[i];
}
if (hashMap[pi.th32ProcessID].th32ParentProcessID == -1)
{
for (auto i = hashMap[-1].nextList.begin(); i != hashMap[-1].nextList.end(); i++)
{
if (*i == pi.th32ProcessID)
{
hashMap[-1].nextList.erase(i);
}
}
}
hashMap[pi.th32ProcessID].th32ParentProcessID = pi.th32ParentProcessID;
if (hashMap.find(pi.th32ParentProcessID) == hashMap.end())
{
hashMap[pi.th32ParentProcessID].th32ParentProcessID = -1;
hashMap[pi.th32ParentProcessID].nextList.insert(pi.th32ProcessID);
hashMap[pi.th32ParentProcessID].th32ProcessID = pi.th32ParentProcessID;
hashMap[-1].nextList.insert(pi.th32ParentProcessID);
}
else if (pi.th32ParentProcessID != pi.th32ProcessID)
{
hashMap[pi.th32ParentProcessID].nextList.insert(pi.th32ProcessID);
}
}
if (pi.th32ParentProcessID == pi.th32ProcessID)
{
hashMap[pi.th32ProcessID].th32ParentProcessID = -1;
hashMap[-1].nextList.insert(pi.th32ParentProcessID);
}
bRet = Process32Next(hSnapshot, &pi);
}
// 遍历哈希表展示进程信息
hashBool[-1] = true;
for (auto i = hashMap[-1].nextList.begin(); i != hashMap[-1].nextList.end(); i++)
{
DWORD ID = *i;
printf("进程ID = %d ,进程路径 = %s\r\n", hashMap[ID].th32ProcessID, hashMap[ID].szExeFile);
for (auto j = hashMap[ID].nextList.begin(); j != hashMap[ID].nextList.end(); j++)
{
showList(*j, 1);
printf("\n");
}
hashBool[ID] = true;
}
// 关闭进程快照句柄
CloseHandle(hSnapshot);
return 0;
}
运行效果
easyX可视化版(选看)
除了控制台做树形输出,我还尝试做了一个用easyX包实现的可视化界面,原理一致,只不过输出做了可视化
#include <iostream>
#include <stdio.h>
#include <windows.h>
#include <Tlhelp32.h>
#include <set>
#include <map>
#include <string>
#include <sstream>
#include <graphics.h>
#include <conio.h>
using namespace std;
struct ListNode
{
DWORD th32ProcessID;
DWORD th32ParentProcessID;
CHAR szExeFile[MAX_PATH];
set<DWORD> nextList;
};
map<DWORD, ListNode> hashMap;
map<DWORD, bool> hashBool;
#define WHITE_SMOKE 0xF5F5F5
#define GAINSBORO 0xDCDCDC
#define DARKSLATEGREY 0xFFF598
#define PaleTurquoise 0x8B8B66
//将要输出到窗口上的进程节点链表
struct ShowList
{
string text; //进程路径
int status; //button状态
IMAGE img; //button图案
ShowList* next; //下一节点指针
DWORD th32ProcessID;
};
ShowList* headL = NULL; //可见进程节点链表头指针
ShowList* tailL = NULL; //可见进程节点链表尾指针
int My_SIZE; //可见进程链表实际大小
const int MAX_SIZE = 20;; //窗口可显示最大进程数
int viewHeight = 0; //滚动条高度
int viewTop = 0; //滚动条顶部位置
bool vviewTop = false; //是否生成滚动条
//对于button: X是未展开的父进程;对应状态0
// Y是鼠标选中的未展开父进程;对应状态2
// Z是子进程(没有子进程的进程);对应状态1
// T是已展开的父进程;对应状态3
// TZ是鼠标选中的已展开父进程;对应状态4
void setButtonX(IMAGE &img, string text)
{
SetWorkingImage(&img);
cleardevice();
Resize(&img, 280, 40);
setbkmode(TRANSPARENT);
setlinecolor(WHITE);
setfillcolor(GAINSBORO);
solidrectangle(0, 0, 279, 39);
setfillcolor(WHITE);
fillrectangle(1, 1, 275, 35);
rectangle(0, 0, 279, 39);
RECT rec{ 0,0,279,39 };
text = " > " + text;
settextcolor(BLACK);
drawtext(text.c_str(), &rec, DT_LEFT | DT_WORD_ELLIPSIS | DT_VCENTER | DT_SINGLELINE);
SetWorkingImage();
}
void setButtonY(IMAGE& img, string text)
{
SetWorkingImage(&img);
cleardevice();
Resize(&img, 280, 40);
setbkmode(TRANSPARENT);
setlinecolor(WHITE);
setfillcolor(GAINSBORO);
solidrectangle(0, 0, 279, 39);
setlinecolor(BLACK);
rectangle(0, 0, 279, 39);
RECT rec{ 0,0,279,39 };
text = " > " + text;
settextcolor(BLACK);
drawtext(text.c_str(), &rec, DT_LEFT | DT_WORD_ELLIPSIS | DT_VCENTER | DT_SINGLELINE);
SetWorkingImage();
}
void setButtonZ(IMAGE& img, string text)
{
SetWorkingImage(&img);
cleardevice();
Resize(&img, 280, 40);
setbkmode(TRANSPARENT);
setlinecolor(GAINSBORO);
setfillcolor(DARKSLATEGREY);
solidrectangle(0, 0, 279, 39);
rectangle(0, 0, 279, 39);
RECT rec{ 0,0,279,39 };
text = " > " + text;
settextcolor(BLACK);
drawtext(text.c_str(), &rec, DT_LEFT | DT_WORD_ELLIPSIS | DT_VCENTER | DT_SINGLELINE);
SetWorkingImage();
}
void setButtonT(IMAGE& img, string text)
{
SetWorkingImage(&img);
cleardevice();
Resize(&img, 280, 40);
setbkmode(TRANSPARENT);
setlinecolor(WHITE);
setfillcolor(PaleTurquoise);
solidrectangle(0, 0, 279, 39);
RECT rec{ 0,0,279,39 };
text = " - " + text;
settextcolor(BLACK);
drawtext(text.c_str(), &rec, DT_LEFT | DT_WORD_ELLIPSIS | DT_VCENTER | DT_SINGLELINE);
SetWorkingImage();
}
void setButtonTZ(IMAGE& img, string text)
{
SetWorkingImage(&img);
cleardevice();
Resize(&img, 280, 40);
setbkmode(TRANSPARENT);
setlinecolor(WHITE);
setfillcolor(CYAN);
solidrectangle(0, 0, 279, 39);
RECT rec{ 0,0,279,39 };
text = " - " + text;
settextcolor(BLACK);
drawtext(text.c_str(), &rec, DT_LEFT | DT_WORD_ELLIPSIS | DT_VCENTER | DT_SINGLELINE);
SetWorkingImage();
}
void dele(ShowList* p, int n)
{
if (n <= 0) return;
if (p == NULL) return;
if (p->next == NULL) return;
int cnt = 0;
p->status = 0;
setButtonX(p->img, p->text);
while (p != NULL && p->next != NULL && cnt != n)
{
ShowList* temp = p->next;
if (temp->status == 4 || temp->status == 3)
{
dele(temp, hashMap[temp->th32ProcessID].nextList.size());
}
p->next = temp->next;
delete temp;
My_SIZE--;
cnt++;
}
}
//更新窗口函数
void reDraw()
{
BeginBatchDraw();
SetWorkingImage();
setbkcolor(WHITE);
cleardevice();
if (!vviewTop && My_SIZE > MAX_SIZE)
{
vviewTop = true;
viewTop = 0;
viewHeight = 40 * MAX_SIZE * MAX_SIZE / My_SIZE;
initgraph(300, 40 * MAX_SIZE);
}
else if (vviewTop && My_SIZE <= MAX_SIZE)
{
vviewTop = false;
viewTop = 0;
viewHeight = 0;
initgraph(280, 40 * MAX_SIZE);
}
if (vviewTop)
{
setfillcolor(0xD3D3D3);
solidrectangle(280, 0, 299, 40 * MAX_SIZE - 1);
setfillcolor(0x696969);
solidrectangle(280, viewTop, 299, viewTop + viewHeight);
}
int cnt(0);
ShowList* p = headL;
while(p!=NULL)
{
int y = cnt * 40 - viewTop * My_SIZE / MAX_SIZE;
putimage(0, y, &p->img);
p = p->next;
cnt++;
}
EndBatchDraw();
}
//主函数
int main(int argc, char* argv[])
{
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (INVALID_HANDLE_VALUE == hSnapshot)
{
return -1;
}
PROCESSENTRY32 pi;
pi.dwSize = sizeof(PROCESSENTRY32); //第一次使用必须初始化成员
BOOL bRet = Process32First(hSnapshot, &pi);
hashMap[-1].th32ParentProcessID = -1;
while (bRet)
{
if (hashMap.find(pi.th32ProcessID) == hashMap.end())
{
hashMap[pi.th32ProcessID].th32ProcessID = pi.th32ProcessID;
hashMap[pi.th32ProcessID].th32ParentProcessID = pi.th32ParentProcessID;
for (int i = 0; i < MAX_PATH; i++)
{
hashMap[pi.th32ProcessID].szExeFile[i] = pi.szExeFile[i];
}
if (hashMap.find(pi.th32ParentProcessID) == hashMap.end())
{
hashMap[pi.th32ParentProcessID].th32ParentProcessID = -1;
hashMap[pi.th32ParentProcessID].nextList.insert(pi.th32ProcessID);
hashMap[pi.th32ParentProcessID].th32ProcessID = pi.th32ParentProcessID;
hashMap[-1].nextList.insert(pi.th32ParentProcessID);
}
else if (pi.th32ParentProcessID != pi.th32ProcessID)
{
hashMap[pi.th32ParentProcessID].nextList.insert(pi.th32ProcessID);
}
}
else
{
for (int i = 0; i < MAX_PATH; i++)
{
hashMap[pi.th32ProcessID].szExeFile[i] = pi.szExeFile[i];
}
if (hashMap[pi.th32ProcessID].th32ParentProcessID == -1)
{
for (auto i = hashMap[-1].nextList.begin(); i != hashMap[-1].nextList.end(); i++)
{
if (*i == pi.th32ProcessID)
{
hashMap[-1].nextList.erase(i);
}
}
}
hashMap[pi.th32ProcessID].th32ParentProcessID = pi.th32ParentProcessID;
if (hashMap.find(pi.th32ParentProcessID) == hashMap.end())
{
hashMap[pi.th32ParentProcessID].th32ParentProcessID = -1;
hashMap[pi.th32ParentProcessID].nextList.insert(pi.th32ProcessID);
hashMap[pi.th32ParentProcessID].th32ProcessID = pi.th32ParentProcessID;
hashMap[-1].nextList.insert(pi.th32ParentProcessID);
}
else if (pi.th32ParentProcessID != pi.th32ProcessID)
{
hashMap[pi.th32ParentProcessID].nextList.insert(pi.th32ProcessID);
}
}
if (pi.th32ParentProcessID == pi.th32ProcessID)
{
hashMap[pi.th32ProcessID].th32ParentProcessID = -1;
hashMap[-1].nextList.insert(pi.th32ParentProcessID);
}
bRet = Process32Next(hSnapshot, &pi);
}
CloseHandle(hSnapshot);
hashBool[-1] = true;
My_SIZE = 0;
for (auto i = hashMap[-1].nextList.begin(); i != hashMap[-1].nextList.end(); i++)
{
ShowList* newL = new ShowList();
newL->next = NULL;
newL->text = hashMap[*i].szExeFile;
if (newL->text.empty())
{
stringstream ss;
ss << "进程号:" << hashMap[*i].th32ProcessID;
newL->text = ss.str();
}
newL->th32ProcessID = *i;
if (hashMap[*i].nextList.size() > 0)
{
newL->status = 0;
setButtonX(newL->img, newL->text);
}
else
{
newL->status = 1;
setButtonY(newL->img, newL->text);
}
if (headL == NULL)
{
headL = newL;
tailL = headL;
}
else
{
tailL->next = newL;
tailL = newL;
}
My_SIZE++;
}
initgraph(280, 40 * MAX_SIZE);
setbkcolor(WHITE);
while (true)
{
reDraw();
ExMessage m;
SetWorkingImage();
getmessage(&m, EM_MOUSE);
{
short x = m.x;
short y = m.y;
if (m.x < 0 || m.x >= 300 || m.y < 0)
{
continue;
}
else if (m.x >= 280 && m.x < 300 && vviewTop)
{
if (m.message == WM_LBUTTONDOWN)
{
viewTop = y;
}
continue;
}
y += viewTop * My_SIZE / MAX_SIZE;
int id = y / 40;
switch (m.message)
{
case WM_MOUSEMOVE:
case WM_LBUTTONDOWN:
{
int cnt(0);
ShowList* p = headL;
while (p != NULL && cnt != id)
{
if (p->status == 2)
{
p->status = 0;
setButtonX(p->img, p->text);
}
else if (p->status == 4)
{
p->status = 3;
setButtonT(p->img, p->text);
}
cnt++;
p = p->next;
}
if (p != NULL)
{
if (p->status == 0)
{
p->status = 2;
setButtonZ(p->img, p->text);
}
else if (p->status == 3)
{
p->status = 4;
setButtonTZ(p->img, p->text);
}
}
if(p!=NULL)
p = p->next;
while (p != NULL)
{
if (p->status == 2)
{
p->status = 0;
setButtonX(p->img, p->text);
}
else if (p->status == 4)
{
p->status = 3;
setButtonT(p->img, p->text);
}
p = p->next;
}
}break;
case WM_LBUTTONUP:
{
int cnt(0);
ShowList* p = headL;
while (p != NULL && cnt != id)
{
cnt++;
p = p->next;
}
if (p != NULL && (p->status == 2 || p->status == 0))
{
int n = p->th32ProcessID;
p->status = 3;
setButtonT(p->img, p->text);
for (auto i = hashMap[n].nextList.begin(); i != hashMap[n].nextList.end(); i++)
{
ShowList* newL = new ShowList();
newL->next = p->next;
newL->text = hashMap[*i].szExeFile;
newL->th32ProcessID = *i;
if (hashMap[*i].nextList.size() > 0)
{
newL->status = 0;
setButtonX(newL->img, newL->text);
}
else
{
newL->status = 1;
setButtonY(newL->img, newL->text);
}
p->next = newL;
p = newL;
My_SIZE++;
}
}
else if (p != NULL && (p->status == 4 || p->status == 3))
{
dele(p, hashMap[p->th32ProcessID].nextList.size());
}
if (p != NULL)
p = p->next;
while (p != NULL)
{
if (p->status == 2)
{
p->status = 0;
setButtonX(p->img, p->text);
}
else if (p->status == 4)
{
p->status = 3;
setButtonT(p->img, p->text);
}
p = p->next;
}
}break;
}
}
}
_getch();
closegraph();
return 0;
}
可视化效果
第二题:
设计一个程序,可以做为主进程和子进程两种模式运行,做为主进程创建共享内存并初始化,然后启动 3 个子进程,最后等待子进程结束,并输出共享内存中的内容;做为子进程,首先打开共享内存,然后以临界区的形式对共享内存中的值加 1,重复 10000 次。
程序设计思路
首先,我们需要了解一些基本概念:
- 共享内存:共享内存是一种最快的进程间通信方式,它允许多个进程访问同一块物理内存空间。在Windows下,我们可以使用CreateFileMapping和OpenFileMapping函数来创建或打开一个命名的文件映射对象(也就是共享内存),然后使用MapViewOfFile函数来将文件映射对象映射到当前进程的地址空间。
- 信号量:信号量是一种用于控制多个进程对共享资源的访问的同步机制。它维护了一个计数器,表示可用资源的数量。当一个进程想要使用资源时,它必须先获取信号量(即减少计数器),如果计数器为0,则表示没有可用资源,该进程必须等待;当一个进程使用完资源后,它必须释放信号量(即增加计数器),以便其他等待的进程可以继续使用资源。在Windows下,我们可以使用CreateSemaphore和OpenSemaphore函数来创建或打开一个命名的信号量对象,并使用WaitForSingleObject和ReleaseSemaphore函数来获取或释放信号量。
- 临界区:临界区是指对共享资源进行访问或修改的代码段,在任意时刻只能有一个线程执行该代码段。如果有多个线程同时想要执行临界区代码,则必须按照某种规则排队等待。在本例中,我们使用信号量来实现临界区机制。
基于以上概念,我们可以设计如下算法:
- 作为主进程时:
- 创建一个命名的文件映射对象,并将其映射到当前地址空间。
- 在映射地址处初始化一个整型变量为0。
- 创建一个命名的信号量对象,并将其初始值设为1。
- 循环3次:
- 创建一个新的子进程,并传递参数“sub”。
- 将新创建的子进程句柄保存到数组中。
- 等待所有子进程结束。
- 输出映射地址处的整型变量值。
- 关闭文件映射对象、信号量对象、子进程句柄等资源。
- 作为子进程时:
- 打开已存在的文件映射对象,并将其映射到当前地址空间。
- 打开已存在的信号量对象。
- 循环10000次:
- 获取信号量(即开始临界区)。
- 将映射地址处将映射地址处的整型变量值加1。
- 释放信号量(即结束临界区)。
- 关闭文件映射对象、信号量对象等资源。
细节设计
同一程序主子进程结构
//主进程
if (argc == 1)
{
...
}
//子进程
else
{
...
}
主进程实现
HANDLE h = CreateFileMapping(NULL, NULL, PAGE_READWRITE, 0, SHARED_MEM_SIZE, TEXT("myShareMemery"));
void* pt = MapViewOfFile(h, FILE_MAP_WRITE, 0, 0, 0); //尾填0扩展到末尾
*(int*)pt = 0;
HANDLE ph[3];
TCHAR szCommandLine[] = TEXT("Project2.exe sub");
for (int i = 0; i < 3; i++)
{
STARTUPINFO si{ sizeof(si) };
PROCESS_INFORMATION pi;
CreateProcess(NULL, szCommandLine, NULL, NULL, FALSE, NULL, NULL, NULL, &si, &pi);
ph[i] = pi.hProcess;
}
WaitForMultipleObjects(3, ph, TRUE, -1);
std::cout << "Content of ShareMemery:" << *(int*)pt;
UnmapViewOfFile(pt);
CloseHandle(h);
子进程实现
HANDLE hShare = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, TEXT("myShareMemery"));
void* pt = MapViewOfFile(hShare, FILE_MAP_WRITE, 0, 0, 0);
HANDLE hCriticalSection = CreateSemaphore(NULL, 1, 1, TEXT("myCriticalSection"));
for (int i = 0; i < 10000; i++)
{
WaitForSingleObject(hCriticalSection, -1);
*(int*)pt += 1;
ReleaseSemaphore(hCriticalSection, 1, NULL);
}
CloseHandle(hCriticalSection);
UnmapViewOfFile(hShare);
CloseHandle(hShare);
实现代码
#include <iostream>
#include <Windows.h>
const int SHARED_MEM_SIZE = 1024;
int main(int argc,char* argv[])
{
if (argc == 1)
{
HANDLE h = CreateFileMapping(NULL, NULL, PAGE_READWRITE, 0, SHARED_MEM_SIZE, TEXT("myShareMemery"));
void* pt = MapViewOfFile(h, FILE_MAP_WRITE, 0, 0, 0); //尾填0扩展到末尾
*(int*)pt = 0;
HANDLE ph[3];
TCHAR szCommandLine[] = TEXT("Project2.exe sub");
for (int i = 0; i < 3; i++)
{
STARTUPINFO si{ sizeof(si) };
PROCESS_INFORMATION pi;
CreateProcess(NULL, szCommandLine, NULL, NULL, FALSE, NULL, NULL, NULL, &si, &pi);
ph[i] = pi.hProcess;
}
WaitForMultipleObjects(3, ph, TRUE, -1);
std::cout << "Content of ShareMemery:" << *(int*)pt;
UnmapViewOfFile(pt);
CloseHandle(h);
}
else
{
HANDLE hShare = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, TEXT("myShareMemery"));
void* pt = MapViewOfFile(hShare, FILE_MAP_WRITE, 0, 0, 0);
HANDLE hCriticalSection = CreateSemaphore(NULL, 1, 1, TEXT("myCriticalSection"));
for (int i = 0; i < 10000; i++)
{
WaitForSingleObject(hCriticalSection, -1);
*(int*)pt += 1;
ReleaseSemaphore(hCriticalSection, 1, NULL);
}
CloseHandle(hCriticalSection);
UnmapViewOfFile(hShare);
CloseHandle(hShare);
}
return 0;
}
运行结果
第三题
基于线程的服务器和多进程客户端,实现简单四则运算。可以通过网络通信也可以通过
共享内存结合信号量进行通迅,同时请求的客户端进程数可以超过最大 worker 数,当没
有请求时 worker 需要等待。
设计思路
我们首先定义了两个结构体,分别用于存储请求数据和结果数据:
struct RequestData{
int v; // 标志位,0表示空闲,1表示有请求
double a; // 操作数1
double b; // 操作数2
char op; // 运算符
};
struct ResultData{
int v; // 标志位,0表示空闲,1表示待计算,2表示有结果
double result; // 计算结果
};
然后我们定义了一个共享内存区域的头部结构体,用于记录当前的头尾索引:
struct IndexHead{
int headIndex; // 头索引,指向第一个有请求的位置
int tailIndex; // 尾索引,指向最后一个有请求的位置的下一个位置
};
我们假设共享内存区域可以容纳最多MAX_REQUESTS
个请求数据和结果数据。因此共享内存区域的布局如下:
|| IndexHead || RequestData[0] | ResultData[0] || RequestData[1] | ResultData[1] || … || RequestData[MAX_REQUESTS-1] | ResultData[MAX_REQUESTS-1] ||
请求数据和结果数据存在同一块内存区域
我们还需要定义一个信号量workSem
来控制对共享内存区域的访问。每次访问前需要等待信号量可用,并在访问后释放信号量。
服务器端
服务器端采用线程池的方式创建固定数量MAX_WORKERS
的 worker 线程,每个 worker 线程负责处理一个请求。服务器端使用共享内存SHARED_MEM_NAME
作为通讯缓冲区,其中包含以下三部分:
- IndexHead:一个结构体,用于记录当前缓冲区中请求数据和结果数据的头尾索引。
- RequestData:一个结构体数组,用于存储客户端发送的请求数据,每个元素包含一个标志位 v(0 表示空闲,1 表示已写入),两个操作数 a 和 b,以及一个操作符 op。
- ResultData:一个结构体数组,用于存储服务器返回的结果数据,每个元素包含一个标志位 v(0 表示空闲,1 表示已写入,2 表示已读取),以及一个结果值 result。
服务器端还使用一个信号量SEM_NAME
作为同步机制,用于控制对共享内存的访问。信号量的初始值为 1(表示有空闲资源),最大值为 MAX_REQUESTS
(表示缓冲区大小)。
worker 线程的工作流程如下:
- 等待信号量(WaitForSingleObject)
- 打开共享内存并映射到本地地址空间(OpenFileMapping 和 MapViewOfFile)
- 检查 IndexHead 中的头尾索引是否相等,如果相等,则表示缓冲区为空,没有请求需要处理,则释放共享内存并返回信号量,并重新等待信号量
- 如果不相等,则表示缓冲区中有请求需要处理,则根据头索引从 RequestData 数组中读取一个请求数据,并将头索引加一取模
MAX_REQUESTS
- 释放共享内存并返回信号量
- 根据请求数据中的操作符 op 对操作数 a 和 b 进行四则运算,并将结果封装到 ResultData 结构体中
- 等待信号量
- 打开共享内存并映射到本地地址空间
- 根据头索引从 ResultData 数组中写入一个结果数据,并将其标志位 v 设置为 2
- 释放共享内存并返回信号量
主线程的工作流程如下:
- 创建共享内存并设置其大小(CreateFileMapping)
- 映射到本地地址空间并初始化 IndexHead 中的头尾索引为 0(MapViewOfFile 和 UnmapViewOfFile)
- 创建信号量并设置其初始值为 1(CreateSemaphore)
- 创建
MAX_WORKERS
个 worker 线程,并传入各自的 id (从 0 到 MAX_WORKERS - 1) - 返回信号量以唤醒第一个 worker 线程(ReleaseSemaphore)
- 循环等待
客户端
客户端的主要逻辑如下:
- 创建或打开信号量
workSem
- 循环执行以下步骤:
- 从标准输入读取用户输入的两个操作数和一个运算符,并封装成
RequestData
结构体 - 等待信号量可用,并打开共享内存区域,并映射到虚拟地址空间中
- 检查当前是否有空闲位置可以放置请求数据(即头尾索引不相邻),如果没有,则释放资源并跳过本次循环
- 否则,在尾索引对应的位置写入请求数据,并更新尾索引(取模
MAX_REQUESTS
) - 释放资源并继续循环执行以下步骤:
- 等待信号量可用,并打开共享内存区域,并映射到虚拟地址空间中
- 检查当前是否有结果数据(即标志位为2),如果有,则输出结果,并将标志位置为0
- 如果头索引等于当前位置,则更新头索引(取模
MAX_REQUESTS
),直到遇到非空闲位置或回到原点为止(这样可以清理已经处理过的请求) - 释放资源并跳出循环
- 否则,释放资源并继续循环
实现代码
服务器端
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <vector>
#include <queue>
#include <sstream>
#include <Windows.h>
#include <sysinfoapi.h>
using namespace std;
// 客户端请求数据结构
struct RequestData {
int v; //标记内存空间是否可用
double a; // 操作数1
double b; // 操作数2
char op; // 操作符
};
// 服务器处理结果数据结构
struct ResultData {
int v; //标记内存空间是否可用
double result; // 计算结果
};
struct IndexHead
{
int headIndex;
int tailIndex;
};
const int MAX_WORKERS = 4; // 最大worker数
const int MAX_REQUESTS = 20; // 最大请求队列长度
const int SHARED_MEM_SIZE = sizeof(IndexHead) + MAX_REQUESTS * sizeof(RequestData); // 共享内存大小
const TCHAR* SHARED_MEM_NAME = L"SharedMemory";
const TCHAR* SEM_NAME = L"WorkSemaphore";
HANDLE workSem; // 请求信号量
vector<thread> workers; // worker线程池
//worker线程函数
void worker(int id)
{
while (true)
{
WaitForSingleObject(workSem, INFINITE);
HANDLE hMemory = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, SHARED_MEM_NAME);
if (hMemory == nullptr)
{
printf("线程 %d,内存访问失败!\n",id);
ReleaseSemaphore(workSem, 1, NULL);
Sleep(1000);
continue;
}
void* pData = MapViewOfFile(hMemory, FILE_MAP_ALL_ACCESS, 0, 0, 0);
if(pData == nullptr)
{
CloseHandle(hMemory);
ReleaseSemaphore(workSem, 1, NULL);
printf("线程 %d,建立映射失败!\n", id);
Sleep(10);
continue;
}
IndexHead* pIndex = (IndexHead*)pData;
if(((IndexHead*)pIndex)->headIndex == ((IndexHead*)pIndex)->tailIndex)
{
UnmapViewOfFile(pData);
CloseHandle(hMemory);
ReleaseSemaphore(workSem, 1, NULL);
Sleep(10);
continue;
}
printf("线程 %d,正在响应\n", id);
int id = ((IndexHead*)pIndex)->headIndex;
((IndexHead*)pIndex)->headIndex = (((IndexHead*)pIndex)->headIndex + 1) % MAX_REQUESTS;
RequestData req;
req.a = ((RequestData*)((char*)pData + sizeof(IndexHead) + sizeof(RequestData) * id))->a;
req.b = ((RequestData*)((char*)pData + sizeof(IndexHead) + sizeof(RequestData) * id))->b;
req.op = ((RequestData*)((char*)pData + sizeof(IndexHead) + sizeof(RequestData) * id))->op;
UnmapViewOfFile(pData);
CloseHandle(hMemory);
ReleaseSemaphore(workSem, 1, NULL);
ResultData res;
switch (req.op)
{
case '+':
{
res.result = req.a + req.b;
}break;
case '-':
{
res.result = req.a - req.b;
}break;
case '*':
{
res.result = req.a * req.b;
}break;
case '/':
{
if (req.b == 0)
{
res.result = 2147483647;
}
else
{
res.result = req.a / req.b;
}
}break;
}
WaitForSingleObject(workSem, INFINITE);
hMemory = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, SHARED_MEM_NAME);
pData = MapViewOfFile(hMemory, FILE_MAP_ALL_ACCESS, 0, 0, 0);
ResultData* pRes = (ResultData*)((char*)pData + sizeof(IndexHead) + sizeof(RequestData) * id);
((ResultData*)pRes)->result = res.result;
((ResultData*)pRes)->v = 2;
UnmapViewOfFile(pData);
CloseHandle(hMemory);
ReleaseSemaphore(workSem, 1, NULL);
}
}
int main()
{
HANDLE hShare = CreateFileMapping(NULL, NULL, PAGE_READWRITE, 0, SHARED_MEM_SIZE, SHARED_MEM_NAME);
if (hShare == nullptr)
{
printf("共享内存创建失败!\n");
return -1;
}
LARGE_INTEGER liFileSize;
workSem = CreateSemaphore(NULL, 0, 1, SEM_NAME);
if(workSem == nullptr)
{
CloseHandle(hShare);
printf("信号量创建失败!\n");
return -1;
}
void* pData = MapViewOfFile(hShare, FILE_MAP_ALL_ACCESS, 0, 0, 0);
if (pData == nullptr)
{
//UnmapViewOfFile(pData);
CloseHandle(workSem);
CloseHandle(hShare);
printf("内存映射失败!\n");
return -1;
}
ZeroMemory(pData, SHARED_MEM_SIZE);
UnmapViewOfFile(pData);
pData = MapViewOfFile(hShare, FILE_MAP_ALL_ACCESS, 0, 0, sizeof(IndexHead));
((IndexHead*)pData)->headIndex = 0;
((IndexHead*)pData)->tailIndex = 0;
UnmapViewOfFile(pData);
for (int i = 0; i < MAX_WORKERS; i++)
{
workers.emplace_back(worker, i);
}
cout << "服务器已就绪!\n";
ReleaseSemaphore(workSem, 1, NULL);
while (true);
CloseHandle(workSem);
CloseHandle(hShare);
return 0;
}
客户端
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <vector>
#include <queue>
#include <sstream>
#include <Windows.h>
#include <string>
using namespace std;
// 客户端请求数据结构
struct RequestData {
int v; //标记内存空间是否可用
double a; // 操作数1
double b; // 操作数2
char op; // 操作符
};
// 服务器处理结果数据结构
struct ResultData {
int v; //标记内存空间是否可用
double result; // 计算结果
};
struct IndexHead
{
int headIndex;
int tailIndex;
};
const int MAX_WORKERS = 4; // 最大worker数
const int MAX_REQUESTS = 20; // 最大请求队列长度
const int SHARED_MEM_SIZE = sizeof(IndexHead) + MAX_REQUESTS * sizeof(RequestData); // 共享内存大小
const TCHAR* SHARED_MEM_NAME = L"SharedMemory";
const TCHAR* SEM_NAME = L"WorkSemaphore";
HANDLE workSem; // 请求信号量
int main()
{
workSem = OpenSemaphore(SEMAPHORE_ALL_ACCESS, FALSE, SEM_NAME);
if (workSem == nullptr)
{
// CloseHandle(workSem);
cerr <<"信号量打开失败,请检查服务器是否启动!\n";
system("pause");
return -1;
}
while (true)
{
RequestData req;
printf("请输入想要计算的四则运算式(数字与符号之间请用空格隔开,不支持括号,仅支持单次 + - * /):\n");
cin >> req.a >> req.op >> req.b;
printf("正在请求服务器,请耐心等待...\n");
WaitForSingleObject(workSem, INFINITE);
HANDLE hMemory = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, SHARED_MEM_NAME);
if (hMemory == NULL)
{
printf("内存访问失败!\n");
ReleaseSemaphore(workSem, 1, NULL);
Sleep(1000);
continue;
}
void* pData = MapViewOfFile(hMemory, FILE_MAP_ALL_ACCESS, 0, 0, 0);
if (pData == nullptr)
{
ReleaseSemaphore(workSem, 1, NULL);
CloseHandle(hMemory);
printf("内存映射失败!\n");
continue;
}
if (((IndexHead*)pData)->headIndex == (((IndexHead*)pData)->tailIndex + 1) % MAX_REQUESTS)
{
UnmapViewOfFile(pData);
ReleaseSemaphore(workSem, 1, NULL);
CloseHandle(hMemory);
Sleep(10);
continue;
}
int id = ((IndexHead*)pData)->tailIndex;
((IndexHead*)pData)->tailIndex = (((IndexHead*)pData)->tailIndex + 1) % MAX_REQUESTS;
RequestData* pReq = (RequestData*)((char*)pData + sizeof(IndexHead) + sizeof(RequestData) * id);
pReq->v = 1;
pReq->a = req.a;
pReq->b = req.b;
pReq->op = req.op;
UnmapViewOfFile(pData);
CloseHandle(hMemory);
ReleaseSemaphore(workSem, 1, NULL);
Sleep(1000);
while (true)
{
WaitForSingleObject(workSem, INFINITE);
hMemory = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, SHARED_MEM_NAME);
pData = MapViewOfFile(hMemory, FILE_MAP_ALL_ACCESS, 0, 0, 0);
ResultData* pRes = (ResultData*)((char*)pData + sizeof(IndexHead) + sizeof(RequestData) * id);
if (pRes->v == 2)
{
cout << "结果为:" << pRes->result << "\n";
pRes->v = 0;
IndexHead *pIndex = (IndexHead*)pData;
while (pIndex->headIndex == id)
{
pRes = (ResultData*)((char*)pData + sizeof(IndexHead) + sizeof(RequestData) * id);
if (pRes->v == 0)
{
pIndex->headIndex = (pIndex->headIndex + 1) % MAX_REQUESTS;
id = pIndex->headIndex;
}
else
{
UnmapViewOfFile(pData);
break;
}
}
ReleaseSemaphore(workSem, 1, NULL);
break;
}
else
{
UnmapViewOfFile(pData);
}
CloseHandle(hMemory);
ReleaseSemaphore(workSem, 1, NULL);
Sleep(100);
}
}
CloseHandle(workSem);
return 0;
}