Linux 的 Zero Copy 技术
mmap 文件映射
通常情况下,我们可以使用read()
和write()
去访问文件,除此之外,Linux 还提供了mmap()
系统调用,它可以将文件映射到进程的地址空间,这样程序就可以通过访问内存的方式去访问文件了。那么与read()
和write()
相比,使用mmap()
去访问文件能带来什么好处呢?
使用mmap()
一个明显的好处就是减少一次 I/O 拷贝,譬如说,当我们使用read()
读取文件时,通常的做法是这样:
1 2 |
char buffer[SIZE]; ssize_t n = read(fd, buffer, SIZE); |
这个过程实际上发生了两次 I/O 拷贝,第一次是将磁盘中的文件内容拷贝到 OS 的文件系统缓冲区,第二次是将 OS 缓冲区的数据拷贝到用户缓冲区。而使用mmap()
读取文件时,只会发生第一次拷贝操作,也就是将文件内容拷贝到 OS 文件系统缓冲区,完成这个拷贝操作之后,mmap()
还会执行其它一些复杂的操作,例如将相应的 OS 缓冲区映射到进程的地址空间。
尽管mmap()
可以减少一次 I/O 拷贝,但由于mmap()
的实现很复杂,调用mmap()
将会带来额外的开销,因此在一些情况下,没有使用mmap()
的必要:
- 访问小文件时,直接使用
read()
或write()
将更加高效。 - 单个进程对文件执行顺序访问时(sequential access),使用
mmap()
几乎不会带来性能上的提升。譬如说,使用read()
顺序读取文件时,文件系统会使用 read-ahead 的方式提前将文件内容缓存到文件系统的缓冲区,因此使用read()
将很大程度上可以命中缓存。
那么,在什么情况下使用mmap()
去访问文件会更高效呢?
- 对文件执行随机访问时,如果使用
read()
或write()
,则意味着较低的 cache 命中率。这种情况下使用mmap()
通常将更高效。 - 多个进程同时访问同一个文件时(无论是顺序访问还是随机访问),如果使用
mmap()
,那么 OS 缓冲区的文件内容可以在多个进程之间共享,从操作系统角度来看,使用mmap()
可以大大节省内存。
sendfile()
Web Server 处理静态页面请求时,通常是从磁盘中读取网页的内容,然后发送给客户端:
1 2 |
read(fd, buffer, len); write(sockfd, buffer, len); |
正如我们前面说到的,使用read()
读取文件时,将发生两次 I/O 拷贝。然而,数据发送的过程也发生了两次 I/O 拷贝,第一次是write()
将用户缓冲区的数据写入内核的 socket 发送缓冲区,成功写入之后write()
会返回,在write()
返回之后,内核会将 socket 发送缓冲区的数据拷贝到网卡驱动。可以看到,整个过程发生了四次 I/O 拷贝操作。
然而除了考虑 I/O 拷贝带来的开销,我们还要考虑系统 context switch 带来的开销,当程序调用read()
时,系统会从用户态切换到内核态,而当read()
返回时,又会导致系统从内核态切换到用户态,所以调用read()
发生两次 context switch,同理,调用write()
也会发生两次 context switch。
Linux 提供了sendfile()
用来减少我们前面提到的 I/O 拷贝和 context switch 的次数:
1 |
sendfile(sockfd, fd, NULL, len); |
使用sendfile()
发送文件时,实际发生了三次 I/O 拷贝,第一次是将磁盘中的文件内容拷贝到 OS 的文件系统缓冲区,第二次是将 OS 缓冲区的数据拷贝到 socket 的发送缓冲区,最后一次是将 socket 发送缓冲区的数据发送到网卡驱动。可以看到,与使用read()
和write()
发送文件相比,使用sendfile()
减少了一次 I/O 拷贝和两次 context switch。
如果使用的网卡支持 scatter-gather 特性,那么还可以再减少一次 I/O 拷贝:
这种情况下,使用sendfile()
发送文件只会发生两次 I/O 拷贝,第一次是将磁盘中的文件拷贝到 OS 的文件系统缓冲区,而第二次是将 OS 缓冲区的数据直接拷贝到网卡驱动。可以使用下面的命令查看网卡是否支持 scatter-gather 特性:
1 2 3 4 |
$ ethtool -k eth0 | grep scatter-gather scatter-gather: on tx-scatter-gather: on tx-scatter-gather-fraglist: off [fixed] |
参考资料
- tee() with your splice()?
- Zero Copy I: User-Mode Perspective
- Efficient data transfer through zero copy
- Two new system calls: splice() and sync_file_range()
- Zero-Copy in Linux with sendfile() and splice()
- Linux: Explaining splice() and tee()
- Linux MMAP & Ioremap introduction
- mmap() vs. reading blocks
- splice()系统调用族探秘