ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

操作系统——网络系统

2022-03-03 17:32:57  阅读:144  来源: 互联网

标签:socket 网卡 描述符 内核 缓冲区 网络系统 拷贝 操作系统


常见OSI七层:物数网传会表应

四层:应用层(负责向用户提供一组应用程序,如HTTP/DNS/TCP);传输层(负责端到端通信,如TCP/UDP);网络层(负责网络包的封装、分片、路由、转发,比如IP/ICMP);网络接口层(负责网络包在物理网络中的传输,比如网络包的封帧、MAC寻址、差错检测,通过网卡传输网络帧)

Linux网络协议栈:应用层到传输层,加了TCP;传输层到网络层,加了IP头;到网络接口层,加了帧头和帧尾。

有MTU最大传输单元,单次传输的最大IP包的大小。

当网络包超过MTU,就会在网络层分片,MTU越小吞吐能力越差。

应用程序——系统调用——lvs——socket——TCP/UDP/ICMP——IP——ARP——MAC——网卡驱动程序——网卡。

应用程序需要通过系统调用,来跟socket层进行数据交互。

网卡负责接收和发送网络包,接收到的时候,会通过DMA技术,把网络包放到环形缓冲区(Ring buffer)

Linux接收网络包的流程:

以前是网卡发中断,中断太多了影响CPU效率。

所以引入了NAPI机制,混合中断和轮询接收网络包,不采用中断的方式读数据,而是首先采用中断唤醒数据接收的服务程序,然后poll的方法来轮询数据。

有网络包到达,网卡发起硬件中断,执行网卡硬件中断处理函数,中断处理函数处理完需要暂时屏蔽中断,然后唤醒软中断来轮询处理数据,直到没有新数据时才恢复中断,这样一次中断处理多个网络包。

第一步就是从环形缓冲区中拷贝数据到内核缓冲区中,作为一个网络包给网络协议栈进行逐层处理。最后,应用程序调用socket接口,从内核的socket接收缓冲区中读取新到来的数据到应用层。

Linux发送网络包的流程:

首先,应用程序调用socket发送数据包的接口,系统调用使得从用户态进入内核态的socket层,将应用层的数据拷贝到socket的发送缓冲区。

接下来,网络协议栈从socket发送缓冲区取出数据包,从上到下传输。最后,触发软中断告诉网卡驱动程序,放到网卡队列,用物理网卡发出去。

 

硬盘很慢,速度和内存相差十倍以上。优化磁盘速度有很多方案:零拷贝、直接IO、异步IO等,这些优化的目的都是为了提高吞吐量。(操作系统内核中的磁盘高速缓存区可以有效减少磁盘访问次数)

如果没有DMA,磁盘控制器有磁盘控制器缓冲区,CPU有PageCache,read系统调用都要CPU亲自搬送数据。有了DMA,将数据从磁盘控制器缓冲区搬送到系统内核缓冲区的工作就是DMA做,CPU只要调用返回。

传统的文件传输:服务器如果要提供文件传输功能,就要从磁盘上读取,然后通过网络协议传给客户端。

用户态有用户缓冲区,内核态有缓存区和socket缓冲区。然后两边是磁盘文件和网卡。上述就是要从磁盘文件到网卡。

从磁盘文件通过DMA拷贝到内核缓存区;通过CPU拷贝到用户缓冲区;通过CPU拷贝到内核态的socket缓冲区;通过DMA拷贝到网卡。 

 用户进程从磁盘read,write到网卡发送出去,发生了四次用户态与内核态的上下文切换,还发生了四次数据拷贝,两次CPU两次DMA。

要想提高文件传输的性能,就需要减少用户态和内核态的上下文切换和数据拷贝的次数。

(1)减少上下文切换,就是减少系统调用的次数。读取磁盘数据的时候,之所以发生上下文切换,是因为用户空间没有权限操作磁盘或网卡,内核的权限最高,所以要交给内核,就需要系统调用。

(2)减少数据拷贝,从内核到用户,从用户到内核,这两个步骤是没有必要的。因此,用户的缓冲区存在是没有必要的。

零拷贝:mmap+write、sendfile

mmap()系统调用会直接把内核缓冲区里的数据映射到用户空间(应用进程和操作系统内核共享这个缓冲区),这样内核与用户区就少了一次拷贝操作。

