《操作系统原理》实验报告三

一、实验目的
(1)理解线程/进程的通信机制和编程;
(2)理解线程/进程的死锁概念和如何解决死锁

二、实验内容
(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 之间。
:[1,3,4]中任意 1 题和第2,5 题,共计 3 道题

三、实验过程
(一)实验步骤
1)使用共享内存实现进程间的通信
1. 何为共享内存?
共享内存就是允许两个不相关的进程访问同一个逻辑内存。不同进程之间共享的内存通常安排为同一段物理内存。进程可以将同一段共享内存连接到它们自己的地址空间中,所有进程都可以访问共享内存中的地址
2.重要函数介绍:
声明在头文件 sys/shm.h 中
shmget函数:该函数用来创建共享内存
int shmget(key_t key, size_t size, int shmflg);
key_t key: 它有效地为共享内存段命名,shmget函数成功时返回一个与key相关的共享内存标识符(非负整数)。调用失败返回-1.
size_t size: size以字节为单位指定需要共享的内存容量
int shmflg: 共享内存的权限标志
shmat函数: shmat函数的作用就是用来启动对该共享内存的访问,并把共享内存连接到当前进程的地址空间
void *shmat(int shm_id, const void *shm_addr, int shmflg);
shm_id: shm_id是由shmget函数返回的共享内存标识。
shm_addr: shm_addr指定共享内存连接到当前进程中的地址位置,通常为空,表示让系统来选择共享内存的地址。
shmflg: shm_flg是一组标志位,通常为0。
shmdt函数: 该函数用于将共享内存从当前进程中分离,使该共享内存对当前进程不再可用。
int shmdt(const void *shmaddr);
参数shmaddr: 是shmat函数返回的地址指针,调用成功时返回0,失败时返回-1
shmctl函数: 与信号量的semctl函数一样,用来控制共享内存
int shmctl(int shm_id, int command, struct shmid_ds *buf);
shm_id: 是shmget函数返回的共享内存标识符。
command: 是要采取的操作,它可以取下面的三个值 :
IPC_STAT: 把shmid_ds结构中的数据设置为共享内存的当前关联值,即用共享内存的当前关联值覆盖shmid_ds的值。
IPC_SET:如果进程有足够的权限,就把共享内存的当前关联值设置为shmid_ds结构中给出的值
IPC_RMID: 删除共享内存段
buf: 是一个结构指针,它指向共享内存模式和访问权限的结构。
1.

	if (pid < 0)    
2.	   {
    
        
3.	       //printf('子进程创建失败');    
4.	       fprintf(stderr, "can't fork ,error %d\n", errno);    
5.	       exit(1);    
6.	   }    
7.	   else if (pid == 0) //子进程    
8.	   {
    
        
9.	       while (cRunning) //读取共享内存中的数据    
10.	       {
    
        
11.	           //没有进程向共享内存定数据有数据可读取    
12.	           if (shared->written != 0)    
13.	           {
    
        
14.	               int i = 0;    
15.	               while (i < 100)    
16.	               {
    
        
17.	                   printf("child  read: %d\n", shared->text[i]);    
18.	                   i++;    
19.	               }    
20.	               //读取完数据,设置written使共享内存段可写    
21.	               shared->written = 0;    
22.	               //退出循环(程序)    
23.	               cRunning = 0;    
24.	           }    
25.	           else //有其他进程在写数据,不能读取数据    
26.	               sleep(1);    
27.	       }    
28.	   }    
29.	   else //父进程    
30.	   {
    
        
31.	       int i = 1;    
32.	       while (pRunning) //向共享内存中写数据    
33.	       {
    
        
34.	           //数据还没有被读取,则等待数据被读取,不能向共享内存中写入文本    
35.	           while (shared->written == 1)    
36.	           {
    
        
37.	               sleep(1);    
38.	           }    
39.	           //向共享内存中写入数据    
40.	           shared->text[i - 1] = i;    
41.	           printf("parent write: %d\n", shared->text[i - 1]);    
42.	           i++;    
43.	           if (i > 100)    
44.	           {
    
        
45.	               //写完数据,设置written使共享内存段可读,退出循环(程序)    
46.	               shared->written = 1;    
47.	               pRunning = 0;    
48.	           }    
49.	       }    
50.	   }   

