您的位置:首页 > 其它

进程间的通信--共享内存

2012-11-01 19:59 337 查看

1内存共享

1.1SYSTEM V共享

共享内存是系统出于多个进程之间通讯的考虑,而预留的的一块内存区。在/proc/sys/kernel/目录下,记录着共享内存的一些限制,如一个共享内存区的最大字节数shmmax,系统范围内最大共享内存区标识符数shmmni等,可以手工对其调整,但不推荐这样做。

共享内存的使用,主要有以下几个API:ftok()、shmget()、shmat()、shmdt()及shmctl()。

1.1.1 ftok

函数原型

key_t ftok(const char *pathname, int proj_id); 

参数

pathname:文件名,必须保证在系统中存在,并且进程能够访问。

proj_id:子序号,一个1-255之间的一个整数值,典型的值是一个ASCII值。

返回值

成功:返回一个key_t值;

失败:返回-1,可以用strerror(errno)打印错误。

注:

1、 考虑到应用系统可能在不同的主机上,可以直接定义一个key_t值,而不是由ftok获取。

#define IPCKEY 0x344378

2、采用ftok来生成key的情况下,如果ftok的参数pathname指定文件被删除后重建,则文件系统会赋予这个同名文件(或目录)新的i节点信息,于是这些进程所调用的ftok虽然都能正常返回,但得到的键值却并不能保证相同。

1.1.2 shmget

函数原型

int shmget(key_t key, size_t size, int shmflg);

功能

shmget()用来获得共享内存区域的ID,如果不存在指定的共享区域就创建相应的区域。

参数

Key:是这块共享内存的标识符。如果是父子关系的进程间通信的话,这个标识符用IPC_PRIVATE来代替。如果两个进程没有任何关系,所以就用ftok()算出来一个标识符(或者自己定义一个)使用了。

Size:共享内存的大小;

Shmflg:内存的模式(mode)以及权限标识。

模式取值如下:

IPC_CREAT 新建(如果已创建则返回目前共享内存的id);

IPC_EXCL   与IPC_CREAT结合使用,如果已创建则则返回错误;

“模式” 和“权限标识”进行“或”运算,做为第三个参数。

如:    IPC_CREAT | IPC_EXCL | 0640   

例子中的0666为权限标识,4/2/1 分别表示读/写/执行3种权限,第一个0是UID,第一个6(4+2)表示拥有者的权限,第二个4表示同组权限,第3个0表示他人的权限。

返回值

成功:返回共享内存的ID;

失败:返回-1。

注:创建共享内存时,shmflg参数至少需要 IPC_CREAT | 权限标识,如果只有IPC_CREAT 则申请的地址都是k=0xffffffff,不能使用;获取已创建的共享内存时,shmflg不要用IPC_CREAT(只能用创建共享内存时的权限标识,如0640),否则在某些情况下,比如用ipcrm删除共享内存后,用该函数并用IPC_CREAT参数获取一次共享内存(当然,获取失败),则即使再次创建共享内存也不能成功,此时必须更改key来重建共享内存。

1.1.3 shmat

函数原型

void    *shmat( int shmid , char *shmaddr , int shmflag );

功能

用来允许本进程访问一块共享内存的函数。

参数

Shmid:共享内存的ID;

Shmaddr:共享内存的起始地址,如果shmaddr为NULL,内核会把共享内存映像到调用进程的地址空间中选定位置;如果shmaddr不为NULL,内核会把共享内存映像到shmaddr指定的位置。所以一般把shmaddr设为NULL。

Shmflag:本进程对该内存的操作模式。如果是SHM_RDONLY的话,就是只读模式。其它的是读写模式。

返回值

成功:返回内存映射的地址;

失败:返回-1;

1.1.4 shmdt

函数原型

int shmdt( char *shmaddr );

功能

删除本进程对这块内存的使用,shmdt()与shmat()相反,是用来禁止本进程访问一块共享内存的函数。

参数

Shmaddr:共享内存的起始地址。

返回值

成功:返回0;

失败:返回-1;

1.1.5 shmctl

函数原型

int  shmctl( int shmid , int cmd , struct shmid_ds *buf );

功能

控制对这块共享内存的使用。

参数

Shmid:共享内存的ID;

Cmd:控制命令,具体如下:

IPC_STAT        得到共享内存的状态