通过使用mmap()代替read(),可以减少一次数据拷贝的过程。但这并不是最理想的零拷贝,因为仍然需要通过CPU把内核缓冲区的数据拷贝到socket缓冲区里,而且仍然需要四次上下文切换,因为系统调用还是两次。

 

 sendfile是专门发送文件的系统调用函数,可以直接把内核缓冲区的数据拷贝到socket缓冲区中,不用再拷贝到用户态。

这样只要一次系统调用(从用户到内核、从内核到用户)这样就只有两次上下文切换,三次数据拷贝。

 

 这还不是真正的零拷贝技术,如果网卡支持SG-DMA技术,我们可以进一步减少通过CPU把内核缓冲区中的数据拷贝到socket缓冲区的过程。

两次上下文切换、两次数据拷贝。总体来看,零拷贝技术可以把文件传输的性能提高至少一倍以上。Kalfa就用到了零拷贝。

 

 上面提到了内核态的缓冲区就是磁盘高速缓存PageCache。零拷贝技术采用了PageCache技术。因为读写磁盘很慢,所以应该想办法把读写磁盘换成读写内存,于是通过DMA技术把磁盘数据搬到内存里,就可以用读内存替换读磁盘。但是内存空间远比磁盘小,智能拷贝一小部分数据。根据局部性,刚被访问的数据在短时间内再次访问的概率很高。读磁盘的时候,先在PageCache找,如果存在立即返回;如果没有,从磁盘中读取,然后缓存在PageCache里。

但是传输大文件的时候,PageCache不起作用,白白浪费了DMA拷贝的数据,造成性能降低。大文件放进去就没有小文件的空间了,而且大文件也不是热点访问数据。所以大文件传输不用零拷贝。

同步IO如下图。

异步IO如下图。

 

 异步IO没有用到PageCache。绕过PageCache叫做直接IO。使用PageCache叫做缓存IO。

在高并发场景下,针对大文件传输的方式,应该使用直接IO+异步IO的方式来代替零拷贝技术。

 

IO多路复用技术

socket技术就像在客户端和服务器都开了一个网口,然后用一根网线把两端连接起来。

服务端sbla(socket/bind/listen/accept)

首先调用socket函数,创建指定网络协议、传输协议的socket;接着调用bind函数,给这个socket绑定一个端口和IP地址(当内核收到报文,通过端口号找到应用程序,然后传递数据。一台及其有很多网卡,每个网卡有对应的IP地址,应绑定一个网卡时,内核收到网卡的数据包才会发送给我们);接着调用listen函数进行监听;最后调用accept函数,从内核获得客户端连接,如果没有客户端连接,会阻塞等待客户端到来(所以有多路IO复用技术)

客户端sc(socket/connect)

connect指定服务端的IP和端口号,开始TCP三次握手。

TCP连接的过程中,服务器内核为每一个socket维护了两个队列。一个是还没完全建立连接的队列,叫做TCP半连接队列,服务器处于syn_rcvd。一个是已经建立连接的队列,叫做TCP全连接队列,服务器处于established。

 

Q:你知道服务器单机理论最大能连接多少客户端吗?

TCP连接是由四元组去欸的那个的,本地IP、本地端口、对端IP、对端端口。

服务端的IP和端口是确定的。所以最大TCP连接数=客户端IP数*客户端端口数

对于IPV4,客户端IP数最多2^32,客户端端口数最多2^16,也就是服务器单机最大TCP连接数为2^48

但是实际服务器肯定承载不了那么大的连接数主要受文件描述符(默认1024)和系统内存(TCP连接在内存有对应数据结构)限制。

如果服务器内存2GB,网卡千兆,能支持并发一万请求吗?硬件可行,但重点在于网络IO模型。

 

为了解决多用户C10K问题,我们分析一下多进程/多线程/IO多路复用三种解决方案。

(1)多进程

 

父进程主要负责监听socket,子进程主要负责已连接socket。(因为子进程会负责父进程的文件描述符,可以直接使用已连接socket和客户端通信)

(注意:在子进程退出的时候,内核里还会保留该进程的一些信息,也会占用内存,不做好回收功能,就会变成僵尸进程。随着僵尸进程越来越多,就会耗尽我们的系统资源。子进程退出后回收资源,分别是wait和waitpid)

