深入理解网络技术内幕 阅读笔记(五)

第十章 帧的接收


处理L2层的帧的函数是由中断事件推动的,硬件会使用中断事件通知CPU,该帧已经可用了。
接收中断事件的CPU会执行do_IRQ函数。IRQ编号会引发正确的处理函数被启用。此处理函数通常是设备驱动程序在设备驱动程序初始化期间所注册的函数。IRQ函数处理例程会在中断模式下执行,即后续的中断事件都会被关闭。
中断处理函数会:
把帧拷贝到sk_buff数据结构,如果该设备使用DMA,驱动程序只需初始化一个指针,不需要做拷贝。
对一些sk_buff参数做初始化。比如skb->protocol。
更新其他一些该设备的私用参数。
为NET_RX_SOFTIRQ软IRQ调度以准备执行,借此通知内核新帧的事。
Linux驱动程序通知内核新帧的事有两种方式:
  • 通过旧函数netif_rx
  • 通过NAPI机制
NAPI背后的主要想法很简单:混合使用中断事件和轮询,而不使用纯粹的中断事件模型。如果接受到新帧时,内核还没完成处理前几个帧的工作,驱动程序就没用必要产生其他的中断事件:让内核一直处理输入队列中的数据会比较简单一点(该设备的中断功能关闭),然后当该队列为空时再重新开启中断功能,如此一来,驱动程序就能获得中断事件和轮询的优点:
  • 异步事件,如帧的接收,是由中断事件指出,如此一来如果设备的入口队列是空的,内核就不用一直去检查了。
  • 如果内核知道设备的入口队列中有数据存在,就没有必要浪费时间去处理中断事件通知信息,用简单的轮询就够了。

struct napi_struct结构

    NAPI驱动有自己的struct napi_struct结构,非NAPI函数共用该CPU对应的softnet_data里的backlog(struct napi_struct )。sofanet_data->poll_list存储处于轮询状态的napi_struct,每个该结构对应一个设备。
    poll:用于把缓冲区从设备的输入队列中退出。NAPI驱动用自己的函数赋值该虚函数,非NAPI驱动共用sofanet_data->backlog ->poll即process_backlog函数。NAPI函数使用自己的私有队列,非NAPI函数使用相关CPU的sofanet_data->input_pkt_queue队列。
    poll_list:在使用NAPI的驱动程序的struct napi_struct中,poll_list仅为指向自己的指针,在调用__napi_schedule时,将poll_list加入到该CPU对应的sofanet_data->poll_list链表中。在执行软IRQ函数net_x_action时,根据此指针获得整个napi_struct结构,并将该结构传入该结构对应的poll函数中,以把缓冲区从设备的输入队列中退出 。
    net_rx_action会持续为softnet_data->poll_list队列中的(处于轮询状态且有数据)设备调用其设备驱动程序所提供的方法,直到入口队列为空。那时就不用再轮询了,而设备驱动程序就可充新开启该设备的中断事件通知功能。值得强调的是,中断功能关闭只针对那些在poll_list中的设备,也就是那些使用NAPI而不共享backlog的设备。
从设备驱动程序的角度看,NAPI和非NAPI之间只有两点差异。首先,NAPI驱动程序必须提供一个poll方法,其次,为帧调度所调用的函数有别:非NAPI调用netif_rx,而NAPI驱动程序调用定义在include/linux/detdevice.h种的__napi_schedule。内核提供一个napi_schedule的包裹函数,检查以确保该设备正在运行,而且该软IRQ还未调度,然后才调用__napi_schedule。这些检查由napi_schedule_prep进行。
这两种驱动程序都会把输入设备排入轮询表(对应CPU的 softnet_data->poll_list),为NET_RX_SOFTIRQ软中断调度以准备执行,最后再由net_rx_action予以处理。这两种驱动最终都会调用__napi_schedule(非NAPI驱动在netif_rx中调用)。一项重要细节是,在两种情况下传给__napi_chedule的struct napi_struct结构。非NAPI设备使用的是内建至CPU的softnet_data->backlog结构,而NAPI设备使用的是涉及他们自己的struct napi_struct结构。

neiif_rx

不同的CPU可以同时执行netif_rx,这不是问题,因为每个CPU都配有一个私有的softnet_data结构以维护状态信息,此外,CPU的softnet_data结构还包括一个私有的输入队列。
netif_rx的主要任务如下:
    1、对sk_buff数据结构的一些字段做初始化(如帧的接收时间—)。
    2、对已接收的帧存储到CPU的私有输入队列,然后出发相关联的软IRQ NET_RX_SOFTIRQ以通知内核。

    3、更新拥塞等级的统计数据。

输入队列由softnet_data->input_pkt_queue管理。缓冲区会用_skb_queue_tail(&queue->input_pkt_queue,skb)排入输入队列,而该CPU的IRQ状态就会恢复,然后此函数返回。把帧排入队列是相当快的,因为不涉及任何内存拷贝,只是指针操作而已。input_pkt_queue是指针列表。__skb_queue_tail把指向新缓冲区的指针添加到列表中,而没有拷贝该缓冲区。

NET_RX_SOFTIRQ软中断可通过napi_sechdule调度以准备执行。注意,只有当新缓冲区添加到空队列时,__napi_sechdule才会被调用,其原因在于如果队列不为空,则NET_RX_SOFTIRQ已经被调度了。


netif_rx_action

netif_rx_action的触发是在驱动程序通知内核有关输入帧存在的时候(NET_RX_SOFTIRQ )。
帧可以在两个地方等待net_rx_action予以处理:
    一个共享的CPU专用队列。非NAPI设备的中断处理例程,调用netif_rx能把帧放入CPU的softnet_data->input_pkt_queue,中断处理函数在此执行。
    设备内存。NAPI驱动程序所用的poll方法会直接从设备(或设备驱动程序的接收环)中取出帧。

net_rx_action的工作相当简单 :浏览poll_list设备列表,而这些设备的入口队列中都有数据,然后为每个设备启用关联的poll虚拟函数。


process_backlog轮询虚拟函数

napi_struct结构的poll虚拟函数会由net_rx_action执行,以处理设备的积压队列,对那些不使用NAPI的设备而言,其初始化值默认设置为process_backlog(softnet_data->backlog = process_backlog)。


netif_receive_skb

netif_receive_skb是处理帧的函数,所以poll虚拟函数都会用到这个函数,包括NAPI和非NAPI。

netif_receive_skb的三个主要任务是:
  • 把帧的副本传给每个协议的分流器,如果正在运行的话。
  • 把帧的副本传给skb->protocol所关联的L3协议处理函数。
  • 负责此层必须处理的一些功能,例如桥接。

如果桥接代码或入口流量控制代码都没有消化该帧,则该帧会传给L3协议处理函数,比如IP。

因为net_rx_action代表的是设备驱动程序和L3协议处理例程的边界,所以netif_receive_skb中必须处理Bringing功能。

深入理解网络技术内幕 阅读笔记(五)_第1张图片



你可能感兴趣的:(Linux)