实验二、进程通信

一、实验名称

进程通信

二、实验目的

掌握用邮箱方式进行进程通信的方法,并通过设计实现简单邮箱理解进程通信中的同步问题以及解决该问题的方法。

三、实验原理

邮箱机制类似于日常使用的信箱。对于用户而言使用起来比较方便,用户只需使用send()向对方邮箱发邮件 receive()从自己邮箱取邮件, send()和 receive()的内部操作用户无需关心。因为邮箱在内存中实现,其空间有大小限制。其实send()和 receive()的内部实现主要还是要解决生产者与消费者问题。

四、实验内容

1.背景知识介绍

1.1 生产者消费者模型

生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。这个阻塞队列就是用来给生产者和消费者解耦的。生产者与消费者对缓冲区的访问是互斥关系。

1.2 Linux 进程间通信之消息队列

​ Linux 中进程间的通信方法有很多:管道、命名管道、消息队列、共享内存、信号量。本次实验要求先使用系统调用来编写程序完成进程间通信。我这里采用的是“消息队列”的方式,简单介绍一下。

​ 消息队列就是一个消息的链表。可以把消息看做一个记录,具有特定的格式及特定的优

先级。对消息队列有写权限的进程可以向其中按照一定的规则添加新消息;对消息队列有读

权限的进程则可以从消息队列中读走消息。消息队列是随内核持续的。

​ 特点是可以实现多个进程间的双向通信,缺点是传输的数据量小,仅可以传递一些控制信息可以比喻成公共邮箱,所有的进程向这个公共邮箱发送消息,或者从这个邮箱取某一类型的消息。取出的消息将从邮箱中消失。从中可以看出消息是有类型的(用一个长整型的数据表示类型),同时它也遵守某些格式(消息的内容是一个结构体)。也可以看出不用关心不同类型的消息的发送先后顺序,取出消息将会取出目标类型的最早的消息。

1.3 Linux 中信号量的操作

1.3.1 信号量的数据结构
union semun{int val;struct semid_ds *buf;  //semid_ds 指针结构unsigned short *array;  //数组类型struct seminfo *__buf;  //信号量内部结构
}
1.3.2 创建信号量:semget

函数原型:int semget(key_t key, int num_sems:, int sem_flags);

作用:第一次使用时创建信号量,以后使用时获取信号量。

Key:一个整型值对应内核中一个信号量对象,不同信号量的key值不一样。

num_sems:信号量的数目

sem_flags:设置一组标志,与open函数的标志非常相似,包括信号量的权限等。

1.3.3 初始化、删除 :semctl

函数原型:int semctl(int sem_id, int sem_num, int command, …);

sem_id:信号量id。

sem_num:信号量的下标,从0开始。

command:具体的操作命令,有SETVAL(设置初值)、IPC_RMID(移除信号量),这个参数可以没有。

1.3.4 信号量操作函数:semop

函数原型:int semop(int sem_id, struct sembuf *sem_ops,size_t num_sem_ops);

sem_id:信号量的id,用作标识。

sem_ops:指向一个结构体数组的指针。结构体内容如下

sembuf 结构在 <linux/sem.h> 中定义

struct sembuf{ushort sem_num;  //信号量的编号short sem_op;    //信号量的操作short sem_flg;   //信号量的操作标志
}

sem_num 是信号量的下标,sem_op 是信号量一次操作总需要改变的数值,+1是 v 操作,-1 是 p 操作,sem_flg 通常设置为SEM_UNDO,表示操作系统会跟踪当前进程对这个信号量的修改情况,如果这个进程在没有释放该信号量的情况下终止,操作系统将自动释放该进程持有的信号量,防止其他进程一直处于等待状态。
num_sem_ops:指的是结构数组的元素个数。

1.4 Linux 环境下使用 C 语言进行文件读写

1.4.1 打开文件

使用 <stdio.h> 头文件中的 fopen() 函数即可打开文件,它的用法为: **FILE *fopen(char filename, char mode);

可选的模式有 r/w/a/r+/w+/a+,分别对应不同的文件访问权限。

1.4.2 读、写文件

将文件打开后,可使用:int fprintf ( FILE *fp, char * format, … ); 对文件进行写入操作。fp 为文件指针,format 为格式控制字符串,… 表示参数列表。fprintf() 返回成功写入的字符的个数,失败则返回负数。

可使用 system("cat filename "); 进行打印文件中的所有内容

1.4.3 文件流

所有的文件(保存在磁盘)都要载入内存才能处理,所有的数据必须写入文件(磁盘)才不会丢失。数据在文件和内存之间传递的过程叫做文件流,类似水从一个地方流动到另一个地方。数据从文件复制到内存的过程叫做输入流,从内存保存到文件的过程叫做输出流。我们可以说,打开文件就是打开了一个流。

