应用程序从磁盘中读取数据的过程:
- 调用read()系列函数,产生系统调用,陷入内核态;
- CPU设置DMA,包括数据源地址,数据目的地址,数据长度;
- 向磁盘设备控制器发送指令,要求磁盘控制器将目标数据搬运到内存中;
- 磁盘控制器将数据存放到指定的自己的缓冲区,达到一定条件后,将控制器中的数据拷贝到内存中(此时是内核态的内存空间,该过程是DMA的过程,是两个高速存储介质之间的数据搬运);
- 向DMA确认成功,DMA控制器产生一个中断给CPU,中断服务程序返回用户态,继续执行中断时的下一条指令;
- 将内核空间内存中数据拷贝返回给用户空间的内存,完成数据读取
从磁盘读取数据,通过网络将数据发送出去:
char buf[4096] = {0};
read(buf);
socket.send(buf);
- 磁盘控制器将数据从磁盘读取到自己的缓冲区中;
- 然后通过设置DMA,将磁盘设备缓冲区中的数据通过DMA拷贝到数据内核态的缓冲区中;
- 我们的应用程序将数据从内核态的缓冲区拷贝进应用程序的buf;
- 然后通过系统调用,将buf拷贝到内核态的socket缓冲区中;
- 最后,CPU再次设置DMA,DMA设备控制器将socket缓冲区中的数据拷贝到网卡中,最终发送到网络中;
零拷贝:
上述网络数据传输过程中,经历了四次数据拷贝,效率是比较底下的。
引入零拷贝,可以实现将磁盘中的数据直接写入到网卡的缓冲区,不涉及用户态和内核态之间的数据的重复拷贝。
根据socket描述符的信息,直接将数据拷贝给网卡,比如
从Linux 2.1版内核开始,Linux引入了sendfile,就是一个比较典型的零拷贝过程。
#include<sys/sendfile.h>
ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);