0. 前言

共享内存可以说是最有用的进程间通信方式,也是最快的IPC 形式。两个不同的进程A、B共享内存的基本原理是,同一块物理内存映射到进程A、B各自的进程地址空间。进程A可以即时看到B对共享内存中数据的更新,反之亦然。

如图:

shmem shmem ipc nanomsg 速度_linux

共享内存特点:

  • 共享内存是最快的IPC形式,因为内存映射到共享它的进程的地址空间,这些进程数据传递就不再涉及到内核了,也就是说说不再通过执行进入内核的系统调用来传递彼此的数据,所以,它的速度是最快的。
  • 共享内存的生命周期随进程,也需要显示地删除。
  • 共享内存没有互斥与同步机制,因此,我们在使用时,需要自己添加。

1. 创建共享内存

系统调用shmget() 创建或获取一个已经存在的共享内存的标识符,函数原型:

#include <sys/shm.h>       //里面包含 #include <sys/ipc.h>

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

参数:

  • key 共享内存的关键字,可以通过ftok() 创建,详细看进程间通信——消息队列
  • size 共享内存的大小
  • shmflg 有两个选项,IPC_CREAT 表示内核中没有此共享内存则创建它。IPC_EXCL 当和IPC_CREAT一起使用时,如果共享内存已经存在,则返回错误。

返回值:成功返回标识符,否则返回-1。通过errno和perror函数可以查看错误信息。

2. 连接共享内存

shmat() 可以获取一个共享内存的地址,并将其连接到进程中,函数原型:

#include <sys/shm.h>

void *shmat(int shmid, const void *shmaddr, int shmflg);

参数:

  • shmid 共享内存的标识符。
  • shmaddr 指定的连接地址。若shmaddr取NULL,表示核心自动选择一个地址;若不为NULL且shmflg⽆SHM_RND标记,则以shmaddr为连接地址;若不为NULL且shmflg设置了SHM_RND标记,则连接的地址会⾃自动向下调整为SHMLBA的整数倍。公式:shmaddr - (shmaddr % SHMLBA)
  • shmflg:它的两个可能取值是SHM_RND和SHM_RDONLY(只读共享内存)

返回值:成功返回一个指针,指向共享内存的第⼀个节;失败返回-1

3. 脱离共享内存

当一个进程不在需要共享内存时,用shmdt() 把共享内存从进程地址空间中脱离,但不等于将共享内存段从系统内核中删除。函数原型:

#include <sys/shm.h>

int shmdt(const void *shmaddr);

参数:shmaddr 为shmat() 函数的连接共享内存的地址。

返回值:成功返回0;失败返回-1

4. 控制共享内存

进程间通信——共享内存 ,在共享内存中标识符的属性被记录在一个shmid_ds的结构中:

struct shmid_ds{
    struct ipc_perm   shm_perm;         /* operation permission struct */
    size_t            shm_segsz;        /* size of segment in bytes */
    time_t            shm_amime;        /* time of last shmat() */
    time_t            shm_dtime;        /* time of last shmdt() */
    time_t            shm_ctime;        /* time of last change by shmctl() */
    pid_t             shm_cpid;         /* pid of creator */
    pid_t             shm_lpid;         /* pid of last shmdt */
    shmatt_t          shm_nattach;      /* number of current attaches */
    ...
}

通过 shmctl() 可以对共享内存进行控制或一些属性的修改,函数原型:

#include <sys/shm.h>

int shmctl(int shmid, int cmd, struct shmid_ds *buf);

参数: 

  • shmid:共享内存的标识码 
  • cmd:有三个可选的值,在此我们使用IPC_RMID

选项

功能

IPC_STAT

把msqid_ds结构中的数据设置为共享内存的当前关联值

IPC_SET

进程有足够权限的前提下,把共享内存的当前关联值设置为msqid_ds数据结构中给出的值

IPC_RMID

删除共享内存段

5. 实例

test_shm.h

#ifndef __TEST_SHARED_MEMORY_INCLUDE__
#define __TEST_SHARED_MEMORY_INCLUDE__

