IPC之Posix共享内存详解


1.概念

共享内存区,按标准可分为Posix共享内存区和System V共享内存区,两者在概念上类似。

Posix 表示可移植操作系统接口(Portable Operating System Interface ,缩写为 POSIX ),POSIX标准定义了操作系统应该为应用程序提供的接口标准,是IEEE为要在各种UNIX操作系统上运行的软件而定义的一系列API标准的总称,其正式称呼为IEEE 1003,而国际标准名称为ISO/IEC 9945。

System V,曾经也被称为 AT&T System V,是Unix操作系统众多版本中的一支。它最初由 AT&T 开发,在1983年第一次发布。一共发行了4个 System V 的主要版本:版本1、2、3 和 4。

2.Posix.1提供了两种在无亲缘关系进程间共享内存区的方法
a) 内存映射文件(memory-mapped file),由open函数打开,由mmap函数把所得到的描述符映射到当前进程空间地址中的一个文件。

b)共享内存区对象(shared-memory object),由shm_open函数打开一个Posix.1 IPC名字,所返回的描述符由mmap函数映射到当前进程的地址空间。

IPC之Posix共享内存详解_第1张图片


这两种共享内存区的区别在于共享的数据的载体(底层支撑对象)不一样:

内存映射文件的数据载体是物理文件。
共享内存区对象,也就是共享的数据载体是物理内存。

3.我们经常说的共享内存,一般是指共享内存区对象,也就是共享物理内存。

4.关键函数说明


Posix共享内存区涉及以下两个步骤要求。

(1)指定一个名字参数调用shm_open,以创建一个新的共享内存区对象或打开一个已存在的共享内存区对象。

(2)调用mmap把这个共享内存区映射到调用进程的地址空间。

传递给shm_open函数的名字参数随后由希望共享该内存区的任何其他进程使用。

SHM_OPEN(3)                Linux Programmer's Manual               SHM_OPEN(3)

NAME
       shm_open,  shm_unlink  -  create/open  or  unlink  POSIX  shared memory
       objects

SYNOPSIS
       #include 
       #include         /* For mode constants */
       #include            /* For O_* constants */

       int shm_open(const char *name, int oflag, mode_t mode);

       Link with -lrt.
返回:若成功则非负描述符,若出错则为-1

oflag参数与open函数的flags一样,必须含有O_RDONLY或O_RDWR标准。

mode参数与open函数的mode一样,是指定权限位。如果没有指定O_CREAT标志,那么该参数可以指定为0。


shm_unlink函数删除一个共享内存区对象的名字,删除一个名字不会影响对于其底层支撑对象的现有引用,直到对于该对象的引用全部关闭为止。

SHM_OPEN(3)                Linux Programmer's Manual               SHM_OPEN(3)

NAME
       shm_open,  shm_unlink  -  create/open  or  unlink  POSIX  shared memory
       objects

SYNOPSIS
       #include 
       #include         /* For mode constants */
       #include            /* For O_* constants */

       int shm_unlink(const char *name);

       Link with -lrt.
返回:若成功则为0,若出错则为-1

处理mmap的时候,普通文件或共享内存区对象的大小都可以通过调用ftruncate函数修改。

TRUNCATE(2)                Linux Programmer's Manual               TRUNCATE(2)

NAME
       truncate, ftruncate - truncate a file to a specified length

SYNOPSIS
       #include 
       #include 

       int truncate(const char *path, off_t length);
       int ftruncate(int fd, off_t length);
返回:若成功则为0,若出错则为-1

我们调用ftruncate来指定新创建的共享内存区对象的大小,或者修改已存在的对象的大小。当打开一个已存在的共享内存区对象时,我们可调用 fstat来获取有关该对象的信息。

STAT(2)                    Linux Programmer's Manual                   STAT(2)

NAME
       stat, fstat, lstat - get file status

SYNOPSIS
       #include 
       #include 
       #include 

       int fstat(int fd, struct stat *buf);
返回:若成功则为0,若出错则为-1

stat结构有12个或以上的成员,然而当fd指代一个内存共享区对象时,只有四个成员含有信息。

struct stat {
  mode_t    st_mode;    /* protection */
  uid_t     st_uid;     /* user ID of owner */
  gid_t     st_gid;     /* group ID of owner */
  off_t     st_size;    /* total size, in bytes */
};

/* mmap函数把一个文件或者一个Posix共享内存区对象映射至调用进程的地址空间 */

MMAP(2)                    Linux Programmer's Manual                   MMAP(2)  
  
NAME  
       mmap, munmap - map or unmap files or devices into memory  
  
SYNOPSIS  
       #include   
  
       void *mmap(void *addr, size_t length, int prot, int flags,  
                  int fd, off_t offset);  
  
       See NOTES for information on feature test macro requirements.
返回:若成功则为被映射区的起始地址,若出错则为MAP_FAILED

其中addr可以指定描述符fd应被映射到进程内空间的起始地址,它通常被指定为一个空指针,这样告诉内核自己去选择起始地址,无论哪种情况下,该函数的返回值都是描述符fd所映射到内存区的其实地址。这里需要注意的是,文件需要初始化长度,否则对内存操作时会产生SIGBUS信息(硬件错误)。

  

length是映射到调用进程地址空间中字节数,它从被映射文件开头offset个字节出开始算。offset通常设置为0。


