【Linux进程间通信】 - 共享内存

原创作品,转载请标明:http://blog.csdn.net/Xiejingfa/article/details/50888870

今天我们来介绍Linux下最高效的一种进程间通信方式 – 共享内存。


1、什么是共享内存?

顾名思义,共享内存就是两个(或多个)进程共同占有一段内存空间,这些进程可以是有亲缘关系的进程,也可以是完全不相关的进程。同一块物理内存空间被映射到两个进程,两个进程都可以访问这段共享空间从而实现了进程间通信。但是值得注意的是:Linux的共享内存通信只提供了数据交换功能,并没有提供同步机制,需要使用其它机制来同步不同进程对共享内存的读写操作(如信号量)。

在Linux下,使用ipcs -m可以查看系统中共享内存的使用情况。下面是我在自己电脑中使用该命令的结果:

fred@ubuntu:~$ ipcs -m

------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status      
0x00000000 294912     fred       600        524288     2          dest         
0x00000000 393217     fred       600        524288     2          dest         
0x00000000 491522     fred       600        524288     2          dest         
0x00000000 688131     fred       600        524288     2          dest         
0x00000000 786436     fred       600        524288     2          dest         
0x00000000 819205     fred       600        16777216   2                       
0x00000000 983046     fred       600        16777216   2          dest         
0x00000000 1081351    fred       600        524288     2          dest         
0x00000000 1114120    fred       600        1048576    2          dest         
0x00000000 1245193    fred       600        524288     2          dest

对于每一个共享内存,内核会为其定义一个shmid_ds结构体类型。shmid_ds结构体定义在头文件<sys/shm.h>中。

2、共享内存的相关操作

关于共享内存的操作,主要有shmget、shmat、shmdt、shmctl四个函数,它们都定义在<sys/shm.h>头文件中。下面我们分别介绍一下这几个函数。

2.1、创建或打开共享内存

我们知道,在Linux中创建或打开一个文件都可以使用open函数。与此类似,shmget调用也可以用来创建或打开一个共享内存区域。原型如下:

  #include <sys/ipc.h>
  #include <sys/shm.h>

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

若成功则返回共享内存的ID标识,否则返回-1。各个参数的含义如下:

(1)、第一个参数key

参数key表示所要创建或打开的共享内存的键值。无论何时创建一个IPC对象(如调用shmget),都需要指定一个key,它的数据类型是key_t。这个key由内核转变为标识符,它可以由以下方法产生:

  1. 在创建共享队列时,将该参数指定为IPC_PRIVATE,它保证创建一个新的共享内存区域。注意:IPC_PRIVATE只能用来创建一个新的共享内存区域,而不能用来引用一个已经存在的共享内存区域。
  2. 可以在一个公共头文件中定义一个客户进程和服务进程都认可的key,然后服务进程用该key创建一个新的共享内存区域。但是这种方法的问题是该key可能已经与另外一个共享内存结合,这时shmget出错返回。
  3. 使用ftok函数根据路径名和项目id创建一个key,ftok的函数原型为:
    #include <sys/types.h>
    #include <sys/ipc.h>

    key_t ftok(const char *pathname, int proj_id);

(2)、第二个参数size

参数size以字节为单位制定共享内存区域的长度。

(3)、第三个参数shmflg

参数shmflg用来设置shmget函数的操作类型,也可以用来设置共享内存的访问权限。两者可以通过逻辑或(“|”)来连接。

其中第一个参数key和第三个参数shmflg共同决定了shmget的操作类型,具体如下:

  • 如果key被设置为IPC_PRIVATE时,第三个参数shmflg无论取什么值,shmget都只用来创建一个新的共享内存区域。
  • 如果key不是被设置为IPC_PRIVATE时,由key和shmflg共同决定shmget是创建和是打开共享内存区域。如函数调用shmget(key, size, IPC_CREAT)中,如果key为内核中某个已经存在的共享区域的键值,则执行打开操作。否则创建一个新的共享内存区域。
2.2、附加到进程地址空间

当创建或打开一个共享内存区域后,一个进程如果想要访问该共享内存需要将其附加到该进程的地址空间。该操作使用shmat函数完成。

     #include <sys/types.h>
     #include <sys/shm.h>

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

该函数如果调用成功则返回指向共享内存区域的指针,否则返回-1。各参数的含义如下:

(1)、第一个参数shmid

参数shmid表示需要引入的共享内存标识符,也就是shmget函数的返回值。

(2)、第二个参数addr和第三个参数shmflg

