在这里完成操作原理实验三
博主选做完成的是第2,3,5题。
注:第5题因为太长了,细节很多,放到下一篇单独讲
链接:操作系统原理实验三(二)。
【当堂完成:[1,3,4]中任意 1 题和第2,5 题,共计 3 道题。】
(1)在 Ubuntu 或 Fedora 环境创建一对父子进程,使用共享内存的方式实现进程间的通信。父进程提供数据(1-100,递增),子进程读出来并显示。
(2)(考虑信号通信机制)在 Ubuntu 或 Fedora 环境创建父子 2 个进程 A,B。进程 A 不断获取用户从键盘输入的字符串或整数,通过信号机制传给进程 B。如果输入的是字符串,进程 B 将
其打印出来;如果输入的是整数,进程 B 将其累加起来,并输出该数和累加和。当累加和大于 100 时结束子进程,子进程输出“My work done!”后结束,然后父进程也结束。
(3)在 windows 环境使用创建一对父子进程,使用管道(pipe)的方式实现进程间的通信。父进程提供数据(1-100,递增),子进程读出来并显示。
(4)(考虑匿名管道通信)在 windows 环境下创建将 CMD 控制台程序封装为标准的 windows 窗口程序。
(5)在 windows 环境下,利用高级语言编程环境(限定为 VS 环境或 VC 环境或QT)调用 CreateThread 函数哲学家就餐问题的演示。要求:(1)提供死锁的解法和非死锁的解法;(2)有图形界面直观显示哲学家取筷子,吃饭,放筷子,思考等状态。(3)为增强结果的随机性,各个状态之间的维持时间采用随机时间,例如100ms-500ms 之间。
#include
#include
#include
#include
#include
#include
//判断是不是整数,是则返回这个整数,不是则返回-1
int check(char a[])
{
bool flag=true;
int num=0;
int i=0;
while(a[i]!='\0')
{
if(a[i]<'0'||a[i]>'9')
{
flag=false;
break;
}
else
{
num*=10;
num+=(int)(a[i]-'0');
}
i++;
}
if(flag)
return num;
else
return -1;
}
void main()
{
//创建管道
int fd[2];
if(pipe(fd)<0)
{
printf("create pipe failed!");
exit(1);
}
//创建进程
pid_t pid;
if((pid=fork())<0)
{
printf("fork failed!");
exit(1);
}
//父进程读取用户数,并通过管道传给子进程
//子进程判断输入类型,并打印或者累加
if(pid==0)//子进程的fork()返回值为0
{
close(fd[1]);
int sum=0;
while(true)
{
char readBuff[100];
read(fd[0],readBuff,100);
int m=check(readBuff);
if(m!=-1)
{
sum+=m;
if(sum>=100)
{
printf("子进程:sum=%d\n",sum);
break;
}
}
else
printf("子进程:%s\n",readBuff);
}
printf("子进程:My work done!\n");
exit(1);
}
else//父进程fork()返回值为子进程的进程号
{
close(fd[0]);
while(true)
{
if(kill(pid,0)==0)//kill函数返回值为0则进程pid在运行
{
char writeBuff[100];
scanf("%s",writeBuff);
write(fd[1],writeBuff,100);
sleep(1);
}
else//返回值为-1,则子进程已退出
{
printf("父进程:子进程已结束!\n");
break;
}
}
printf("父进程:父进程结束!");
}
}
1. 子进程创建
fork() 会创建一个子进程,
在父进程中返回值为子进程的id,在子进程中返回值为0
2. 使用了管道通信,由pipe函数创建:
#include
int pipe(int filedes[2]);
用pipe函数时在内核中开辟一块缓冲区(称为管道)用于通信,它有一个读端一个写端,然后通过filedes参数传出给用户程序两个文件描述符,filedes[0]指向管道的读端,filedes[1]指向管道的写端(很好记,就像0是标准输入,1是标准输出一样)。
所以管道在用户程序看起来就像一个打开的文件,通过read(filedes[0])或者write(filedes[1])向这个文件读写数据其实是在读写内核缓冲区。
pipe函数调用成功返回0,调用失败返回-1。
int pipe(int filedes[2])中的两个文件描述符被强制规定:filedes[0]只能指向管道的读端,如果进行写操作就会出现错误;同理filedes[1]只能指向管道的写端,如果进行读操作就会出现错误。
代码中**int fd[2]; pipe(fd);**就是在创建管道
(1)父进程调用pipe开辟管道,得到两个文件描述符指向管道的两端。
(2)父进程调用fork创建子进程,那么子进程也有两个文件描述符指向同一管道。
(3)父进程关闭管道读端,子进程关闭管道写端。父进程可以往管道里写,子进程可以从管道里读,管道是用环形队列实现的,数据从写端流入从读端流出,这样就实现了进程间通信。
pipe管道操作学习自:fork创建子进程利用pipe管道通信
大佬写的很详细,大家可以去看一下。
3. 自定义函数check(char a[])
检测每一位是不是整数,如果每一位都是整数那么整体也是一个整数。
4. 一个隐含的bug
孩子想做一个父进程检查到子进程退出,父进程也退出的功能。
查到kill(pid, 0)的返回值0表示进程pid存在,<0表示进程pid已经结束(如何判断fork之后的子进程是否已经结束)
但是无论怎样调试,在子进程退出后,父进程还必须得输入一个字符,才会反应过来。。。
救救孩子吧!希望看到的大佬在评论区指点一下,thank you~
父进程:
#define _CRT_SECURE_NO_WARNINGS
#include
#include
#include
//操作原理实验三(3)
int main()
{
printf("这是父进程");
//创建两个句柄
HANDLE hParentWrite, hChildRead;
SECURITY_ATTRIBUTES sa = {
0 };//安全属性描述符
STARTUPINFO si = {
0 };//信息结构体
PROCESS_INFORMATION pi = {
0 };
sa.nLength = sizeof(sa);
sa.lpSecurityDescriptor = NULL;
sa.bInheritHandle = TRUE;//设置句柄可继承
//创建管道(父进程写,子进程读)
CreatePipe(&hChildRead, &hParentWrite, &sa, 0);
//CreatePipe(&hParentRead, &hChildWrite, &sa, 0);
//GetStartupInfo(&si);
si.cb = sizeof(STARTUPINFO);
si.wShowWindow = TRUE; //新创建的进程是否可见
si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;//STARTF_USESTDHANDLES --->使用hStdInput、hStdOutput和hStdError成员
si.hStdInput = hChildRead; //重定向子进程的输入为管道
char childPath[128] = "D:\\C语言相关\\我的代码\\操作系统原理\\子进程\\Debug\\子进程.exe";
TCHAR ChildPath[128];
//将char 转为 TCHAR,因为(下面是一位大神讲的)
//lpCommandLine是LPTSTR而不是LPCTSTR,所以这个参数不能是字符串常量,
//必须是可写的字符串数组
//然后我看另一位大神的代码 发现他是用的 TCAHR 可行0.0嘿嘿
MultiByteToWideChar(CP_ACP, 0, childPath, -1, ChildPath, 128);
//创建子进程
if (!CreateProcess(NULL, ChildPath, NULL, NULL, TRUE, CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi))
{
CloseHandle(hChildRead);
printf("子进程创建失败");
return 0;
}
Sleep(100);
//向子进程传数据
char writeBuff[8];
for (int i = 1; i <= 100; i++)
{
memset(writeBuff,'\0',8);//清空
writeBuff[0]=(char)i;
WriteFile(hParentWrite, writeBuff, 8, NULL, 0);//使用writeFile操作管道,给子进程发送数据命令
Sleep(50);
}
//传送完数据后,关闭句柄
CloseHandle(hChildRead);
CloseHandle(hParentWrite);
return 0;
}
子进程:
#define _CRT_SECURE_NO_WARNINGS
#include
#include
int main()
{
//这个程序被父进程:进程间通讯调用
//父进程传入数据,该进程读并输出
printf("子进程被创建成功\n");
HANDLE read = GetStdHandle(STD_INPUT_HANDLE);
unsigned long BytesRead = 0;
char readBuff[8];
while (1)
{
memset(readBuff, '\0', 8);//清空
ReadFile(read, readBuff,8,&BytesRead,NULL);//读取管道内数据
if (BytesRead == 0)
{
//没有读到信息
Sleep(10);
continue;
}
int i = 0;
while (readBuff[i] != '\0')
{
printf("%d\n", readBuff[i]);
i++;
}
}
}
1. 创建进程
进程相关知识学习自:
各个参数的含义:Windows下创建进程-CreateProcess()
实例:VC++ 使用CreateProcess创建新进程
BOOL bRet = CreateProcess(
NULL,// 不在此指定可执行文件的文件名
(LPWSTR)szCommandLine,// 命令行参数
NULL, // 默认进程安全性
NULL, //默认进程安全性
TRUE, // 指定当前进程内句柄可以被子进程继承
CREATE_NEW_CONSOLE, //为新进程创建一个新的控制台窗口
NULL, // 使用本进程的环境变量
NULL, // 使用本进程的驱动器和目录
&si,
&pi);
需要注意的
(1)子进程的路径名要用宽字符格式
代码中:MultiByteToWideChar(CP_ACP, 0, childPath, -1, ChildPath, 128);就是将char转换为TCHAR格式。
(2) 重定向子进程的输入输出
si.hStdInput = hChildRead; //重定向子进程的输入为管道
(3)设置si时需要注意:
si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
STARTF_USESTDHANDLES —>使用hStdInput、hStdOutput和hStdError成员
(如果不设置这个,他就会不管你重定向的输入输出,而是采用默认的标准输入输出)
2. 创建管道
函数说明转载自百度百科
BOOL WINAPI CreatePipe(
_Out_PHANDLE hReadPipe,//返回一个可用于读管道数据的文件句柄
_Out_PHANDLE hWritePipe, //返回一个可用于写管道数据的文件句柄
_In_opt_LPSECURITY_ATTRIBUTES lpPipeAttributes,
_In_DWORD nSize
);
参数_In_opt_LPSECURITY_ATTRIBUTES lpPipeAttributes:传入一个SECURITY_ATTRIBUTES结构的指针,该结构用于决定该函数返回的句柄是否可被子进程继承。如果传NULL,则返回的句柄是不可继承的。
该结构的lpSecurityDescriptor成员用于设定管道的安全属性,如果传NULL,那么该管道将获得一个默认的安全属性,该属性与创建该管道的用户账户权限ACLs的安全令牌(token)相同。
参数_In_DWORD nSize:管道的缓冲区大小。但是这仅仅只是一个理想值,系统根据这个值创建大小相近的缓冲区。如果传入0 ,那么系统将使用一个默认的缓冲区大小。
3. 管道写入与写出
管道的写入写出采用文件操作WriteFile(),ReadFile().
(注:仅限于此题。
事实上,当你把一个进程的输出端重定为a时,那么你所有的输出比如printf(),都会传送到a.
此题,我并没有改变父进程的标准输出,所以才采用文件输入WriteFile()操作,
至于子进程为了跟父进程代码保持统一,采用了ReadFile()(子进程使用scanf也是可以读取到的)
)
另外第5题哲学家问题在下一篇:操作系统原理实验三(二)
希望以上可以帮到你!
如有错误,或不同想法,欢迎指出,互相学习共同进步!