2.设计方案

2.1 思路分析

首先说明一下思路:分析一下实验要求,是要模拟一个中间站储存信息,然后两个进程一个写一个读。因为不能使用系统提供的一些调用机制,所以我想了很久如何让两个进程共享同一个“信箱”。逛了很久的论坛,也看了 Linux 下 C 编程的一些书,但是大部分都是调用系统提供的一些机制,共享内存,消息队列等等。于是最后想出来采用的办法是使用文件机制,创建一个文件当做“信箱”,然后 A 进程写文件当做信箱的 “send”B 进程读文件当做信箱的 “receive”

同时采用信号量的 PV 操作限制同时对同一个文件的操作,使得进程对文件操作是互斥的,避免进程 A 在写文件的时候,进程 B 在读文件。我设计的信箱中只能有一条信件,相当于是生成者消费者模型中的缓冲池。因为只能保存一封邮件的话比较好设计互斥操作,对生成者和消费者来说对信箱的访问就始终是互斥的了,同一时刻只能有一方对信箱进行读写。

2.2 代码结构说明

有三个代码文件,分别是自定义头文件 test2.h,发送方 send.c ,接收方 receive.c

2.2.1 test2.h

我将需要使用到的创建,销毁邮箱的方法,以及对信号量操作的方法都在这个头文件中声明,并实现。减少总的代码量,使代码更简洁一些。

  1. 定义信号量的数据结构

    // 定义信号量的数据结构
    union semun
    {int val;
    };
    
  2. 信号量初始化函数:void sem_init()

  3. P 操作函数:void sem_p()

  4. V 操作函数:void sem_v()

  5. 销毁信号量函数:void sem_destroy()

  6. 创建信箱函数:void createMainbox(char *filename)

  7. 撤销信箱函数:void removeMainbox(char *filename)

2.2.2 send.c

主要操作都在 test2.h 这个头文件中定义好了。send.c 的主要功能如下:

  1. 在最开始时创建邮箱
  2. 读入键盘输入
  3. 判断“信箱是否为空”,若为空,则将输入内容写入“信箱”。若“信箱“已满,则提示发送失败。
  4. 最后结束通信时,撤销信箱
2.2.3 receive.c

receive.c 的主要功能如下:

  1. 读入键盘输入
  2. 若输入为 “receive” 则判断信箱是否为空。
  3. 若为空则输出错误提示;若不为空则从信箱中取出一封邮件并输出,同时将信箱清空。

3.预计实验结果

  1. A 进程首先创建信箱,应输出:创建信箱成功!

  2. A 进程往信箱发送一条消息:This is a msg 111

  3. 此时信箱收到这条消息,因为信箱中只能有一条消息的原因,此刻信箱已满。

    若此时 A 进程再发送消息:This is a msg 222,应该提示输出 “邮箱已满,发送失败”

  4. 再 A 进程发送完后,B 进程读取信箱,取出信箱中的信息。此时应输出 A 进程此前发出的第一条消息,即:This is a msg 111

  5. 此时取出这条消息后信息应为空,若 B 进程再次访问信箱,应输出:邮箱为空!

  6. A 进程发送一条信息:This is a msg 333

  7. B 进程访问信箱,此时应输出为:This is a msg 333;编号为 222 的消息因为邮箱已满,应该被直接丢弃。

  8. A 进程结束,此时撤销信箱,应输出:撤销信箱成功!

4.关键代码分析

4.1 test2.h

4.1.1 创建信箱函数
// 创建信箱函数,根据输入的文件名创建文件
void createMainbox(char *filename)
{// 0755 代表是对文件的操作权限if (creat(filename, 0755) < 0){printf("create file %s failure!\n", filename);// 根据传入的文件名创建一个文件exit(EXIT_FAILURE);}else{printf("创建信箱成功!\n");}
}
4.1.2 撤销信箱函数
void removeMainbox(char *filename)
{if (remove(filename) == 0)// 删除成功返回 0 {printf("撤销信箱成功!\n ");}else{perror("remove\n");}
}
4.2 send.c 发送信件
 FILE *fp; // 定义文件指针createMainbox("mailbox.txt"); // 创建一个名为 mailbox.txt 的文件作为信箱sem_init();// 初始化信号量printf("可在下方输入发送消息(输入 end 删除信箱并结束进程)\n");while (1){printf("发送内容: ");char input[256];memset(input, 0, 256); scanf("%[^\n]", input);// 这样 scanf 可以读取整行的字符串,包括空格getchar();if (strncmp(input, "end", 3) == 0) // 输入等于 "end" 时,调用信箱撤销函数{removeMainbox("mailbox.txt");exit(0);}// 用 r+ 方式打开信箱文件,可读可写,若文件不存在则创建;// 若文件存在,则在文件末尾追加写入if ((fp = fopen("mailbox.txt", "r+")) == NULL){printf("fail to open 1!\n");break; /*出错退出*/}char ch = fgetc(fp);if (ch == EOF)// ch == EOF 表示信箱为空,可写{sem_p();// p 操作fprintf(fp, "%s", input);printf("发送成功!\n");sem_v(); //v操作,释放信号量}else // 反之则信箱已满,不可写入printf("信箱已满,发送失败\n");fclose(fp);// 关闭文件流usleep(1000); //本进程睡眠.}sem_destroy(); //把semid指定的信号集从系统中删除
