IPC之共享内存

共享内存
共享内存(shared memory)是进程间通信中最简单的方式之一。共享内存允许两个或更多进程访问同一块内存,就如同 malloc() 函数向不同进程返回了指向同一个物理内存区域的指针。当一个进程改变了这块地址中的内容的时候,其它进程都会察觉到这个更改。
内存模型
要使用一块共享内存,进程必须首先分配它。随后需要访问这个共享内存块的每一个进程都必须将这个共享内存绑定到自己的地址空间中。当完成通信之后,所有进程都将脱离共享内存,并且由一个进程释放该共享内存块。
理解 Linux 系统内存模型可以有助于解释这个绑定的过程。在 Linux 系统中,每个进程的虚拟内存是被分为许多页面的。这些内存页面中包含了实际的数据。每个进程都会维护一个从内存地址到虚拟内存页面之间的映射关系。尽管每个进程都有自己的内存地址,不同的进程可以同时将同一个内存页面映射到自己的地址空间中,从而达到共享内存的目的。
分配一个新的共享内存块会创建新的内存页面。因为所有进程都希望共享对同一块内存的访问,只应由一个进程创建一块新的共享内存。再次分配一块已经存在的内存块不会创建新的页面,而只是会返回一个标识该内存块的标识符。一个进程如需使用这个共享内存块,则首先需要将它绑定到自己的地址空间中。这样会创建一个从进程本身虚拟地址到共享页面的映射关系。当对共享内存的使用结束之后,这个映射关系将被删除。当再也没有进程需要使用这个共享内存块的时候,必须有一个(且只能是一个)进程负责释放这个被共享的内存页面。
所有共享内存块的大小都必须是系统页面大小的整数倍。系统页面大小指的是系统中单个内存页面包含的字节数。在 Linux 系统中,内存页面大小是4KB,不过您仍然应该通过调用 getpagesize 获取这个值。
相关函数
int shmget(key_t key,size_t size,int shmflg);//创建共享内存

第一个参数,与信号量的semget函数一样,程序需要提供一个参数key(非0整数),它有效地为共享内存段命名,shmget函数成功时返回一个与key相关的共享内存标识符(非负整数),用于后续的共享内存函数。调用失败返回-1。不幸的是,其它程序也可能挑选了同样的特定值作为自己分配共享内存的键值,从而产生冲突。用特殊常量IPC_PRIVATE作为键值可以保证系统建立一个全新的共享内存块。

第二个参数指定了所申请的内存块的大小。因为这些内存块是以页面为单位进行分配的,实际分配的内存块大小将被扩大到页面大小的整数倍,但是最后一页的剩余部分内存是不可用的。

第三个参数是一组标志,通过特定常量的按位或操作来shmget。这些特定常量包括:

IPC_CREAT:这个标志表示应创建一个新的共享内存块。通过指定这个标志,我们可以创建一个具有指定键值的新共享内存块。

IPC_EXCL:这个标志只能与 IPC_CREAT 同时使用。当指定这个标志的时候,如果已有一个具有这个键值的共享内存块存在,则shmget会调用失败。也就是说,这个标志将使线程获得一个“独有”的共享内存块。如果没有指定这个标志而系统中存在一个具有相同键值的共享内存块,shmget会返回这个已经建立的共享内存块,而不是重新创建一个。


void *shmat(int shm_id,const void *shm_adder,int shmflg);//绑定到共享内存

将 shmget 返回的共享内存标识符传递给这个函数作为第一个参数。

该函数的第二个参数是一个指针,指向您希望用于映射该共享内存块的进程内存地址;如果您指定NULL则Linux会自动选择一个合适的地址用于映射。

第三个参数,shm_flg是一组标志位,通常为0。调用成功时返回一个指向共享内存第一个字节的指针,如果调用失败返回-1。shmat函数成功执行会将shm_id段的shmid_ds结构的shm_nattch计数器的值加1.

