一、共享内存 共享内存可以提供给服务器进程和客户进程之间进行通信,不需要进行数据的复制,所以速度是最快的,只需要让两个进程通过页表映射到同一块物理内存上面即可,这块物理内存是两个进程都可以访问到的,所以信息传递的效率是非常高的。在通常情况下,我们要确保的是一个进程在进行读的时候,另一个进程不能去执行写操作,这是同过信号量来实现的。共享内存是最快的一种IPC。 下面我们来看一下,共享内存实现的原理图: 利用共享内存来完成进程之间的通信,两个进程读通过虚拟地址空间到用户页表,再从用户页表到物理内存之间的一块共享的内存区域,这样的话就可以实现两个进程之间的通信。
二、共享内存中的结构体
ipc_perm这个结构体,在消息队列、信号量、共享内存中都是存在的。 struct ipc_perm { key_t __key; /* Key supplied to shmget(2) */ uid_t uid; /* Effective UID of owner */ gid_t gid; /* Effective GID of owner */ uid_t cuid; /* Effective UID of creator */ gid_t cgid; /* Effective GID of creator */ unsigned short mode; /* Permissions + SHM_DEST and SHM_LOCKED flags */ unsigned short __seq; /* Sequence number */ }; shmid_ds这个结构体的第一个参数就是一个ipc_perm类型的成员。这个结构体用来描述共享内存的id的。 struct shmid_ds { struct ipc_perm shm_perm; /* Ownership and permissions */ size_t shm_segsz; /* Size of segment (bytes) */ time_t shm_atime; /* Last attach time */ time_t shm_dtime; /* Last detach time */ time_t shm_ctime; /* Last change time */ pid_t shm_cpid; /* PID of creator */ pid_t shm_lpid; /* PID of last shmat(2)/shmdt(2) */ shmatt_t shm_nattch; /* No. of current attaches */ ... };的 三、共享内存中的函数 1、获得共享内存 int shmget(key_t key,size_t size,int flags) 返回值:成功就返回的是共享内存的存储区的shmid,失败的话就返回的是-1。 参数key就是我们调用fork函数得到的标识符,作为系统对共享内存资源的唯一标识。 参数size就是申请的共享存储区的大小,以字节为单位,这个树一般为系统页长的整数倍(4K的整数倍)。如果申请的不是4k的整数倍,系统也会按照器整数倍进行分配的。如果不足4096(4k也就是1页)那么系统还会为他分配4096,但是最后余下的不能用。如果创建一个新的资源,则必须指定size,且段的内容会被初始化为0。若引用一个已经存在的,则将size置为0。 参数flag与消息队列相同,有两个选项,IPC_CREAT和IPC_EXCL。使用的时候有两种情况: 1. IPC_CREAT和IPC_EXCL一起使用(IPC_CREAT|IPC_EXCL),表示申请创建一个新的IPC资源,若要申请的资源已经存在,则错误返回。若不存在,则创建。 2. IPC_CREAT单独使用,表示申请创建一个IPC资源,若要申请的IPC资源已经存在,则直接使用;若不存在,则创建新的。 一般我们还会在后面加上资源的默认权限(如0666)
2、销毁共享内存
int shmctl(int shmid,int cmd,struct shmid_ds *buf);
这个函数的作用是删除共享内存。返回值:成功就返回的是0,失败就返回的是-1。参数shmid为我们调用shmget得到的共享内存的id;参数cmd制定了要执行的命令,当cmd被设置为IPC_RMID时,该函数执行删除动作。此时,第三个参数设为NULL。
3、挂接共享内存
void *shmat(int shmid, const void *shmaddr, int shmflg);
创建好一个进程还要把这个进程挂接到共享内存上,返回值:若成功,返回指向共享内存存储的指针,若失败,返回-1。 attr参数决定了共享存储段连接到调用进程的那个地址。一般我们设置为NULL或0,表示此段连接到由内核选择的第一个可用地址上。 如果参数flag指定了SHM_RDONLY,则以只读方式连接,否则以读写的方式连接。 如果此函数成功执行的话,那么内核将使与该共享存储段相关的shmid_ds结构中的shm_nattch计数器值加1。
4、去关连共享内存
int shmdt(const void *shmaddr);
返回值:成功返回0,失败返回-1。 参数addr是之前调用shmat的返回值。如果函数执行成功的话,shmdt将使相关shmid_ds结构中的shm_nattch计数器减1。 当使用完共享存储后,调用此函数令进程与它分离,但共享存储并不会消失,其标识符仍然还在,直到有进程调用shmctl将其删除。
除了这些函数之外,系统还给了我们命令来查看、管理ipc资源。 ipcs -m //查看系统中创建的共享内存的信息 ipcrm -m shmid //销毁创建的共享内存 四、共享内存的特点 在前面关于进程间的通信方式我们已经讲过了4种:匿名管道、命名管道、消息队列、信号量,共享内存是进程间通信的最后一种介绍的方式。我们可以总结一下:前两种的生命周期随进程、后三种的生命周期是随内核的。而共享内存是这五种效率最高的,虽然共享内存是最快的,但是由于它没有相应的互斥机制,所以共享内存通常是和信号量一起来配合使用的。 五、共享内存的简单代码 头文件:
#ifndef _COMM_H__ #define _COMM_H__ #include <stdio.h> #include <sys/ipc.h> #include <sys/shm.h> #include <unistd.h> #define PATH "." #define PROJ_ID 0x6666 int creatShm(int size); //创建 int getShm(int size); //获取 int destroyShm(int shmid); //删除 #endif //_COMM_H__server.c:
#include "comm.h" int main() { int shmid=creatShm(4096); //创建共享内存 //printf("create success!!! shmid:%d\n",shmid); sleep(3); char* addr=shmat(shmid,NULL,0); //使当前进程与共享内存挂接 ,此函数返回进程虚拟地址 int i=0; //第二个参数为指定的虚拟地址,一般由操作系统指定,所以设为null while(i<26) { printf("%s\n",addr); //打印地址(读共享内存处内容)处内容 sleep(1); ++i; } shmdt(addr); //使当前进程与共享内存断开关联 int ret=destroyShm(shmid); //printf("destroy success. ret:%d\n",ret); return 0; }client.c:
#include "comm.h" int main() { int shmid=getShm(4096); //获取已创建好的共享内存 printf("getShm success!! shmid:%d\n",shmid); char* addr=shmat(shmid,NULL,0); //使当前进程与共享内存挂接 ,此函数返回进程虚拟地址 int i=0; while(i<26) { addr[i]='A'+i; //向共性内存处写内容 sleep(1); ++i; addr[i]=0; } shmdt(addr); //使当前进程与共享内存断开关联 return 0; }comm.c:
#include "comm.h" static int commShm(int size,int flags) { key_t _key=ftok(PATH,PROJ_ID); if(_key<0) { perror("ftok"); return -1; } int shmid=shmget(_key,size,flags); //创建或获取共享内存 参数size为页的整数倍(会自动调整) 1页=4k if(shmid<0) { perror("shmget"); return -2; } return shmid; } int creatShm(int size) { return commShm(size,IPC_CREAT|IPC_EXCL|0666); } int getShm(int size) { return commShm(size,IPC_CREAT); } int destroyShm(int shmid) { if(shmctl(shmid,IPC_RMID,NULL)<0) //立即删除shmid标识的共享内存 { perror("destroy:shmctl"); return -1; } return 0; }