进程间通信之共享内存

共享内存

  • 1.共享内存的概念
  • 2.共享内存函数
    • 2.1 shmget函数
    • 2.2 shmat函数
    • 2.3 shmdt函数
    • 2.4 shmctl函数
  • 3. 共享内存的使用

1.进程间通信的分类:
(1)管道:1、匿名管道pipe;2、命名管道mkfifo
(2)System V IPC:1、System V 消息队列;2、System V 共享内存;3、System V 信号量。
(3)POSIX IPC:1、消息队列;2、共享内存;3、信号量;4、互斥量;5、条件变量;6、读写锁。
前面已经了解了进程间管道通信,那么共享内存又是什么原理?

1.共享内存的概念

什么是共享内存?
共享内存通信是一种进程间通信的方式,它允许两个或更多进程访问同一块内存,就如同 malloc () 函数向不同进程返回了指向同一个物理内存区域的指针。共享内存是 Unix/Linux下的多进程之间的通信方法,这种方法通常用于一个程序的多进程间通信,实际上多个程序间也可以通过共享内存来传递信息。而且共享内存区是最快的IPC形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据。

共享内存和管道的区别:

管道通信和共享内存都是进程间通信的方式,但是它们的实现方式不同。管道通信需要在内核和用户空间进行四次的数据拷贝:由用户空间的buffer中将数据拷贝到内核中 -> 内核将数据拷贝到内存中 -> 内存到内核 -> 内核到用户空间的buffer。而共享内存是最快的一种 IPC,因为进程是直接对内存进行存取。多个进程可以同时操作,所以需要进行同步。

共享内存示意图:
进程间通信之共享内存_第1张图片

共享内存数据结构:
用man shmctl指令可以查看共享内存的数据结构。

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 */
    ...
};

而其中ipc_perm是一个内核为每个IPC对象所维护的一个数据结构,如下:

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.共享内存函数

2.1 shmget函数

shmget函数功能:用来创建共享内存。

NAME
       shmget - allocates a System V shared memory segment
	   //分配System V共享内存段
SYNOPSIS
       #include 
       #include 

       int shmget(key_t key, size_t size, int shmflg);

DESCRIPTION
       shmget() returns the identifier of the System V shared memory segment associated with the value of the argument key.  A new shared memory seg?
       ment, with size equal to the value of size rounded up to a multiple of PAGE_SIZE, is created if key has the value  IPC_PRIVATE  or  key  isn't
       IPC_PRIVATE, no shared memory segment corresponding to key exists, and IPC_CREAT is specified in shmflg.

       If shmflg specifies both IPC_CREAT and IPC_EXCL and a shared memory segment already exists for key, then shmget() fails with errno set to EEX?
       IST.  (This is analogous to the effect of the combination O_CREAT | O_EXCL for open(2).)

       The value shmflg is composed of:

       IPC_CREAT   to create a new segment.  If this flag is not used, then shmget() will find the segment associated with key and check  to  see  if
                   the user has permission to access the segment.

       IPC_EXCL    used with IPC_CREAT to ensure failure if the segment already exists.

RETURN VALUE
       On success, a valid shared memory identifier is returned.  On errir, -1 is returned, and errno is set to indicate the error.
       //成功返回有效的共享内存标识符,失败返回-1,并且错误码被设置。

int shmget(key_t key, size_t size, int shmflg);
key_t key:这个值用ftok生成,ftok会经过算法生成出一个冲突概率低的值,这个值保证唯一;目的是申请的共享内存块是尽可能不同的;
size_t size:申请共享内存块的大小;
int shmflg:这个参数常用两个选项,分别是IPC_CREAT and IPC_EXCL;
单独使用IPC_CREAT: 创建一个共享内存,如果共享内存不存在,就创建之,如果已经存在,获取已经存在的共享内存并返回;
IPC_CREAT | IPC_EXCL: 创建一个共享内存,如果共享内存不存在,就创建之, 如果已经存在,则立马出错返回
IPC_EXCL不能单独使用,一般都要配合IPC_CREAT;
返回值:成功返回一个非负整数,即该共享内存段的标识码;失败返回-1。

ftok函数:

NAME
       ftok - convert a pathname and a project identifier to a System V IPC key

SYNOPSIS
       #include 
       #include 

       key_t ftok(const char *pathname, int proj_id);
       
RETURN VALUE
       On success, the generated key_t value is returned.  On failure -1 is returned, with errno indicating the error as for the stat(2) system call.

ftok会将这个路径pathname和proj_id(可以随便写)经过算法生成出一个冲突概率低的值。

2.2 shmat函数

shmat函数功能:将共享内存段连接到进程地址空间。

void *shmat(int shmid, const void *shmaddr, int shmflg);
shmid: 共享内存标识;
shmaddr:指定连接的地址;
shmflg:它的两个可能取值是SHM_RND和SHM_RDONLY;
返回值:成功返回一个指针,指向共享内存;失败返回-1;
shmaddr为NULL,核心自动选择一个地址;
shmaddr不为NULL且shmflg无SHM_RND标记,则以shmaddr为连接地址;
shmaddr不为NULL且shmflg设置了SHM_RND标记,则连接的地址会自动向下调整为SHMLBA的整数倍;(所以一般直接设为nullptr就可)
shmflg=SHM_RDONLY,表示连接操作用来只读共享内存。

