lab Week 07.
实验内容:
实验内容:进程间通信 — Linux System Call 共享内存
建立一个足够大的共享内存空间结构 (lock, M),逻辑值 lock 用来保证同一时间只有一个进程进入 M;测试在你的系统上 M 的容量上限。
设计一个程序,在 M 上建立一个结点信息结构为 (flag, 学号, 姓名) 的列表 L,逻辑值 flag 用作结点的删除标识;在 L 上建立一个以学号为关键字的二元小顶堆,自行设计结点的控制结构 (如静态指针数据域)。
设计一个程序对上述堆结构的结点实现插入、删除、修改、查找、排序等操作。该程序的进程可以在同一主机的多个终端并发执行。
思考:使用逻辑值 lock 实现的并发机制不能彻底解决访问冲突问题。
Part1:
建立一个足够大的共享内存空间 (lock, M),逻辑值lock用来保证同一时间只有一个进程进入M;测试你的系统上 M 的上限。
代码如下
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/shm.h>
#include <fcntl.h>
#define TEST_SIZE 2147483640
struct shared_struct {
char test[TEST_SIZE];/* buffer for message reading and writing */
int lock;/* lock = 0: buffer writable; others: readable */
};//共享结构体
#define PERM S_IRUSR|S_IWUSR|IPC_CREAT
#define ERR_EXIT(m) \
do { \
perror(m); \
exit(EXIT_FAILURE); \
} while(0)
int main(int argc, char *argv[])
{
struct stat fileattr;
key_t key; /* of type int*/
int shmid; /* shared memory ID */
void *shmptr;
struct shared_struct *shared; /* structured shm*/
pid_t childpid1, childpid2;
char pathname[80], key_str[10], cmd_str[80];
int shmsize, ret;
shmsize = sizeof(struct shared_struct); //共享内存的大小
printf("shm size = %d\n", shmsize);
if(argc <2) {
printf("Usage: ./a.out pathname\n");
return EXIT_FAILURE;
}
strcpy(pathname, argv[1]);
if(stat(pathname, &fileattr) == -1) {
ret = creat(pathname, O_RDWR);
if (ret == -1) {
ERR_EXIT("creat()");
}
printf("shared file object created\n");
}
key = ftok(pathname, 0x27); /* 0x27 a project ID 0x0001 - 0xffff, 8 least bits used */
//把路径名和整数标识符转换成IPC键值
if(key == -1) {
ERR_EXIT("shmcon: ftok()");
}
printf("key generated: IPC key = %x\n", key); /* can set any nonzero key without ftok()*/
shmid = shmget((key_t)key, shmsize, 0666|PERM);
if (shmid == -1) {
printf("The shared memory size is %d, which exceeds the maximum shared memory limit\n", shmsize);//申请超过上限
ERR_EXIT("hread: shmget()");
}
printf("shmcon: shmid = %d\n", shmid);
//成功返回共享存储段的指针,出错返回-1
shmptr = shmat(shmid, 0, 0);
if(shmptr == (void *)-1) {
ERR_EXIT("shread: shmat()");
}
printf("shmcon: shared Memory attached at %p\n", shmptr);
shared = (struct shared_struct *)shmptr;
shared->lock = 0;
//设置lock使共享内存可写
sprintf(cmd_str, "ipcs -m | grep '%d'\n", shmid);
//ipcs -m,查看共享内存
printf("\n------ Shared Memory Segments ------\n");
system(cmd_str);
if (shmdt(shmptr) == -1)
{
//把共享内存从当前进程中分离,使该共享内存对当前进程不再可用
ERR_EXIT("shmread: shmdt()");
}
printf("The shared memory size is %d, which is within the maximum shared memory range\n", shmsize);
if (shmctl(shmid, IPC_RMID, 0) == -1) { //删除共享内存
ERR_EXIT("shmcon: shmctl(IPC_RMID)");
}
exit(EXIT_SUCCESS);
}
经过手动二分,确定该系统能创建共享内存的最大值。
当TEST_SIZE设置为2147483640时,创建的共享内存数据段大小为2147483644,创建成功,为该系统能创建共享内存的最大值,接近int的上限。
当TEST_SIZE设置为2147483641时,创建的共享内存数据段大小为2147483645超过限制,因此创建失败。
Part2:
设计一个程序,在 M 上建立一个结点信息结构为 (flag, 学号, 姓名) 的列表 L,逻辑值 flag 用作结点的删除标识;在 L 上建立一个以学号为关键字的二元小顶堆,自行设计结点的控制结构 (如静态指针数据域)。
设计一个程序对上述堆结构的结点实现插入、删除、修改、查找、排序等操作。该程序的进程可以在同一主机的多个终端并发执行。
学生struct包含 id ,name ,flag。
flag为1 表示该学生已经删除。
代码文件1 heapdata.h
#include<string.h>
#define TEXT_SIZE 4*1024 /* size of each message */
#define STUDENT_NUM 10007 /* maximal number of students */
#define QUE_NUM 1 /* maximal number of students queue */
/* total size can not exceed current shmmax,
or an 'invalid argument' error occurs when shmget */
/* a demo structure, modified as needed */
struct student{
int flag;
int id;
char name[TEXT_SIZE];
};
struct shared_struct {
int written; /* flag = 0: buffer writable; others: readable */
int quit; /* flag=0: running; others:quit process */
int exit;
int l; /* head of queue */
int r; /* tail of queue */
int n; /* size of queue set by user */
int num,totnum;
int lock;
struct student heap[STUDENT_NUM]; /* buffer for students queue reading and writing */
};
void swap(struct student *a,struct student *b)
{
int tmp=a->flag;
a->flag=b->flag;
b->flag=tmp;
tmp=a->id;
a->id=b->id;
b->id=tmp;
char tt[TEXT_SIZE];
strcpy(tt,a->name);
strcpy(a->name,b->name);
strcpy(b->name,tt);
}
void push(struct shared_struct* x, char *s,int idd)
{
x->totnum++;
x->num++;
x->heap[x->totnum].flag=0;// 1 means deleted
x->heap[x->totnum].id=idd;
strcpy( x->heap[x->totnum].name , s);
int now=x->totnum;
while( now!=1 && x->heap[now].id < x->heap[now/2].id )
{
swap( &x->heap[now] , &x->heap[now/2]);
now/=2;
}
}
void pop(struct shared_struct* x)
{
swap(&x->heap[x->totnum],&x->heap[1]);
if( x->heap[x->totnum].flag==0)
x->num--;
x->totnum--;
int now=1;
while(1)
{
if(now*2 > x->totnum)
break;
if( ( now*2+1> x->totnum || x->heap[now*2].id < x->heap[now*2+1].id ) && x->heap[now].id > x->heap[now*2].id)
swap( &x->heap[now], &x->heap[now*2]);
else
if( now*2+1<= x->totnum && x->heap[now].id > x->heap[now*2+1].id)
swap( &x->heap[now], &x->heap[now*2+1]);
else
break;
}
}
void work(struct shared_struct* x)
{
while(x->heap[1].flag==1 && x->totnum>=1)
pop(x);
}
struct student top(struct shared_struct* x)
{
work(x);
return x->heap[1];
}
int small(struct student x, struct student y)
{
if( x.flag==y.flag)
return x.id<y.id;
return x.flag<y.flag;
}
void ssort(struct shared_struct* x)
{
for(int i=1;i<=x->totnum;i++)
for(int j=i+1;j<=x->totnum;j++)
if( small(x->heap[j] , x->heap[i]))
swap(&x->heap[i] , &x->heap[j]);
x->totnum=x->num;
}
void del(struct shared_struct* x,char *s)
{
for(int i=1;i<=x->totnum;i++)
if(strcmp(x->heap[i].name,s)== 0)
{
if( x->heap[i].flag==0)
x->num--;
x->heap[i].flag=1;
}
}
void init(struct shared_struct* x)
{
x->exit=0;
x->totnum=0;
x->num=0;
x->lock=0;
}
#define PERM S_IRUSR|S_IWUSR|IPC_CREAT
#define ERR_EXIT(m) \
do { \
perror(m); \
exit(EXIT_FAILURE); \
} while(0)
其中num表示有效的学生数量,totnum表示当前堆的大小,lock为0表示当前共享内存空间可操作。
该程序没有使用静态指针域,而是直接创建了一个堆,heap数组的元素满足小根堆的性质,父亲的id小于它的两个儿子(若存在)。
在共享数据空间创建了一个堆,以id为键值的小根堆,以传统的方法实现了堆的各种操作,有push,pop,删除,排序。
根据堆的性质,push操作和pop操作保证了log (n) 复杂度,但删除操作O(n) 实现,排序以 O(n^2) 实现。
删除时直接操作学生节点的 flag,不破坏堆的结构,不把它弹出堆中,即堆中可能包括已被删除的学生。
代码文件2 heapcon.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/shm.h>
#include <fcntl.h>
#include "heapdata.h"
int main(int argc, char *argv[])
{
struct stat fileattr;
key_t key; /* of type int */
int shmid; /* shared memory ID */
void *shmptr;
struct shared_struct *shared; /* structured shm */
pid_t childpid1, childpid2;
char pathname[80], key_str[10], cmd_str[80];
int shmsize, ret;
shmsize = QUE_NUM*sizeof(struct shared_struct);
printf("max record number = %d, shm size = %d\n", QUE_NUM, shmsize);
if(argc <2) {
printf("Usage: ./a.out pathname\n");
return EXIT_FAILURE;
}
strcpy(pathname, argv[1]);
if(stat(pathname, &fileattr) == -1) {
ret = creat(pathname, O_RDWR);
if (ret == -1) {
ERR_EXIT("creat()");
}
printf("shared file object created\n");
}
key = ftok(pathname, 0x27); /* 0x27 a project ID 0x0001 - 0xffff, 8 least bits used */
if(key == -1) {
ERR_EXIT("shmcon: ftok()");
}
printf("key generated: IPC key = 0x%x\n", key); /* or you can set any nonzero key without ftok()*/
shmid = shmget((key_t)key, shmsize, 0666|PERM);
if(shmid == -1) {
ERR_EXIT("shmcon: shmget()");
}
printf("shmcon: shmid = %d\n", shmid);
shmptr = shmat(shmid, 0, 0); /* returns the virtual base address mapping to the shared memory, *shmaddr=0 decided by kernel */
if(shmptr == (void *)-1) {
ERR_EXIT("shmcon: shmat()");
}
printf("\nshmcon: shared Memory attached at %p", shmptr);
shared = (struct shared_struct *)shmptr;//
sprintf(cmd_str, "ipcs -m | grep '%d'\n", shmid);
printf("\n------ Shared Memory Segments ------\n");
system(cmd_str);
init(shared);
sleep(2);
while(shared->exit==0)
{
printf("input 0 to exit");
int x;
scanf("%d",&x);
if(x==0)
break;
sleep(5);
}
printf("there are %d students in the heap\n",shared->num);
for(int i=1;i<=shared->totnum;i++)
if(shared->heap[i].flag==0)
{
printf("id %d name: %s \n",shared->heap[i].id,shared->heap[i].name);
}
if(shmdt(shmptr) == -1) {
ERR_EXIT("shmcon: shmdt()");
}
printf("\nshmcon: shared Memory detached at %p", shmptr);
printf("\n------ Shared Memory Segments ------\n");
system(cmd_str);
sprintf(key_str, "%x", key);
char *argv1[] = {" ", key_str, 0};
if (shmctl(shmid, IPC_RMID, 0) == -1) {
ERR_EXIT("shmcon: shmctl(IPC_RMID)");
}
else {
printf("shmcon: shmid = %d removed \n", shmid);
printf("\n------ Shared Memory Segments ------\n");
system(cmd_str);
printf("\n\n");
}
exit(EXIT_SUCCESS);
}
主要代码部分,创建了一个shared_struct结构的共享内存空间,得到了对应的KEY值和shmid,再得到指针shmptr 和强制类型转换shared_struct类型的shared指针。
随后对shared_struct初始化。
再以I/O挂起该程序,直到输入0之后退出,遍历一遍共享空间并输出。
再断开共享空间和该进程的连接,并注销共享空间。
代码文件3 heapwork.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <string.h>
#include <sys/shm.h>
#include "heapdata.h"
int main(int argc, char *argv[])
{
void *shmptr = NULL;
struct shared_struct *shared;
int shmid;
key_t key;
sscanf(argv[1], "%x", &key);
//printf("%*sheapwork: IPC key = 0x%x\n", 30, " ", key);
shmid = shmget((key_t)key, QUE_NUM*sizeof(struct shared_struct), 0666|PERM);
//sscanf(argv[1], "%x", &shmid);
if (shmid == -1) {
ERR_EXIT("shread: shmget()");
}
shmptr = shmat(shmid, 0, 0);
if(shmptr == (void *)-1) {
ERR_EXIT("shread: shmat()");
}
printf("%*sheapwork: shmid = %d\n", 30, " ", shmid);
printf("%*sheapwork: shared memory attached at %p\n", 30, " ", shmptr);
printf("%*sheapwork process ready ...\n", 30, " ");
shared = (struct shared_struct *)shmptr;
while (shared->exit ==0)
{
while (shared->lock == 1) {
sleep(1); /* message not ready, waiting ... */
}
shared->lock=1;
int op;
printf("1: push 2:pop 3:delete 4:sort 5:top 6: display 7:quit\n");
printf("please input option number\n");
scanf("%d",&op);
char s[TEXT_SIZE];
int id;
if(op==1)
{
printf("please input the student name and id split by space\n");
scanf("%s%d",s,&id);
push(shared,s,id);
}
if(op==2)
{
work(shared);
if(shared->num==0)
printf("the heap is already empty!!\n");
else
{
printf("the student %s id %d has been poped\n",shared->heap[1].name,shared->heap[1].id);
pop(shared);
}
}
if(op==3)
{
printf("please input the name of the student that you want to delete\n");
scanf("%s",s);
printf("the students called %s are all deleted\n",s);
del(shared ,s);
}
if( op==4)
{
ssort(shared);
printf("the heap has been sorted\n");
}
if( op ==5)
{
work(shared);
if(shared->num==0)
printf("the heap is already empty!!\n");
else
{
printf("the student at the top is %s with id %d\n",shared->heap[1].name,shared->heap[1].id);
}
}
if( op == 6)
{
printf("there are %d students in the heap\n",shared->num);
for(int i=1;i<=shared->totnum;i++)
if(shared->heap[i].flag==0)
{
printf("id: %d name: %s \n",shared->heap[i].id,shared->heap[i].name);
}
}
if( op==7)
{
//shared->exit=1;
shared->lock=0;
break;
}
shared->lock = 0;
sleep(2);
} /* it is not reliable to use shared->written for process synchronization */
if (shmdt(shmptr) == -1) {
ERR_EXIT("heapwork: shmdt()");
}
// sleep(1);
exit(EXIT_SUCCESS);
}
输入时需要接收主程序生成的key值,并通过shmget函数,以key值与整数生成相同的shmid。虽然shmid相同,但shmptr的值不同,即共享内存在两个程序内的虚拟地址不相同。再attach上共享内存,在程序结束时断开连接。
进入该程序后,会一直休眠到lock为0,并判断是否有其他相同进程改动了exit标志,即结束了该进程。
lock为0后,程序设置lock为1,并弹出用户操作界面。
提供了7种操作,分别为push、pop、top、删除、排序、展示,结束。分别调用heapdata.h中定义的对应函数,完成对共享内存的修改。
每一种操作完成后,设置lock为0,即内存空间可操作,并睡眠两秒。若有其他相同进程操作该空间,则可以接入,由系统随机选择。
如图所示,同时开启了3个终端访问创建的共享内存。
在没有删除操作时,display操作直接展示堆中全部元素,它们的顺序满足堆的性质。有删除操作后删除的节点在display中不显示。在sort后,相当于删去了堆中所有无效的学生,并按id大小严格排序。
可见该程序实现了堆的操作,在插入一个最小的学生 A 2,及 gg 0 时,能安按照堆的交换规则,以log(n) 的复杂度交换到top
在删除学生后,并非将其移除堆,而是找到对应的节点,打上标记。若查询到top为已经删除的节点,则pop即可,相当于懒惰标记。
在输入退出指令后,不再弹出用户界面,设置exit标志为1,新的进程不能再访问该共享空间。主程序在遍历一遍共享空间,确定程序正确性后,断开与共享内存的连接,并注销共享内存,输出反馈。
本次实验主要以上周实验为框架编写,运用了共享内存的创建,并把key值传递给需要利用该空间的进程,不同之处在于两个相同的进程同时访问共享内存,要设置lock保证同一时间只有一个进程访问,其他进程在休眠。
Part3:
思考:使用逻辑值 lock 实现的并发机制不能解决条件冲突问题。
使用lock逻辑值来防止多进程对共享空间的同时访问,以防止共享空间的混乱和异常,在单个cpu的状态下极小概率出现错误,仅当两个进程在同一时刻sleep完毕,同时苏醒,同时设置lock值,但没有转到寄存器,则有可能两个程序同时访问共享内存。出现概率极小,但可以人为精确调整系统时钟,或使用不合法的I/O使其出错。
但是对于多cpu来说,效果不理想,lock值的赋值要经过内存,寄存器,ALU,再到内存。
当进程有多个cpu占用的时候,lock的值会在寄存器操作的过程中被多次访问或者同时访问。如内存,寄存器的同时读写,从而导致条件冲突问题。