每产生一个进程,都会占据一定的系统资源,而且进程间上下文切换会降低性能。进程间的上下文切换不仅包含虚拟内存、栈、全局变量等用户资源,还包括堆、栈、寄存器等内核空间资源。

(2)多线程

 

 单进程运行多个线程,同进程的线程共享进程的部分资源,比如文件描述符列表、进程空间、代码、全局数据、堆、共享库等等。

这些共享资源在上下文切换的时候不需要切换,只需要切换栈、寄存器等不共享的数据。

服务器和客户端TCP完成连接后,通过pthread_create()函数创建线程,然后将已连接socket的文件描述符传递给线程函数,接着在线程里和客户端进行通信,从而达到并发处理的目的。

也得创建销毁线程,麻烦。可以使用线程池。提前创建若干个线程,当新连接建立的时候,将已连接socket放到队列,线程池的线程负责从队列取出已连接socket进程处理。

(3)IO多路复用技术

只使用一个进程来维护多个socket。处理每个请求的事件耗时控制在1毫秒,1秒内就可以处理上千个请求。把时间拉长来看,其实就是在一定时间内一个进程模拟并发时间。

select/poll/epoll就是内核提供给用户态的多路复用系统调用,一个用户进程可以通过这个调用从内核中获取多个事件。

如何获取网络事件?先把所有文件描述符传给内核,然后内核检查是否产生事件的连接,然后在用户态中处理这些连接对应的请求就可以。

select:把已经连接的socket放到文件描述符集合,然后调用该函数将文件描述符集合拷贝到内核,让内核来遍历检查是否有事件产生。当检查到有事件产生,就对这个socket做标志,接着再把整个文件描述符集合拷贝回用户态,然后用户态再遍历处理。

需要两次遍历,两次拷贝。select使用固定bitsmap,表示文件描述符集合,而且所支持的文件描述符的个数有限制,FD_SETSIZE。

poll不再使用bitsmap来存储文件描述符,而是用动态数组,以链表形式组织,突破了函数设置的文件描述符个数限制,但是还是会受到系统文件描述符限制。

相同点就在于二者都是用线性结构存储进程关注的文件描述符集合,因此都需要两次遍历和两次拷贝。

 epoll就不一样了。epoll_create在内核创建epoll对象。

epoll在内核里使用红黑树来跟踪文件描述符,把需要监控的所有socket通过epoll_ctl()函数挂到红黑树,因为在内核就不需要进行整个文件描述符集合的拷贝操作,减少了内核和用户空间大量的数据拷贝和内存分配。

epoll使用事件驱动机制,内核里维护了一个链表来记录就绪事件。当有某个socket发生事件,就会通过回调函数把它加入就绪事件列表中。即使用epoll_wait()函数返回有事件发生的文件描述符。调用epoll_wait返回之后将数据从内核拷贝到用户空间。

对于epoll_wait部分的处理,epoll支持两种不同的事件触发模式,分别是边缘触发ET和水平触发LT。

其中,水平触发就是只要出现满足事件的条件,比如内核有数据需要读,就会一直把这个事件传给用户。当内核通知用户那个文件描述符可读写,接下来还会继续通知。所以用户在收到通知之后,没必要一次执行尽可能多的操作。

边缘触发就是只有第一次有数据需要读才会传递这个事件给用户。内核只会通知用户一次,所以用户需要在收到通知后尽可能读写数据,以免错失机会。

因此,我们会循环从文件描述符读写数据,如果文件描述符是阻塞的,没有数据可读的时候,进程就会阻塞在读写函数,无法向下执行。

因此,ET模式一般和非阻塞IO搭配,程序一直执行IO操作,直到系统调用read或write返回错误,比如EAGAIN或EWOULBLOCK。

ET模式相比于LT模式,可以减少epoll_wait的系统调用次数。因为系统调用也是有上下文切换的开销。

重点:多路复用API返回的事件并不一定是可读写的,如果使用阻塞IO,那么在调用read和write的时候就会发生程序阻塞,因此最好搭配非阻塞IO。

 

标签:socket,网卡,描述符,内核,缓冲区,网络系统,拷贝,操作系统
来源: https://www.cnblogs.com/Candy003/p/15957454.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有