Linux进程间通信之 共享内存

对共享内存的理解

基于共享内存的进程间通信方式,本质上是将两个进程的页表把各自地址空间上的数据映射到同一块物理内存上。使得两块进程之间在自己的地址空间同时看到一份公共资源。

基于共享内存的进程间通信一旦将两个进程地址空间映射到同一物理内存后,两进程的消息传递不涉及内核。也就是说信息传递不是通过调用内核系统调用来完成的。 因此这也是System V版本进程间通讯的几个方式中效率最高的方式。

共享内存 将物理内存上的一块空间通过两个进程的页表映射到两个不同的进程地址空间上 一个进程改了这个空间数据 另一个进程就能看见
共享内存 避免了收发数据时频繁的在内核态和用户态的切换
只要创建好共享内存 这个状态就保持好了

共享内存原理图示

这里写图片描述

与共享内存有关的系统调用
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);
    返回值:失败返回-1,成功返回共享内存的id。
    key : 共享内存段的名字。
    size:表示要申请的共享内存的大小,一般是4k的整数倍。
    flags:IPC_CREAT和IPC_EXCL一起使用,则创建一个新的关系内存,否则返回-1。IPC_CREAT单独使用时返回一个共享内存,有就直接返回,没有就创建

  2. void * shmat(int shmid,const void* shmaddr, int shmflg);
    shmat的作用是将申请的共享内存挂接在该进程的页表上,是的虚拟内存和物理内存向对应。
    返回值:返回这块内存的虚拟地址。
    shmaddr 用户可以指定贡献内存地址(共享内存的虚拟地址)。
    shmflg 九个权限标志位构成, 用法和open()函数的mode位一样.

shmaddr 设置为NULL时 内核为共享内存安排地址。
shmaddr 不为NULL 且未设置SHM_RND 标志位时 共享内存地址是shmaddr 不为NULL 设置了SHM_RND 标志问 共享内存的地址向下调整为SHM_LAB的整数倍。
shmflg 设置为 SHM_RDONLY 时 共享内存段与进程的地址空间的链接只能读取共享内存。

  1. int shmdt(const void*shmaddr);
    该函数负责将将这块共享内存从页表上剥离下来。
    返回值:失败返回-1.
    shmaddr:表示这块物理内存的虚拟地址。

  2. int shmctl(int shmid, int cmd, struct shmid_ds * buf)
    该函数用于控制共享内存段。
    shmid 是由shmget函数返回的表示一段共享内存的标识码。
    cmd 表示对该共享内存段的操作。
    buf 指向一个保存该共享内存段访问权限和模式状态的结构体。

cmd:
IPC_STAT 把shmid_ds中的值设置为当前共享内存的关联值。
IPC_SET 如果进程权限足够 将shmid_ds 的值设置给当前共享内存的关联值。
IPC_RMID 用来删除该共享内存段。

命令:
ipcs -s 查看所有共享内存。
ipcrm -s (shmid) 删除某共享内存。

共享内存基本特性
不存在面向字节流 面向数据块的概念 就是一块多个进程共同可见的内存 大家都可以在上面读写。
在匿名管道(pipe),命名管道(mkfifo),消息队列(msgget)、信号量(semget)、共享内存(shmget)这五种进程间通信的方式中,前两种生命周期随进程,后三种生命周期随内核。而共享内存是这五种方式中效率最高的。虽然共享内存提供了进程间通信的方式,但是他没有相应的互斥机制,所以一般共享内存和信号量配合起来使用。

六、共享内存比管道和消息队列效率高的原因
共享内存区是最快的可用IPC形式,一旦这样的内存区映射到共享它的进程的地址空间,这些进程间数据的传递就不再通过执行任何进入内核的系统调用来传递彼此的数据,节省了时间。
共享内存和消息队列,FIFO,管道传递消息的区别:
后者,消息队列,FIFO,管道的消息传递方式一般为
1:服务器得到输入
2:通过管道,消息队列写入数据,通常需要从进程拷贝到内核。
3:客户从内核拷贝到进程
4:然后再从进程中拷贝到输出文件
上述过程通常要经过4次拷贝,才能完成文件的传递。
而共享内存只需要
1:从输入文件到共享内存区
2:从共享内存区输出到文件
上述过程不涉及到内核的拷贝,所以花的时间较少

代码:
//makefile
sersrc=serve.c comm.c
clisrc=client.c comm.c
ser=serve
cli=client
commh=comm.h
cc=gcc
.PHONY:all
all:$(ser) $(cli)
$(ser):$(sersrc) $(commh)
    $(cc) -o $@ $<
$(cli):$(clisrc) $(commh)
    $(cc) -o $@ $<
.PHONY:clean
clean:
    rm -r $(ser) $(cli)

//comm.h
#ifndef __COMM_H__
#define __COMM_H__
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<sys/shm.h>
#include<sys/ipc.h>
#define PATHNAME "."
#define PROJID 0666
#define SIZE 4096*1
int creatshm();
int getshm();
char *atshm(int shmid);
int dtshm(const void* shmaddr);
int destroyshm(int shmid);
#endif

//comm.c
#include"comm.h"
static int commshm(int flags)
{
    key_t _k=ftok(PATHNAME,PROJID);
    if(_k==-1)
    {
        perror("ftok");
        return -1;
    }
    int shmid=shmget(_k,SIZE,flags|0666);
    if(shmid==-1)
    {
        perror("shmget");
        return -1;
    }
    return shmid;
}

int creatshm()
{
    int shmid=commshm(IPC_CREAT | IPC_EXCL);
    return shmid;
}

int getshm()
{
    int shmid=commshm(IPC_CREAT);
    return shmid;
}

char *atshm(int shmid)
{
    char* addr=shmat(shmid,NULL,0);
    return (char*)addr;
}

int dtshm(const void* shmaddr)
{
    return shmdt(shmaddr);
}

int destroyshm(int shmid)
{
    if(shmctl(shmid,IPC_RMID,NULL)<0)
    {
        perror("shmctl");
        return -1;
    }
    return 0;
}

//serve.c
#include"comm.c"
int main()
{
    int shmid=creatshm();
    char* buf=(char*)atshm(shmid);
    int i=0;
    while(i<(SIZE-1))
    {
        buf[i]='A';
        i++;
    }    
    buf[SIZE-1]='\0';
    dtshm(buf);
//  destroyshm(shmid);
    return 0;
}

//client
#include"comm.c"
int main()
{
    int shmid=getshm();
    char* buf=(char*)atshm(shmid);
    printf("%s\n",buf);
    dtshm(buf);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值