Linux下有两种类型的共享内存系统调用,一种是基于System V的,另一种是基于POSIX的。上一篇文章Linux编程基础4 - 进程间通信(共享内存) 说的就是System V下的共享内存系统调用;之前介绍过的mmap()
函数就是基于POSIX的,除了mmap()
外,shm_open()
和shm_unlink()
也是基于POSIX的函数。这篇文章主要记录POSIX下共享内存系统调用的使用。
mmap()
mmap()
主要作用是将文件映射到进程的内存空间,从而进程可以快速地读取文件内容(不用再使用write()
、read()
经过磁盘、内核缓冲区、用户空间缓冲区等);同时,我们也可以指定多个进程映射同一个文件,这样也实现了共享内存的作用。mmap()
一般适合临时性的大文件映射,当进程结束时,映射自动就被销毁了(System V需要手动销毁)。
mmap()
的函数原型如下(munmap()
用于销毁映射):
1 | // 成功返回共享内存的地址,失败返回(void *)-1 |
mmap()
函数参数比较多,addr
是映射地址一般填NULL
,内核会自动分配地址进行映射;length
是需要映射的长度;prot
是该空间的范围权限有PROT_READ
,PROT_WRITE
等;flags
是标志位,用的比较多的是MAP_SHARED
和MAP_PRIVATE
,它们的作用下面会讲;fd
是文件描述符;offset
是文件映射起始地址的偏移量,它的值必须是页大小的整数倍。
上面提到MAP_SHARED
和MAP_PRIVATE
,这两个标志位有什么不同呢?前者对进程内部映射区的修改会更新文件,其他同样映射到该文件的进程也可以看到修改后的文件(修改文件同样也会修改映射区?);对于后者进程使用COW(copy on write),只修改内部映射区,不会修改文件,因此其它进程是看不到文件修改的(修改文件是否会影响映射区是没有定义的行为)。
上面有提到offset
必须是页大小的整数倍,因为Linux内核是通过页的方式管理内存空间的,因此mmap()
的操作是按页进行操作的。文件会按照文件大小 / 页大小 + 1
的形式被映射到进程,因此mmap()
实际会使用比文件大小更多的内存空间。对于mmap()
的参数,虽然只有offset
被要求是页的整数倍,但addr
在函数内部会向上转化成页大小的整数倍。
除了实现文件到进程空间的映射,mmap()
还可以通过匿名映射实现父进程和子进程之间的通信(匿名映射还可以用来实现malloc()
)。
匿名映射
当我们设置flags
为AP_ANONYMOUS
或者MAP_ANON
时,mmap()
将实现匿名映射。在该设置下,fd
通常会被忽略,在有些系统中需要设置fd = -1
。在匿名映射下,进程将内存空间而不是文件映射到进程空间,并且这个空间除了子进程外的其他进程是不能访问的,因此匿名映射可以用作父子进程间的通信。
shm_open()和shm_unlink()
如果要用mmap()
来实现任意进程间的通信,我们还需要使用另外两个系统调用 - shm_open()
和shm_unlink()
(当然还需要额外的函数实现进程同步)。
shm_open()
用于创建或者打开一个共享内存空间,这个空间的地址在/dev/shm/xxx
,其中/xxx
就是shm_open()
创建共享内存空间时的参数。shm_open()
和shm_unlink()
函数原型如下:
1 | // 成功返回文件描述符,失败返回-1 |
name
是创建文件的名字,建议使用/xxx
的方式命名。oflag
是标志位,用于设置可读、可写、创建文件等;mode
和open
的mode
一样,后9bit是设置文件的访问权限。
shm_unlink()
用于解除共享内存空间和文件描述符的映射,当没有进程映射到该内存空间,内核就会销毁它。