4.3 receive.c 接受信件
FILE *fp;// 定义文件指针sem_init();// 初始化信号量while (1){printf("输入 receive 从信箱中接受消息:");char input[256];memset(input, 0, 256);scanf("%s", input);if (strncmp(input, "receive", 7) == 0)// 输入为 receive 则判断信箱是否有内容{// 以 r+ 打开信箱if ((fp = fopen("mailbox.txt", "r+")) == NULL){printf("fail to open 1!\n");break; /*出错退出*/}char ch = fgetc(fp);fclose(fp);// 关闭文件流// 判断是否为空if (ch == EOF)printf("信箱为空!\n");else{sem_p();// p 操作system("cat mailbox.txt");// 显示邮箱文件中的全部内容printf("\n");// 定义文件描述符,打开文件int fd = open("mailbox.txt", O_CREAT | O_WRONLY | O_TRUNC, 0666);ftruncate(fd, 0);// 清空文件lseek(fd, 0, SEEK_SET);// 移动文件读写指针,移动到文件开头的位置close(fd);sem_v();// V 操作}}else{printf("输入有误\n");}usleep(1000);}

5.调试记录

  1. 启动两个终端,一个执行 send.c,一个执行 receive.c,A 进程创建信箱

  2. A 进程先发送消息:This is a msg 111

  3. A 进程再发送第二条消息:This is a msg 222

  4. B 进程接收消息

  5. B 进程再次接收消息

  6. A 进程发送第三条消息:This is a msg 333

  7. B 进程接收消息

  8. A 进程撤销信箱

6.实际的实验结果

7.实验结果分析

7.1 结果分析

通过实际的实验结果与预计的实验结果比较,发现二者是一致的。

  1. 首先 A 进程创建信箱成功,返回了创建成功提示
  2. A 进程向信箱中发送 This is a msg 111 ,收到发送成功提示。此时在 B 进程读取信箱之前又发送了一条 This is a msg 222 ,此时返回了发送失败的提示,是因为我设计的信箱只能保存一条信息。此时上一条发送的 111 的消息还没被 B 进程接收,所以发送失败。
  3. B 进程读取信箱,接收信息。此时返回的是 A 进程最开始发的 111 的信息,这是正确的。再次读取信箱接收信息,返回邮箱为空的提示,是因为 B 进程每次接收一条信息之后,就会从信箱中取走这条信息,所以此时信箱是空的。
  4. A 进程再次发送一条信息 This is a msg 333,信箱为空,发送成功。此时 B 进程接收信息,接收到的是 333 的信息。因为 222 的信息再发送失败的时候就直接被丢弃了,并不会再次发送,所以当邮箱清空后接下来接收到的消息是 333。
  5. 最后 A 进程撤销信箱成功,返回了撤销成功的提示。

7.2 与系统自带的“消息队列”机制的对比

除了自己设计的信箱外,我还使用了系统自带的消息队列机制来编写程序,实现进程间的通信,从而做出对比。文件名为 msgqueue.c,下图是调用该程序通信的演示结果。

从上图的程序运行结果可以发现,左边进程发送的三条消息 (1)msg111(2)msg222 (3)msg333 ,右边进程都正常的接收到了。

不同点:
  1. 消息队列机制的数据结构(struct ipc_ids msg_ids)位于内核中,系统中的所有消息队列都可以在结构msg_ids中找到访问入口。因此不存在只能保存一条信息的设定,每次发送都能被正确接受。而我设定的信箱是一个特定的文件,只能保存一条信息,而且如果发送出去的信息还没有被接收而这个文件被删除的话,那发送的消息就丢失了。
  2. 消息队列不需要采用信号量机制来控制同步与互斥的问题
相同点:
  1. 都能实现进程间的通信。

