Linux-API:mmap&flock
Linux API: introduction to mmap(2) and flock(2)
mmap
mmap(2)
比较折磨人,它的接口如下:
1 | #include <sys/mman.h> |
mmap 有两种 mapping:
- file mapping
- anonymous mapping
对于 1,fd
在 mmap
后可以 close
:
1 | After the mmap() call has returned, the file descriptor, fd, can |
mmap
的 prot
有下列的语义:
PROT_EXEC
: Pages may be executed.PROT_READ
: Pages may be read.PROT_WRITE
: Pages may be written.PROT_NONE
:Pages may not be accessed.
mmap 的逻辑和 os 的 page cache
强相关,上述语义可能作用在 page 上,必要的时候给你个 SIGSEGV
rwx
三个选项都理解,PROT_NONE
可能用于做 Guard Page: https://stackoverflow.com/questions/12916603/what-s-the-purpose-of-mmap-memory-protection-prot-none
同时,flags
也有许多选项,除了 MAP_NONBLOCK
MAP_LOCKED
这些,需要留意 MAP_SHARED
和 MAP_PRIVATE
, 使用可以见下表。
上述也会影响 fork
的时候父子进程的 mmap
行为(可以回到一下之前对 fork
的吐槽)
文件映射
mmap
的时候,文件会被按照 offset
和 length
映射到内存中,如图:
这种 mapping 可能是 on-demand 的,即 mmap
之后不把 page 加载,需要的时候再访问。
对于 MAP_PRIVATE
, 它可以:
- 被用在
PROT_READ | PROT_EXEC
, 运行程序,这样不会修改程序的数据 - 共享一个只读的
.text
而共享的文件则如下图所示:
这种 memory mapping IO 会有特点:文件和 kernel 的 buffer 是由 os 自动 manage 的,同时,内核和用户进程不再和 write 一样,先写用户 buffer, 再写 kernel buffer.
当需要强制 flush 的时候,可以走 msync(2)
来强制刷盘
mmap 的映射可能比文件本身还大,未来的内存可能需要 ftruncate
处理:
匿名映射
数据会被初始化为 0
1 | MAP_ANONYMOUS |
杂项
mprotect(2)
切换进程页的访问权限
mlock
会讲内存 Page 驻留在物理内存中,不会swap 出去
madvise(2)
用户改内核的代码和调度是不现实的,但是很多时候用户比内核自己清楚需要调度成啥样。
madvise(2)
相当于提供上述相关的信息,“建议”内核做相关的操作。详见:https://www.man7.org/linux/man-pages/man2/madvise.2.html
Usage
BoltDB 的读取使用了 mmap
. 内存空间是小于磁盘的,而 bolt 没有自己写 BufferPool, 而是用了 mmap, 可以看看代码:
1 | // mmap memory maps a DB's data file. |
DB::mmap
具体走到 bolt_unix.go
下的 mmap
, 这里用 madvise
+ MADV_RANDOM
来表达访问的模式,同时把这次数据存储到 db
上
dataref
防止 syscall.Mmap
的结果被回收,具体访问的数据被丢到 data
上。
munmap
是一个反向操作:
1 | // munmap unmaps a DB's data file from memory. |
flock
https://man7.org/linux/man-pages//man2/flock.2.html
flock(2)
源自 BSD,flock
可以指定 LOCK_SH
和 LOCK_EX
, 表示共享/互斥,也可以 LOCK_UN
解锁
本身调用 flock
会阻塞进程,不希望阻塞可以带上 LOCK_UB
flag
flock
本身和 fd
是绑定的,也就是说,dup
产生的 fd 和 fork
子进程 产生的 fd 是同一把锁,这意味着,对它们的 LOCK_UN
处理需要额外注意。
可以参考一下代码的 flock
和 funlock
, 内容在 bolt_unix.go
:
1 | func flock(db *DB, mode os.FileMode, exclusive bool, timeout time.Duration) error { |
这里的 funlock
比较朴素,flock
采取了 LOCK_NB
和一个 retry 尝试结合,超过 timeout 后,程序会自动退出。
flock
只能以文件为粒度上锁,需要更细的控制需要使用 fcntl
。