Linux网络数据流通

硬中断和软中断

硬中断

由与系统相连的外部设备(网卡、硬盘等)产生,通知操作系统外部设备状态变更。比如当网卡收到一个数据包时,就会产生硬中断。

  • 硬中断是外部设备对 CPU 的中断;
  • 硬中断是由硬件产生的,比如网卡、磁盘、时钟等。
  • 处于中断的驱动是需要运行在 CPU 上的,因此中断产生时 CPU 会中断当前正在运行的任务,来处理中断。
  • 硬中断可以直接中断 CPU。它会引起内核中相关的代码被触发。对于那些需要花费一些时间去处理的进程,中断代码本身也可以被其他的硬中断中断。

软中断

通常是硬中断服务程序对内核的中断;为了满足实时系统的要求,中断处理应该是越快越好。linux为了实现这个特点,当中断发生的时候,硬中断处理那些短时间就可以完成的工作,而将那些处理事件比较长的工作,放到中断之后来完成,也就是软中断(softirq)来完成。

  • 软中断处理类似于硬中断,但是它只由当前运行的进程产生;
  • 通常软中断是一些对 I/O 的请求,这些请求会调用 I/O 程序。根据设备区分,比如磁盘I/O 可以排队并等待稍后处理;
  • 软中断仅与内核联系,软中断并不会直接中断 CPU,而是需要内核为正在运行的进程做一些事情;

二者区别

  • 硬中断由外部事件引起,具有随机性和突发性。软中断是执行中断指令产生的,是程序安排好的不具有随机性;
  • 硬中断中断CPU;软中断不中断CPU;
  • 硬中断流程为 硬件——>CPU——>内核,软中断流程为 进程——>内核。软中断比硬中断少一个硬件发送信号的步骤,产生软中断的一定是当前进程,它不会中断CPU,但是会中断调用代码的流程;如果硬件需要 CPU 做一些事情,硬件会使 CPU 中断当前正在运行的代码,然后 CPU 会将当前进程的状态存放到堆栈中,以便之后返回继续运行。

Linux网络数据包接收过程

Linux 系统中数据包是如何一步步从网卡传到进程内的,这里只讨论物理网卡不涉及虚拟设备,以一个 UDP 包的接收过程作为示例:

网卡到内存

网卡需要有驱动才能工作,驱动是加载到内核的模块,负责衔接网卡和内核网络模块,驱动在加载时将自己注册进网络模块,当网卡收到数据包时调用相应驱动程序。

                   +-----+
                   |     |                            Memroy
+--------+   1     |     |  2  DMA     +--------+--------+--------+--------+
| Packet |-------->| NIC |------------>| Packet | Packet | Packet | ...... |
+--------+         |     |             +--------+--------+--------+--------+
                   |     |<--------+
                   +-----+         |
                      |            +---------------+
                      |                            |
                    3 | Raise IRQ                  | Disable IRQ
                      |                          5 |
                      |                            |
                      ↓                            |
                   +-----+                   +------------+
                   |     |  Run IRQ handler  |            |
                   | CPU |------------------>| NIC Driver |
                   |     |       4           |            |
                   +-----+                   +------------+
                                                   |
                                                6  | Raise soft IRQ
                                                   |
                                                   ↓

数据包从网卡到内存的流程如下:

  • 数据包从外部网络进入物理网卡,根据目的地址区分,如果不是网卡则会被丢弃;
  • 网卡将数据以 DMA 方式写入到内存中,该内存地址由网卡驱动分配并初始化;
  • 网卡通过硬件中断通知 CPU 有数据;
  • CPU 根据中断表调用已经注册的中断函数,中断函数会调用相应的驱动程序;
  • 驱动程序先禁用网卡中断,表示内存中已经有数据了,并且下次再收到数据直接写内存,不需要通知 CPU;
  • 启用软中断并恢复硬中断,由于硬中断执行过程中会中断 CPU 导致无法响应其它硬件中断。这样内核将硬中断处理过程中耗时操作移到软中断处理函数中慢慢处理。

内核网络模块

                                                     +-----+
                                             17      |     |
                                        +----------->| NIC |
                                        |            |     |
                                        |Enable IRQ  +-----+
                                        |
                                        |
                                  +------------+                                      Memroy
                                  |            |        Read           +--------+--------+--------+--------+
                 +--------------->| NIC Driver |<--------------------- | Packet | Packet | Packet | ...... |
                 |                |            |          9            +--------+--------+--------+--------+
                 |                +------------+
                 |                      |    |        skb
            Poll | 8      Raise softIRQ | 6  +-----------------+
                 |                      |             10       |
                 |                      ↓                      ↓
         +---------------+  Call  +-----------+        +------------------+        +--------------------+  12  +---------------------+
         | net_rx_action |<-------| ksoftirqd |        | napi_gro_receive |------->| enqueue_to_backlog |----->| CPU input_pkt_queue |
         +---------------+   7    +-----------+        +------------------+   11   +--------------------+      +---------------------+
                                                               |                                                      | 13
                                                            14 |        + - - - - - - - - - - - - - - - - - - - - - - +
                                                               ↓        ↓
                                                    +--------------------------+    15      +------------------------+
                                                    | __netif_receive_skb_core |----------->| packet taps(AF_PACKET) |
                                                    +--------------------------+            +------------------------+
                                                               |
                                                               | 16
                                                               ↓
                                                      +-----------------+
                                                      | protocol layers |
                                                      +-----------------+