内存映射区的保护由port参数指定,通常设置为PROT_READ | PROT_WRITE(可读与可写):

PORT_READ    -> 可读

PORT_WRITE  -> 可写

PORT_EXEC    -> 可执行

PORT_NONE   -> 数据不可访问


flags用于设置内存映射区的数据被修改时,是否改变其底层支撑对象(这里的对象是文件),MAP_SHAREDMAP_PRIVATE必须指定一个

MAP_SHARED  -> 变动是共享的

MAP_PRIVATE  -> 变动是私自的

MAP_FIXED        -> 准确的解析addr参数


举例:当flags设定为MAP_SHARED时,在内存中对文件的修改会同步到物理文件中,可通过less查看

flags设定为MAP_PRIVATE时,在内存中对文件的修改不会同步到物理文件中,可通过less查看


mmap成功返回后,fd参数可以关闭。该操作对由于mmap建立的映射关系没有影响。


/* 从某个进程空间删除一个映射关系 */

MMAP(2)                    Linux Programmer's Manual                   MMAP(2)  
  
NAME  
       mmap, munmap - map or unmap files or devices into memory  
  
SYNOPSIS  
       #include   
  
       int munmap(void *addr, size_t length);  
  
       See NOTES for information on feature test macro requirements.  
返回:若成功则为0,若出错则为-1

其中addr参数是由mmap返回的地址,len是映射区的大小。再次访问这些地址将导致向调用进程产生一个SIGSEGV信号。


5.特别提醒
共享内存并未提供同步机制,也就是说,在第一个进程结束对共享内存的写操作之前,并无自动机制可以阻止第二个进程开始对它进行读写。所以我们通常需要同步机制控制对共享内存的访问,比如信号量


例子1,创建修改输出删除共享内存区。

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define MAXSIZE 1024*1024*16   /*共享内存的大小,建议设置成内存页的整数倍*/
#define FILENAME "shm.test"

int main()
{
    /* 创建共享对象,可以查看/dev/shm目录 */
    int fd = shm_open(FILENAME, O_CREAT | O_TRUNC | O_RDWR, 0777);
    if (fd == -1) {
        perror("open failed:");
        exit(1);
    }

    /* 调整大小 */
    if (ftruncate(fd, MAXSIZE) == -1) {
        perror("ftruncate failed:");
        exit(1);
    }

    /* 获取属性 */
    struct stat buf;
    if (fstat(fd, &buf) == -1) {
        perror("fstat failed:");
        exit(1);
    }
    printf("the shm object size is %ld\n", buf.st_size);

    sleep(30);

    /* 如果引用计数为0,系统释放内存对象 */
    if (shm_unlink(FILENAME) == -1) {
        perror("shm_unlink failed:");
        exit(1);
    }
    printf("shm_unlink %s success\n", FILENAME);

    return 0;
}

编译运行:

IPC之Posix共享内存详解_第2张图片


例子2,一个进程写,一个进程读:

写进程:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define MAXSIZE 1024*4   /*共享内存的大小,建议设置成内存页的整数倍*/
#define FILENAME "shm.test"

int main()
{
    /* 创建共享对象,可以查看/dev/shm目录 */
    int fd = shm_open(FILENAME, O_CREAT | O_TRUNC | O_RDWR, 0777);
    if (fd == -1) {
        perror("open failed:");
        exit(1);
    }

    /* 调整大小 */
    if (ftruncate(fd, MAXSIZE) == -1) {
        perror("ftruncate failed:");
        exit(1);
    }

    /* 获取属性 */
    struct stat buf;
    if (fstat(fd, &buf) == -1) {
        perror("fstat failed:");
        exit(1);
    }
    printf("the shm object size is %ld\n", buf.st_size);

    /* 建立映射关系 */
    char *ptr = (char*)mmap(NULL, MAXSIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (ptr == MAP_FAILED) {
        perror("mmap failed:");
        exit(1);
    }
    printf("mmap %s success\n", FILENAME);
    close(fd); /* 关闭套接字 */

    /* 写入数据 */
    char *content = "hello world";
    strncpy(ptr, content, strlen(content));

    sleep(30);

    return 0;
}

读进程:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define FILENAME "shm.test"

int main()
{
    /* 创建共享对象,可以查看/dev/shm目录 */
    int fd = shm_open(FILENAME, O_RDONLY, 0);
    if (fd == -1) {
        perror("open failed:");
        exit(1);
    }

    /* 获取属性 */
    struct stat buf;
    if (fstat(fd, &buf) == -1) {
        perror("fstat failed:");
        exit(1);
    }
    printf("the shm object size is %ld\n", buf.st_size);

    /* 建立映射关系 */
    char *ptr = (char*)mmap(NULL, buf.st_size, PROT_READ, MAP_SHARED, fd, 0);
    if (ptr == MAP_FAILED) {
        perror("mmap failed:");
        exit(1);
    }
    printf("mmap %s success\n", FILENAME);
    close(fd); /* 关闭套接字 */

    printf("the read msg is:%s\n", ptr);

    sleep(30);

    return 0;
}

由于只有一个进程写,而且写进程先执行,所以不需要进行同步。

编译运行:

IPC之Posix共享内存详解_第3张图片


原文出自:http://blog.csdn.net/daiyudong2020/article/details/50500651


参考:《unix网络编程》·卷2

End;

你可能感兴趣的:(linux之IPC,Linux之IPC)