Linux网络包从中断到接收的一生

系统 Linux
如果在虚拟化环境下,VMM配置GIC ITS (Interrupt Translation Service) ,建立物理中断与虚拟中断的映射完成中断虚拟化使得网卡能直接向VM发出中断,同时通过IO虚拟化,网卡通过IOMMU将packet直接写入虚拟机内核的rx_ring

 [[333072]]

 

Linux网络包从中断到接收的一生

 

linux

既然要讲,那就把一个包的整个包生都说了算了

触发中断

  • 在非虚拟化环境下,网卡通过DMA将packet写入内核的rx_ring环形队列缓冲区,并触发中断。
  • 如果在虚拟化环境下,VMM配置GIC ITS (Interrupt Translation Service) ,建立物理中断与虚拟中断的映射完成中断虚拟化使得网卡能直接向VM发出中断,同时通过IO虚拟化,网卡通过IOMMU将packet直接写入虚拟机内核的rx_ring

Top Half

  • CPU在收到中断之后,调用网卡ISR也就是所谓的中断handler
  • 分配sk_buf并入input_pkt_queue(如果队列已满则丢弃)
  • 发出一个软中断NET_RX_SOFTIRQ,软中断可以被调度例如通过tasklet

Bottom Half

  • sk_buf从input_pkt_queue传入process_queue,根据协议类型调用网络层协议的handler
  • ip_rcv执行包头检查,ip_router_input()进行路由,决定本机/转发/丢弃
  • tcp_v4_rcv执行包头检查,tcp_v4_lookup查询对应的socket和connection,如果正常,tcp_prequeue将skb放进socket接收队列
  • socket随即唤醒所在的进程

 

Linux网络包从中断到接收的一生

 

kqueue

因为epoll没有论文,就说说kqueue是怎么做的吧,kqueue会根据socket绑定的knote链表(每个监听的kqueue都可能创建一个knote),将knote通过反向指针获得kqueue,将knote加入kqueue的就绪队列末尾。如果此时恰好有进程正在监听的话,将会唤醒进程,kqueue会被扫描,并从就绪队列处获得所有的event,从而了解已经就绪的所有socket。

  • 唤醒的进程调用socket recv系统调用,如果是TCP则调用tcp_recvmsg从sk_buffer拷贝数据

Batch

  1. netif_receive_skb_list() 

Linux的NAPI还会继续延迟软中断的处理,等待其积累足够的skb后进行轮询,一次性处理所有的skb。

SKB

skb并不是直接存储报文,而是存储指针,指针只需要移动,就能完成解包,而本身的报文并不需要修改。上一层的协议栈会在处理当前层的同时设置好下一层的头指针,并且移动data指针。与此同时,skb本身是双向链表实现的队列。qlen为链表元素长度,lock为添加元素时的锁。

 

Linux网络包从中断到接收的一生

 

skb结构

谈到指针的用法,这里举个做OS lab时印象深刻的奇淫巧技,也是C的指针变态的地方

  1. #define list_entry(ptr, type, field) \ 
  2.     container_of(ptr, type, field) 
  3. #define container_of(ptr, type, field) \ 
  4.     ((type *)((void *)(ptr) - (u64)(&(((type *)(0))->field)))) 

(u64)(&(((type *)(0))->field))))指的是field在结构体type中的偏移量,通过减去这个偏移量我们就能找出某个对象所在上级type对象的地址,也就是container。

一般来说,我们都会使用下面这样的方式,让链表节点去包裹数据。

  1. struct page_list_node { 
  2.         struct page p; 
  3.     struct list_node *prev; 
  4.     struct list_node *next
  5. }; 

但是,通过指针操作,却可以让数据去包裹链表节点

  1. struct list_head { 
  2.     struct list_head *prev; 
  3.     struct list_head *next
  4. }; 
  5.  
  6. struct page{ 
  7.     struct list_head      list_node; 

在仅仅知道链表节点的情况下,借助成员偏移量即可知道容器对象的位置并取出

  1. list_entry(somenode,struct page,list_node); 

list_head本身可以存在于任何对象上,而他们的entry却能根据参数而指向不同的类型,感觉有点泛型的味道了。

内容来自SJTU,IPADS OS-16-Network

责任编辑:武晓燕 来源: 今日头条
相关推荐

2021-08-06 22:43:54

中断架构传递

2015-04-23 08:51:53

2018-01-18 09:05:05

存储数据包分层

2015-08-03 09:33:21

PH程序员一生

2016-08-24 11:13:30

2023-01-10 08:20:55

RocketMQ消息源码

2017-03-28 13:25:14

Linux网络数据包

2020-10-29 15:05:31

Linux网络包代码

2020-11-29 17:08:50

程序员IT

2018-01-05 12:42:01

Lisa电脑苹果Mac

2012-12-04 10:08:16

2015-03-24 13:39:08

IE

2021-09-28 08:05:56

黑客网络安全网络攻击

2014-10-11 11:35:49

2012-04-16 09:24:49

程序员

2016-01-25 13:22:45

SparkSparkSQL数据分析

2021-12-28 18:23:49

Java指令

2010-01-07 09:32:19

2022-04-15 15:11:41

清华计算机研究所

2017-04-11 17:22:57

编程程序员语言
点赞
收藏

51CTO技术栈公众号