IPC_SET          改变共享内存的状态

IPC_RMID        删除共享内存

Buf:一个结构体指针。IPC_STAT的时候,取得的状态放在这个结构体中。如果要改变共享内存的状态,用这个结构体指定。

返回值

成功:返回0;

失败:返回-1;

1.1.6 示例

进程1:

#include <sys/ipc.h>
#include <sys/shm.h>
#include<stdio.h>
#include<signal.h>
#include<stdlib.h>
#include<string.h>

#define IPCKEY 0x213930

void signalHandle()
{
printf("exit!\n");
exit(1);
}

typedef struct Shm
{
int pid;
char str[100];
}Shm_t;

int main()
{
int d = 0;
int pid;
int shm_id;
Shm_t *shm;

pid = getpid();

shm_id = shmget(IPCKEY, sizeof(Shm_t),0640);

if(shm_id != -1)
{
printf("shm is exit!\n");
shm = (Shm_t *)shmat(shm_id,NULL,0);
if(shm != (void *)(-1))
{
shmdt(shm);
shmctl(shm_id,IPC_RMID,0);
}
}

shm_id = shmget(IPCKEY, sizeof(Shm_t),0640|IPC_CREAT|IPC_EXCL);

printf("shm_id:%d\n",shm_id);

if(shm_id != -1)
{
shm = (Shm_t *)shmat(shm_id,NULL,0);
if(shm != (void *)(-1))
{
shm->pid = pid;
strcpy(shm->str,"wangyuanxiang!");
}
}

signal(SIGTERM,signalHandle);
printf("PID:%d\n",pid);
printf("PID:%d\n",getpid());

while(1)
{
printf("time:%d\n",d++);
sleep(1);
}

return 0;
}

进程2:

#include <sys/ipc.h>
#include <sys/shm.h>
#include<stdio.h>
#include<signal.h>
#include<string.h>

#define IPCKEY 0x213930

typedef struct Shm
{
int pid;
char str[100];
}Shm_t;

int main()
{
int pid;
Shm_t *shm;

int shm_id = shmget(IPCKEY, sizeof(Shm_t),0640);
if(shm_id != -1)
{
printf("shm_id:%d\n",shm_id);
shm = (Shm_t *)shmat(shm_id,NULL,0);
if(shm != (void *)(-1))
{
pid = shm->pid;
printf("pid:%d\n",pid);
printf("str:%s!\n",shm->str);
kill(pid,SIGTERM);
}
}
return 0;
}

程序运行:

首先运行进程1,进程1在操作共享内存;

再运行进程2:读取共享内存,并发送SIGTERM信号给进程1,结束进程1.

1.2 mmap()系统调用

mmap()系统调用使得进程之间通过映射同一个普通文件实现共享内存。普通文件被映射到进程地址空间后,进程可以向访问普通内存一样对文件进行访问,不必再调用read(),write()等操作。
注:实际上,mmap()系统调用并不是完全为了用于共享内存而设计的。它本身提供了不同于一般对普通文件的访问方式,进程可以像读写内存一样对普通文件的操作。而Posix或系统V的共享内存IPC则纯粹用于共享目的,当然mmap()实现共享内存也是其主要应用之一。

1.2.1mmap

函数原型

void *mmap(void *start,size_t length,int prot,int flags,int fd,off_t offsize);

功能以及参数

详情参见《Linux_C函数库参考手册2.pdf》。

1.2.2mumap

函数原型

int munmap(void *start,size_t length);

表头文件

#include<unistd.h>

#include<sys/mman.h>

功能

解除内存映射。

参数

Start:映射的起始地址;

Length:内存映射的长度。

返回值

如果解除映射成功则返回0,否则返回-1,错误原因存于errno中。

1.2.2 msync()

函数原型

int msync ( void * addr , size_t len, int flags)

表头文件

#include<unistd.h>

#include<sys/mman.h>

功能

一般说来,进程在映射空间的对共享内容的改变并不直接写回到磁盘文件中,往往在调用munmap()后才执行该操作。可以通过调用msync()实现磁盘上文件内容与共享内存区的内容一致。

参数

Addr:需要保持的起始地址;

Len:保持一致的长度;

Flag:。

返回值

1.2.3示例

系统调用mmap()用于共享内存的两种方式:

1、 使用普通文件提供的内存映射:适用于任何进程之间;此时,需要打开或创建一个文件,然后再调用mmap();