内核处理网络数据包的流程如下:

  • 内核 ksoftirqd 进程负责处理软中断,当它收到软中断后调用相应的软中断处理函数处理,ksoftirqd 调用内核网络模块的 net_rx_action 函数;
  • net_rx_action 调用网卡驱动的 poll 函数处理数据包;
  • poll 函数一个接一个读取网卡写到内存中的数据;
  • 驱动将内存中的数据包转换为内核模块可以识别的 skb 数据格式,并调用 napi_gro_receive 函数合并数据包,这样只需要调用一次协议栈就可以处理所有数据包;
  • 接着 CPU 将数据包交给内核协议栈处理,等待内存所有数据包处理完毕后,启用网卡硬中断,这样下次网卡收到数据时会通知 CPU;

协议栈处理

          |
          |
          ↓         promiscuous mode &&
      +--------+    PACKET_OTHERHOST (set by driver)   +-----------------+
      | ip_rcv |-------------------------------------->| drop this packet|
      +--------+                                       +-----------------+
          |
          |
          ↓
+---------------------+
| NF_INET_PRE_ROUTING |
+---------------------+
          |
          |
          ↓
      +---------+
      |         | enabled ip forword  +------------+        +----------------+
      | routing |-------------------->| ip_forward |------->| NF_INET_FORWARD |
      |         |                     +------------+        +----------------+
      +---------+                                                   |
          |                                                         |
          | destination IP is local                                 ↓
          ↓                                                 +---------------+
 +------------------+                                       | dst_output_sk |
 | ip_local_deliver |                                       +---------------+
 +------------------+
          |
          |
          ↓
 +------------------+
 | NF_INET_LOCAL_IN |
 +------------------+
          |
          |
          ↓
    +-----------+
    | UDP layer |
    +-----------+

IP层处理流程

  • ip_rcv: ip_rcv函数是IP模块的入口函数,在该函数里面,第一件事就是将垃圾数据包(目的mac地址不是当前网卡,但由于网卡设置了混杂模式而被接收进来)直接丢掉,然后调用注册在NF_INET_PRE_ROUTING上的函数
  • NF_INET_PRE_ROUTING: netfilter放在协议栈中的钩子,可以通过iptables来注入一些数据包处理函数,用来修改或者丢弃数据包,如果数据包没被丢弃,将继续往下走
  • routing: 进行路由,如果是目的IP不是本地IP,且没有开启ip forward功能,那么数据包将被丢弃,如果开启了ip forward功能,那将进入ip_forward函数
  • ip_forward: ip_forward会先调用netfilter注册的NF_INET_FORWARD相关函数,如果数据包没有被丢弃,那么将继续往后调用dst_output_sk函数
  • dst_output_sk: 该函数会调用IP层的相应函数将该数据包发送出去,同下一篇要介绍的数据包发送流程的后半部分一样。
  • ip_local_deliver:如果上面routing的时候发现目的IP是本地IP,那么将会调用该函数,在该函数中,会先调用NF_INET_LOCAL_IN相关的钩子程序,如果通过,数据包将会向下发送到UDP层。

UDP层处理流程

  • udp_rcv: udp_rcv函数是UDP模块的入口函数,它里面会调用其它的函数,主要是做一些必要的检查,其中一个重要的调用是__udp4_lib_lookup_skb,该函数会根据目的IP和端口找对应的socket,如果没有找到相应的socket,那么该数据包将会被丢弃,否则继续
  • sock_queue_rcv_skb: 主要干了两件事,一是检查这个socket的receive buffer是不是满了,如果满了的话,丢弃该数据包,然后就是调用sk_filter看这个包是否是满足条件的包,如果当前socket上设置了filter,且该包不满足条件的话,这个数据包也将被丢弃(在Linux里面,每个socket上都可以像tcpdump里面一样定义filter,不满足条件的数据包将会被丢弃)
  • __skb_queue_tail: 将数据包放入socket接收队列的末尾
  • sk_data_ready: 通知socket数据包已经准备好

最后调用完 sk_data_ready 后,数据包处理完成,并等待应用层程序读取,上面所有函数的执行过程都在软中断上下文中。

Socket层

应用层有两种方式接收数据,一种是 recvfrom 函数阻塞等待数据,这种情况下 socket 接收到数据数据后 recvfrom 函数就会唤醒然后读取接收队列的数据;另一种是通过 epoll/select 监听 socket,收到通知后调用 recvfrom 函数去读取接收队列数据。


Linux 数据包发送过程

https://segmentfault.com/a/1190000008926093


posted @ 2024-02-17 15:53  Stitches  阅读(19)  评论(0编辑  收藏  举报