[Linux Network I/O] 你的电脑是如何接受来自网络的数据的?

前言


相信读者已经熟知OSI 7层模型TCP/IP 4层模型的构成,这是现代计算机网络里最基本的两类模型。在我们日常工作中,一般都不会太在意传输层(TCP、UDP等)之下的协议。因为TCP、UDP等协议是定位到特定程序的,上一层IP则是定位到特定计算机的。一般都不会太在意更底层的运作原理,笔者将在本文以Linux为例,与你一起探讨现代计算机是如何接受来自网络上的数据包的。

本文将主要参考 参考[1] 的文章《8.3. OVERVIEW OF PACKET RECEPTION》,其为红帽子Linux的公开文档。

参考


  1. 8.3. OVERVIEW OF PACKET RECEPTION - Redhat Linux Doc
  2. Internet protocol suite - wikipedia
  3. OSI model - wikipedia
  4. Registed port for git - IANA
  5. Interrupt request - wikipedia
  6. Interrupt - wikipedia
  7. (转载)细说内核中断机制 - csdn: kerneler_
  8. Programmable interrupt controller - wikipedia
  9. 8259A Interrupt Controller - Intel
  10. polling(computer science) - wikipedia
  11. polling vs interrupt
  12. Do built in keyboards use polling or interrupts - stackoverflow
  13. 软中断和硬中断 - jianshu: Joe_HUST
  14. Interrupt flag - wikipedia
  15. The MSI Driver Guide HOWTO - github: Linux REPO

前提知识


在写正文之前,笔者想先简单介绍一下本文涉及到的基本知识点。

  1. 中断(Interrupt):处理器中断当前正在执行的程序,切换至中断请求处理程序(Interrupt Handler) 处理紧急任务。此类紧急处理程序通常很短小,能迅速恢复到原执行中的程序。
  2. 轮询(Polling):与硬件主动发起中断请求,请求CPU服务相对,CPU定期主动询问硬件是否需要服务被称为轮询。键盘有可能通过中断请求也有可能通过轮询来使其输入被计算机处理。而网卡这种频繁产生中断的设备不会使用轮询。像键盘相对网卡而言中断产生的频率差了好几个数量级,可以使用轮询。
  3. 中断请求(IRQ: Interrupt request):硬件主动发起中断请求请求CPU服务紧急任务,中断请求并不总是会成功。有说UDP的数据包有可能因为中断请求不成功而被网卡丢弃。而TCP的数据包即使因中断请求不成功而被丢弃了,也会因没有返回ACK给发送方,发送方会再次发送该数据包直到成功接收到ACK消息。
  4. 网卡(NIC: Network Interface Controller):是电脑接收外部数据的第一站。接收到数据之后会用中断的方式请求CPU处理堆积的网络数据帧。
  5. 可编程中断控制器(PIC:Programmable Interrupt Controller):旧时,为硬件设备。中断信号无法由硬件直接产生,而是硬件通知中断控制器,并由中断控制器代为发送中断请求(IRQ)。典型如因特尔的8259A IC,现主流是MSI。但网上部分教程还依然在用PIC教学。
  6. 消息告知中断(MSI):现在外设硬件的连接方式,大都为通过PCI的方式连接到电脑。这些设备都被称为PCI Device,而在PCI Specification里定义了PCI设备通过MSI的方式来通知CPU中断的到来。

网络数据包的接收流程


本节将以Linux为例,讲解网络数据包的处理流程。在本文笔者不会去深挖硬件的实现细节,将在高纬度观察和探讨网络数据包的处理流程。

网络数据包的接收主要分为以下四步。

1. 硬件接收(Hardware Reception)


网卡接收来自外部的数据(网络帧),单位是帧(Frame),即OSI L2数据链路层的单位。并把其数据暂时保存在网卡内部的存储器里等待CPU去处理。

Hardware Reception:
the network interface card (NIC) receives the frame on the wire. Depending on its driver configuration, the NIC transfers the frame either to an internal hardware buffer memory or to a specified ring buffer.

2. 硬中断请求(Hard IRQ)


网卡把其接收到来自网络的数据的这一“事件”,通过发送中断请求要求CPU处理网卡接收到的数据

