共享内存使用示例

  • 环境:ubuntu 18.04
  • 命令:ipcs(查看ipc信息)、ipcrm(删除指定ipc)、ipcmk(创建指定类型的ipc),可以通过​​--help​​查看帮助信息

公共代码

// sys.h
#pragma once

#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>

void exit_on_error(bool failed, const char *err_msg,int exit_code = 1)
{
if (!failed)
{
return;
}
perror(err_msg);
exit(exit_code);
}

void list_cmd(const char *path)
{
char cmd[256];
snprintf(cmd,255,"ls -al %s",path);
system(cmd);
}

void rm_file(const char *path)
{
char cmd[256];
snprintf(cmd,255,"rm -rf %s",path);
system(cmd);
}

共享内存示例代码及说明

shmget/shmctl

通过​​shmget​​​创建共享内存,并使用​​ls​​​命令查看​​/dev/shm​​路径下是否会创建对应的文件

结论:没有在该路径下创建shm文件,只有通过shm_open创建的共享内存才会在该路径下创建文件

另外,shm不属于进程,因此当进程结束时并不会销毁,需要使用shmctl进行rm或者使用ipcrm命令显示的rm

#include <sys/types.h>
#include <sys/shm.h>
#include <stdlib.h>
#include <stdio.h>
#include "sys.h"

size_t shm_size = 1024;

int main()
{
int id = shmget(0x123456,shm_size,IPC_CREAT | 0600);
printf("shm id:%d\r\n",id);
exit_on_error(id == -1,"shmget failed");
list_cmd("/dev/shm");

int ret = shmctl(id,IPC_RMID,NULL);
exit_on_error(ret == -1, "shmctl rmid failed");
return 0;
}

通过共享内存在父子进程中通信

创建共享内存,然后在子进程获取对应的内存地址,并进行修改,然后在父进程中输出对应信息

注意:如果使用fork,那么不要在fork后的if-else语句外面再执行其他代码,避免在子进程中忘记return造成本来不想在子进程中执行的代码被执行

#include <sys/types.h>
#include <sys/shm.h>
#include <stdlib.h>
#include <stdio.h>
#include "sys.h"
#include <unistd.h>

size_t shm_size = 1024;

int main()
{
// 创建/获取共享内存id
int id = shmget(0x123456,shm_size,IPC_CREAT | 0600);
printf("shm id:%d\r\n",id);
exit_on_error(id == -1,"shmget failed");


pid_t ret = fork();
if (ret == 0) // 子进程
{
void *shm_addr = (char *)shmat(id,NULL,0); // attach 创建的共享内存id
exit_on_error(shm_addr == (void *)-1,"child shmat failed");
char *buf = (char *)shm_addr;
snprintf(buf,1023,"hello from child,pid = %d\r\n",getpid());// 向共享内存中写入数据
ret = shmdt(shm_addr);// detach
exit_on_error(ret == -1,"child shm detach failed");
}
else if (ret > 0) // 父进程
{
sleep(1); // 等待子进程写入数据
void *shm_addr = (char *)shmat(id,NULL,0);
exit_on_error(shm_addr == (void *)-1,"parent shmat failed");
char *buf = (char *)shm_addr;
printf("%s",buf); // 读取子进程向共享内存中写入的数据

ret = shmdt(shm_addr);// detach
exit_on_error(ret == -1,"parent shm detach failed");

ret = shmctl(id,IPC_RMID,NULL);// 父进程后结束,因此在父进程中删除共享内存
exit_on_error(ret == -1, "shmctl rmid failed");
}
else // 出错
{
exit_on_error(true,"fork error");
}

return 0;
}

通过shm_open和mmap共享内存进行通信

通过​​shm_open​​​创建共享内存文件,并使用​​ls​​​命令查看​​/dev/shm​​路径下是否会创建对应的文件

步骤:

  1. 使用 ​​shm_open​​​ 打开/创建一个共享内存文件,只需要指定名字,成功返回大于​​0​​​的​​fd​
  2. 使用 ​​ftruncate​​​ 将 ​​shm_open​​​ 返回的 ​​fd​​ 设置为指定大小
  3. 使用 ​​mmap​​​ 将 ​​shm_open​​​ 返回的 ​​fd​​ 映射到内存,并返回对应的内存地址
  4. 对内存地址进行操作
  5. 使用完毕后,需要进行 ​​munmap​​​ 以及 ​​shm_unlink​​ 操作

