一、实验目的
(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)哲学家就餐问题演示
五、体会
通过本次实验,我对操作系统线程/进程的通信机制和编程、操作系统线程/进程的死锁概念有了更深刻的理解,掌握了如何解决死锁,对操作系统的功能与原理有了进一步的了解。