int shmdt(const void * shmaddr);//与共享内存脱离
      当一个进程不再使用一个共享内存块的时候应通过调用 shmdt(Shared Memory Detach,脱离共享内存块)函数与该共享内存块脱离。将由 shmat 函数返回的地址传递给这个函数。如果当释放这个内存块的进程是最后一个使用该内存块的进程,则这个内存块将被删除。参数shmaddr是shmat函数返回的地址指针,调用成功时返回0,失败时返回-1。
int shmctl(int shm_id,int command,struct shmid_ds *buf);//控制共享内存

 第一个参数,shm_id是shmget函数返回的共享内存标识符。
      第二个参数,command是要采取的操作,可以取IPC_STA、IPC_SETT、IPC_RMID三个值 

要获取一个共享内存块的相关信息,则为该函数传递 IPC_STAT 作为第二个参数,同时传递一个指向一个 struct shmid_ds对象的指针作为第三个参数。

要删除一个共享内存块,则应将 IPC_RMID 作为第二个参数,而将 NULL 作为第三个参数。当最后一个绑定该共享内存块的进程与其脱离时,该共享内存块将被删除。

在结束使用每个共享内存块的时候都使用 shmctl 进行释放,以防止超过系统所允许的共享内存块的总数限制。调用 exit 和 exec 会使进程脱离共享内存块,但不会删除这个内存块。

      共享内存的结构体
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 */
};
struct shmid_ds//描述共享内存的id
{
	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 */
};
      命令 
ipcs  -qms//查看系统中创建的共享内存的信息  -q -m -s分别是显示消息队列、共享内存和信号量的信息,如果都不指定那就显示全部。
ipcrm -m shmid //销毁创建的共享内存
 
#pragma once
#include <stdio.h>
#include <sys/shm.h>
#include <sys/ipc.h>
#include <sys/wait.h>
#include <unistd.h>

#define __PATH__ "."
#define __PROJECT__ 8888
#define __SHM_SIZE__ 4*1024

int get_shm();
char* at_shm();
int delete_shm();
int rm_shm();
 
#include "shm.h"
int get_shm()
{
    key_t key = ftok(__PATH__, __PROJECT__);
    int flag = IPC_CREAT | 0666;
    int shm_id = shmget(key, __SHM_SIZE__, flag);
    if(shm_id == -1)
    {
        printf("get share memory error!\n");
    }
    else
    {
        printf("get share memory success!\n");
    }
    return shm_id;
}

char* at_shm(int shm_id)
{
    return (char*)shmat(shm_id, NULL,0);
}

int delete_shm(char *addr)
{
    return shmdt(addr);
}

int rm_shm(int shm_id)
{
    return shmctl(shm_id, IPC_RMID, NULL);
}

int main()
{
    int shm_id = get_shm();
    pid_t id = fork();
    if(id < 0)
    {
        printf("fork error\n");
        return 1;
    }
    else if(id == 0)//child
    {
        char *buf = at_shm(shm_id);
        int i = 0;
        while(i < 4096)
        {
            buf[i] = 'X';
            i++;
        }
        buf[4096] = '\0';
        delete_shm(buf);
    }
    else//father
    {
        char *buf = at_shm(shm_id);
        sleep(5);
        printf("%s\n", buf);
        delete_shm(buf);
        waitpid(id, NULL, 0);
        rm_shm(shm_id);
    }
    return 0;
}

共享内存的特点

在匿名管道(pipe),命名管道(mkfifo),消息队列(msgget)、信号量(semget)、共享内存(shmget)这五种进程间通信的方式中,前两种生命周期随进程,后三种生命周期随内核。而共享内存是这五种方式中效率最高的。虽然共享内存提供了进程间通信的方式,但是他没有相应的互斥机制,所以一般共享内存和信号量配合起来使用。

因为所有进程共享同一块内存,共享内存在各种进程间通信方式中具有最高的效率。访问共享内存区域和访问进程独有的内存区域一样快,并不需要通过系统调用或者其它需要切入内核的过程来完成。同时它也避免了对数据的各种不必要的复制。



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值