操作系统之网络IO

零拷贝 传统的文件传输
在传统的文件传输中,由于涉及到两次系统调用read和write,所以会产生4次上下文切换 。并且存在4次数据拷贝(2次DMA拷贝和2次CPU拷贝),①第一次拷贝,把磁盘上的数据拷贝到内存的操作系统内核的缓冲区里,这个拷贝的过程是通过 DMA完成的;②第二次拷贝,把内核缓冲区的数据拷贝到用户的缓冲区里,这个拷贝到过程是由 CPU 完成的;③第三次拷贝,把刚才拷贝到用户的缓冲区里的数据,再拷贝到内核的的缓冲区里,这个过程依然还是由 CPU 搬运的;④第四次拷贝,把内核的缓冲区里的数据,拷贝到网卡的缓冲区里,这个过程又是由 DMA 搬运的 。
mmap+write实现零拷贝
在传统的文件传输中,读文件需要先将文件拷贝到内核缓冲区中,然后再拷贝到用户空间中 。
mmap()函数会在进程的虚拟地址空间中,寻找一段空闲的满足要求的连续虚拟地址,然后建立页表项,将该段虚拟地址映射到该文件的文件地址上,并且在第一次访问的时候会触发缺页中断,然后将文件内容从磁盘调入内存中,这样的话就可以像访问普通内存一样来访问该文件 。可以发现通过mmap()系统调用,只使用一次数据拷贝,就从磁盘中将数据传入内存的用户空间中,供进程使用 。同时,用户空间和内核空间中的一方对映射区域的修改,都可以立即被另一方捕捉到 。
通过使用 mmap() 来代替 read(), 可以减少一次数据拷贝的过程 。但是还是存在2次系统调用,4次上下文切换和3次拷贝(2次DMA拷贝和1次CPU拷贝) 。
实现零拷贝
通过()函数可以实现read和write两个函数的功能,将数据从源文件描述符端口拷贝到目的文件描述符端口,减少了一次系统调用 。并且直接将数据从内核缓冲区拷贝到网卡,不再经过缓冲区了,所以只有2次拷贝,①通过 DMA 将磁盘上的数据拷贝到内核缓冲区里;②将内核缓存中的数据拷贝到网卡的缓冲区里 。所以只需要2次上下文切换和2次数据拷贝即可,而且2次的数据拷贝过程都是由 DMA 来完成的,不需要通过CPU 。
IO模型
对于一次IO访问(以read为例),数据(位于磁盘或网卡等地方)会先被拷贝到操作系统内核的缓冲区中,然后才会从操作系统内核的缓冲区拷贝到应用程序的空间 。所以会经历两个阶段:①等待内核将数据准备好;②将数据从内核拷贝到进程的用户空间中 。
①阻塞IO模型:默认情况下所有的文件操作都是阻塞的,在用户空间调用(以套接字接口为例)读取数据时,其系统调用直到数据包到达且被复制到应用缓冲区中或者发送错误时才返回,在此期间一直会等待,进程从调用到返回这段时间内都是被阻塞;
②非阻塞IO:应用程序调用读取数据时,如果该内核缓冲区没有数据的话,就会直接返回一个错误,不会让应用一直等待中;如果数据已经准备好了,则会将数据从内核缓冲区拷贝到用户空间中 。我们在应用程序中可以循环调用函数并判断是否出现该错误标识,直到读取到它数据要的数据为止 。
③IO多路复用:进程通过将一个或多个fd传递给/poll系统调用,阻塞在操作上,/poll帮我们侦测多个fd是否准备就绪,当有fd准备就绪时,返回数据可读状态,应用程序再调用读取数据 。/poll顺序扫描fd是否就绪,每次执行的时间复杂度和fd的数量成正比,且支持的fd数量受到限制,因此Linux提供了epoll系统调用,它使用事件驱动的方式代替了顺序扫描,当有fd就绪时,立即执行其回调函数,性能更高 。IO多路复用的好处就是通过或poll、epoll 来监控多个fd ,从而不必为每个fd创建一个对应的监控线程,从而创建多个线程的开销并减少了资源占用 。