基于共享内存的多终端运行进程间通信

进程间通信—共享内存。

实现一个带有n个单元的线性表的并发维护

实验分析

 此次实验的基本原理还是基于共享内存的进程间通信,难点在于怎么建立一个并发维护的线性表。
 粗略地理解一下:就是先通过控制程序向操作系统申请一个共享内存空间,然后通过访问程序在该共享内存空间上建立一个静态链表,再在该静态链表的基础上建立一个以学号为关键字的二元小顶堆。最终实现的是访问程序之间的进程通信需要注意的是,我们必须对维护二元小顶堆的进程进行限制,也就是说,同一时间只有一个进程进入M并对二元小顶堆进行相关操作

实现原理

  1. 在向操作系统申请空间之前,我们需要先确定我们所要储存的元素及其结构。首先共享内存空间需要一个lock变量(用来控制同一时间只有一个进程对二元小顶堆进行操作)。

  2. 其次就是我们要存放的信息:姓名,学号,flag。所以每个节点的信息结构应包含这三个信息。在这里我限制了每个学生的姓名长度不超过20,节点信息结构结构定义如下
    在这里插入图片描述

  3. 为了在共享内存空间上建立起一个静态链表,我们需要在共享内存空间上定义一个数组,元素类型为上述定义的结构体。此外,为了能够识别终端中的进程想要对二元小顶堆进行的操作,在共享内存空间中定义了一个service变量,进程只需要识别service变量并调用对用的函数即可。
    在这里插入图片描述

  4. 建立完静态链表之后,还需要在静态链表的基础上建立一个以学号为关键字的二元小顶堆。对小顶堆的操作:插入,删除,修改,查找,重排。

  • 插入:具体操作是在最后一个元素的后面插入,然后采用上浮的方法,让该节点到达该到的entry。需要注意的是,在插入时我们需要知道的是最后一个元素的静态链表中的位置,所以这里需要设置一个变量来记录数组中一已有的元素个数,所以在共享内存空间中我们需要加入size变量。其次,在上浮的过程中,虽然比较的是学号,但是在交换节点的位置是,需要注意学生姓名的处理,不能直接采取类似于name1 = name2的操作,比较好的一个做法是:先定义一个buffer字符数组,再使用strncpy();函数完成对姓名的位置交换。注意:将用来记录二元小顶堆大小的size变量递增一次
    在这里插入图片描述

  • 删除:删除操作在此程序的实现方法是:通过终端输入的学号来删除该学号对应的学生的信息。首先需要注意的是怎么获取终端输入的学号:实现方法:
    在这里插入图片描述

然后通过该学号,对二元小顶堆的数组进行遍历,找到该学号对应的entry后使用该节点对应的儿子节点中最小的那个节点来替代父亲节点(也就是被删除的那个节点),这个操作一直循环到数组的最后一个entry。注意:将用来记录二元小顶堆大小的size变量递减一次

  • 修改:修改操作在此程序的实现方法为:通过终端输入的学号找到该学号对应的entry,将该entry记录的name替换成终端输入的新name。具体操作跟上面有重合,这里不再赘述。
  • 查找:查找操作在此程序中的实现方法为:通过终端输入的学号找到对应的entry然后输出该entry对应的学生的姓名。
  • 重排:重排操作体现在插入与删除操作中,也就是在破坏二元小顶堆的结构之后需要恢复该二元小顶堆的结构。实现方法分上浮和下潜两种:上浮一般用于插入操作,下潜用于删除操作。插入时将要插入的元素放在小顶堆最后一个元素的后面,再通过关键字的比较,若其父亲节点的关键字大于儿子结点的关键字则互换位置。删除时一般都是用小顶堆的最后一个元素替代要删除的元素,然后通过关键字的比较,确保父亲节点的关键字要小于儿子结点的关键字。

细节解释

注意:因为IPC Key是自己定义的,所以在运行程序时需要加入非零参数:
gcc Lab7.c
./a.out 2019

参数只要是非零整数即可。

最大内存测试程序

原理:让程序一直申请共享内存空间,如果申请成功意味着此次申请的内存空间的大小并未达到上限,应当回收该申请好的共享内存空间。当申请失败时,输出申请失败的共享内存空间的大小。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-t6Ki6QAC-1617851253177)(/api/users/image?path=8500/images/1617851018374.png)]

控制程序

控制程序在申请内存方面是之前的实验内容,这里不再赘述。
值得注意的点是,进行通信的进程只负责对共享内存空间进行操作,当一个访问进程结束不再想继续通信时会结束进程,但是所申请的共享内存空间还存在着,所以共享内存的回收需要通过控制进程来实现。
在这里插入图片描述

访问进程(进行通信的进程)

对小顶堆进行的操作如下:
在这里插入图片描述
由于Insert()和Delete()函数已经解释过了,下面介绍Modify()函数与Search():
在这里插入图片描述
在这里插入图片描述
main()函数中的共享内存空间申请与映射也与之前的实验重合,这里不再赘述。
main()中主要部分是控制进程访问共享内存的部分:

这里需要注意的是怎么控制同一时间只有一个进程访问共享内存空间。显然,当lock=0也就是没有进程在访问共享内存空间时就可以直接访问,当lock=1表示正有进程在访问共享内存空间则暂时无法访问共享内存空间。

那么如何实现两个进程都能对共享内存空间进行有序地访问呢?程序中的解决方法是通过询问正在访问共享内存空间的进程要进行什么操作来实现有序访问共享内存空间的:正在访问共享内存空间的进程可以选择继续进行插入删除等对小顶堆的操作,也可以选择停止对共享内存空间的访问让出访问权,还可以终止进程。具体实现如下:
在这里插入图片描述
主要就是这一步的空控制,剩下的就是按对应的数字调用相应的函数即可。
在这里插入图片描述

思考:

使用逻辑值lock实现的并发机制并不能解决条件冲突问题
 使用逻辑值lock的话,同一时间内只允许一个进程对共享内存空间进行访问,而且当进行通信的进程数超过两个时,一个正在访问共享内存空间的进程让出访问权时,其他进程会争夺访问权,这时就会出现混乱。


  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
好的,我理解了你的问题。这是一个经典的进程通信问题,可以使用共享内存和信号量进行解决。以下是基于System V信号量的示例代码: sender.c ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/shm.h> #include <sys/sem.h> #define SHM_SIZE 1024 #define SEM_KEY 1234 #define SHM_KEY 5678 union semun { int val; struct semid_ds *buf; unsigned short *array; }; int main() { int sem_id, shm_id; char *shm_ptr; union semun sem_arg; struct sembuf sem_op; // 创建信号量 sem_id = semget(SEM_KEY, 1, IPC_CREAT | 0666); if (sem_id < 0) { perror("semget error"); exit(1); } // 初始化信号量的值为0 sem_arg.val = 0; if (semctl(sem_id, 0, SETVAL, sem_arg) < 0) { perror("semctl error"); exit(1); } // 创建共享内存 shm_id = shmget(SHM_KEY, SHM_SIZE, IPC_CREAT | 0666); if (shm_id < 0) { perror("shmget error"); exit(1); } // 连接共享内存 shm_ptr = shmat(shm_id, NULL, 0); if (shm_ptr == (char *) -1) { perror("shmat error"); exit(1); } // 从终端读取输入 printf("Please input a message:\n"); fgets(shm_ptr, SHM_SIZE, stdin); // 发送信号量,通知receiver可以读取共享内存 sem_op.sem_num = 0; sem_op.sem_op = 1; sem_op.sem_flg = 0; if (semop(sem_id, &sem_op, 1) < 0) { perror("semop error"); exit(1); } // 等待receiver的应答 sem_op.sem_num = 0; sem_op.sem_op = -1; sem_op.sem_flg = 0; if (semop(sem_id, &sem_op, 1) < 0) { perror("semop error"); exit(1); } // 显示receiver的应答 printf("Received response: %s\n", shm_ptr); // 删除共享内存 shmdt(shm_ptr); shmctl(shm_id, IPC_RMID, NULL); // 删除信号量 semctl(sem_id, 0, IPC_RMID, sem_arg); return 0; } ``` receiver.c ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/shm.h> #include <sys/sem.h> #define SHM_SIZE 1024 #define SEM_KEY 1234 #define SHM_KEY 5678 union semun { int val; struct semid_ds *buf; unsigned short *array; }; int main() { int sem_id, shm_id; char *shm_ptr; union semun sem_arg; struct sembuf sem_op; // 创建信号量 sem_id = semget(SEM_KEY, 1, IPC_CREAT | 0666); if (sem_id < 0) { perror("semget error"); exit(1); } // 创建共享内存 shm_id = shmget(SHM_KEY, SHM_SIZE, IPC_CREAT | 0666); if (shm_id < 0) { perror("shmget error"); exit(1); } // 连接共享内存 shm_ptr = shmat(shm_id, NULL, 0); if (shm_ptr == (char *) -1) { perror("shmat error"); exit(1); } // 等待sender写入共享内存 sem_op.sem_num = 0; sem_op.sem_op = -1; sem_op.sem_flg = 0; if (semop(sem_id, &sem_op, 1) < 0) { perror("semop error"); exit(1); } // 显示sender写入的消息 printf("Received message: %s", shm_ptr); // 向sender发送应答消息 strcpy(shm_ptr, "over"); // 发送信号量,通知sender可以读取共享内存 sem_op.sem_num = 0; sem_op.sem_op = 1; sem_op.sem_flg = 0; if (semop(sem_id, &sem_op, 1) < 0) { perror("semop error"); exit(1); } // 删除共享内存 shmdt(shm_ptr); shmctl(shm_id, IPC_RMID, NULL); // 删除信号量 semctl(sem_id, 0, IPC_RMID, sem_arg); return 0; } ``` 使用gcc编译以上代码: ``` $ gcc sender.c -o sender $ gcc receiver.c -o receiver ``` 然后打开两个终端,一个运行sender,一个运行receiver,就可以进行进程通信了。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值