CPU接收到中断请求,在当前指令执行完毕之后,跳转到中断处理器的指令地址,并开始执行,中断处理器选择合适的硬件驱动程序来进行下一步处理。在这里,会选择网卡(NIC)驱动去进行下一步“软中断请求”的处理。

驱动程序一般在运行时是作为内核进程的一部分,而不是作为进程存在。

Hard IRQ:
the NIC asserts the presence of a net frame by interrupting the CPU. This causes the NIC driver to acknowledge the interrupt and schedule the soft IRQ operation.

Q: 网卡是如何通知CPU的?


在以前,网卡通过传递中断信号到中断控制器(如8259 Interrupt Controller),由中断控制器统一通知CPU。而这一实现局限于中断控制器INT引脚的数量(8个,2个8259级联的话是15个)的限制和性能等原因,已经有新的技术去替代这一实现。

目前计算机里,大都支持使用MSI (Message Singled Interrupt)或其扩展MSI-X的方式来实现中断,其原理简单来说就是硬件(如网卡),直接向特定地址(名为pci configuration space)写入数据,通过一些硬件组件的配合,会导致CPU接收到一个中断信号。

What are MSIs?
A Message Signaled Interrupt is a write from the device to a special
address which causes an interrupt to be received by the CPU.

延伸阅读:计算机中断体系 - 知乎专栏:老狼

Q: 中断处理器(Interrupt Handler)是什么?


