共享内存
1.共享内存的定义
共享内存就是允许多个不相关的进程访问同一个逻辑内存。共享内存是在两个正在运行的进程之间共享和传递数据的一种非常有效的方式。不同进程之间共享的内存通常安排为同一段物理内存。进程可以将同一段共享内存连接到它们自己的地址空间中,所有进程都可以访问共享内存中的地址,就好像它们是由用C语言函数malloc分配的内存一样。而如果某个进程向共享内存写入数据,所做的改动将立即影响到可以访问同一段共享内存的任何其他进程。
实现进程间通信最简单也是最直接的方法就是共享内存——为参与通信的多个进程在内存中开辟一个共享区。
注意,共享内存并未提供同步机制,也就是说,在第一个进程结束对共享内存的写操作之前,并无自动机制可以阻止第二个进程开始对它进行读取。所以我们通常需要用其他的机制来同步对共享内存的访问,例如信号量。
2.共享内存的原理
在Linux中,每个进程都有属于自己的进程控制块(PCB)和地址空间(Addr Space),并且都有一个与之对应的页表,负责将进程的虚拟地址与物理地址进行映射,通过内存管理单元(MMU)进行管理。两个不同的虚拟地址通过页表映射到物理空间的同一区域,它们所指向的这块区域即共享内存。
当两个进程通过页表将虚拟地址映射到物理地址时,在物理地址中有一块共同的内存区,即共享内存,这块内存可以被两个进程同时看到。这样当一个进程修改共享内存的数据时,另一个进程访问共享内存时就会得到新的数据。
3.共享内存的使用
1、shmget函数
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
注意,只有shmget函数才直接使用信号量键,所有其他的信号量函数使用由semget函数返回的信号量标识符
- 功能:创建共享内存或者取得一个既有的共享内存段的标识符。
- 参数:
- key:程序需要提供一个参数key(非0整数),它有效地为共享内存段命名;
- size:size以字节为单位指定需要共享的内存容量,如果正在创建一个新段,则必须指定size。如果正在引用一个现存的段,则size指定为0.当创建一个新段,段内的内容初始化为0;
- shmflg:权限标志,它的作用与open函数的mode参数一样,如果要想在key标识的共享内存不存在时,创建它的话,可以与IPC_CREAT做或操作。共享内存的权限标志与文件的读写权限一样,举例来说,0644,它表示允许一个进程创建的共享内存被内存创建者所拥有的进程向共享内存读取和写入数据,同时其他用户创建的进程只能读取共享内存。
- 返回值:成功:返回一个与key相关的共享内存标识符(非负整数),用于后续的共享内存函数。失败:返回-1。
2、shmat函数
#include <sys/types.h>
#include <sys/shm.h>
void *shmat(int shm_id, const void *shm_addr, int shmflg);
- 功能:启动对该共享内存的访问,并把共享内存附加到当前进程的地址空间。
- 参数:
- shm_id:由shmget函数返回的共享内存标识;
- shm_addr:指定共享内存连接到当前进程中的地址位置,通常为空,表示让系统来选择共享内存的地址;
- shm_flg:一组标志位,通常为0。
- 返回值:成功:返回一个指向共享内存第一个字节的指针。失败:返回-1。
3、shmdt函数
#include <sys/types.h>
#include <sys/shm.h>
int shmdt(const void *shmaddr);
- 功能:将共享内存从当前进程中分离。注意,将共享内存分离并不是删除它,只是使该共享内存对当前进程不再可用。
- 参数:
- shmaddr:shmat函数返回的地址指针。
- 返回值:成功:返回0。失败:返回-1。
4、shmctl函数
#include <sys/shm.h>
int shmctl(int shm_id, int command, struct shmid_ds *buf);
- 功能:用来控制共享内存。
- 参数:
- shm_id:shmget函数返回的共享内存标识符;
- command:要采取的操作,它可以取下面的三个值 :
- IPC_STAT:把shmid_ds结构中的数据设置为共享内存的当前关联值,即用共享内存的当前关联值覆盖shmid_ds的值;
- IPC_SET:如果进程有足够的权限,就把共享内存的当前关联值设置为shmid_ds结构中给出的值;
- IPC_RMID:删除共享内存段。
- buf:一个结构指针,它指向共享内存模式和访问权限的结构。
struct shmid_ds
{
uid_t shm_perm.uid;
uid_t shm_perm.gid;
mode_t shm_perm.mode;
};
返回值:成功:返回0。失败:返回-1。
我们可以把对共享内存的使用总结为这5步
1. 调用函数shmget()创建一个新共享内存段或者取得一个既有的共享内存段的标识符;
2. 调用函数shmat()将共享内存附加到进程的虚拟地址空间中;
3. 为了引用共享内存,程序需要使用由shmat()函数返回的addr值,它是一个指向进程的虚拟地址空间中该共享内存段起点的指针;
4. 调用函数shmdt()分离共享内存段,调用之后,进程无法再引用这段共享内存。
5. 调用函数shmctl()删除共享内存段。只有一个进程需要执行这一步。
4.代码
我们创建共享内存,并对其进行读写操作,我们先不用信号量机制对其进行同步操作。
a.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <signal.h>
#include <sys/shm.h>
int main()
{
int shmid = shmget((key_t)1234,256,IPC_CREAT | 0600);
assert(shmid != -1);
char *s = (char*)shmat(shmid,NULL,0);//NULL不指定映射到进程的逻辑地址,让系统去决定
while(1)
{
char buff[128]={
0
};
printf("input:\n");
fgets(buff,128,stdin);
strcpy(s,buff);
if(strncmp(buff,"end",3) == 0)
{
break;
}
}
shmdt(s);
exit(0);
}
b.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <signal.h>
#include <sys/shm.h>
int main()
{
int shmid = shmget((key_t)1234,256,IPC_CREAT | 0600);
assert(shmid != -1);
char *s = (char*)shmat(shmid,NULL,0);
assert(s != NULL);
while(1)
{
if(strncmp(s,"end",3)==0)
{
break;
}
printf("s = %s\n",s);
sleep(4);
}
shmdt(s);
if(shmctl(shmid,IPC_RMID,NULL)==-1)
{
perror("shmctl error");
}
exit(0);
}
执行结果
现在我们对读写操作使用信号量进行同步
sem.h
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <signal.h>
#include <sys/sem.h>
union semun
{
int val;
};
void sem_init();
void sem_p(int index);
void sem_v(int index);
void sem_destroy();
sem.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <signal.h>
#include <sys/sem.h>
#include "sem.h"
#define MAX_SEM 2
static int semid = -1;
void sem_init()
{
semid = semget((key_t)1234,MAX_SEM,IPC_CREAT | IPC_EXCL | 0600);
if(semid == -1)
{
semid = semget((key_t)1234,MAX_SEM,0600);
if(semid == -1)
{
perror("semget error");
return ;
}
}
else
{
union semun a;
int arr[MAX_SEM ]={
1,0
};
int i = 0;
for(;i<MAX_SEM;i++)
{
a.val = arr[i];
if(semctl(semid,i,SETVAL,a) == -1)
{
perror("semctl error");
}
}
}
}
void sem_p(int index)
{
if(index < 0 || index >= MAX_SEM)
{
printf("p argc error\n");
return ;
}
struct sembuf buf;
buf.sem_num = index;
buf.sem_op = -1;
buf.sem_flg = SEM_UNDO;
if(semop(semid,&buf,1) == -1)
{
perror("semop error");
}
}
void sem_v(int index)
{
if(index < 0 || index >= MAX_SEM)
{
printf("p argc error");
return ;
}
struct sembuf buf;
buf.sem_num = index;
buf.sem_op = 1;
buf.sem_flg = SEM_UNDO;
if(semop(semid,&buf,1) == -1)
{
perror("semop error");
}
}
void sem_destroy()
{
if(semctl(semid,0,IPC_RMID) == -1)
{
perror("semctl error");
}
}
a.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <signal.h>
#include <sys/shm.h>
int main()
{
int shmid = shmget((key_t)1234,256,IPC_CREAT | 0600);
assert(shmid != -1);
char *s = (char*)shmat(shmid,NULL,0);//NULL不指定映射到进程的逻辑地址,让系统去决定
while(1)
{
char buff[128]={
0
};a。
printf("input:\n");
fgets(buff,128,stdin);
strcpy(s,buff);
if(strncmp(buff,"end",3) == 0)
{
break;
}
}
shmdt(s);
exit(0);
}
b.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <signal.h>
#include <sys/shm.h>
#include "sem.h"
int main()
{
int shmid = shmget((key_t)1234,256,IPC_CREAT | 0600);
assert(shmid != -1);
char *s = (char*)shmat(shmid,NULL,0);
assert(s!=NULL);
sem_init();
while(1)
{
sem_p(1);
if(strncmp(s,"end",3)==0)
{
break;
}
printf("s = %s\n",s);
sem_v(0);
}
shmdt(s);
sem_destroy();
if(shmctl(shmid,IPC_RMID,NULL) == -1)
{
perror("shmctl error");
}
exit(0);
}
接着执行这两条命令
执行结果
5. 共享内存的优缺点
优点:
- 效率高:因为进程可以直接读写内存,而不需要任何数据的拷贝。
- 不像匿名管道那样要求通信的进程有一定的父子关系,可用于任意两个进程之间通信。
缺点:
- 没有同步机制
- 利用内存缓冲区直接交换信息,只能同一个计算机系统中的诸多进程共享,不方便网络通信。