关于共享内存,首先我们来看看它在地址空间里的位置:
共享内存区是最快的 IPC 形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据。
- 共享内存的创建
#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);
这里的 oflag 标志要包含 O_RDONLY 或 O_RDWR 标志位,包含 O_CREAT 标志位时表示创建,O_EXCL 配合 O_CREAT 使用,表示排他创建。另外一个标志位是 O_TRUNC,表示将共享内存的 size 截断成 0。
mode 参数可配合 O_CREAT 标志位使用,用于设定共享内存的访问权限,如果仅仅是打开共享内存,可以设置为 0。
shm_open 函数调用成功时会返回一个文件描述符,并且内核会自动设置 FD_CLOEXEC 标志位,即如果进程执行了 exec 函数,那么这文件描述符将被自动关闭。
下面我们来创建一个共享内存
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
int shm_fd = shm_open("/myshm", O_RDWR|O_CREAT, 0644);
if(shm_fd == -1) {
perror("create shm failed");
exit(1);
}
return 0;
}
在 linux 下,我们创建的所有共享内存都在 /dev/shm/ 目录下,在这个文件夹下可以看到我们新创建的 myshm
但这个时候的共享内存还不能使用,可以看到这个共享内存的大小是 0,就像我们在代码里申请一段空间,是说明了要申请的空间的名字,但并没有说明要申请多大的内存,因此这里我们需要调用 ftruncate 函数来说明我们要申请多大的空间。
#include <unistd.h>
#include <sys/types.h>
int ftruncate(int fd, off_t length);
调整了 size 之后,就可以调用 mmap 函数将共享内存映射到进程的地址空间,在 mmap 将共享内存映射到进程的地址空间之后,就可以通过操作内存来通信的。对这块内存的所有修改,其他进程都可以看到
#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int fstat(int fd, struct stat *buf);
对于其他参与通信的进程,可能需要调用 fstat 接口来获取共享内存区的大小
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int fstat(int fd, struct stat *buf);
当通信结束后,可以通过调用 munmap 函数解除映射,如果彻底不需要这块共享内存了,需要通过 shm_unlink 函数来删除这块共享内存
#include <sys/mman.h>
int munmap(void *addr, size_t length);
#include <sys/mman.h>
#include <sys/stat.h> /* For mode constants */
#include <fcntl.h> /* For O_* constants */
int shm_unlink(const char *name);
删除一个共享内存对象,并不会影响既有的映射。内核维护有引用计数,当所有的进程都通过 munmap 解除映射之后,该共享内存对象才会真正被删除。
下面我们来写一个简单的例子来实现使用共享内存进行通信
// server.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
int main()
{
int shm_fd = shm_open("/myshm", O_CREAT|O_RDWR, 0644);
if(shm_fd == -1) {
perror("create shm failed");
exit(1);
}
if(ftruncate(shm_fd, 100) == -1) {
perror("ftruncate failed");
exit(2);
}
if(mmap(NULL, 100, PROT_READ, MAP_SHARED, shm_fd, 0) == MAP_FAILED) {
perror("mmap failed");
exit(3);
}
char buf[1024] = {0};
while(1) {
int len = read(shm_fd, buf, 1024);
if(len == -1) {
perror("read failed");
exit(4);
} else if(len > 0) {
printf("client say:> %s\n", buf);
}
}
munmap(NULL, 100);
shm_unlink("/myshm");
return 0;
}
// client.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
int main()
{
int shm_fd = shm_open("/myshm", O_RDWR, 0);
if(shm_fd == -1) {
perror("open failed");
exit(1);
}
struct stat buf;
fstat(shm_fd, &buf);
if(mmap(NULL, buf.st_size, PROT_WRITE, MAP_SHARED, shm_fd, 0) == MAP_FAILED) {
perror("mmap failed");
exit(2);
}
while(1) {
char buf[1024] = {0};
if(read(0, buf, 1024) == -1) {
perror("read from stdin failed");
exit(3);
}
if(write(shm_fd, buf, strlen(buf)) == -1) {
perror("write into myshm failed");
exit(4);
}
}
munmap(NULL, buf.st_size);
shm_unlink("/myshm");
return 0;
}
运行后,可以看到,client 的输入 server 可以接收到
并且可以在 /dev/shm/myshm 文件内看到我们刚写入的内容