中断处理器是一段程序,操作系统内核(Kernel)进程的一部分。
中断处理器非常短小精干,由于CPU严格要求中断处理器的执行时间。所以中断处理器不是一个单独的进程。 (※单独进程意味着昂贵的CPU上下文切换的开销

中断处理器主要负责把硬中断请求路由至真正负责处理某种中断请求的驱动程序去。而路由时所使用的情报来自中断向量(Interrupt vector)和中断描述符表(Interrupt descriptor table)对于这两者的介绍在上述知乎专栏里有详细介绍。

中断处理器通常也被称为ISR(Interrupt Service Routine),也就是“中断服务例程”。

源码:linux/kernel/irq/handle.c - Github

3. 软中断请求(Soft IRQ)


直到这一阶段,内核才真正会去网卡内部的buffer中去拿取数据。

Soft IRQ:
this stage implements the actual frame-receiving process …
… the kernel actually removes the frame from the NIC hardware buffers and processes it through the network stack. From there, the frame is either forwarded, discarded, or passed to a target listening socket.

内核从网卡buffer中拿到数据,并通过网络栈(Network Stack)解析其数据中的内容,并根据其内容做出不同的反应。

如果数据包的内容是tcp数据包,则根据tcp协议的目标端口(destination port)向对应的socket文件写入。可以参考笔者在下文的实验一节

这一过程一直持续到网卡buffer里的数据被读光或已读取数据帧数量达到dev_weight变量设定的单次接受上限值时才结束。网卡buffer中相邻帧的数据不一定是送给同一个进程的。(在线看电影和下载文件可以同时进行)

Device weight:
This attribute refers to the maximum number of frames that the NIC can receive before the softirq context has to yield the CPU and reschedule itself. It is controlled by the /proc/sys/net/core/dev_weight variable.

4. 应用接收(Application receive)


在第三步里,数据包被写入到了socket文件里,如果有任何进程与这个socket文件关联着,该进程就能通过POSIX标准定义的系统调用接口(read,recv,recvfrom)从其中获取01串数据。至此,数据已经进入到应用程序的内存里

Application receive:
the application receives the frame and dequeues it from any owned sockets via the standard POSIX calls (read, recv, recvfrom). At this point, data received over the network no longer exists on the network stack.

如果监听的程序为http server,那么数据(01串)就会按照http协议来解析并构建一个http request数据结构,并通过回调把控制权和构建好的http request数据结构交给应用程序员所写的代码去进行下一步的处理。

自此控制权回到我们应用程序员的手里,我们根据其http request内容做不同的反应,如渲染html并返回、或写入数据到数据库等等。

如果并没有程序监听该Sockert文件,那么其数据会被保留在Sockert文件之中,直到有谁取走其中的数据,具体请参考下文实验一节。

Socket文件


Linux里一切皆文件,socket也不例外。一个被打开的socket,是以文件的形式存在系统里。在本节延伸阅读里,笔者看到一个有趣的实验,《手动建立socket文件并用其接收http的返回包。》

笔者将其各部分命令解读之后记载于本节实验的部分。

bash重定向参考文档

CSDN图片传不上了(汗😓),笔者把一部分的说明贴在了下面。

Bash在重定向时对特殊的文件名有特殊的解读。/dev/tcp/host/port会使bash打开一个tcp的socket。

Bash handles several filenames specially when they are used in redirections, as described in the following table. If the operating system on which Bash is running provides these special files, bash will use them; otherwise it will emulate them internally with the behavior described below.

/dev/tcp/host/port
  If host is a valid hostname or Internet address, 
  and port is an integer port number or service name, 
  Bash attempts to open the corresponding TCP socket.

出处 3.6 Redirections

实验

下面进行一个实验,笔者将用bash打开一个tcp连接并尝试手动发送数据到对方服务器上并验证我们的socket文件是否能被写入。

# 为bash打开一个文件描述符(fd),编号6,类型为tcp socket,目标host 谷歌,端口80。
humao:dev toranekojp$ exec 6<>/dev/tcp/www.google.com/80
# 向Socket输入字符串 ”GET /index.html HTTP/1.1“。
# 这会导致系统使用该socket文件描述的tcp连接向目标host的目标端口,发送了上述字符串。
humao:dev toranekojp$ echo 'GET /index.html HTTP/1.1' >&6
# 这个时候谷歌并没有回复。
humao:dev toranekojp$ cat <&6
^C
# 告知对方,己方发送已经结束。
humao:dev toranekojp$ echo >&6
# 此时,socket文件里有了对方的回复,其内容为01串
# http是textual protocol,所以可以用文本解码的方式解读其内容。
# cat命令用默认编码utf-8解码其中01串,并打印到终端。
# 注:笔者未证实默认编码,仅猜测,因为笔者的终端设置默认编码为utf-8,能否反映到cat命令,笔者暂不清楚。
humao:dev toranekojp$ cat <&6
HTTP/1.1 200 OK
Date: Sat, 07 Dec 2019 14:12:08 GMT
Expires: -1
Cache-Control: private, max-age=0
Content-Type: text/html; charset=ISO-8859-1
P3P: CP="This is not a P3P policy! See g.co/p3phelp for more info."
Server: gws
X-XSS-Protection: 0
X-Frame-Options: SAMEORIGIN
Set-Cookie: 1P_JAR=2019-12-07-14; expires=Mon, 06-Jan-2020 14:12:08 GMT; path=/; domain=.google.com
Set-Cookie: NID=193=Uob9GQnS9iVnhD3YT6FVLRj0rmxfpF__eyl7C1welKrH1WRrjr5MUJUO8pFvDsZQULiDSwLrFosgII_1ADgd6DvnbouHioALNikLCRQ1_43QaLVcpxms4LLqUtS0VH5y2_hByV68mESHnm3oo8UBq1mpNMbrC5g8NWzeeb3Hhbw; expires=Sun, 07-Jun-2020 14:12:08 GMT; path=/; domain=.google.com; HttpOnly
Accept-Ranges: none
Vary: Accept-Encoding
Transfer-Encoding: chunked

# 下略......

# 再次打印发现内容已经被取走。
humao:dev toranekojp$ cat <&6
humao:dev toranekojp$ 

延伸阅读:Linux socket文件系统体现“一切皆文件” - CSDN: dog250
延伸阅读:[Linux] 盘点I/O 重定向符与使用例 - CSDN:虎猫_EICHO

结语


互联网时代,理解网络的工作原理,可以说是作为程序员极为重要的功课。只有理解网络的工作原理,你才有可能从根本原因,着手去分析大数据时代单个服务器的性能和其瓶颈。希望通过本文,能让你对网络数据的接收过程,有一个全新的认知。

附录


TCP/IP 4层模型

  • 应用层(Application Layer): HTTP、SMPT、POP、GIT(Git Server与Git Client的通信协议,注册端口 9418)
  • 传输层(Transport Layer):TCP、UDP
  • 网络层(Internet Layer):IPv4、IPv6
  • 链路层(Link Layer)

OSI 7层模型

  • 应用层(Application Layer)
  • 表现层(Presentation Layer)
  • 会话层(Session Layer)
  • 传输层(Transport Layer)
  • 网络层(Network Layer)
  • 数据链路层(Data Link Layer)
  • 物理层(Physical Layer)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值