Linux IPC POSIX 共享内存
模型
#include <unistd.h> //for fstat()
#include <sys/types.h> //for fstat()
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
shm_open() //创建/获取共享内存fd
ftruncate() //创建者调整文件大小
mmap() //映射fd到内存
munmap() //去映射fd
shm_unlink() //删除共享内存
shm_open
//创建/获取共享内存的文件描述符,成功返回文件描述符,失败返回-1
//Link with -lrt.
int shm_open(const char *name, int oflag, mode_t mode);
oflag
- Access Mode:
- O_RDONLY以只读的方式打开共享内存对象
- O_RDWR以读写的方式打开共享内存对象
- Opening-time flags(Bitwise Or):
- O_CREAT 表示创建共享内存对象,刚被创建的对象会被初始化为0byte可以使用ftuncate()调整大小
- O_EXCL用来确保共享内存对象被成功创建,如果对象已经存在,那么返回错误
- O_TRUNC表示如果共享内存对象已经存在那么把它清空
mode: eg,0664 etc
ftruncate()
//调整fd指向文件的大小,成功返回0,失败返回-1设errno
//VS truncate()
int ftruncate(int fd, off_t length);
如果原文件大小>指定大小,原文件中多余的部分会被截除
int res=ftruncate(fd,3*sizeof(Emp));//要用sizeof,且是Emp(类型)不是emp(对象)
if(-1==res)
perror("ftruncate"),exit(-1);
fstat()
//获取文件状态,成功返回0,失败返回-1设errno
//VS stat()
int lstat(const char *pathname, struct stat *buf);
int fstat(int fd, struct stat *buf);
buf:stat类型的指针
struct stat {
dev_t st_dev; /* ID of device containing file */
ino_t st_ino; /* inode number */
mode_t st_mode; /* protection */ 八进制 usigned int o%
nlink_t st_nlink; /* number of hard links */
uid_t st_uid; /* user ID of owner */
gid_t st_gid; /* group ID of owner */
dev_t st_rdev; /* device ID (if special file) */
off_t st_size; /* total size, in bytes */ ld%
blksize_t st_blksize; /* blocksize for filesystem I/O */
blkcnt_t st_blocks; /* number of 512B blocks allocated */
struct timespec st_atim; /* time of last access */
struct timespec st_mtim; /* time of last modification */ ld%,秒
struct timespec st_ctim; /* time of last status change */
};
//eg:
st_mode=100664 //100是文件类型
//664是权限, 通过100664和0777BitwiseAND得到
st_mtime=1462787968 //秒
mmap()
//映射文件或设备到进程的虚拟内存空间,映射成功后对相应的文件或设备操作就相当于对内存的操作
//映射以页为基本单位,文件大小, mmap的参数 len 都不能决定进程能访问的大小, 而是容纳文件被映射部分的最小页面数决定传统文件访问
//要求对文件进行可读可写的的打开!!!
//成功返回映射区的指针,失败返回-1设errno
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset); //prot:protection, 权限
addr:映射的起始地址, 如果为NULL则由kernel自行选择->最合适的方法
length:映射的区域长度
prot:映射内存的保护权限
- PROT_EXEC表示映射的内存页可执行
- PROT_READ表示映射的内存可被读
- PROT_WRITE表示映射的内存可被写
- PROT_NONE表示映射的内存不可访问
flags
must include one of :
- MAP_SHARED表示共享这块映射的内存,读写这块内存相当于直接读写文件,这些操作对其他进程可见,由于OS对文件的读写都有缓存机制,所以实际上不会立即将更改写入文件,除非带哦用msync()或mumap()
- MAP_PRIVATE表示创建一个私有的copy-on-write的映射, 更新映射区对其他映射到这个文件的进程是不可见的
can be Bitwise ORed:
- MAP_32BIT把映射区的头2GB个字节映射到进程的地址空间,仅限域x86-64平台的64位程序,在早期64位处理器平台上,可以用来提高上下文切换的性能。当设置了MAP_FIXED时此选项自动被忽略
- MAP_ANONYMOUS映射不会备份到任何文件,fd和offset参数都被忽略,通常和MAP_SHARED连用
- MAP_DENYWRITEignored.
- MAP_EXECUTABLEignored
- MAP_FILE用来保持兼容性,ignored
- MAP_FIXED不要对addr参数进行处理确确实实的放在addr指向的地址,此时addr一定时页大小的整数倍,
- MAP_GROWSDOWN用在栈中,告诉VMM映射区应该向低地址扩展
- MAP_HUGETLB (since Linux 2.6.32)用于分配"大页"
fd: file decriptor
offset: 文件中的偏移量
void* pv=mmap(NULL,4,PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_ANONYMOUS,0,0);
if(MAP_FAILED==pv)
perror("mmap"),exit(-1);
映射机制小解
- mmap()就是建立一个指针,这个指针指向页高速缓存的一页,并假设这个页有我们想要访问的文件内容(此时都在虚拟地址空间),当然,这个页描述符会自动的加入的调用进程的页表中。当我们第一次使用这个指针,去访问这个虚拟地址的页时,发现这个页还没有分配物理页框,没有想要的文件,引起缺页中断,系统会把相应的(fd)文件内容放到高速缓存的物理页框(此时才会有对物理地址空间的读写)
- 映射过程中使用的文件相当于药引子,因为所有进程都是可以通过VFS访问磁盘文件的,所以这个文件相当于对映射内存的一个标识,有了这个位于磁盘的药引子,很多进程都可以根据它找到同一个物理页框,进而实现内存的共享,并不是说就在磁盘上读写
- 页高速缓存的内容不会立即写到磁盘中,会等几秒,这种机制可以提高效率并保护磁盘
- 只要内存够大,页高速缓存的内容就会一直存在内存中,以后再有进程对该缓存页的内容访问的需求,就不需要从磁盘中搜索,直接访问缓存页(把这个页加入到进程的页表)就行
- mmap()是系统调用,使用一次的开销比较大,但比文件读写的read()/write()进行内核空间到用户空间的数据复制要快,通常只有需要分配的内存>128KB(malloc一次分配33page就是128KB)的时候才会使用mmap()
- /shm是一个特殊的文件系统,它不对应磁盘中的区域,而是内存中,所以使用mmap()在这个文件系统中创
- /proc 不占用任何磁盘空间
- linux采用的是页式管理机制。对于用mmap()映射普通文件来说,进程会在自己的地址空间新增一块空间,空间大小由mmap()的len参数指定,注意,进程并不一定能够对全部新增空间都能进行有效访问。进程能够访问的有效地址大小取决于文件被映射部分的大小。
- 简单的说,能够容纳文件被映射部分大小的最少页面个数决定了进程从mmap()返回的地址开始,能够有效访问的地址空间大小。超过这个空间大小,内核会根据超过的严重程度返回发送不同的信号给进程。
- 经过内核!=在内核空间和用户空间来回切换!=在内核空间和用户空间传递复制的数据
- 页是内存映射的基本单位, 可以理解为实际分配给物理内存的基本单位, 但不是数据操作的基本单位;
- 页机制是操作系统和CPU约定好的一种方式,OS按照页给CPU按页发虚拟地址,CPU按页解析并处理
- 操作系统(包括Linux)大量使用的缓存的两个原理:
* CPU访问内存的速度远远大于访问磁盘的速度(访问速度差距不是一般的大,差好几个数量级)
* 数据一旦被访问,就有可能在短期内再次被访问(临时局部原理) - 页高速缓存(page cache)是个内存区域,是Linux 内核使用的主要磁盘高速缓存,在绝大多数情况下,内核在读写磁盘时都引用页高速缓存,新页被追加到页高速缓存以满足用户态进程的读请求,如果页不在高速缓存中,新页就被加到高速缓存中,然后就从磁盘读出的数据填充它,如果内存有足够的空闲空间,就让该页在高速缓存中长期保留,使其他进程再使用该页时不再访问磁盘, 即磁盘上的文件缓存到内存后,它的虚拟内存地址可以有多个,但是物理内存地址却只能有一个
- 我们要读写磁盘文件时,实质是对页高速缓存进行读写,所以无论读写,都会首先检查页高速缓存有没有这个文件对应的页,如果有,就直接访问,如果没有,就引起缺页中断,给OS发信号,让它把文件放到高速缓存再进行读写,这个过程不经过内核空间到用户空间复制数据
- OS中的页机制,对应到硬件中可不一定在主存中,也可以是高速缓存etc,但不会是磁盘,因为磁盘文件的地址和内存不一样,不是按照32位编址的,而是按照ext2 etc方式编址的,需要使用文件管理系统,在Linux中使用VFS和实际文件管理系统来管理文件,所以对于Linux,有两个方式使用系统资源:VMM,VFS,前者用来管理绝大部分的内存,后者用来管理所有的文件和部分特殊文件系统(eg:/shm是内存的一块区域)
- page cache可以看作二者的桥梁,把磁盘文件放到高速缓存,就可以按照内存的使用方式使用磁盘的文件,使用完再释放或写回磁盘page cache中的页可能是下面的类型:
* 含有普通文件数据的页
* 含有目录的页
* 含有直接从块设备(跳过文件系统层)读出的数据的页
* 含有用户态进程数据的页,但页中的数据已经被交换到硬盘
* 属于他书文件系统文件的页 - 映射:一个线性区可以和磁盘文件系统的普通文件的某一部分或者块设备文件相关联,这就意味着内核把对区线性中页内某个字节的访问转换成对文件中相应字节的操作
- TLB(Translation Lookaside Buffer)高速缓存用于加快线性地址的转换,当一个线性地址第一次被使用时,通过慢速访问RAM中的页表计算出相应的物理地址,同时,物理地址被存放在TLB表项(TLB entry),以便以后对同一个线性地址的引用可以快速得到转换
- 在初始化阶段,内核必须建立一个物理地址映射来指定哪些物理地址范围对内核可用,哪些不可用
- swap(内存交换空间)的功能是应付物理内存不足的情况下所造成的内存扩展记录的功能,CPU所读取的数据都来自于内存,那当内存不足的时候,为了让后续的程序可以顺序运行,因此在内存中暂不使用的程序和数据就会被挪到swap中,此时内存就会空出来给需要执行的程序加载,由于swap是使用硬盘来暂时放置内存中的信息,所以用到swap时,主机硬盘灯就会开始闪个不同
- Q:CPU只能对内存进行读写,但又是怎么读写硬盘的呢???A:把数据写入page cache,再经由。。。写入磁盘(包括swap) --《鸟哥》P10
- 内存本身没有计算能力,寻址之类的都是CPU的事,只是为了简便起见,我们通常画成从内存地址A跳到内存地址B
- OS是软件的核心,CPU是执行的核心
* 前者给后者发指令我要干什么,CPU把他的指令变成现实
* 二者必须很好的匹配计算机才能很好的工作 - Linux内核中与文件Cache操作相关的API有很多,按其使用方式可以分成两类:一类是以拷贝方式操作的相关接口, 如read/write/sendfile等,其中sendfile在2.6系列的内核中已经不再支持;另一类是以地址映射方式操作的相关接口,如mmap等。
* 第一种类型的API在不同文件的Cache之间或者Cache与应用程序所提供的用户空间buffer之间拷贝数据,其实现原理如图7所示。
* 第二种类型的API将Cache项映射到用户空间,使得应用程序可以像使用内存指针一样访问文件,Memory map访问Cache的方式在内核中是采用请求页面机制实现的,首先,应用程序调用mmap(),陷入到内核中后调用do_mmap_pgoff。该函数从应用程序的地址空间中分配一段区域作为映射的内存地址,并使用一个VMA(vm_area_struct)结构代表该区域,之后就返回到应用程。当应用程序访问mmap所返回的地址指针时(图中4),由于虚实映射尚未建立,会触发缺页中断。之后系统会调用缺页中断处理函数,在缺页中断处理函数中,内核通过相应区域的VMA结构判断出该区域属于文件映射,于是调用具体文件系统的接口读入相应的Page Cache项,并填写相应的虚实映射表。经过这些步骤之后,应用程序就可以正常访问相应的内存区域了。
mumap()
//接触文件或设备对内存的映射,成功返回0,失败返回-1设errno
int munmap(void *addr, size_t length);
shm_unlink()
//关闭进程打开的共享内存对象,成功返回0,失败返回-1
//Link with -lrt.
int shm_unlink(const char *name);