1. 简述
Linux系统中实现进程间通信(IPC,Inter-Process Communication)有多种手段,共享内存(Shared Memory)作为一种其中比较高效的IPC机制,允许两个或多个进程共享一个给定的存储区。
这种通信方式是(几乎是)最快的IPC方式,因为进程是直接对内存进行访问,不需要像管道和消息队列一样进行数据的拷贝。持有相同区域共享内存的进程可以直接读写这块内存,无需进行数据拷贝,从而提高了通信速度。
2. 为什么共享内存是最快的IPC手段
共享内存之所以被认为是最快的进程间通信(IPC)手段,主要是基于以下几个原因:
直接内存访问
共享内存允许进程直接访问同一块物理内存区域,无需像管道、消息队列等方式那样,在发送和接收数据时需要进行复制操作。这种直接的内存访问方式避免了不必要的数据拷贝,从而大大提高了数据交换的效率。
减少内核介入
在使用共享内存进行通信时,操作系统内核的介入相对较少。一旦共享内存被映射到进程的地址空间,进程就可以直接读写这块内存,而不需要通过系统调用来完成数据的传输。这减少了系统调用的开销,提升了通信的速度。
无序列化/反序列化开销
在使用消息传递的IPC机制时,通常需要将数据结构序列化成字节流进行传输,接收方再反序列化这些数据。这个过程会带来额外的CPU开销。而共享内存则无需这种序列化和反序列化的过程,因为数据直接在内存中共享。
灵活性
共享内存提供了很大的灵活性,允许多个进程同时读写同一块内存。这种并行访问能力可以显著提高数据处理的速度,尤其是在需要大量数据传输和处理的场景中。
局部性原理
由于数据被直接存放在内存中,并且可以被多个进程访问,这有助于利用计算机体系结构的局部性原理,提高缓存利用率,从而减少访问主存的次数,进而提升性能。
需要注意的是,共享内存虽快,但也有其他的挑战。比如当多个进程同事操作共享内存里的数据时,这些数据的同步和一致性是一个不得不考虑的问题。
3. 基本原理
共享内存允许两个或多个进程共享物理内存中的同一块区域。这块区域可以被各个进程直接访问,而无需进行数据的复制。因此,共享内存提供了一种非常高效的进程间数据交换方式。但是,由于多个进程可以同时访问和操作共享内存,因此需要一种同步机制来防止数据的不一致和冲突。
4. 常用API
(1)shmget:开辟空间
在使用共享内存之前,需要先在内存中开辟一段空间,shmget就是为了解决开辟空间的问题。其原型如下。
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
其中,key:用于标识共享内存的键值。一般是通过ftok函数来生成,也可以自己随意设置一个整数。如果key的取值为`IPC_PRIVATE`,则会创建一个新的共享内存段。
size:共享内存的大小,以字节为单位。
shmflg:标志位,用于设置共享内存的访问权限和其他选项。
(2)shmat: 映射共享内存
shmat用于将shmget创建的内存中的一个区域映射到当前进程中。其原型如下。
#include <sys/types.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
其中,shmid: 共享内存段的标识符。
shmaddr: 指定附加共享内存的地址(通常为NULL)。
shmflg: 控制共享内存如何附加的标志。
(3)shmdt:解除内存映射
shmdt用于接触内存映射。其原型如下。
#include <sys/types.h>
#include <sys/shm.h>
int shmdt(const void *shmaddr);
其中,shmaddr: 要分离的共享内存地址。
(4)shmctl: 管理共享内存
shmctl用于管理和控制空想内存,通过设定不同的参数可以执行不同的操作。其中我们比较常用的就是删除共享内存。
前面讲到,shmdt能够解除共享内存的映射,但此时这一段内存仍然处于存在的状态,为了高效利用,我们还需要从内存中删除这一段内存占用。
#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
其中,
shmid:共享内存的对象ID
cmd:IPC_RMID:删除共享内存段及关联的shmid_ds数据结构
buf:指向包含共享模式和访问权限的结构
返回值:成功返回0,失败返回-1
5. 使用历程
接下来,我们将串间一个生产者和消费者进程,来演示使用共享内存进行通信。其中使用到了信号量来保证数据的同步和一致性问题。
生产者程序
#include <sys/ipc.h>
#include <sys/shm.h>
#include <semaphore.h>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <unistd.h>
#include <fcntl.h>
typedef struct _SharedMemory{
sem_t sem; ///< 用于同步的信号量
char data[1024];
}SharedMemory;
int main(int argc, char* argv[])
{
/** 生成key. */
key_t key = ftok("/tmp", 'R');
/** 创建内存. */
int shmid = shmget(key, sizeof(SharedMemory), IPC_CREAT | 0666);
if (shmid == -1) {
perror("shmget");
exit(1);
}
/** 映射内存. */
SharedMemory* sharedMemory = (SharedMemory*)shmat(shmid, NULL, 0);
if (sharedMemory == (SharedMemory*) -1) {
perror("shmat");
exit(1);
}
/** 初始化信号量(注意,该信号量必须处于共享内存中). */
if (sem_init(&sharedMemory->sem, 1, 1) != 0) {
perror("sem_init");
exit(1);
}
/** 生产数据. */
sem_wait(&sharedMemory->sem);
strncpy(sharedMemory->data, "Taiwan is an inalienable part of our motherland.!", sizeof(sharedMemory->data));
sem_post(&sharedMemory->sem);
/** 分离共享内存,但不删除,因为消费者可能还在使用. */
shmdt(sharedMemory);
return 0;
}
消费者程序
#include <sys/ipc.h>
#include <sys/shm.h>
#include <semaphore.h>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <unistd.h>
typedef struct _SharedMemory{
sem_t sem; ///< 用于同步的信号量
char data[1024];
}SharedMemory;
int main(int argc, char* argv[])
{
/** 生成key. */
key_t key = ftok("/tmp", 'R');
/** 创建内存. */
int shmid = shmget(key, sizeof(SharedMemory), 0666);
if (shmid == -1) {
perror("shmget");
exit(1);
}
/** 映射内存. */
SharedMemory* sharedMemory = (SharedMemory*)shmat(shmid, NULL, 0);
if (sharedMemory == (SharedMemory*) -1) {
perror("shmat");
exit(1);
}
/** 消费数据. */
sem_wait(&sharedMemory->sem);
printf("Copy: %s\n", sharedMemory->data);
sem_post(&sharedMemory->sem);
/** 分离共享内存. */
shmdt(sharedMemory);
/** 清理信号量并删除共享内存(在实际应用中,这部分应由一个确定的进程来执行,以避免竞争条件. */
sem_destroy(&sharedMemory->sem);
shmctl(shmid, IPC_RMID, NULL);
return 0;
}