System IPC 与Posix IPC(共享内存)
系统v(共享内存)
2.shmget()用来获得共享内存区域的ID,如果不存在指定的共享区域就创建相应的区域。
shmat()把共享内存区域映射到调用进程的地址空间 中去,这样,进程就可以方便地对共享区域进行访问操作。
shmdt()调用用来解除进程对共享内存区域的映射。
shmctl实现对共享内存区域的控制操 作。
这里我们不对这些系统调用作具体的介绍,读者可参考相应的手册页面,后面的范例中将给出它们的调用方法。
注:shmget的内部实现包含了许多重要的系统V共享内存机制;shmat在把共享内存区域映射到进程空间时,并不真正改变进程的页 表。当进程第一次访问内存映射区域访问时,会因为没有物理页表的分配而导致一个缺页异常,然后内核再根据相应的存储管理机制为共享内存映射区域分配相应的 页表。
在/proc/sys/kernel/目录下,记录着系统V共享内存的一下限制,如一个共享内存区的最大字节数shmmax,系统范围内最大共享内存区标识符数shmmni等,可以手工对其调整,但不推荐这样做。
#include <stdlib.h>
#include <stdio.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
typedef struct {
char name[4];
int age;
}people;
int main(int argc,char *argv[]){
int shm_id ,i;
key_t key;
char temp;
people *p_map;
char* name = "./myshm";
key = ftok(name,0);
if(key==-1){
perror("ftok error!");
}
shm_id = shmget(key,4096,IPC_CREAT);
if(shm_id ==-1){
perror("shmget error");
}
p_map = (people*)shmat(shm_id,NULL,0);
temp = 'a';
for(i = 0;i<10;i++)
{
temp +=1;
memcpy((*(p_map +i )).name,&temp, 1);
(*(p_map + i)).age = 20+i;
}
if(shmdt(p_map)==-1)
perror("detach error");
}
#include <sys/shm.h>
#include <sys/types.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
typedef struct {
char name[4];
int age;
}people;
int main(int argc,char *argv[]){
int shm_id ,i;
key_t key;
char temp;
people *p_map;
char* name = "./myshm";
key = ftok(name,0);
if(key==-1){
perror("ftok error!");
}
shm_id = shmget(key,4096,IPC_CREAT);
if(shm_id ==-1){
perror("shmget error");
}
p_map = (people*) shmat(shm_id,NULL,0);
for(i = 0;i<10;i++){
printf("Name: %s,Age: %d\n",(*(p_map+i)).name,(*(p_map+i)).age);
}
if(shmdt(p_map)==-1)
{
perror("detach error");
}
}
注意:
1、 系统V共享内存中的数据,从来不写入到实际磁盘文件中去;而通过mmap()映射普通文件实现的共享内存通信可以指定何时将数据写入磁盘文件中。 注:前面讲到,系统V共享内存机制实际是通过映射特殊文件系统shm中的文件实现的,文件系统shm的安装点在交换分区上,系统重新引导后,所有的内容都丢失。
2、 系统V共享内存是随内核持续的,即使所有访问共享内存的进程都已经正常终止,共享内存区仍然存在(除非显式删除共享内存),在内核重新引导之前,对该共享内存区域的任何改写操作都将一直保留。
3、 通过调用mmap()映射普通文件进行进程间通信时,一定要注意考虑进程何时终止对通信的影响。而通过系统V共享内存实现通信的进程则不然。 注:这里没有给出shmctl的使用范例,原理与消息队列大同小异。
共享内存允许两个或多个进程共享一给定的存储区,因为数据不需要来回复制,所以是最快的一种进程间通信机制。共享内存可以通过 mmap()映射普通文件(特殊情况下还可以采用匿名映射)机制实现,也可以通过系统V共享内存机制实现。应用接口和原理很简单,内部机制复杂。为了实现 更安全通信,往往还与信号灯等同步机制共同使用。
共享内存涉及到了存储管理以及文件系统等方面的知识,深入理解其内部机制有一定的难度,关键还要紧紧抓住内核使用的重要数据结构。系统 V共享内存是以文件的形式组织在特殊文件系统shm中的。通过shmget可以创建或获得共享内存的标识符。取得共享内存标识符后,要通过shmat将这 个内存区映射到本进程的虚拟地址空间。
POSIX mmap函数的应用
#include <stdlib.h>
#include <string.h>
#include <iostream>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdint.h>
#include <time.h>
#include <sys/time.h>
int main( int argc, char ** argv )
{
const uint32_t SHM_SIZE = 256;
int fd;
char* ptr = NULL;
if( argc != 2 )
{
printf("Enter your file name!\n");
exit( EXIT_FAILURE );
}
if( ( fd = open( argv[1], O_RDWR|O_CREAT, 0666 ) ) < 0 )
{
perror( argv[1] );
exit(EXIT_FAILURE);
}
// 修改文件长度为共享内存长度,若要同步文件此处很重要,文件大小,决定了文件中能存多少的东西
ftruncate(fd, SHM_SIZE);
/// 以写方式映射
if( ( ptr = (char*)mmap( 0, SHM_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0 ) ) == MAP_FAILED )
{
perror("mmap");
exit( EXIT_FAILURE );
}
memset(ptr,'a', SHM_SIZE/4);
/// 加个时间点检查,比较MS_ASYNC和MS_SYNC的时间差异
//time_t now;
//time_t end;
struct timeval now;
struct timeval end;
gettimeofday(&now,NULL);
// msync异步模式也很有意思,测试发现,执行该函数后,即使马上让程序coredown, 数据也可以成功写出而不会丢失
// 写200M的数据,异步模式只用了4us, 如果采用同步模式,则时间是原来的千倍万倍!
msync(ptr, SHM_SIZE/4, MS_ASYNC);
msync(ptr, SHM_SIZE/4, MS_SYNC);
//time(&end);
gettimeofday(&end,NULL);
double second =end.tv_sec - now.tv_sec;
std::cout<<"second"<<second<<std::endl;
double usecond= end.tv_usec - now.tv_usec;
std::cout<<"usecond"<<usecond<<std::endl;
#if 1
char buf[20]= {0};
double tt = second+(usecond/1000000);
sprintf(buf,"%f",tt);
std::cout << "sync td:" <<buf<<"(s)"<< std::endl;
#endif
munmap(ptr, SHM_SIZE);
///修正文件大小,改为实际内容的长度
ftruncate(fd, SHM_SIZE/4);
close(fd);
return 0;
}
C-S模式
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
typedef struct {
char name[4];
int age;
}people;
int main(int argc,char *argv[]){
int fd,i;
people *p_map;
fd = open(argv[1],O_CREAT|O_RDWR,00777);
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 );
return 0;
}
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
typedef struct{
char name;
int age;
}people;
int main(int argc,char *argv[]){
int fd,i;
people *p_map;
char temp;
fd = open(argv[1],O_CREAT|O_RDWR,00777);
lseek(fd,0,SEEK_SET);
write(fd,"",sizeof(people)*10);
p_map = (people *)mmap(NULL,sizeof(people)*10,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
//close(fd);
#if 0
temp = 'a';
for(i = 0;i<10;i++)
{
temp += 1;
memcpy((*(p_map+i)).name,&temp,2);
(*(p_map+i)).age = 20+i;
}
#endif
#if 0
memset(p_map,'b',sizeof(people)*10);
msync(p_map,sizeof(people)*5,MS_SYNC);
#endif
printf("initializer over \n");
if(-1==munmap(p_map,sizeof(people)*10)){
perror("munmap error\n");
}
//msync(p_map,sizeof(people)*10,MS_SYNC);
//msync(p_map,sizeof(people)*10,MS_INVALIDATE);
printf("unmap ok \n");
close(fd);
return 0;
}
功能:用来创建或打开一个共享内存对象
原型 int shm_open(const char *name, int oflag, mode_t mode);
参数
name:共享内存对象的名字,必须以/打头,并且后续不能有其它/ ,形如/somename长度不能超过NAME_MAX(255)
oflag:与open函数类似,可以是O_RDONLY、O_RDWR,还可以按位或上O_CREAT、O_EXCL、O_TRUNC等。
mode:此参数总是需要设置,如果oflag没有指定了O_CREAT,可以指定为0
返回值:成功返回非负整数文件描述符;失败返回-1
注意,不存在所谓的shm_close 函数,可以直接使用close 来关闭文件描述符。
功能:修改共享内存对象大小,shm_open不像shmget一样可以设置共享内存的大小,但可以使用ftruncate 设置大小。
原型 int ftruncate(int fd, off_t length);
参数
fd: 文件描述符
length:长度
返回值:成功返回0;失败返回-1
功能:获取共享内存对象信息
原型
int fstat(int fd, struct stat *buf);
参数
fd: 文件描述符
buf:返回共享内存状态
返回值:成功返回0;失败返回-1
struct stat 可以参考这里。
类似 shm_ctl(,IPC_STAT,);
功能:删除一个共享内存对象
原型 int shm_unlink(const char *name);
参数
name: 共享内存对象的名字
返回值:成功返回0;失败返回-1
shm_unlink 类似 shm_ctl(,IPC_RMID,);
功能:将共享内存对象映射到进程地址空间。
原型 void *mmap(void *addr, size_t len, int prot, int flags, int fd, off_t offset);
参数
addr: 要映射的起始地址,通常指定为NULL,让内核自动选择
len:映射到进程地址空间的字节数
prot:映射区保护方式
flags:标志
fd:文件描述符
offset:从文件头开始的偏移量
返回值:成功返回映射到的内存区的起始地址;失败返回-1
在前面曾经介绍了mmap 函数 将文件映射到进程地址空间的作用,其实它还可以将共享内存对象映射到进程地址空间,类似shmat的作用,只是传入的文件描述符fd 是shm_open 返回的。同样地,解除映射可以用munmap,类似shmdt 的作用。
编译时候加上 -lrt 选项,即连接librt 库 (实时库)
#include <string.h>
#include <stdlib.h>
#include <sys/time.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <sys/mman.h>
#define ERR_EXIT(m) \
do { \
perror(x); \
exit(EXIT_FAILURE);\
}while(0)
#define SHM_SIZE 256
int main(){
int fd ;
char path[10] = {0};
memcpy(path,"./tmp",10);
int flag = O_RDWR|O_CREAT;
fd = shm_open(path,flag,00777);
ftruncate(fd,SHM_SIZE);
void *m_map ;
m_map = mmap(NULL,SHM_SIZE+1,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
memset(m_map,'a',SHM_SIZE);
struct stat buf;
fstat(fd,&buf);
printf("file size is %d\n",buf.st_size);
close(fd);
return 0;
}
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/time.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <sys/mman.h>
#define ERR_EXIT(m) \
do { \
perror(x); \
exit(EXIT_FAILURE);\
}while(0)
#define SHM_SIZE 256
int main(){
int fd ;
char path[10] = {0};
memcpy(path,"./tmp",10);
int flag = O_RDWR|O_CREAT;
fd = shm_open(path,flag,00777);
ftruncate(fd,SHM_SIZE);
void *m_map ;
m_map = mmap(NULL,SHM_SIZE+1,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
printf("%s \n",m_map);
close(fd);
return 0;
}