返回值为一个指针,并且指针指向共享内存,所以使用这个指针进行数据的写入或读出。

2.3 shmdt函数

shmdt函数功能:将共享内存段与当前进程脱离。

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

2.4 shmctl函数

shmctl函数功能:用于控制共享内存。

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

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

3. 共享内存的使用

使用之前,先认识下面的IPC指令,共享内存,消息队列,信号量等指令基本相似,所以在使用共享内存,消息队列,信号量进行通信时,其都有一批函数,总的说是大同小异,但是原理是不同的。

查看命令 删除命令
ipcs -m : 查看共享内存 ipcrm -m shmid : 删除共享内存
ipcs -q : 查看共享内存 ipcrm -q msqid : 删除消息队列
ipcs -s : 查看共享内存 ipcrm -s semid : 删除信号量

comm.hpp代码如下:

#pragma once
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;

//  IPC_CREAT and IPC_EXCL
// 单独使用IPC_CREAT: 创建一个共享内存,如果共享内存不存在,就创建之,如果已经存在,获取已经存在的共享内存并返回
// IPC_EXCL不能单独使用,一般都要配合IPC_CREAT
// IPC_CREAT | IPC_EXCL: 创建一个共享内存,如果共享内存不存在,就创建之, 如果已经存在,则立马出错返回 -- 如果创建成功,对应的shm,一定是最新的!

#define PATHNAME "."
#define PROJID 0x6666

const int gsize = 4096; // 共享内存的大小

key_t getKey()
{
    key_t k = ftok(PATHNAME, PROJID); //key_t ftok(const char *pathname, int proj_id);
    if(k == -1)
    {
        cerr << "error: " << errno << " : " << strerror(errno) << endl;
        exit(1);
    }
    return k;
}

server.cc代码如下:

#include "comm.hpp"

int main()
{
    //1.创建共享内存先要创建一个key_t k(ftok)
    key_t k = getKey();

    //2.创建一个共享内存(shmget)
    umask(0); //默认权限
    int shmid = shmget(k, gsize, IPC_CREAT | IPC_EXCL | 0666); //int shmget(key_t key, size_t size, int shmflg)
    // 因为server创建共享内存,所以第三个参数为IPC_CREAT | IPC_EXCL,这个共享内存一定是最新的
    if(shmid == -1)
    {
        cerr << "error: " << errno << " : " << strerror(errno) << endl;
        exit(2);
    }

    //3.将共享内存段连接到进程地址空间(shmat)
    char* start = (char*)shmat(shmid, nullptr, 0);
    if(*start == -1)
    {
        cerr << "error: " << errno << " : " << strerror(errno) << endl;
        exit(3);
    }

    //3.写入信息"i am process server"
    char buffer[64] = "i am process server";
    int i = 0;
    while (buffer[i])
    {
        start[i] = buffer[i];
        ++i;
    }
    //start = buffer; 错误写法,因为这样写直接就将start指针修改,start就不是指向共享内存的地址
    sleep(10);

    //将共享内存段与当前进程脱离(shmdt)
    int n = shmdt(start);
    assert(n != -1);
    (void)n;

    //4.删除共享内存(shmctl)
    int m = shmctl(shmid, IPC_RMID, nullptr); // IPC_RMID | 删除共享内存段
    assert(m != -1);
    (void)m;

    return 0;
}

client.cc代码如下:

#include "comm.hpp"

int main()
{
    //1.获取已经存在的共享内存
    key_t k = getKey();

    int shmid = shmget(k, gsize, IPC_CREAT); //int shmget(key_t key, size_t size, int shmflg)
    // 单独使用IPC_CREAT: 创建一个共享内存,如果共享内存不存在,就创建之,如果已经存在,获取已经存在的共享内存并返回;
    // 这里获取的是已经存在的


    //2.将共享内存段连接到进程地址空间(shmat)
    char* start = (char*)shmat(shmid, nullptr, 0);
    if(*start == -1)
    {
        cerr << "error: " << errno << " : " << strerror(errno) << endl;
        exit(3);
    }

    //3.从共享内存中读取数据
    int m = 3;
    while (m--)
    {
        cout << "i am client,i read: " << start << endl;
        sleep(3);
    }

    //将共享内存段与当前进程脱离(shmdt)
    int n = shmdt(start);
    assert(n != -1);
    (void)n;

    return 0;
}

makefile代码如下:

.PHONY:all
all:server client

server:server.cc
	g++ -o $@ $^ -std=c++11
client:client.cc
	g++ -o $@ $^ -std=c++11

.PHONY:clean
clean:
	rm -f server client

运行结果如下:
进程间通信之共享内存_第2张图片

如果在自写代码中有如下错误,File exists,这是因为执行server.cc程序,程序并不是完整退出,而是程序进行一半时退出,例如:程序进行一半时按ctrl+c强制退出,程序没有执行到最后,也就是共享内存没有被删除,这时,就可以用ipcs -m查看共享内存;
在这里插入图片描述
然后ipcrm -m shmid(4)如下shmid为4,进行删除,重新运行程序即可。
在这里插入图片描述
如果是别的问题,那一定是代码错着,仔细检查代码吧。

你可能感兴趣的:(linux,进程间通信)