mb共享内存及实验(读者写者问
)wp
Linux课程设计
专业班级:
学号:
姓名:
Linux共享内存通信的原理
及实验,读者写者问题,
一、原理说明及分析
1:共享内存的相关原理
1)共享内存就是内存中一个区域(段)的映射,这个区域可以被更多的进程所共享。它是IPC机制中最快的一种形式,因为它不需要中间环节,而是直接把信息从一
个内存段映射到调用进程的地址空间。因为进程可以直接读写内存,而不需要任何数据的拷贝。对于像管道和消息队列等通信方式,则需要在内 核和用户空间进行四次的数据拷贝,而共享内存则只拷贝两次数据:一次从输入文件到共享内存区,另一次从共享内存区到输出文件。实际上,进程之间在共 享内存时,并不总是读写少量数据后就解除映射,有新的通信时,再重新建立共享内存区域。而是保持共享区域,直到通信完毕为止,这样,数据内容一直保存在共 享内存中,并没有写回文件。共享内存中的内容往往是在解除映射时才写回文件的。因此,采用共享内存的通信方式效率是非常高的。
2)一个段可以直接由一个进程创建,随后可以有任意多个进程对其进读和写。但是,一旦内存被共享之后,对共享内存的访问同步需要由其他的IPC机制,例如信号量来实现。Linux对共享内存的存取控制是通过对访问键和访问权限的检查来控制的。 2:mmap()及相关系统调用
1)mmap()系统调用使得进程之间通过映射同一个普通文件实现共享内存。普通文件被映射到进程地址空间后,进程可以向访问普通内存一样对文件进行访问,不必再调用read(),write()等操作。mmap()系统调用形式如下:
void* mmap ( void * addr , size_t len , int prot , int flags , int fd , off_t
offset )例如:p_map=(people*)
mmap(NULL,sizeof(people),PROT_READ|PROT_WRITE,MAP_SHARE|MAP_ANONYMOUS,-1,0);
2)系统调用mmap()用于共享内存有两种方式:
a)使用普通文件提供的内存映射:适用于任何进程之间; 此时,需要打开或创建一个文件,然后再调用mmap();典型调用代码如下:
fd=open(name, flag, mode); if(fd<0)
...
ptr=mmap(NULL, len , PROT_READ|PROT_WRITE, MAP_SHARED , fd , 0);
b)使用特殊文件提供匿名内存映射:适用于具有亲缘关系的进程之间; 由于父子进程特殊的亲缘关系,在父进程中先调用mmap(),然后调用fork()。那么在调用fork()之后,子进程继承父进程匿名映射后的地址空 间,同样也继承mmap()返回的地址,这样,父子进程就可以通过映射区域进行通信了。见实验代码。
3:共享内存重要数据结构及实现过程:
1) 重要数据结构如下图:struct shmid_kernel ;struct vm_area_struct;
Struct file;struct dentry;struct inode;struct address_space;
2) 对mmap()返回地址的访问
前面对范例运行结构的讨论中已经提到,linux采用的是页式管理机制。对于用mmap()映
射普通文件来说,进程会在自己的地址空间新增一块空间,空间大小由mmap()的len参数
指定,注意,进程并不一定能够对全部新增空间都能进行有效访问。进程能够访问的有效
地址大小取决于文件被映射部分的大小。简单的说,能够容纳文件被映射部分大小的最少
页面个数决定了进程从mmap()返回的地址开始,能够有效访问的地址空间大小。超过这个
空间大小,内核会根据超过的严重程度返回发送不同的信号给进程。可用如下图示说明:
3) 共享内存实现过程
a) page cache及swap cache中页面的区分:一个被访问文件的物理页面都驻留在page cache或swap cache中,一个页面的所有信息由struct page来描述。struct page中有一个域为指针mapping ,它指向一个struct address_space类型结构。page cache或swap cache中的所有页面就是根据address_space结构以及一个偏移量来区分的。 b) 文件与address_space结构的对应:一个具体的文件在打开后,内核会在内存中为之建立一个struct inode结构,其中的i_mapping域指向一个address_space结构。这样,一个文件就对应一个address_space结构,一个 address_space与一个偏移量能够确定一个page cache 或swap cache中的一个页面。因此,当要寻址某个数据时,很容易根据给定的文件及数据在文件内的偏移量而找到相应的页面。
c) 进程调用mmap()时,只是在进程空间内新增了一块相应大小的缓冲区,并设置了相应的访问标识,但并没有建立进程空间到物理页面的映射。因此,第一次访问该空间时,会引发一个缺页异常。
d) 对于共享内存映射情况,缺页异常处理程序首先在swap cache中寻找目标页(符合address_space以及偏移量的物理页),如果找到,则直接返回地址;如果没有找到,则判断该页是否在交换区 (swap area),如果在,则执行一个换入操作;如果上述两种情况都不满足,处理程序将分配新的物理页面,并把它插入到page cache中。进程最终将更新进程页表。
二、设计原理及实现
1)问题描述:一个数据文件或
,可以被多个进程共享,我们把要求读该文件的进程称为“reader”进程,其他进程则称为“writer”进程。允许多个进程同时读一个共享对象,因为读操作不会使数据混乱。但不允许一个writer进程和其他reader进程或writer进程同时访问共享对象,因为这种访问将引起混乱。所谓“读者写者问题”是指保证一个writer进程必须与其他进程互斥的访问共享对象的同步问题。
2)解决原理及实现见代码,如下:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
typedef struct{
char name[50];
}people;
int e1,f1,readcount,num;
main(int argc,char **argv){
e1=semget(IPC_PRIVATE,1,0666|IPC_CREAT);//设置读者信号量e1
f1=semget(IPC_PRIVATE,1,0666|IPC_CREAT);//设置写者信号量f1
readcount=0;
semctl(e1,0,SETVAL,1);//读者初值设为1
semctl(f1,0,SETVAL,1);// 写者初值设为1
int i,j;
people *p_map;
sem_c相当于V操作每次加struct sembuf sem_b,sem_c;//sem_b相当于p操作每次减一 一
sem_b.sem_num=0;
sem_b.sem_op=-1;
sem_b.sem_flg=SEM_UNDO;
sem_c.sem_num=0;
sem_c.sem_op=1;
sem_c.sem_flg=SEM_UNDO;
char temp[50];
pid_t pid1;
p_map=(people*)mmap(NULL,sizeof(people),PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANO
NYMOUS,-1,0);//通过mmap函数建立一个共享内存
pid1=fork(); //可以产生子进程
while(1){
if(pid1==0){
semop(e1,&sem_b,1);//e1减一若可以则可以进入读进程
printf("now i want to read!\n"); if(readcount==0)semop(f1,&sem_b,1);//f1减一确保没有写进程 printf("now i am reading!\n"); readcount++;//读者个数加一
semop(e1,&sem_c,1);//e1加一
printf("name:%s\n",(*p_map).name); semop(e1,&sem_b,1);//e1减一确保在修改readcount时没有其他读进程产生干扰 printf("readcount is :%d \n",readcount);
readcount--;//readcount个数减一
if(readcount==0)semop(f1,&sem_c,1);//f1加一使写者可以进行写操作 semop(e1,&sem_c,1);//e1加一使读者可以继续进行读操作 }
if(pid1>0){
num=rand()%2;//设置一个随机函数,这样使程序有些变化 if(num>1){//若大于1则休息5秒
printf("i am sleeping\n"); sleep(5);
}
semop(f1,&sem_b,1);//f1减一,若可以则进入写操作
printf("now i am writing!\n"); printf("Please inputer some words:\n");
scanf("%s",temp);//等待输入一个字符串
memcpy((*p_map).name,temp,sizeof(temp));//将字符串写到结构体中 sleep(1);
printf("readcount is :%d \n",readcount);
semop(f1,&sem_c,1);//f1加一,使后续写者可以继续写
}
}
}
三、测试与总结
1)测试文件名为nctx.c 经过gcc nctx.c –o nctx 后在./nctx即可。截图如下
2) 运行截图如下:
3) 个人总结:
a) 读者写者问题就是让写者不与读者和其他写者冲突。设置读者信号量和写者信号量以及读者个数,初值分别为1,1,0。当写者写的时候写者信号量减一,那么其他写者想进行写操作的时候在对写信号量进行减一时就会阻塞,只能进行等待。除非写者已经写完了,并且把写信号量加一。而读者进行读的时候,先要对读者信号量减一。这样做是为了防止由于其他读者正进行对readcount进行修改而产生错误。如果readcount等于0,那就说明这是第一个读者,就需要对写信号量进行修改。这样做的效果有两个:一是防止有写者正在进行写操作从而导致读写出问题;二是为了防止读者正在读的时候写者突然进入进行写操作,那样也会产生令人意想不到的错误。再后来对读信号量减一也是为了在对readcount进行修改时不要有读者进行干扰而设计的。当readcout为0是证明没有读者正在进行读操作了,因而可以允许写者进入,故写信号量加一。然后读信号量也加一,也就是说读者也可以进入了~
b) 对mmap函数的理解:mmap函数可以有两种形式,匿名或命名。对于有亲缘关系的可以使用匿名,而对于没有亲缘关系的可以使用命名。命名的会在硬盘上产生一个实实在在的文件,以后一旦建立映射就可以从这里读取数据当然也可以写入数据。在建立共享内存时,建立的共享内存大小可以与在硬盘上保存的文件大小不一样。因为只有在关闭映射时,在内存中的数据才会写到硬盘中,也就是说平时读写文件只在内存中进行,故而效率比较高。匿名映射(本实验用的就是匿名,当然命名也进行了实验这里就不贴出代码和截图了)和命名差不多,我觉得好像就是可以不写入硬盘了。
c) 在进行实验的过程中产生一些错误,这里列举一些错误及解决。warning: incompatible implicit declaration of built-in function
‘memcpy’ 没有include 所致 。在运行程序时提示:段错误, 那是在运行命名mmap时没有加入后面的文件名例如./read /tmp/test_shm
忘记写/tmp/test_shm。在头文件中写了一些函数但运行时函数说undefined,那是因为在头文件里写函数时没有加大括号。在用scanf函数进行输入的时
候也出现了一些错误,后来看看网页就不知道怎么改好了。错误还很多,这
里不一一列举了。
参考文献:
1. linux操作系统
2. linux应用与开发典型实例精讲
3. 计算机操作系统
4. Linux环境进程间通信: 共享内存——郑颜兴 5. 面向对象技术与VC++6.0教程