为什么要把这两个参数放在一起讲呢?因为共享内存区域附加到调用进程的哪个地址上与addr参数和shmflg参数共同决定。具体规则如下:

  • 如果addr为0,表示由内核来决定第一个可用的附加地址。这也是推荐的使用方式,除非你只计划在一种硬件上运行你的程序,否则最好让系统选择附加地址。
  • 如果addr为非0,并且shmflg没有指定SHM_RND值,则附加到addr所指定的地址上。
  • 如果addr为非0,并且shmflg指定了SHM_RND值,则附加到(addr - (addr mod SHMLBA)所指定的地址上。其中,SHM_RND表示“取整”的意思,SHMLBA表示“低边界地址倍数”的意思。

如果shmflg中指定了SHM_RDONLY位,则以调用进程只能以“只读方式”访问共享内存,否则以“读写方式”访问。

2.3、分离共享内存

当进程对共享内存区域操作完成后,应该调用shmdt函数将该共享内存区域从当前进程地址空间中分离开来。

       #include <sys/shm.h>

       int shmdt(const void *shmaddr);

其中参数shmaddr是shmat函数返回的地址指针。如果调用成功该函数返回0,否则返回-1。

shmdt函数只是将指定的共享内存区域与调用进程的地址空间分离,而不是删除共享内存区域。

2.4、控制共享内存

共享内存是一种特殊的资源,系统提供了一个专门的操作函数用于共享内存的控制。原型如下:

       #include <sys/ipc.h>
       #include <sys/shm.h>

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

如果操作成功返回0,否则返回-1。各参数的含义如下:

(1)、第一个参数shmid

参数shmid表示需要操作的共享内存标识符,也就是shmget函数的返回值。

(2)、第二个参数cmd

参数cmd指明了所要进行的操作类型,主要有以下五种:

cmd取值操作类型
IPC_STAT取该共享内存的shmid_ds结构,并存在第三个参数中
IPC_SET使用buf指定的结构设置相关属性
IPC_RMID删除指定共享内存,只有当buf中的shm_nattch值为0时才真正删除
IPC_LOCK在内存中对共享内存加锁(超级用户权限)
IPC_UNLOCK解锁共享内存(超级用户权限)

(3)第三个参数buf

参数buf主要配合参数cmd使用,shmid_ds至少包含以下字段(摘自《Unix环境高级编程》):

这里写图片描述

2.5、实例演示

下面我们通过一个实例来演示一下共享内存的使用:

写进程shm_write.c如下:

#include <stdlib.h>
#include <stdio.h>
#include <sys/shm.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <errno.h>

#define BUF_SIZE 4096

int main()
{
    void *shm_addr = NULL;
    char buffer[BUF_SIZE];

    int shmid;
    // 使用约定的键值创建共享内存
    shmid = shmget((key_t) 1234,  BUF_SIZE, 0666 | IPC_CREAT);
    printf("shmid : %u\n", shmid);
    if (shmid < 0)
    {
        perror("shmget error!");
        exit(1);
    }

    // 将共享内存附加到本进程
    shm_addr = shmat(shmid, NULL, 0);
    if (shm_addr == (void *) -1)
    {
        perror("shmat error!");
        exit(1);
    }

    // 写入数据
    bzero(buffer, BUF_SIZE);
    sprintf(buffer, "Hello, My PID is %u\n", (unsigned int) getpid());
    printf("send data: %s\n", buffer);

    memcpy(shm_addr, buffer, strlen(buffer));

    sleep(5);

    // 分离
    if (shmdt(shm_addr) == -1)
    {
        printf("shmdt error!\n");
        exit(1);
    }
}

读进程shm_read.c如下:

#include <stdio.h>
#include <stdlib.h>
#include <sys/shm.h>
#include <sys/ipc.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>

#define BUF_SIZE 4096

int main()
{
    void *shm_addr = NULL;

    int shmid;
    // 使用约定的键值打开共享内存
    shmid = shmget((key_t) 1234, BUF_SIZE, IPC_CREAT);
    printf("shmid : %u\n", shmid);
    if (shmid == -1)
    {
        perror("shmget error!");
        exit(1);
    }

    // 将共享内存附加到本进程
    shm_addr = shmat(shmid, NULL, 0);
    if (shm_addr == (void *) -1)
    {
        perror("shmat error!");
        exit(1);
    }

    // 读取数据
    char tmp[BUF_SIZE];
    bzero(tmp, BUF_SIZE);
    memcpy(tmp, shm_addr, BUF_SIZE);
    printf("read from shared memory: %s\n", tmp);

    sleep(5);

    // 分离
    if (shmdt(shm_addr) == -1)
    {
        printf("shmdt error!\n");
        exit(1);
    }

    // 删除共享内存
    if (shmctl(shmid, IPC_RMID, 0) == -1)
    {
        printf("shmctl error!\n");
        exit(1);
    }
}

我们先运行写进程,输出如下:

shmid : 1736715
send data: Hello, My PID is 3122

此时,我们使用ipcs -m命令查看系统中的确存在标识符为1736715的共享内存区域。如下

------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status 
...(省略无关项)
0x000004d2 1736715    root       666        4096       0            

此时,写进程已经跟共享内存分离,所以nattch为0。

接下来,我们运行读进程。输出如下:

shmid : 1736715
read from shared memory: Hello, My PID is 3122

读进程执行完毕后删除了共享内存区域,所以使用ipcs -m命令中发现系统中并没有shmid值等于1736715的共享内存区域。

3、共享内存的优缺点

很明显,由于进程可以直接读写内存,所以采用共享内存进行进程间通信的一个优点就是快速、效率高。像我们前面介绍的匿名管道和命名管道通信方式,需要在用户空间和内核空间之间进行数据拷贝,而共享内存通信方式只在内存中操作,要高效得多。

另外一方面,由于多个进程对同一段内存区域都具有读写权限,在共享内存通信中,进程间的同步问题就显得尤为重要。必须保证同一时刻只有一个进程对共享内存进行写操作,否则会导致数据被覆盖。而共享内存通信又没有提供同步机制,需要使用诸如信号量等手段进行同步控制,这又增加了其复杂性。


参考:《Unix环境高级编程》

  • 1
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值