2)使用信号通信机制实现进程间的通信
进程可以通过调用kill函数向包括它本身在内的其他进程发送一个信号。如果程序没有权限,kill函数会调用失败,失败的常见原因是目标进程由另一个用户所拥有,这个函数跟shell同名命令kill的功能完全一样,定义如下:
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
kill函数中,pid表示要发送信号到达的目标进程的进程id,sig为发送的信号值。
成功时,返回0。失败时,返回-1,并设置 errno变量
由于信号通信不能传递数据,故结合管道通信
管道如何实现进程间的通信
(1)父进程创建管道,得到两个⽂件描述符指向管道的两端
(2)父进程fork出子进程,⼦进程也有两个⽂件描述符指向同⼀管道
(3)父进程关闭fd[0],子进程关闭fd[1],即⽗进程关闭管道读端,⼦进程关闭管道写端(因为管道只支持单向通信)。⽗进程可以往管道⾥写,⼦进程可以从管道⾥读,管道是⽤环形队列实现的,数据从写端流⼊从读端流出,这样就实现了进程间通信。
在这里插入图片描述

3)哲学家就餐问题演示
筷子编号: 0-4 (哲学家左手边的筷子与自己编号相同)
int S[5]={1,1,1, 1,1}; //信号量: i号筷子是否可用: 0不可用,1可用
UINT Philosopher (int i)//线程函数,i是哲学家的编号
{ while (TRUE){
思考;
休息;
P(S[i]); //取左手边的筷子
P(S[(i+4) % 5]); //取右手边的筷子
吃饭; //正在用 两只筷子…
V(S[(i+4) % 5]); //放下右手边的筷子
V(S[i); //放下左手边的筷子}
}
核心代码:
//创建5个信号量
for(int i=0;i<5;i++){
S[i] = CreateSemaphore(NULL ,1 ,1,name[i]);
}
//打开信号量:
HANDLE right,left;
left =OpenSemaphore(SEMAPHORE_ALL_ACCESS,FALSE,name[id]);
right=OpenSemaphore(SEMAPHORE_ALL_ACCESS,FALSE,name[(id+4)%5]);
WaitForSingleObject(left,INFINITE); //p操作
ReleaseSemaphore(left,1,NULL); //v操作
(二)解决错误和优化
1. 编译错误,不能从const char *转换为LPCWSTR。原因:因为你的程序在UNICODE(宽字节)字符集下运行,如果调用了 MessageBox ,实际上调用的是 MessageBoxW 函数;如果你的程序在 ANSI 字符集运行,调用 MessageBox ,就相当于调essageBoxA;其中 MessageBoxW 支持 UNICODE;MessageBoxA 支持ANSI;UNICODE与ANSI 有什么区别呢?简单的说,UNICODE版的字符比ANSI 的内存占用大,比如:Win32程式中出现的标准定义 char 占一个字节,而 char 的UNICODE版被定义成这样:typedef unsigned short wchar_t ;占2个字节。所以有字符做参数的函数相应也用两个版本了。
解决:项目菜单——项目属性(最后一个)——配置属性——常规——项目默认值——字符集,将使用Unicode字符集改为未设置即可。
2.特殊语法错误,linux c之提示format‘%d’ expects argument of type ‘int’, but argument 2 has type ‘long int’ 。解决办法md,m为指定的输出字段的宽度。如果数据的位数小于m,则左端补以空格,若大于m,则按实际位数输出。%ld(%mld 也可),输出长整型数据。最后 printf(“data is %ld”, data)解决。
总结:%md,m为指定的输出字段的宽度。如果数据的位数小于m,则左端补以空格,若大于m,则按实际位数输出。%ld(%mld 也可),输出长整型数据。u格式符,用来输出unsigned型数据,无符号数,以十进制数形式输出。格式:%u,%mu,%lu都可
3.特殊语法错误,变量重复申明。错误分析:变量“xxxx”在同一作用域中定义了多次。检查“xxxx”的每一次定义,只保留一个,或者更改变量名。
4. test.c:59:5: warning: incompatible implicit declaration of built-in function ‘memset’ [enabled by default],添加头文件: #include<string.h>解决。

四、实验结果
1)使用共享内存实现进程间的通信
如下图所示
在这里插入图片描述
在这里插入图片描述

2)使用共享内存实现进程间的通信
在这里插入图片描述

3)哲学家就餐问题演示
在这里插入图片描述
在这里插入图片描述

五、体会
通过本次实验,我对操作系统线程/进程的通信机制和编程、操作系统线程/进程的死锁概念有了更深刻的理解,掌握了如何解决死锁,对操作系统的功能与原理有了进一步的了解。

猜你喜欢

转载自blog.csdn.net/weixin_45341339/article/details/112413422
今日推荐