O————–共享内存
可以说是最有用的进程间通信方式,也是最快的IPC形式。
两个不同进程A、B共享内存的意思是,同一块物理内存被映射到进程A、B各自的进程地址空间
进程A可以即时看到进程B对共享内存中数据的更新,反之亦然
由于多个进程共享同一块内存区域,必然需要某种同步机制,互斥锁和信号量都可以
O————–采用共享内存通信的一个显而易见的好处是效率高,因为进程可以直接读写内存,而不需要任何数据的拷贝
对比
①管道和消息队列等通信方式,则需要在内核和用户空间进行四次的数据拷贝
②共享内存则只拷贝两次数据,
一次从输入文件到共享内存区
另一次从共享内存区到输出文件
O————–进程之间在共享内存时
并不总是读写少量数据后就解除映射,有新的通信时,再重新建立共享内存区域
而是保持共享区域,直到通信完毕为止,这样,数据内容一直保存在共享内存中,并没有写回文件
往往是在解除映射时才写回文件的
多种共享内存方式:mmap()系统调用,Posix共享内存,以及系统V共享内存
O————–内核怎样保证各个进程寻址到同一个共享内存区域的内存页面
①.一个具体的文件在打开后,内核会在内存中为之建立一个 struct inode 结构
②.struct inode 结构中的 i_mapping 域指向一个 address_space 结构
③.一个 address_space结构 与一个偏移量能够确定一个 page cache 或 swap cache 中的一个页面
④.进程调用mmap()时,只是在进程空间内新增了一块相应大小的缓冲区,设置相应的访问标识
但并没有建立进程空间到物理页面的映射。因此,第一次访问该空间时,会引发一个缺页异常
⑤.缺页异常处理程序首先在swap cache中寻找目标页(符合address_space以及偏移量的物理页)
①如果找到,则直接返回地址
②如果没有找到,则判断该页是否在交换区(swap area),如果在,则执行一个换入操作
③如果上述两种情况都不满足,处理程序将分配新的物理页面,并把它插入到page cache中。进程最终将更新进程页表
⑥.所有进程在建立线性地址与物理地址之间的映射之后,不论进程各自的返回地址如何,实际访问的必然是同一个共享内存区域对应的物理页
O————–mmap()及其相关系统调用
O------mmap() 系统调用
使得进程之间通过映射同一个普通文件实现共享内存。
普通文件被映射到进程地址空间后,进程可以向访问普通内存一样对文件进行访问
不必再调用read(),write()等操作
O------mmap() 系统调用形式
void* mmap ( void * addr , size_t len , int prot , int flags , int fd , off_t offset )
//返回值为最后文件映射到进程空间的地址,进程可直接操作起始地址为该值的有效地址
参数:
addr----指定文件应被映射到进程空间的起始地址
{
一般被指定一个空指针,此时选择起始地址的任务留给内核来完成
}
len-----是映射到调用进程地址空间的字节数,
{
###
**它从被映射文件开头 offset 个字节开始算起**
}
prot----参数指定共享内存的访问权限
{
可取如下几个值的或:
PROT_READ (可读)
PROT_WRITE (可写)
PROT_EXEC (可执行)
PROT_NONE (不可访问)
}
flags---由以下几个常值指定
{
MAP_SHARED , MAP_PRIVATE 必选其一
MAP_FIXED, 则不推荐使用
}
fd------为即将映射到进程空间的文件描述字,一般由open()返回
//亲缘关系的进程实现共享内存最好的方式应该是采用匿名内存映射的方式
{
###
**fd可以指定为 -1,此时须指定 flags 参数中的 MAP_ANON,表明进行的是匿名映射
不涉及具体的文件名,避免了文件的创建及打开,很显然只能用于具有亲缘关系的进程间通信
{
父进程中先调用mmap(),然后调用fork()。
那么在调用fork()之后,子进程继承父进程匿名映射后的地址空间
同样也继承mmap()返回的地址
这样,父子进程就可以通过映射区域进行通信了
}**
}
offset--通常设为0,表示从文件头开始映射
O------系统调用munmap()
int munmap( void * addr, size_t len )
/*
该调用在进程地址空间中解除一个映射关系
当映射关系解除后,对原来映射地址的访问将导致段错误发生
*/
参数:
addr-----是调用mmap()时返回的地址
len------是映射区的大小
O-------系统调用msync()
int msync ( void * addr , size_t len, int flags)
/*
一般说来,进程在映射空间的对共享内容的改变并不直接写回到磁盘文件中
往往在调用munmap()后才执行该操作
可以通过调用msync()实现磁盘上文件内容与共享内存区的内容一致
*/
O————mmap()返回地址的访问
/*
对于用mmap()映射普通文件来说,进程会在自己的地址空间新增一块空间,空间大小由mmap()的len参数指定
但是,进程并不一定能够对全部新增空间都能进行有效访问。
进程能够访问的有效地址大小取决于文件被映射部分的大小
*/
O————read & write 两个进程间通讯代码示例
```
/*-------------read.c-----------*/
#include <stdio.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
typedef struct
{
char name[4];
int age;
}people;
int main(int argc, char** argv) // map a normal file as shared mem:
{
int fd,i;
people *p_map;
fd=open( argv[1],O_CREAT|O_RDWR,0777 );
p_map = (people*)mmap( NULL,sizeof(people)*10,
PROT_READ|PROT_WRITE,
MAP_SHARED,fd,0
);
for(i = 0;i<10;i++)
{
printf( "name: %s age %d;\n",(p_map+i)->name, (p_map+i)->age );
}
munmap( p_map,sizeof(people)*10 );
exit(0);
}
```
```
/*-------------write.c-----------*/
#include <stdio.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>
typedef struct
{
char name[4];
int age;
}people;
int main(int argc, char** argv) // map a normal file as shared mem:
{
int fd,i;
people *p_map;
char temp;
fd = open(argv[1],O_CREAT|O_RDWR|O_TRUNC,0777);
assert(fd != -1);
lseek(fd,sizeof(people)*5-1,SEEK_SET);
write(fd,"",1);
p_map = (people*) mmap( NULL,sizeof(people)*10,
PROT_READ|PROT_WRITE,
MAP_SHARED,fd,0 );
close( fd );
temp = 'a';
for(i=0; i<10; i++)
{
temp += 1;
memset((p_map+i)->name,0,4);
memcpy((p_map+i)->name, &temp,2 );
(p_map+i)->age = 20+i;
}
printf(" initialize over \n ");
sleep(10);
munmap( p_map, sizeof(people)*10 );
printf( "umap ok \n" );
exit(0);
}
```
O———–父子进程间通讯代码示例
```
#include <sys/mman.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <sys/wait.h>
#include <stdlib.h>
typedef struct
{
char name[4];
int age;
}people;
int main(int argc, char** argv)
{
int i;
char temp = 'a';
people *p_map = (people*)mmap( NULL,sizeof(people)*10,
PROT_READ|PROT_WRITE,
MAP_SHARED|MAP_ANONYMOUS,-1,0 );
if(fork() == 0)
{
sleep(2);
for(i = 0;i<5;i++)
{
printf("child read: the %d people's age is %d\n",i+1,(p_map+i)->age);
}
p_map->age = 100;
munmap(p_map,sizeof(people)*10); //实际上,进程终止时,会自动解除映射。
exit(10);
}
for(i = 0;i<5;i++)
{
temp += 1;
memcpy((p_map+i)->name, &temp,2);
(p_map+i)->age=20+i;
}
int status;
wait(&status);
sleep(5);
printf( "parent read: the first people,s age is %d\n",p_map->age );
printf("umap\n");
munmap( p_map,sizeof(people)*10 );
printf( "umap ok\n" );
if(WEXITSTATUS(status))
{
exit(0);
}
printf("child become a zombie!\n");
exit(-1);
}
```
O———–mmap()返回指针所能使用的页面大小
```
#include <sys/mman.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <assert.h>
#include <string.h>
typedef struct
{
char name[4];
int age;
}people;
int main(int argc, char** argv)
{
int fd,i;
int pagesize = sysconf(_SC_PAGESIZE);
int offset;
people *p_map;
printf("pagesize is %d\n",pagesize);
fd = open(argv[1],O_CREAT|O_RDWR|O_TRUNC,0777);
assert(fd != -1);
lseek(fd,pagesize*2-100,SEEK_SET);//make the size of file is two times of pigesize
write(fd,"a",1);
//i can access two piges ,if i access from offset(pigesize),i can access one page
offset = 0; //此处offset = 0编译成版本1;offset = pagesize编译成版本2
p_map = (people*)mmap(NULL,pagesize*3,PROT_READ|PROT_WRITE,MAP_SHARED,fd,offset);
close(fd);
for(i = 1; i<10; i++)
{
(p_map+pagesize/sizeof(people)*i-2)->age = 100;
printf("access page %d over\n",i);
(p_map+pagesize/sizeof(people)*i-1)->age = 100;
printf("access page %d edge over, now begin to access page %d\n",i, i+1);
(p_map+pagesize/sizeof(people)*i)->age = 100;
printf("access page %d now\n",i+1);
}
munmap(p_map,sizeof(people)*10);
exit(0);
}
```