c语言学习之共享内存

前言

简介

共享内存是可被多个进程读取的内存。用特殊的系统调用分配和释放内存并设置权限;通过一般的读写操作读写内存段中的数据。使用共享内存是进程间进行本地通信的方法之一。最重要的是,它能够较少由于I/O操作带来的开销。

适用范围

它适用于任何类型的协作,尤其适合需要安全性的情况。

来源

共享内存并非从某一个进程中划分出来的,进程的内存总是私有的,而共享内存是从系统的空闲内存池中分配的,希望访问它的每个进程连接它。这个连接过程称为映射,它给共享内存段分配每个进程的地址空间中的本地地址。

API

有两套共享内存 API:POSIX API 和比较老(但是仍然有效)的 System V API。下面做了一个简单的对比。

POSIX API system V API
获取共享内存ID int shm_open(const char *name, int oflag, mode_t mode); int shmget(key_t key, size_t size, int shmflg);
映射内存 void mmap(void addr, size_t length, int prot, int flags, int fd, off_t offset); void shmat(int shmid, const void shmaddr, int shmflg);
解除映射 int munmap(void *addr, size_t length); void shmat(int shmid, const void shmaddr, int shmflg);

因为 POSIX 是 UNIX 和 Linux® 及其衍生系统上的公认标准,所以下面介绍POSIX API。

POSIX 为创建、映射、同步和取消共享内存段提供五个入口点:

  • int shm_open(const char *name, int oflag, mode_t mode);
    功能描述:创建或者打开已经存在的共享内存块
    返回值:成功返回文件描述符,失败返回-1
    name:共享内存区的名称
    oflag:标志位,可选择读,写等
    mode:权限位
  • int shm_unlink(const char *name);
    功能描述:
    根据(shm_open() 返回的)文件描述符,删除共享内存段。实际上,这个内存段直到访问它的所有进程都退出时才会删除,这与在 UNIX 中删除文件很相似。但是,调用 shm_unlink() (通常由原来创建共享内存段的进程调用)之后,其他进程就无法访问这个内存段了。
    返回值:失败返回-1
    name:共享内存区名称

  • void mmap(void addr, size_t length, int prot, int flags,int fd, off_t offset);
    功能描述:把共享内存段映射到进程的内存。这个系统调用需要 shm_open() 返回的文件描述符,它返回指向内存的指针。在某些情况下,还可以把一般文件或另一个设备的文件描述符映射到内存。
    返回值:成功返回映射区地址,失败返回-1
    addr:映射区的开始地址,设置为0时表示由系统决定映射区的起始地址
    length:映射区的长度,以字节为单位。
    prot:内存保护标志
    flags:标志位
    fd:文件描述符
    offset:被映射对象内容的起点。

  • void mmap(void addr, size_t length, int prot, int flags, int fd, off_t offset);
    功能描述:作用与 mmap() 相反。
    返回值:成功返回0,失败返回-1

  • int msync(void *addr, size_t length, int flags);
    功能描述:用来让共享内存段与文件系统同步 — 当把文件映射到内存时,这种技术有用。
    返回值:成功返回0,失败返回-1
    addr:文件映射到进程空间的地址;
    length:映射空间的大小;
    flags:刷新的参数设置,可以取值MS_ASYNC/ MS_SYNC/ MS_INVALIDATE
    fd:文件描述符
    offset:被映射对象内容的起点

    使用过程

    1.创建内存段(shm_open)
    2.映射内存段(mmap)
    3.使用
    4.删除(munmap(),shm_unlink)

程序示例

下面的程序简单说明了共享内存的使用方法。在开辟一片共享内存区域后,子进程写入数据,而父进程在子进程写入数据后,将数据读出,并打印。具体代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
/*================================================================
* Copyright (C) 2018 Ltd. All rights reserved.
*
* 文件名称:mmap.c
* 创 建 者:hyb
* 创建日期:2018年01月07日
* 描 述:
*
================================================================*/
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/file.h>
#include<sys/mman.h>
#include<sys/wait.h>
#include<fcntl.h>
void error_and_die(const char *msg)
{
perror(msg);
exit(-1);
}

int main(int argc,char *argv[])
{
int r=0;
const char *memname = "sample";
const size_t region_size=sysconf(_SC_PAGE_SIZE);
int fd=shm_open(memname,O_CREAT|O_TRUNC|O_RDWR,0666);
if(-1 == fd)
{
error_and_die("shm_open");
}
//修改fd指针指向文件或者内存区的大小
r=ftruncate(fd,region_size);
if(r!=0)
{
error_and_die("ftruncate");

}
//将ptr指针指向共享内存区
void *ptr = mmap(0,region_size,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
if(NULL == ptr)
{
error_and_die("mmap");
}
close(fd);
pid_t pid=fork();

if(pid==0)
{
//子进程写入数据
char *d=(char *)ptr;
snprintf(d,region_size,"hello world");
exit(0);
}
else
{
//等待子进程退出后,读取共享内存数据
int status;
waitpid(pid,&status,0);
printf("child wrote %s\n",(char *)ptr);
}
//解除内存映射
r=munmap(ptr,region_size);
if(r!=0)
{
error_and_die("munmap");
}
//删除共享内存
r=shm_unlink(memname);
if(0!=r)
{
error_and_die("shm_unlink");
}

return 0;
}

编译:

1
gcc -g mmap.c -lrt -o mmap

运行:

1
child wrote hello world

总结

本文简单介绍了共享内存以及简单使用。后面将会有更多的介绍。

守望 wechat
关注公众号[编程珠玑]获取更多原创技术文章
出入相友,守望相助!