在Linux中,有三种主要的方式可以实现共享内存:使用shmget
、mmap
和POSIX共享内存
。代码仅供示例,实际使用时需要添加错误处理和适当的同步机制来确保共享内存的安全访问。
以下是每种方式的示例代码:
1、使用shmget
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int main() {
int shm_id;
key_t key;
int *shm_ptr;
// 创建共享内存
key = ftok("shared_memory_example", 'R');
shm_id = shmget(key, sizeof(int), IPC_CREAT | IPC_EXCL | 0666);
if (shm_id < 0) {
perror("shmget");
return 1;
}
// 连接共享内存
shm_ptr = (int*) shmat(shm_id, NULL, 0);
if (*shm_ptr == -1) {
perror("shmat");
return 1;
}
// 使用共享内存
*shm_ptr = 42;
printf("共享内存中的值:%d\n", *shm_ptr);
// 解除连接
shmdt((void *)shm_ptr);
// 删除共享内存
shmctl(shm_id, IPC_RMID, NULL);
return 0;
}
通过shmget,根据key值去分配一个共享内存会返回一个shm_id用于表示该共享内存,如果调用了shmctl去删除该共享内存,即使后续再去根据想用的key值去获取共享内存,还是由于是新分配了一个共享内存,因此会返回与上次不一样的shm_id。
上面的代码仅展示了简单的获取共享内存的方法,实际使用过程中,按照man shmget中的介绍
If shmflg specifies both IPC_CREAT and IPC_EXCL and a shared memory segment already exists for key, then shmget() fails with errno set to EEXIST.
(This is analogous to the effect of the combination O_CREAT | O_EXCL for open(2).)
我们会这样写:
if ((shm_id = shmget(key, sizeof(SHM), 0666|IPC_CREAT | IPC_EXCL)) < 0)
{
if (EEXIST == errno)//共享内存已经存在
{
// 重新调用shmget,但是不加上IPC_CREAT flag,否则是创建新的共享内存了
shm_id = shmget(key, sizeof(SHM), 0666);
// 连接共享内存
shm_ptr = (int*) shmat(shm_id, NULL, 0);
}
else//出错
{
perror("fail to shmget");
exit(-1);
}
}
else//成功
{
// 连接共享内存
shm_ptr = (int*) shmat(shm_id, NULL, 0);
}
shmctl不是删除共享内存的函数,它指示发指令的函数,删除共享内存的操作是通过传入IPC_RMID这样的cmd实现的。
SYSCALL_DEFINE3(shmctl, int, shmid, int, cmd, struct shmid_ds __user *, buf)
{
return ksys_shmctl(shmid, cmd, buf, IPC_64);
}
2、使用mmap
#include <stdio.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
int main() {
int fd;
int *ptr;
// 创建映射文件
fd = open("shared_memory_example", O_RDWR | O_CREAT, 0666);
if (fd == -1) {
perror("open");
return 1;
}
// 设置文件大小
lseek(fd, sizeof(int)-1, SEEK_SET);
write(fd, "", 1);
// 映射内存
ptr = (int *)mmap(0, sizeof(int), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (ptr == MAP_FAILED) {
perror("mmap");
return 1;
}
// 使用共享内存
*ptr = 42;
printf("共享内存中的值:%d\n", *ptr);
// 解除映射
if (munmap(ptr, sizeof(int)) == -1) {
perror("munmap");
return 1;
}
// 关闭文件,也可以提前,只要映射成功。
close(fd);
// 删除映射文件
if (unlink("shared_memory_example") == -1) {
perror("unlink");
return 1;
}
return 0;
}
关于mmap系统调用,如下所示:
#define offset_in_page(p) (((unsigned long)p) % PAGE_SIZE)
SYSCALL_DEFINE6(mmap, unsigned long, addr, unsigned long, len,
unsigned long, prot, unsigned long, flags,
unsigned long, fd, unsigned long, off)
{
if (offset_in_page(off) != 0)
return -EINVAL;
return ksys_mmap_pgoff(addr, len, prot, flags, fd, off >> PAGE_SHIFT);
}
3、使用POSIX共享内存
#include <stdio.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
int main() {
int shm_fd;
int *ptr;
// 创建共享内存对象
shm_fd = shm_open("/shared_memory_example", O_CREAT | O_RDWR, 0666);
if (shm_fd == -1) {
perror("shm_open");
return 1;
}
// 设置共享内存大小
if (ftruncate(shm_fd, sizeof(int)) == -1) {
perror("ftruncate");
return 1;
}
// 映射共享内存
ptr = (int *)mmap(NULL, sizeof(int), PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0);
if (ptr == MAP_FAILED) {
perror("mmap");
return 1;
}
// 使用共享内存
*ptr = 42;
printf("共享内存中的值:%d\n", *ptr);
// 解除映射
if (munmap(ptr, sizeof(int)) == -1) {
perror("munmap");
return 1;
}
// 关闭共享内存对象
if (shm_unlink("/shared_memory_example") == -1) {
perror("shm_unlink");
return 1;
}
return 0;
}
注意,shm_open使用gcc编译时,需要加上-lrt。否则会报error:
posixshm.c:(.text+0x1a):对‘shm_open’未定义的引用
posixshm.c:(.text+0xfe):对‘shm_unlink’未定义的引用
collect2: error: ld returned 1 exit status
相关说明(man shm_open)如下:
NAME
shm_open, shm_unlink - create/open or unlink POSIX shared memory objects
SYNOPSIS
#include <sys/mman.h>
#include <sys/stat.h> /* For mode constants */
#include <fcntl.h> /* For O_* constants */
int shm_open(const char *name, int oflag, mode_t mode);
int shm_unlink(const char *name);
Link with -lrt.
4、请注意线程同步问题
线程同步的方式很多,无外乎信号量、互斥锁、条件变量等手段,一些示例可参考Linux进程间共享内存通信时如何同步?(附源码)