共享内存
共享内存
最快的进程间通信方式—— 共享内存 。
1、概念
共享内存即多个进程共享指定的一块物理内存空间,一旦此物理内存映射到共享它的进程的虚拟地址空间,这些进程间数据传递不再涉及到内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据
- 共享内存允许多个进程共享指定的物理内存空间。
- 共享内存少了两次涉及用户态和内核态数据拷贝的时间和资源消耗 ,带来极高的效率;
2、共享内存数据结构
3、共享内存操作函数
1. shmget() 创建或得到共享内存标识符
创建一个共享内存对象并获得其标识符或打开一个共享内存标识符
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
int shmget(key_t key, size_t size, int shmflg);
参数:
key:共享内存段名称。0(IPC_PRIVATE)表示建立新共享内存对象。若为其他数值则来源于ftok返回的IPC键值
size:共享内存的大小。内存分配以页为单位,所需大小和所占大小可能不同
shmflg:模式标志,与IPC对象存取权限(如0600)进行 | 运算来确定信号量集的存取权限,取值如下
0:取共享内存标识符,若不存在则函数会报错
IPC_CREAT:当shmflg&IPC_CREAT为真时,如果内核中不存在键值与key相等的共享内存,则新建一个共享内存;如果存在这样的共享内存,返回此共享内存的标识符
IPC_CREAT|IPC_EXCL:如果内核中不存在键值与key相等的共享内存,则新建一个消息队列;如果存在这样的共享内存则报错
返回值:
成功:返回共享内存标识符
失败:返回 -1,错误值在error中
需要注意的是,Linux中对于创建的共享内存进行了初始化为:0x00
2、shmat() 将共享内存映射到进程地址空间
连接共享内存标识符为shmid的共享内存,连接成功后把共享内存区对象映射到调用进程的地址空间,随后可像本地空间一样访问
void *shmat(int shmid, const void * shmaddr,int shmflg);
参数:
shmid:共享内存标识符
shmaddr:指定共享内存映射到进程内存空间中的地址。
shmflg:SHM_RDONLY 只读模式,另一个值为SHM_RND,详见下解
返回值:
成功:返回一个指针,指向连接的共享内存的起始地址
失败:返回-1,错误值在error中
注意:
- shmaddr传NULL表示由内核决定随机选定地址
- shmaddr不为NULL且shmflg无SHM_RND标记,则以shmaddr为连接地址。
- shmaddr不为NULL且shmflg设置了SHM_RND标记,则连接的地址会自动向下调整为SMLBA的整数倍。公式:shmaddr - (shmaddr % SHMLBA)。
- fork后子进程继承已连接的共享内存地址。
- exec后该子进程与已连接的共享内存地址自动脱离(detach)。
- 进程结束后,已连接的共享内存地址会自动脱离(detach)
3、shmdt() 将共享内存与当前进程脱离
shmdt与shmat函数相反,是用来断开与共享内存附加点的地址,禁止本进程访问此片共享内存
int shmdt(const void* shmadr);
参数:
shmadr:连接的共享内存的起始地址,即由shmat返回的指针
返回值:
成功:返回 0
失败:返回-1,错误值在error中
注意: 将共享内存段与当前进程脱离不等于删除共享内存段
共享内存中有一个映射链接数,进程调用shmat成功时该链接数值自动增加1。调用函数shmdt并不能删除共享内存,它仅仅删除共享内存在进程中的一个链接,并将该共享内存映射数减1 。
4、shmctl() 共享内存管理
完成对共享内存的控制,包括删除共享内存、改变其状态等
int shmctl(int shmid, int cmd, struct shmid_ds * buf);
参数:
shmid:由shmget返回的共享内存标识码
cmd:将要采取的动作(有三个可取值)
IPC_STAT:把shmid_ds结构中的数据设置为消息队列的当前关联值
IPC_SET:在进程有足够权限的前提下,把消息队列的当前关联值设置为shmid_ds数据结构中给出的值
IPC_RMID:删除消息队列以及其上的所有数据
buf:指向一个保存着共享内存的模式状态和访问权限的数据结构
返回值:
成功:返回 0
失败:返回-1,错误值在error中
注意:
- 在使用共享内存,结束程序退出后。如果没在程序中用shmctl()删除共享内存的话,一定要在命令行下用ipcrm命令删除这块共享内存。共享内存是随内核态的
- 如果共享内存已经与所有访问它的进程断开了连接,则调用IPC_RMID子命令后,系统将立即删除共享内存的标识符,并删除该共享内存区,以及所有相关的数据结构;
- 如果仍有别的进程与该共享内存保持连接,则调用IPC_RMID子命令后,该共享内存并不会被立即从系统中删除,而是被设置为IPC_PRIVATE状态,并被标记为"已被删除";直到已有连接全部断开,该共享内存才会最终从系统中消失。
- 一旦通过shmctl对共享内存进行了删除操作,则该共享内存将不能再接受任何新的连接,即使它依然存在于系统中!
- 在对共享内存删除之后不可能再有新的连接,则执行删除操作是安全的;否则,在删除操作之后如仍有新的连接发生,则这些连接都将失败!
4、共享内存程序示例
server.c
#include<stdlib.h>
#include<string.h>
#include<fcntl.h>
#include<pthread.h>
#include<sys/ipc.h>
#include<sys/shm.h>
struct ipc{
int var;
char str[256];
};
void sys_err(const char* str)
{
perror(str);
exit(1);
}
int main()
{
struct ipc t;
int tmp;
int ret = shmget(1234, sizeof(t), IPC_CREAT | 0644); // 1、创建共享内存
if(ret == -1)
sys_err("shmget error");
printf("shared memory mid=%d\n", ret);
struct ipc* p = (struct ipc*)shmat(ret, NULL, 0); // 2、将共享内存绑定到进程内存
空间
if( p == (void *)-1)
sys_err("shmat error");
int i = 0;
while(i < 5) // 向共享内存中写消息
{
p->var = i+1;
sprintf(p->str, "hello shmat, this is %d", i+1);
i++;
sleep(1);
}
tmp = shmdt(p); // 3、断开共享内存连接
if(tmp == -1)
sys_err("shmdt error");
printf("已断开连接\n");
sleep(2);
tmp = shmctl(ret, IPC_RMID, NULL); // 4、销毁共享内存
if(tmp == -1)
sys_err("shmtl error");
printf("共享内存已销\n");
return 0;
}
client.c
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<fcntl.h>
#include<pthread.h>
#include<sys/ipc.h>
#include<sys/shm.h>
struct ipc{
int var;
char str[256];
};
void sys_err(const char* str)
{
perror(str);
exit(1);
}
int main()
{
struct ipc tmp;
int ret = shmget(1234, sizeof(tmp), IPC_CREAT | 0644); // 打开共享内存
if(ret == -1)
sys_err("shmget error");
struct ipc* p = (struct ipc*)shmat(ret, NULL, 0); // 建立共享内存连接
if( p == (void *)-1)
sys_err("shmat error");
int i=0;
while(1) // 从共享内存中读取消息
{
printf("%d, %s\n", p->var, p->str);
sleep(1);
i++;
}
ret = shmdt(p); // 断开共享内存连接
if(ret == -1)
sys_err("shmdt error");
printf("已断开连接\n");
return 0;
}
程序结果如下:
运行server:
运行client:
此时查看ipc
分析当server运行结束时,对共享内存进行了删除,但是由于client内是死循环,所以仍然和共享内存具有连接,此时该共享内存并不会被立即从系统中删除,而是被设置为IPC_PRIVATE状态,并被标记为"已被删除"。此时查看ipc仍然存在。将client终止,再查看ipc: