共享内存使用示例
- 环境: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
路径下是否会创建对应的文件
步骤:
- 使用
shm_open
打开/创建一个共享内存文件,只需要指定名字,成功返回大于0
的fd
- 使用
ftruncate
将 shm_open
返回的 fd
设置为指定大小 - 使用
mmap
将 shm_open
返回的 fd
映射到内存,并返回对应的内存地址 - 对内存地址进行操作
- 使用完毕后,需要进行
munmap
以及 shm_unlink
操作
结论:
- 有在该路径下创建同名的文件,其他的和
shmget
一样 - 即使删除了
/dev/shm/
下面的共享内存文件,之前通过mmap
进行映射的内存地址仍然可以用于通信 - 如果删除了
/dev/shm/
下面的共享内存文件,进行 shm_unlink
的时候会报错,提示 No such file or directory
-
shm_open
创建的共享内存文件,只需要在某一个进行进行 shm_unlink
即可,重复进行该操作会报错,提示 No such file or directory
- 可以对同一
mmap
返回的地址进行重复的 munmap
,不会报错 - 进行
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;
}