//include this header file for shared memory
#include <sys/shm.h>

#define shm_key "shm_key"
#define SHARE_BUF 16*1024

typedef struct{
    int flag;
} shm_buf_t;

#define END 1000

int create_shm();
int get_shm();
int remove_shm(int shmid);

#endif //#ifndef __TEST_SHARED_MEMORY_INCLUDE__

test_shm.c

#include <stdio.h>

#include "test_shm.h"

static int create_shm_common(int flags)
{
    printf("create shared memory\n");

    key_t key = ftok(shm_key, 's');
    if (key == -1) {
        perror("ftok error.\n");
        return -1;
    }
    printf("key is 0x%x\n", key);

    int shmid = shmget(key, SHARE_BUF, flags);
    if (shmid == -1) {
        perror("shmget error.\n");
        return -1;
    }
    printf("shmid is %d\n", shmid);

    return shmid;
}

int create_shm()
{
    return create_shm_common(IPC_CREAT | IPC_EXCL | 0644);
}

int get_shm()
{
    return create_shm_common(IPC_CREAT);
}

int remove_shm(int shmid)
{
    if (shmctl(shmid, IPC_RMID, NULL) == -1) {
        perror("shmctl error.\n");
        return -1;
    }

    return 0;
}

test_read.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include "test_shm.h"


// server for testing
int main()
{
    printf("main of server for reading shm.\n");

    int shmid = create_shm();

    shm_buf_t *shbuf = shmat(shmid, 0, SHM_RDONLY);
    if (shbuf == (void*) -1L) {
        perror("shmat error.\n");
        return -1;
    }
    printf("test_read: Memory attched at %X\n", (int)shbuf);

    int n = 100;
    while (1) {
        printf("test_read: buf->flag = %d\n", shbuf->flag);
        
        if (shbuf->flag == END || n < 0)
            break;
        sleep(1);
    }

    return 0;
}

test_write.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <memory.h>

#include "test_shm.h"


// client for testing
int main()
{
    printf("main of server for writing shm.\n");

    int shmid = get_shm();

    shm_buf_t *shbuf = shmat(shmid, NULL, 0);
    if (shbuf == (void*) -1L) {
        perror("shmat error.\n");
        return -1;
    }
    printf("test_write: Memory attched at %X\n", (int)shbuf);

    memset(shbuf, 0, SHARE_BUF);
    shm_buf_t *buf = malloc(sizeof(shm_buf_t));

    int n = 10;
    while (n > 0) {
        if (n == 1) {
            buf->flag = END;
        } else {
            buf->flag = n;
            sleep(2);
        }

        printf("test_write: buf->flag = %d\n", buf->flag);
        memcpy(shbuf, buf, sizeof(shm_buf_t));
        n--;
    }

    free(buf);

    return 0;
}

运行结果:

./test_read 
main of server for reading shm.
create shared memory
key is 0x7300a4fe
shmid is 9273347
test_read: Memory attched at 3F52D000
test_read: buf->flag = 10
test_read: buf->flag = 9
test_read: buf->flag = 9
test_read: buf->flag = 8
test_read: buf->flag = 8
test_read: buf->flag = 7
test_read: buf->flag = 7
test_read: buf->flag = 6
test_read: buf->flag = 6
test_read: buf->flag = 5
test_read: buf->flag = 5
test_read: buf->flag = 4
test_read: buf->flag = 4
test_read: buf->flag = 3
test_read: buf->flag = 3
test_read: buf->flag = 1000
./test_write 
main of server for writing shm.
create shared memory
key is 0x7300a4fe
shmid is 9273347
test_write: Memory attched at 65DBA000
test_write: buf->flag = 10
test_write: buf->flag = 9
test_write: buf->flag = 8
test_write: buf->flag = 7
test_write: buf->flag = 6
test_write: buf->flag = 5
test_write: buf->flag = 4
test_write: buf->flag = 3
test_write: buf->flag = 2
test_write: buf->flag = 1000