结论:

  1. 有在该路径下创建同名的文件,其他的和​​shmget​​一样
  2. 即使删除了​​/dev/shm/​​​下面的共享内存文件,之前通过​​mmap​​进行映射的内存地址仍然可以用于通信
  3. 如果删除了​​/dev/shm/​​​下面的共享内存文件,进行 ​​shm_unlink​​​ 的时候会报错,提示 ​​No such file or directory​
  4. ​shm_open​​​ 创建的共享内存文件,只需要在某一个进行进行 ​​shm_unlink​​​ 即可,重复进行该操作会报错,提示 ​​No such file or directory​
  5. 可以对同一 ​​mmap​​​ 返回的地址进行重复的 ​​munmap​​ ,不会报错
  6. 进行 ​​munmap​​​ 后, 将不能再对 ​​mmap​​​ 返回的地址进行读写操作,否则会烦死段错误​​(Segmentation fault (core dumped))​
#include <sys/types.h>
#include <sys/shm.h>
#include <stdlib.h>
#include <stdio.h>
#include "sys.h"

#include <sys/mman.h> // for shm_open
#include <sys/stat.h> /* For mode constants */
#include <fcntl.h> /* For O_* constants */

size_t shm_size = 1024;

// 将shm_name代表的共享内存文件映射到内存中,并返回内存地址,失败直接退出进程
void *open_shm(const char *shm_name,size_t shm_size)
{
// 打开或者创建shm_name指定的共享内存文件,如果不存在则创建,存在则打开,且以以读写的形式,最后返回文件描述符,并通过八进制数据指定用户、组、其他的访问权限
int fd = shm_open(shm_name,O_CREAT | O_RDWR,0666);
exit_on_error(fd < 0,"shm_open failed");

// 将fd代表的共享内存文件截断为指定大小,此步骤不可忽略,通过 ls /dev/shm/ -al 可以看到对应的文件大小和 shm_size 的大小字节数完全一致
int ret = ftruncate(fd,shm_size);
exit_on_error(ret != 0,"ftruncate failed");

// 以读写的形式,将fd代表的文件映射到内存中,并和其他进程进行共享,映射的地址由内核决定,大小由 shm_size 指定的字节数按内存页向上取整
void *map_addr = mmap(NULL,shm_size,PROT_READ | PROT_WRITE,MAP_SHARED,fd,0);
exit_on_error(map_addr == MAP_FAILED,"mmap failed");

close(fd); // 已经不需要该文件描述符了,可以关闭
return map_addr;
}

int main()
{
const char *shm_name = "shm_open_key";
size_t shm_size = 1025;
int pid = fork();// 创建子进程
if (pid == 0) // child
{
char * buf = (char *)open_shm(shm_name,shm_size);// 获取映射地址
snprintf(buf,shm_size - 1,"hello from child,pid:%d\r\n",getpid());// 向共享内存地址写数据
sleep(2);// 等待父进程更改数据
printf("%s",buf); // 读取父进程写的数据

int ret = shm_unlink(shm_name);// 由于子进程休眠时间长,因此在子进程删除共享文件
exit_on_error(ret != 0,"shm unlink failed");
munmap(buf,shm_size);
return 0;
}
else if (pid > 0) // parent
{
sleep(1);
char * buf = (char *)open_shm(shm_name,shm_size);// 获取映射地址
printf("%s",buf);
//rm_file("/dev/shm/shm_open_key");
snprintf(buf,shm_size - 1,"hello from parent,pid:%d\r\n",getpid());

// int ret = shm_unlink(shm_name);
// exit_on_error(ret != 0,"shm unlink failed");
int ret = munmap(buf,shm_size);
exit_on_error(ret == -1,"munmap failed");

// 已经munmap的内存不能再进行访问,否则会造成Segmentation fault (core dumped)
//snprintf(buf,shm_size - 1,"hello2 from parent,pid:%d\r\n",getpid());

// 重复进行 munmap 不会报错,但不建议这样做!!
ret = munmap(buf,shm_size);
exit_on_error(ret == -1,"munmap failed");
return 0;
}
else
{
exit_on_error(true,"fork failed");
}
return 0;
}