共享内存
概念
共享内存就把一片逻辑内存共享出来,让不同的进程去访问它,修改它。
为什么共享内存是最快的进程间通讯方式
创建一块共享内存,将这块共享内存映射的自己的虚拟地址空间,接下操作都是直接对这块虚拟地址进行操作,进程间数据传递不再涉及到内核(进程不再通过执行进入内核的系统调用来传递彼此的数据),所以相较于其他的进程间通信少了两步内核态用户态之间的数据拷贝。
注意事项
共享内存并未提供同步机制。也就是说,在第一个进程结束对共享内存的写操作之前,并无自动机制可以阻止第二个进程开始对它进行读取。所以我们通常采用signal信号来监督一个进程是否结束,从而确定下一个进程是否能够开始。
原理
在Linux中,每个进程都有属于自己的进程控制块(PCB)和地址空间(Addr Space),并且都有一个与之对应的页表,负责将进程的虚拟地址与物理地址进行映射,通过内存管理单元(MMU)进行管理。两个不同的虚拟地址通过页表映射到物理空间的同一区域,它们所指向的这块区域即共享内存。
查看系统中的共享存储段
ipcs -m
删除系统中的共享存储段
ipcrm -m [shmid]
函数介绍
shmget函数(创建共享内存)
int shmget(key_t key, size_t size, int shmflg); //成功返回一个非负整数,即该共享内存段的标识码;失败返回-1
- key:这个共享内存段名字
- size:共享内存大小
- shmflg:由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的
shmat函数(连接)
void *shmat(int shm_id, const void *shm_addr, int shmflg);
//如果调用成功, 返回一个指向共享内存第一个字节的指针;
//如果失败,返回-1.
- shm_id : 由shmget返回的共享内存标识。
- shm_addr: 指定共享内存连接到当前进程中的地址位置。它通常是一个空指针, 表示让系统来选择共享内存出现的地址。
- shmflg : 是一组标志。它的两个可能取值是SHM_RND, 和shm_addr联合使用, 用来控制共享内存连接的地址。SHM_RDONLY, 它使连接的内存只读。
shmdt(去关联)
将共享内存从当前进程中分离。
int shmdt(const void *shm_addr);
//成功时,返回0,
//失败时,返回-1.
- shm_addr: shmat返回的地址指针。
注意:
共享内存分离并未删除它,
只是使得该共享内存对当前进程不再可用。
shmctl函数
删除共享内存对象
int shmctl(int shm_id, int cmd, struct shmid_ds *buf);
成功时,返回0,
失败时,返回-1.
-
shm_id : 是shmget返回的共享内存标识符。
-
command: 是要采取的动作,
IPC_STAT 把shmid_ds结构中的数据设置为共享内存的当前关联值
IPC_SET 如果进程有足够的权限,
就把共享内存的当前关联值设置为shmid_ds结构中给出的值
IPC_RMID 删除共享内存段 -
buf : 是一个指针,包含共享内存模式和访问权限的结构。
示例代码
share_server.c
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
#include <sys/shm.h>
#include <sys/ipc.h>
struct mybuf
{
int pid;
char buf[100];
};
void myfun(int signum)
{
return ;
}
int main()
{
int shmid;
pid_t pid;
int key;
struct mybuf *p;
key = ftok("./fork.c", 'a');
if(key < 0){
printf("create key fail\n");
return -1;
}
shmid = shmget(key, 128, IPC_CREAT|0777);
if(shmid < 0){
printf("create error\n");
return -1;
}
printf("create share memory ok, shmid = %d\n", shmid);
signal(SIGUSR2, myfun);
p = (struct mybuf*)shmat(shmid, NULL, 0);
if(p == NULL){
printf("create shmat error\n");
return -1;
}
p -> pid = getpid();
pause();
pid = p -> pid; //读取到了客户端的pid
while(1){
printf("parent process start write:\n");
fgets(p -> buf, 128, stdin);
kill(pid, SIGUSR1); //给客户端传SIGUSR1信号
pause();
}
shmdt(p);
shmctl(shmid, IPC_RMID, NULL);
system("ipcs -m");
return 0;
}
share_client.c
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
#include <sys/shm.h>
#include <sys/ipc.h>
struct mybuf
{
int pid;
char buf[100];
};
void myfun(int signum)
{
return ;
}
int main()
{
int shmid;
pid_t pid;
int key;
struct mybuf *p;
key = ftok("./fork.c", 'a');
if(key < 0){
printf("create key fail\n");
return -1;
}
shmid = shmget(key, 128, IPC_CREAT|0777);
if(shmid < 0){
printf("create error\n");
return -1;
}
printf("create share memory ok, shmid = %d\n", shmid);
signal(SIGUSR1, myfun);
p = (struct mybuf*)shmat(shmid, NULL, 0);
if(p == NULL){
printf("create shmat error\n");
return -1;
}
pid = p -> pid; //读取到服务器的pid
p -> pid = getpid();
kill(pid, SIGUSR2);
while(1){
pause();
printf("client process receve data from share memory :%s", p -> buf);
kill(pid, SIGUSR2); //给服务器穿SIGUSR2信号。
}
shmdt(p);
shmctl(shmid, IPC_RMID, NULL);
system("ipcs -m");
return 0;
}
以上是共享内存的通讯的代码实现,由服务器向客户端的单向通讯,运用到了信号的有关知识,一个kill对应一个pause,只有kill传达相应的信号,才能使客户端(服务器)的pause()继续执行下面的语句。
如有疑问,欢迎提问指出。