进程间通信(system V共享内存)

一、共享内存的原理

首先在物理内存中开辟一段空间,然后利用内核级页表和进程的地址空间建立映射关系,当把数据写入共享内存时,即可完成进程间通信。

二、共享内存的特点

  • 共享内存是system V进程间通信过程中速度最快的
  • 共享内存没有同步与互斥机制,需要用户自己完成
  • 共享内存的生命周期随内核,只要不删就一直在

先看一幅图:
进程间通信(system V共享内存)_第1张图片
所以建立共享内存可分为四步:

 1.在物理内存中开辟一段空间
 2.虚拟地址空间和物理内存通过页表建立映射关系
 3.取消映射关系
 4.释放在物理内存中开辟的地址空间

注意:这里的页表是内核级页表,我们平常在进程地址空间中说的页表是用户级页表
为什么共享内存是system V进程间通信过程中速度最快的?
在回答这个问题之前,我们和上篇博客中介绍的管道对比一下,利用管道进行进程间通信时,需要write(将数据从用户拷贝到内核)和read(将数据从内核拷贝到用户),通信需要两次数据拷贝,但是共享内存不需要,在物理内存中开辟空间并写数据,通信的两个进程立马就可以看到,省去了两次数据拷贝,所以它是最快的。

三、认识共享内存函数

创建共享内存-----shmget函数

int shmget(key_t key, size_t size, int shmflg);
参数:
key: 标识此段共享内存,便于另一个进程查找,由ftok函数生成
size: 共享内存大小
shmflg: 可设为缺省,默认为0
返回值:
成功返回一个非负整数,即该共享内存段的标识码,失败返回-1

注意:shmflg两个重要取值:

  • IPC_CREAT:单独使用时,没有此共享内存则创建,有则直接用
  • IPC_CREAT和IPC_EXCL同时使用时获得全新的共享内存,有共享内存则出错

建立映射关系的函数----shmat

void* shmat(int shmid, const void* shmaddr, int shmflg);
参数:
shmid: shmget函数的返回值
shmaddr: 指定连接的地址,为NULL,则自动选择
shmflg: 它的两个可能取值是SHM_RND和SHM_RDONLY
返回值:
成功返回一个指针,指向共享内存第一个节,失败返回-1

共享内存段与当前进程脱离—shmdt

int shmdt(const void* shmaddr)
参数:
shmaddr:shmat所返回的指针
返回值:
成功返回0,失败返回-1
注意:将共享内存段与当前进程脱离不代表删除共享内存段

控制共享内存—shmctl

int shmctl(int shmid, int cmd, struct shmid_ds* buf);
参数:
shmid: shmget函数返回的共享内存标识码
cmd:将要采取的动作,有三个可取值
buf: 指向一个存储共享内存的模式状态和访问权限的数据结构
返回值:
成功返回0,失败返回-1

cmd说明:

  • IPC_STAT: 把shmid_ds结构中的数据设置为共享内存的当前关联值
  • IPC_SET:在进程有足够权限的前提下,把共享内存的当前关联值设置为shmid_ds数据结构中给出的值
  • IPC_RMID:删除共享内存段

四、共享内存测试代码

//makefile
.PHONY:all
all:server client

client:client.c comm.c
	gcc -o $@ $^

server:server.c comm.c
	gcc -o $@ $^

.PHONY:clean
clean:
	rm -f client server
//comm.h
#ifndef COMM_H
#define COMM_H
#include 
#include 
#include 
#include 
#include 
#define PATHNAME "."
#define PROJ_ID 0X6666

int createShm(int size);
int destroyShm(int shmid);
int getShm(int size);
#endif
//comm.c
#include "comm.h"
static int commShm(int size, int flags)
{
    key_t key = ftok(PATHNAME, PROJ_ID);
    if(key < 0)
    {
        perror("ftok error\n");
        return -1;
    }
    int shmid = 0;
    if((shmid = shmget(key, size, flags)) < 0)
    {
        perror("shmget error\n");
        return -2;
    }
    return shmid;
}
int destroyShm(int shmid)
{
    if(shmctl(shmid, IPC_RMID, NULL) < 0)
    {
        perror("shmctl error\n");
        return -1;
    }
    return 0;
}
int createShm(int size)
{
    return commShm(size, IPC_CREAT | IPC_EXCL | 0666);
}
int getShm(int size)
{
    return commShm(size, IPC_CREAT);
}
//client.c
#include "comm.h"
int main()
{
    //客户端不需要再创建共享内存
    int shmid = getShm(4096);
    sleep(1);
    char* addr = shmat(shmid, NULL, 0);
    sleep(2);
    int i = 0;
    while(i < 26)
    {
        //客户端往共享内存中写
        addr[i] = 'A' + i;
        i++;
        //保证字符串每次以\0结尾
        addr[i] = 0;
        sleep(1);
    }
    shmdt(addr);
    sleep(2);
    return 0;
}
//server.c
#include "comm.h"
int main()
{
    //操作系统以页为单位分配共享内存,1页=4k=4096kb,但是当改成4097时并不会分配两个页,而是类似于写时拷贝
    int shmid = createShm(4096);
    char* addr = shmat(shmid, NULL, 0);
    sleep(2);
    int i = 0;
    while(i++ < 26)
    {
        //服务器负责从共享内存中读
        printf("client# %s\n", addr);
        sleep(1);
    }
    shmdt(addr);
    sleep(2);
    destroyShm(shmid);
    return 0;
}

输出结果:
启动服务器和客户端
进程间通信(system V共享内存)_第2张图片
数据是客户端写进共享内存的,服务器只是把数据拿出来。
但是当再次启动服务时,报以下错误:
在这里插入图片描述
由于共享内存的生命周期随内核,只要不删就一直在,除非重启操作系统,所以利用以下命令删除已经获得的内存:

//查看共享内存的id
ipcs -m 
//删除共享共享内存
ipcrm -m +id

五、system V消息队列

消息队列实际上是操作系统在内核为我们创建的一个队列,通过这个队列的标识符key,每一个进程都可以打开这个队列,每个进程都可以通过这个队列向这个队列中插入一个结点或者获取一个结点来完成不同进程间的通信。

  • 如何传输数据:
    用户组织一个带有类型的数据块,添加到队列中,其他的进程从队列中获取数据块,即消息队列发送的是一个带有类型的数据块;消息队列是一个全双工通信,可读可写(可以发送数据,也可以接受数据)
    消息队列生命周期随内核,如果没有释放消息队列或者没有关闭操作系统,消息队列会一直存在。

六、system V信号量

  • 定义:信号量用来描述临界资源中资源数目,本质上是一个计数器(临界资源)
  • 信号量的P(自减)V(自增)操作必须保证自身的原子性

你可能感兴趣的:(Linux,共享内存,进程间通信)