【Linux进程间通信】 - 共享内存
2016-03-14 20:54
477 查看
原创作品,转载请标明:http://blog.csdn.net/Xiejingfa/article/details/50888870
今天我们来介绍Linux下最高效的一种进程间通信方式 – 共享内存。
在Linux下,使用ipcs -m可以查看系统中共享内存的使用情况。下面是我在自己电脑中使用该命令的结果:
对于每一个共享内存,内核会为其定义一个shmid_ds结构体类型。shmid_ds结构体定义在头文件
若成功则返回共享内存的ID标识,否则返回-1。各个参数的含义如下:
(1)、第一个参数key
参数key表示所要创建或打开的共享内存的键值。无论何时创建一个IPC对象(如调用shmget),都需要指定一个key,它的数据类型是key_t。这个key由内核转变为标识符,它可以由以下方法产生:
在创建共享队列时,将该参数指定为IPC_PRIVATE,它保证创建一个新的共享内存区域。注意:IPC_PRIVATE只能用来创建一个新的共享内存区域,而不能用来引用一个已经存在的共享内存区域。
可以在一个公共头文件中定义一个客户进程和服务进程都认可的key,然后服务进程用该key创建一个新的共享内存区域。但是这种方法的问题是该key可能已经与另外一个共享内存结合,这时shmget出错返回。
使用ftok函数根据路径名和项目id创建一个key,ftok的函数原型为:
(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为内核中某个已经存在的共享区域的键值,则执行打开操作。否则创建一个新的共享内存区域。
该函数如果调用成功则返回指向共享内存区域的指针,否则返回-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位,则以调用进程只能以“只读方式”访问共享内存,否则以“读写方式”访问。
其中参数shmaddr是shmat函数返回的地址指针。如果调用成功该函数返回0,否则返回-1。
shmdt函数只是将指定的共享内存区域与调用进程的地址空间分离,而不是删除共享内存区域。
如果操作成功返回0,否则返回-1。各参数的含义如下:
(1)、第一个参数shmid
参数shmid表示需要操作的共享内存标识符,也就是shmget函数的返回值。
(2)、第二个参数cmd
参数cmd指明了所要进行的操作类型,主要有以下五种:
(3)第三个参数buf
参数buf主要配合参数cmd使用,shmid_ds至少包含以下字段(摘自《Unix环境高级编程》):
写进程shm_write.c如下:
读进程shm_read.c如下:
我们先运行写进程,输出如下:
此时,我们使用
此时,写进程已经跟共享内存分离,所以nattch为0。
接下来,我们运行读进程。输出如下:
读进程执行完毕后删除了共享内存区域,所以使用
另外一方面,由于多个进程对同一段内存区域都具有读写权限,在共享内存通信中,进程间的同步问题就显得尤为重要。必须保证同一时刻只有一个进程对共享内存进行写操作,否则会导致数据被覆盖。而共享内存通信又没有提供同步机制,需要使用诸如信号量等手段进行同步控制,这又增加了其复杂性。
参考:《Unix环境高级编程》
今天我们来介绍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由内核转变为标识符,它可以由以下方法产生:
在创建共享队列时,将该参数指定为IPC_PRIVATE,它保证创建一个新的共享内存区域。注意:IPC_PRIVATE只能用来创建一个新的共享内存区域,而不能用来引用一个已经存在的共享内存区域。
可以在一个公共头文件中定义一个客户进程和服务进程都认可的key,然后服务进程用该key创建一个新的共享内存区域。但是这种方法的问题是该key可能已经与另外一个共享内存结合,这时shmget出错返回。
使用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 | 解锁共享内存(超级用户权限) |
参数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环境高级编程》
相关文章推荐
- linux内核分析四
- Linux 下解决pthread_create undefined reference 方法
- Linux 设备驱动开发 —— 设备树在platform设备驱动中的使用
- Linux下的常用终端调试指令(2)
- Linux 下的GDB常用终端命令(1)
- linux语法
- Linux系统下安装Git
- linux设备驱动程序注册过程详解
- LINUX下JNI封装 C++动态链接库
- 本地linux下安装discuz
- 《Linux内核设计与实现》读书笔记 第一、二章
- 【C/C++】Linux下使用system()函数一定要谨慎
- CentOS 6.6 安装 Node.js
- 4_用户态与核心态-用两种C代码实现Linux的某个系统调用
- 《Linux内核分析》 第四节 扒开系统调用的三层皮(上)
- Linux 更改中文
- linux开机自动联网设置
- Linux命令之mkdir
- Linux 系统管理总结笔记
- 动态链接