2020 操作系统 实验二 进程通信相关推荐

  1. 广州大学2020操作系统实验二:银行家算法

    相关资料 广州大学2020操作系统实验一:进程管理与进程通信 广州大学2020操作系统实验二:银行家算法 广州大学2020操作系统实验三:内存管理 广州大学2020操作系统实验四:文件系统 广州大学2 ...

  2. 操作系统——实验贰——进程通信(一)管道及共享内存

    一. 实验目的 熟悉并掌握管道机制,并实现进程间通信 熟悉并掌握共享内存机制,并实现进程间通信 二. 实验内容 任务一: (1)阅读以上父子进程利用管道进行通信的例子(例1),写出程序的运行结果并分析 ...

  3. 操作系统实验·Linux进程通信与内存管理

    预备知识 Linux进程的数据结构 在Linux中,进程用task_struct表示,所有进程被组织到以init_task为表头的双向链表中(见[include/linux/sched.h]SET_L ...

  4. ZUCC_操作系统实验_Lab7进程通信---共享内存

    lab7进程通信-共享内存 一.利用共享内存实现生产者/消费者问题的解决方案 1.代码 #include<stdio.h> #include<stdlib.h> #includ ...

  5. 操作系统——实验二 进程管理

    1.实验目的 (1)加深对进程概念的理解,明确进程和程序的区别. (2)进一步认识并发执行的实质. (3)分析进程竞争资源现象,学习解决进程互斥的方法. 2.实验预备内容 (1)阅读Linux的sch ...

  6. 操作系统实验二 进程管理

    进程管理 一.实验目的 1. 理解进程的概念,明确进程和程序的区别. 2. 理解并发执行的实质. 3. 掌握进程的创建.睡眠.撤销等进程控制方法. 二.实验内容与基本要求 用C语言编写程序,模拟实现创 ...

  7. 操作系统实验二进程的创建控制实验(含代码及实验心得)

    实现工具:PC机 实现环境:Linux 实习内容(功能.目标): 实验目的:  创建进程,体会进程间的并发特征. 实验内容:  编写一段程序,使用系统调用 fork() 创建两个子进程 p1 和 p2 ...

  8. 计算机操作系统实验二 进程管理

    一.实验目的 1.掌握进程的概念,明确进程的含义 2.认识并了解并发执行的实质 二.实验内容 1.编写一段程序,使用系统调用fork( )创建两个子进程.当此程序运行时,在系统中有一个父进程和两个子进 ...

  9. 操作系统实验二——进程调度算法(FCFS、RR)

    目录 进程调度算法 FCFS算法代码 RR算法代码 进程调度算法 FCFS算法代码 #include <stdio.h> #include <string.h> #includ ...

最新文章

  1. 反应式系统实现MQTT客户机
  2. java 8 lambda表达式
  3. 民航资源网招聘出奇兵效法麦当劳
  4. 编写高质量的代码,改善c#程序的157个建议_之1~10
  5. s5pv210——I2C基础详解、I2C时序详解
  6. php自动加载基类文件
  7. 面试官:你知道怎么求素数吗?
  8. 基于SpringBoot的后台管理系统(异常、注解、node、page)(二)
  9. erlang的dict和maps模块
  10. 【写作技巧】毕业论文格式要求
  11. Go语言实现大数开方程序
  12. [030] 微信公众帐号开发教程第6篇-文本消息的内容长度限制揭秘(转)
  13. matlab比较判断简写,MATLAB一词来自( )的缩写。
  14. 遍历获取文件夹下的所有文件
  15. MySQL的索引与事务(面试必考) - 细节狂魔
  16. html旋转 缩放 移动,CSS3旋转缩放移动倾斜等效果——transform
  17. 公司网络很慢很卡的原因分析与处理
  18. BUU-CRYPTO1密码学小白 25道入门题 详细解题思路
  19. flowable工作流简单请假流程,自定义完成的流程图表颜色字体以及连接线的颜色字体。
  20. mysql 访问寄存器_汇编寄存器(内存访问)基础知识之三---mov指令

热门文章

  1. IBM MQ常用的命令
  2. android开发设置Button背景颜色
  3. 电子面单接口申请对接(返回电子面单模板)
  4. 两款简单的拒绝服务攻击工具
  5. python写excel文件不覆盖_python excel多sheet存储,同sheet不覆盖追加数据
  6. kingcms php 列表页bug,kingcms最新版sql注入漏洞
  7. JAVA注释、标识符、常量与变量、数据类型和数据类型转换
  8. Photoshop touch教程全攻略
  9. 公共基础知识:什么是“商品”
  10. 马云的“野心”,阿里的区块链布局