2、 使用特殊文件提供匿名内存映射:适用于具有亲缘关系的进程之间;由于父子进程特殊的亲缘关系,在父进程中先调用mmap(),然后调用 fork()。那么在调用fork()之后,子进程继承父进程匿名映射后的地址空间,同样也继承mmap()返回的地址,这样,父子进程就可以通过映射区 域进行通信了。注意,这里不是一般的继承关系。一般来说,子进程单独维护从父进程继承下来的一些变量。而mmap()返回的地址,却由父子进程共同维护。

对于具有亲缘关系的
c61b
进程实现共享内存最好的方式应该是采用匿名内存映射的方式。此时,不必指定具体的文件,只要设置相应的标志即可,参见范例2。

示例1:

/*mmap1*/
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/mman.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<errno.h>

typedef struct Shm
{
int id;
char name[100];
int time;
}Shm_t;

int main()
{
int fd = -1;
int times = 10;
Shm_t *shm;

fd = open("./shm",O_CREAT|O_RDWR,00777);

if(fd == -1)
{
printf("open file failed!\n");
return -1;
}

//lseek(fd,sizeof(Shm_t)-1,SEEK_SET);
//write(fd,"",1);

shm = (Shm_t *)mmap(NULL, sizeof(Shm_t), PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
if(shm == (void *)(-1))
{
printf("mmap failed:%s!\n",strerror(errno));
return 0;
}

printf("shm is ok!\n");
close(fd);

while(times --)
{
printf("id:[%d] time[%d] name:[%s]\n",shm->id, shm->time, shm->name);
sleep(1);
}

munmap(shm, sizeof(Shm_t));

return 0;
}

/*mmap2*/
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/mman.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<errno.h>

typedef struct Shm
{
int id;
char name[100];
int time;
}Shm_t;

int main()
{
int fd = -1;
Shm_t *shm = NULL;
int times = 100;

fd = open("./shm",O_CREAT|O_RDWR|O_TRUNC,00777);

if(fd == -1)
{
printf("open file failed!\n");
return -1;
}

/*
**用于确保文件的大小空间足够内存映射的大小,否则会出现总线错误
*/
lseek(fd,sizeof(Shm_t)-1,SEEK_SET);
write(fd,"",1);

shm = (Shm_t *)mmap(NULL, sizeof(Shm_t), PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
if(shm == (void *)(-1))
{
printf("mmap failed:%s!\n",strerror(errno));
return 0;
}

printf("shm is ok!\n");
close(fd);

shm->id = 10;
shm->time = 55;
strcpy(shm->name, "wangyuanxiang!");

while(times --)
{
shm->id = times;
shm->time = times + 5;
sleep(1);
}

munmap(shm, sizeof(Shm_t));

return 0;
}

示例2:

#include <sys/mman.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>

typedef struct
{
char name[4];
int age;
}people;

main(int argc, char** argv)
{
int i;
people *p_map;
char temp;

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(1);
}
else
{
printf("wangyuanxiang!\n");
}

temp = 'a';
for(i = 0;i<5;i++)
{
temp += 1;
memcpy((*(p_map+i)).name, &temp,2);
(*(p_map+i)).age=20+i;
}

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" );
}

 

1.2.4对mmap映射的地址访问

前面对范例运行结构的讨论中已经提到,linux采用的是页式管理机制。对于用mmap()映射普通文件来说,进程会在自己的地址空间新增一块空间,空间大小由mmap()的len参数指定,注意,进程并不一定能够对全部新增空间都能进行有效访问。进程能够访问的有效地址大小取决于文件被映射部分的大小。
简单的说,能够容纳文件被映射部分大小的最少页面个数决定了进程从mmap()返回的地址开始,能够有效访问的地址空间大小。超过这个空间大小,内核会根据超过的严重程度返回发送不同的信号给进程。
能够访问空间的大小 = (文件大小决定的最小页面数)*页面大小和len的之间的取小。
示例:

#include<sys/mman.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include<stdio.h>
typedef struct
{
char name[4];
int age;
}people;

main(int argc, char** argv)
{
int fd,i;
int pagesize,offset;
people *p_map;

pagesize = sysconf(_SC_PAGESIZE);
printf("pagesize is %d\n",pagesize);

fd = open(argv[1],O_CREAT|O_RDWR|O_TRUNC,00777);
lseek(fd,pagesize*2-100,SEEK_SET);
write(fd,"",1);

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 over\n",i+1);
}

munmap(p_map,sizeof(people)*10);
}
版本1:

pagesize is 4096

access page 1 over

access page 1 edge over, now begin to access page 2

access page 2 over

access page 2 over

access page 2 edge over, now begin to access page 3

总线错误 (core dumped)

版本2:

pagesize is 4096

access page 1 over

access page 1 edge over, now begin to access page 2

总线错误 (core dumped)

1.3POSIX共享内存

POSIX共享内存分为2步:

1 指定一个名字参数调用shm_open,创建一个新的共享内存对象,或者打开一个已存在的共享内存区对象。

2 调用mmap把共享内存区映射到调用进程的地址空间。

1.3.1shm_open

函数原型

int shm_open(const char *name,int oflag,mode_t mode);

功能

打开或者创建一个共享内存区

表头文件

#include <sys/mman.h>

参数

Name:共享内存区的名字;

Oflag:标志,oflag参数必须含有O_RDONLY和O_RDWR标志,还可以指定如下标志:O_CREAT,O_EXCL或O_TRUNC.

Mode:权限模式,指定O_CREAT标志的前提下使用。

返回值

成功:返回共享内存区ID;

失败:返回-1。

1.3.2shm_unlink

函数原型

int shm_unlink(const char *name);

功能

删除一个共享内存区。

表头文件

#include <sys/mman.h>

参数

Name: 共享内存区的名字。

返回值

成功返回0,出错返回-1。

注:shm_unlink函数删除一个共享内存区对象的名字,删除一个名字仅仅防止后续的open,mq_open或sem_open调用取得成功。

1.3.3ftruncate

函数原型

int ftruncate(int fd,off_t length);

功能

调整文件或共享内存区大小。

表头文件

#include <unistd.h>

参数

Fd:描述符;

Length:文件或共享内存的大小。

返回值

成功返回0,出错返回-1。

1.3.4fstat

函数原型

int fstat(const char *file_name,struct stat *buf);

功能

获得文件或共享内存区的信息。

表头文件

#include <unistd.h>

#include <sys/types.h>

#include <sys/stat.h>

参数

File_name:文件名;

Buf :stat结构。

返回值

成功返回0,出错返回-1。

注:对于普通文件stat结构可以获得12个以上的成员信息,然而当fd指代一个共享内存区对象时,只有四个成员含有信息。

对于共享内存:

struct stat{

        mode_t st_mode;

        uid_t st_uid;

        gid_t st_gid;

        off_t st_size;

};

1.3.5示例

#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <semaphore.h>
int main(int argc,char **argv)
{
int shm_id;
char *ptr;
sem_t *sem;
if(argc!=2)
{
printf(“usage:shm_open <pathname>\n”);
exit(1);
}
shm_id=shm_open(argv[1],O_RDWR|O_CREAT,0644);
ftruncate(shm_id,100);
sem=sem_open(argv[1],O_CREAD,0644,1);
ptr=mmap(NULL,100,PROT_READ|PROT_WRITE,MAP_SHARED,shm_id,0);
strcpy(ptr,”\0”);
while(1)
{
if((strcmp(ptr,”\0”))==0)
continue;
else
{
if((strcmp(ptr,”q\n”))==0)
break;
sem_wait(sem);
printf(“server:%s”,ptr);
strcpy(ptr,”\0”);
sem_pose(sem);
}
sem_unlink(argv[1]);
shm_unlink(argv[1]);
}
}

#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <semaphore.h>
#include <stdio.h>
int main(int argc,char **argv)
{
int shm_id;
char *ptr;
sem_t *sem;
if(argc!=2)
{
printf(“usage:shm_open <pathname>\n”);
exit(1);
}
shm_id=shm_open(argv[1],0);
ptr=mmap(NULL,100,PROT_READ|PROT_WRITE,MAP_SHARED,shm_id,0);
while(1)
{
sem_wait(sem);
fgets(ptr,10,stdin);
printf(“user:%s”,ptr);
if((strcmp(ptr,”q\n”))==0)
exit(0);
sem_pose(sem);
sleep(1);
}
exit(0);
}

gcc -lrt -o posix1 posix-mmap1.c -pthread

 

 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: