共享内存

来源:互联网 发布:姚明和奥尼尔对决数据 编辑:程序博客网 时间: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;}

这里写图片描述

原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 阿里妈妈升级看不到引流人数怎么办 阿里妈妈账号被冻结怎么办 微博昵称到次数怎么办 五星好评之后忘记截图了怎么办 评价后忘了截图怎么办 好评率太低不能买东西了怎么办 淘宝评价被删了怎么办 淘宝店铺有流量没有成交怎么办 淘宝好评被删了怎么办 淘宝评论被系统删除怎么办 淘宝被商家删除评价怎么办 淘宝评价管理商家删除了怎么办 淘宝商家删除评价我该怎么办 我的评价隐藏了怎么办 淘宝把评论删了怎么办 淘宝虚假交易被删除评价怎么办 淘宝好评评错了怎么办 被淘宝骗了好评怎么办 美团好评被删了怎么办 卖家收到好评内容是差评怎么办 淘宝收货电话写错了怎么办 淘宝评价写错了怎么办 饿了么商家差评怎么办 淘宝不给补差价怎么办 淘宝顾客给差评怎么办 淘宝买家账号体检违规怎么办 买家淘宝账户体检中心违规怎么办 淘宝卖家电话骚扰该怎么办 手机欠费销户了怎么办 想下载好多个淘宝怎么办 送快递不记得路怎么办 淘宝物流弄丢了怎么办 邮政快递碰上难缠客户怎么办 举证工伤对方不签收怎么办 快递员收件的钱怎么办 锐捷网卡是空的怎么办 mac系统excel太慢怎么办 二手车交易发票丢了怎么办 转转上买二手电脑被骗了怎么办 如果电脑买贵了怎么办 电脑配置低玩lol卡怎么办