操作系统原理实验三(一)

写在开头

在这里完成操作原理实验三
博主选做完成的是第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 之间。

3.2 Linux父子进程间通讯

运行效果

在这里插入图片描述

代码展示

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <sys/types.h>
#include <signal.h>

//判断是不是整数,是则返回这个整数,不是则返回-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 <unistd.h>
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~

3.2 Windows父子进程间通讯

运行效果

在这里插入图片描述

代码展示

父进程:

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<Windows.h>
//操作原理实验三(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<stdio.h>
#include<Windows.h>
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题哲学家问题在下一篇:操作系统原理实验三(二)

写在结尾

希望以上可以帮到你!
如有错误,或不同想法,欢迎指出,互相学习共同进步!

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值