Linux应用进程间通信四(共享内存)
一、概述
1.1、共享内存的概念
共享内存是指一块可以被多个进程同时访问的内存区域。进程通过将该内存映射到自己的地址空间来读写数据。共享内存是一种非常高效的进程间通信方式,因为它不需要像管道或消息队列那样在内核空间和用户空间之间频繁进行数据复制。
1.2、共享内存的特点
- 高效性:共享内存的读写速度较快,因为数据直接在进程间共享内存中进行操作,不需要通过内核进行复制或数据传输。
- 内存区域共享:多个进程通过映射相同的共享内存区来交换数据,避免了进程间的拷贝开销。
- 无序性:共享内存本身并不提供进程间的同步机制,因此通常需要结合其他同步机制(如信号量、互斥锁)来防止数据竞争和访问冲突。
1.3、共享内存的工作原理
共享内存通过将一块物理内存区域映射到多个进程的虚拟地址空间中,多个进程就可以直接访问同一内存区域。进程通过系统调用来创建、操作和管理共享内存。
1.4、使用共享内存的基本步骤
- 创建共享内存:使用
shmget()
系统调用创建一个共享内存区域,或者获取一个已存在的共享内存。 - 映射共享内存到进程地址空间:使用
shmat()
系统调用将共享内存映射到进程的虚拟地址空间中。 - 访问共享内存:进程可以通过访问映射后的内存地址来读写共享内存区域。
- 同步机制:如果多个进程同时访问共享内存区域,需要使用额外的同步机制(如信号量)来避免并发问题。
- 分离共享内存:使用
shmdt()
系统调用将共享内存从进程的地址空间中分离。 - 删除共享内存:当不再需要共享内存时,使用
shmctl()
系统调用删除共享内存区。
1.5、共享内存的关键系统调用
-
shmget()
:创建或获取共享内存
-
int shmget(key_t key, size_t size, int shmflg);
key
:共享内存的键值,标识共享内存。size
:共享内存的大小。shmflg
:标志位,通常用来指定创建共享内存的权限,如IPC_CREAT
(创建)和IPC_EXCL
(排它创建)等。
-
shmat()
:将共享内存映射到进程的虚拟地址空间 -
void* shmat(int shmid, const void *shmaddr, int shmflg);
shmid
:共享内存标识符。shmaddr
:期望的映射地址,通常设置为NULL
,由操作系统决定映射地址。shmflg
:映射标志,通常设置为0
或其他控制映射行为的选项。
-
shmdt()
:分离共享内存 -
int shmdt(const void *shmaddr);
shmaddr
:共享内存的地址。
-
shmctl()
:控制共享内存区的状态(如删除)
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
shmid
:共享内存标识符。cmd
:操作类型,常用操作如IPC_RMID
(删除共享内存)。buf
:用于存储共享内存信息的结构体(对于删除操作通常为NULL
)。
二、实现过程
2.1、共享内存
共享内存:共享内存是一种最为高效的进程间通信方式。因为数据可以直接读写内存,不需要任何数据的拷贝。内核专门留出一块内存区,这段内存区可以由需要访问的进程将其中映射到自己的私有地址空间。但同时由于多个进程共享一段内存,因此也需要依靠同步机制,如互斥锁和信号量。
内核为每个共享内存段维护着一个结构,该结构至少要为每个共享内存段包含以下成员:
struct shmid_ds{
struct ipc_perm shm_perm;
size_t shm_segsz;
pid_t shm_lpid;
pid_t shm_cpid;
shmatt_t shm_nattch;
time_t shm_atime;
time_t shm_dtime;
time_t shm_ctime;
...
}
2.2、shmget
#include <sys/shm.h>
int shmget(key_t key, int size, int shmflg)
第一个参数key:有效地为共享内存段命名
第二个参数size:以字节为单位制定需要共享的内存容量。(实现通常将其向上取为系统页长的整数倍。但是,若应用指定的size值并非系统页长的整数倍,那么最后最后一页余下部分不可使用。)
第三个参数shmflg:权限标志
返回值: 成功:非负整数即共享内存标识符; 失败:-1
作用:创建新的共享内存或引用一个现有的共享内存。当创建一个新的共享内存时,初始化shmid_ds结构下列成员。
ipc_perm结构。其中mode按shmflg中的相应权限位设置
shm_lpid、shm_nattach、shm_atime和shm_dtime都设置为0.
shm_ctime设置为当前时间
shm_segsz设置为请求的size
如果创建一个新的共享内存段(通常在服务器进程中),则必须指定其size。如果正在引用一个现存的段(一个客户进程),则将size指定为0。当创建一个新共享内存时,段内的内容初始化为0.
2.3、shmat
#include <sys/shm.h>
char *shmat(int shmid, const void*shmaddr,int shmflg)
第一个参数shmid:共享内存标识符
第二个参数shmaddr:将共享内存映射到指定位置 如果shmaddr为0,则此段连接到由内核选择的第一个可用地址上。(这是推荐的使用方式) 如果shmaddr非0,并且没有指定SHM_RND,则此段连接到shmaddr所指定的地址上。 如果shmaddr非0,并且指定了SHM_RND,则此段连接到(shmaddr—(shmaddr mod SHMLBA))所表示的地方。 SHM_RND命令的意思是“取整”。SHMLBA的意思是“低边界地址倍数”(了解以下。现在基本使用这一种方式)
第三个地址:SHM_RND(这个标志与shm_addr联合使用,用来控制共享内存连接的地址)
SHM_RDONLY(使它连接的内存只读)
默认0:共享内存可读可写
返回值:返回一个指向共享内存第一个字节的指针; 失败:-1
作用:第一次创建共享内存时,它不能被任何进程访问。要想启用该共享内存的访问,必须将其连接到一个进程的地址空间中。
2.4、shmdt
#include <sys/shm.h>
int shmdt(const void *shmaddr)
第一个参数shmaddr:
shmaddr:被映射的共享内存段地址,在调用shmat时的返回值。
返回值: 成功:0; 失败:-1
作用:将共享内存从当前进程中分离(并不是删除)。 如果调用成功,shmdt将使相关shmid_ds结构中的shm_nattch计数器值减1。
2.5、shmctl
#include <sys/shm.h>
int shmctl(int shm_id, int command, struct shmid_ds *buf);
第一个参数shm_id:shmget返回的共享内存标识符
第二个参数command:要采取的动作,可以取三个值
IPC_STAT:把shmid_ds结构中的数据设置为共享内存的当前关联值
IPC_SET:如果进程有足够的权限,就把共享内存的当前关联值设置为shmid_ds结构中给出的值
IPC_RMID:删除共享内存段
第三个参数buf:是一个指针,它指向包含共享内存模式和访问权限的结构
返回值:成功:0;失败:-1
struct shmid_ds{
uid_t shm_perm.uid,
uid_t shm_perm.gid;
mode_t shm_perm.mode;
};
三、应用举例
1、示例代码
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>
#define SHM_SIZE 1024 // 定义共享内存大小
int main() {
// 创建共享内存
key_t key = 1234;
int shmid = shmget(key, SHM_SIZE, IPC_CREAT | 0666);
if (shmid == -1) {
perror("shmget failed");
return 1;
}
// 将共享内存映射到进程地址空间
char *shm = (char *)shmat(shmid, NULL, 0);
if (shm == (char *)-1) {
perror("shmat failed");
return 1;
}
// 写数据到共享内存
strcpy(shm, "Hello, Shared Memory!");
// 读取共享内存的数据
printf("Data from shared memory: %s\n", shm);
// 分离共享内存
if (shmdt(shm) == -1) {
perror("shmdt failed");
return 1;
}
// 删除共享内存
if (shmctl(shmid, IPC_RMID, NULL) == -1) {
perror("shmctl failed");
return 1;
}
return 0;
}
2、共享内存的同步问题
由于多个进程可以同时访问共享内存,因此必须小心避免并发访问导致的数据不一致或冲突。常见的同步方法包括:
- 信号量(Semaphores):信号量用于控制进程对共享资源的访问。通过
semop()
系统调用,进程可以获取和释放信号量,以保证对共享内存的访问是互斥的。 - 互斥锁:可以通过互斥锁(如
pthread_mutex_t
)来同步多个进程对共享内存的访问。