共享内存
来源:互联网 发布:姚明和奥尼尔对决数据 编辑:程序博客网 时间:2024/05/22 00:22
1.共享内存指在多处理器的计算机系统中,可以被不同中央处理器访问的大容量内存。由于多个CPU需要快速访问存储器,这样就要对存储器进行缓存。由于其他处理器可能也要存取,任一缓存数据更新后,共享内存就需要立即更新,否则不同处理器可能用到不同的数据
优点:内存共享是最为高效的IPC机制,因为它不涉及到进程之间的任何的数据传输;
缺点:需要其他辅助手段来同步进程对共享内存的访问,否则会产生竞争条件;因为共享内存本身没有提供同步机制,所以通常使用信号量来实现对共享内存访问的同步。
内存共享原理图:
2.linux中的共享内存的实现主要包括4个系统调用,分别是:
(1) int shmget(key_t key, size_t size, int shmflg);
(2) void *shmat(int shmid, const void *shmaddr, int shmflg);
(3)int shmdt(const void *shmaddr)
(4) int shmctl(int shmid, int cmd, struct shmid_ds *buf);
3. 下面对4个api函数的功能和用法进行详细的描述与介绍:
(1) shmget
shmget系统调用的功能是创建一段新的共享内存,或者是获取一段已经存在的共享内存:
#include <sys/ipc.h> #include <sys/shm.h>int shmget(key_t key, size_t size, int shmflg);
参数key: 是一个键值,用来标识一段全局唯一的共享内存。多个进程通过它来访问同一个共享内存,其中有个特殊值IPC_PRIVATE,用于创建当前进程的私有共享内存
参数size :用来指定共享内存的大小,单位是字节。若创建的是新的共享内存,则size值必须被指定;若是获取已经存在的共享内存,则可以将size参数设置为0.
参数shmflgshmflg:权限位标志,和open函数的权限位标志类似,通常使用八进制来表示,同时可以与IPC_CREAT做或操作;
返回值:shmget函数成功的时候,返回一个int正整数值,它是共享内存的标识符。失败时返回:-1并且设置errno;如果shmget用于创建共享内存,则这段共享内存的所有字节都会被初始化为0,同时与之相关联的内核数据结构shmid_ds也将会被创建并且同时初始化,其中shmid_ds结构体的定义如下:
struct shmid_ds { struct ipc_perm shm_perm; /*共享内存的操作权限*/ size_t shm_segsz; /*共享内存的大小、单位是字节*/ time_t shm_atime; /*对这段内存最后一次调用shmat的时间*/ time_t shm_dtime; /*对这段内存最后一次调用shmdt的时间*/ time_t shm_ctime; /*对这段内存最后一次调用shmctl的时间*/ pid_t shm_cpid; /*创建者的pid*/ pid_t shm_lpid; /*最后一次执行shmat或shmdt操作的进程的PID*/ shmatt_t shm_nattch; /*目前关联到此共享内存的进程的数量*/ ... /*省略一些填充字段*/ };其中 struct ipc_perm shm_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 */ };
(2)shmat
共享内存在被创建/获取之后,并不能够立即的去访问和使用,而是需要先将这个创建好的共享内存关联到进程的地址空间中去;
#include <sys/types.h>#include <sys/shm.h>void *shmat(int shmid, const void *shmaddr, int shmflg);
参数shmid:是由shmget函数调用之后返回的共享内存标识符;
参数shm_addr:指定将共享内存关联到进程的哪块地址空间,但是最终的效果还会收到shmflg参数的SHM_RND的影响;
(1) 若shm_addr为NULL,则被关联的地址由操作系统(内核)选择。这也是推荐使用的做法,以确保代码的可移植性。该参数选项类似于mmap函数(内存映射文件方法);mmap函数原型如下:
void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset); 其中第一个参数往往只需要我们填写NULL,就ok了,它也代表由操作系统来选择映射的空间地址,
有关mmap的详细用法请参考:
(2) 若shm_addr非空,并且SHM_RND标志未被设置,则共享内存被关联到addr指定的地址处。
参数 shmflg:除了SHM_RND标志外,shmflg参数还支持如下的标志:
· SHM_RDONLY:表示进程仅能够读取共享内存中的内容。(类似于open中的flag权限设置);若没有指定该标志,表示进程能够对共享内存进行读写的操作(需要在创建共享内存的时候指定其读写的权限);
· SHM_REMAP:若地址shmaddr已经被关联到了一段共享内存上,则重新关联;
· SHM_EXEC: 它指定对共享内存段的执行权限,对共享内存而言,所谓的执行权限实际上和读权限是一样的;shmat函数成功的时候,返回共享内存被关联到的地址;失败则返回:(void *)-1并且设置errno。shmat成功时,将会修改内核数据结构shmid_ds的部分字段,如下:
· 将shm_nattach加1
· 将shm_lpid设置为调用进程的PID
· 将shm_atime设置为当前的时间
总而言之:void *shmat(int shmid, const void *shmaddr, int shmflg); 函数中shmaddr通常指定为NULL,shmflg通常值得为0
(3) shmdt
共享内存使用完之后,我们也需要将它从进程地址空间中分离出来;将共享内存分离并不是删除它,只是使该共享内存对当前的进程不再可用。shmdt函数如下:
#include <sys/types.h> #include <sys/shm.h>int shmdt(const void *shmaddr);
参数shmaddr: 为shmat函数的返回值,即共享内存被关联到的地址;
shmdt函数成功时:返回0,失败返回-1;shmdt函数在被成功的调用的时候也同样会去修改内核的数据结构shmid_ds的部分字段,如下:
· 将shm_nattach减1
· 将shm_lpid设置为调用进程的PID
· 将shm_dtime设置为当前的时间
(4)shmctl
shmctl系统调用用来控制共享内存的某些属性。
#include <sys/ipc.h> #include <sys/shm.h> int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数shmid:为shmget函数调用后返回的共享内存标识符。
参数command:指定要执行的命令。常用到的有3个,分别是:
IPC_STAT 将共享内存相关联的内核数据结构复制到buf(第3个参数中去
IPC_SET 将buf中的部分成员复制到共享内存相关的内核数据结构中,同时内核数据中的shmid_ds.shm_ctime被更新
IPC_RMID 将共享内存打上删除的标记(Mark),这样当最后一个使用它的进程调用shmdt将它从进程中分离时,该共享内存就被删除了;
参数buf:共享内存管理结构体 buf; 是一个结构体指针。IPC_STAT的时候,取得的状态放在这个结构体中。如果要改变共享内存的状态,用这个结构体指定;
struct shmid_ds结构体:struct shmid_ds { struct ipc_perm shm_perm; /*共享内存的操作权限*/ size_t shm_segsz; /*共享内存的大小、单位是字节*/ time_t shm_atime; /*对这段内存最后一次调用shmat的时间*/ time_t shm_dtime; /*对这段内存最后一次调用shmdt的时间*/ time_t shm_ctime; /*对这段内存最后一次调用shmctl的时间*/ pid_t shm_cpid; /*创建者的pid*/ pid_t shm_lpid; /*最后一次执行shmat或shmdt操作的进程的PID*/ shmatt_t shm_nattch; /*目前关联到此共享内存的进程的数量*/ ... /*省略一些填充字段*/ };
IPC_RMID 命令实际上不从内核删除一个段,而是仅仅把这个段标记为删除,实际的删除发生在最后一个进程离开这个共享段时。
注意,有小伙伴可能会对shmdt和shmctl的功能产生混淆,要知道这两个函数实现的功能和作用是不一样的:区别如下:
shmdt(addr)使进程中的shmaddr指针无效化,不可以使用,但是保留空间。
shmctl(shmid,IPC_RMID,0) 删除共享内存,彻底不可用,释放空间。
二. 代码1
/************************************************************************* * File Name: shmget.c * Author: The answer * Function: Other * Mail: 2412799512@qq.com * Created Time: 2017年07月28日 星期五 16时32分00秒 ************************************************************************/#include<stdio.h>#include<stdlib.h>#include<sys/shm.h>#include<sys/ipc.h>#include<string.h>#include<assert.h>int main(int argc,char **argv){ int shmid = shmget(0x00,1024,0777|IPC_CREAT); char *shmaddr = NULL; char *str = "i am lixiaogang."; int len = strlen(str); assert(shmid != -1); assert((shmaddr = shmat(shmid,NULL,0)) != (void* )-1); strncpy(shmaddr,str,len);#if 0 puts(str); puts(shmaddr);#endif printf("str = %s\n",str); printf("shmaddr = %s\n",shmaddr); getchar(); assert( shmdt(shmaddr) != -1 ); assert( shmctl(shmid,IPC_RMID,NULL) != -1); return 0;}------ 程序结果:------str = i am lixiaogang.shmaddr = i am lixiaogang.
该程序会在内核中创建1024字节大小的共享内存,然后可以将字符串复制到该内存空间地址上去,通过ipcs命令(ipcs 命令用简短格式写入一些关于当前活动消息队列、共享内存段、信号量、远程队列和本地队列标题)查看当前的共享内存信息; 程序中的getchar的功能是:等待用户的回车操作来结束对共享内存的删除操作,在用户还没有按下回车时候,这时候的共享内存空间还存在,如下:
这个程序只是简单演示这4个api函数的使用,还没有涉及到两个进程共享这个内存空间;
代码二.
利用共享内存来实现父子进程间的通信:
/************************************************************************* * File Name: SharedMemory.c * Author: The answer * Function: Other * Mail: 2412799512@qq.com * Created Time: 2017年07月29日 星期六 09时33分29秒 ************************************************************************/#include<stdio.h>#include<stdlib.h>#include<string.h>#include<errno.h>#include<sys/ipc.h>#include<sys/shm.h>#include<assert.h>#define MAXSIZE 4096int main(int argc,char **argv){ int shmid; char *shmaddr = NULL; struct shmid_ds buf; /* 用来描述和记录这个共享内存区的访问权限信息 */ pid_t pid; shmid = shmget(IPC_PRIVATE,MAXSIZE,0777|IPC_CREAT|IPC_EXCL); if(shmid == -1) { fprintf(stderr,"shmget error.%d\n",errno); shmid = shmget(IPC_PRIVATE,MAXSIZE,0777|IPC_CREAT); } pid = fork(); if(pid == 0) { shmaddr = shmat(shmid,NULL,0); if(shmaddr == (void *)-1) { perror("shmat failure."); exit(1); } /* 共享内存空间中数据为I am child process. */ strcpy(shmaddr,"I am child process."); printf("child pid is %d and parent is %d\n",getpid(),getppid()); printf("child string is : %s\n",shmaddr); assert(shmdt(shmaddr) != -1); return 0; } else if(pid > 0) { sleep(2); /* IPC_STAT 获取共享内存的状态,把共享内存的shmid_ds结构体复制到buf中 */ if(shmctl(shmid,IPC_STAT,&buf) == -1) { fprintf(stderr,"shmctl failure."); return 0; } printf("I am parent process. pid is %d\n",getpid()); printf("共享内存的大小(字节): %zu\n",buf.shm_segsz); printf("创建者pid: %d\n",buf.shm_cpid); printf("最后一次执行shmat/shmdt操作的进程pid: %d\n",buf.shm_lpid); printf("关联到该共享内存的进程的数量: %d\n",buf.shm_nattch); /* 把共享内存区对象映射到父进程的地址空间中 */ shmaddr = shmat(shmid,NULL,0); if(shmaddr == (void *)-1) { printf("shmat failure."); return 0; } printf("parent string is : %s\n",shmaddr); if(shmdt(shmaddr) == -1) { printf("shmdt error."); return 0; } assert(shmctl(shmid,IPC_RMID,NULL) != -1); return 0; } else { fprintf(stderr,"fork error.%d\n",errno); assert(shmctl(shmid,IPC_RMID,NULL) != -1); } return 0;}
- 【共享内存】共享内存
- 共享内存
- 共享内存
- 共享内存
- 共享内存
- 共享内存
- 共享内存
- 共享内存
- 共享内存
- 共享内存
- 共享内存
- 共享内存
- 共享内存
- 共享内存
- 共享内存
- 共享内存
- 共享内存
- 共享内存
- 解决多张图片排在一起时产生的空隙
- [vijos NOIP模拟题]天神下凡 贪心+搜索
- 【c#】开机自启动
- Template模式
- 【普通算法】字符串字符及个数一致比较
- 共享内存
- Ubuntu MySQL安装
- 【JavaSE学习笔记】常用类介绍04_System,Date,Math,Random,Pattern
- 硬盘分区表知识——详解硬盘MBR
- String 常用方法
- 调用百度地图的javascript接口来查找地名并标注
- web前端面试
- Java_基础—Properties的概述和使用
- 淘宝网不允许出售虚拟产品了,网店/网络服务/软件最新调整规则