little things about dataplane
  • Introduction
  • 1.about Linux Bridge
  • 2.dpdk nic offload
  • 3.2.dpdk nic offload:2
  • 4.1Linux-kernel-softirq.1
  • 5.SystemTap instrument
  • 5.SystemTap instrument.1
  • 6.stap script
  • 6 stap script.1
Powered by GitBook
On this page

Was this helpful?

5.SystemTap instrument.1

上述的函数将napi_struct从softnet-data中取出来,但函数运行的时候硬件中断还是有可能发生并发生抢占,因此另外一个函数完成local irq的disable操作。

void napi_complete(struct napi_struct *n)
{
    unsigned long flags;
    if (unlikely(test_bit(NAPI_STATE_NPSVC, &n->state)))
        return;
    napi_gro_flush(n, false);
    local_irq_save(flags);
    __napi_complete(n);
    local_irq_restore(flags);
}

自此,对napi的基本操作已经完成了,接下来对napi的回调函数遵循的一般处理流量作出简单介绍(例子来自realtek 8139驱动driver/net/ethernet/realtek/8139too.c rtl8139_poll()函数):

int poll_callback_func(napi,budget)
{
    quanta=process_on_their_own_logic();
    if(quanta<budget){
        napi_complete(napi);
    }
    return quanta;
}

当poll函数被调度,poll函数被执行,程序应该确定这一次处理中得到的份额,如果大于或者等于budget,则直接返回,回调函数在之后会被在此再次调度执行;相反如果这个处理数量小于budge,则说明工作已经做完,该回调函数应该将napi从列表中删除。

5.1 数据包L2接收流程

在NIC驱动中数据通过NAPI接收到后,进入标准的协议栈。有两个常用的函数给可以将数据包交给kernel网络协议栈,netif_rx()和netif_receive_skb()。在enable RPS的情况下,其区别在于netif_rx()无论什么情况下都会将数据包交给上层协议栈,而netif_receive_skb()在rps处理失效的情况下或者没有启用RPS的情况下直接开始数据包的L2处理。

RPS根据sk_buff中的rx_hash或者利用软件计算hash来将报文映射到不同的cpu。并将数据包交给目的cpu所对应的softet-data的积压队列上。即将将数据包skb挂到该softnet-data的input_pkt_queue上。

enqueue_to_backlog()函数完成入队列操作。因为有可能有多个cpu向同一个cpu发送数据包,因此每一个softnet-data维护一个input_pkt_queue.lock的spinlock来序列化多个cpu的并发入队列操作。接下来通知上层协议栈在适当的时候开始处理。上层协议栈处理入口任务也在一个特殊的napi_struct里:softnet-data.backlog,其初始化在net/core/dev.c中的net_dev_init()中完成.

在enqueue_to_backlog()函数中,除了获得锁,将数据包入队列外,还得通知该softirq运行,这个通过过程叫处理器间中断IPI(inter-processors interrupt),但这个过程并不立即通知远程cpu执行softirq,仅仅是触发local cpu的软中断。

接下来我们来到softnet-data的中的napi backlog的poll函数processbacklog()中,这个函数首先会完成IPI的通知部分,其机制为通过在特定的cpu上运行一个函数,这个函数来通知远程cpu触发远程softirq,其函数定义在rps_trigger_softirq()中。接下来从队列中取出一个数据包,并调用 _netif_receive_skb(),紧接着调用__netif_receive_skb_core(),接下来简述处理流程。

首先对与802.1q或者802.1d的以太网类型,会调用vlan_untag()来解析vlan数据包,紧接着调用vlan_do_receive()来早到802.1q id所对应的真正的以太网设备。对于一个以太网设备而言,其vlan设备的信息在vlan-info中,用256*8二维数组组成了索引表。

接下来拿到以太网设备的rx_handler函数,该函数可以通过netdev_rx_handler_register()注册,linux网络子系统中许多features如linux bridge,linux bonding,linux macvlan等需要截获数据包均需要注册该handler。

rx_handler返回值为RX_HANDLER_CONSUMED时,说明回调函数已经接管该sk_buff,协议栈不用管。当rx_handler改变了sk_buff的dev时,需要再度进入处理流程,则返回RX_HANDLER_ANOTHER.接下来根据注册的以太网类型执行相应的协议回调函数。

以arp/ipv4为例,说明协议处理流程。 对arp协议句柄,其注册在net/ipv4/arp.c中arp_init()中完成:

static struct packet_type arp_packet_type = {
    .type =    cpu_to_be16(ETH_P_ARP),
    .func =    arp_rcv,
};

其注册如下所示:

void __init arp_init(void)
{
    neigh_table_init(&arp_tbl);
    dev_add_pack(&arp_packet_type);
    arp_proc_init();
#ifdef CONFIG_SYSCTL
    neigh_sysctl_register(NULL, &arp_tbl.parms, "ipv4", NULL);
#endif
    register_netdevice_notifier(&arp_netdev_notifier);
}

其中调用dev_add_pack()来注册该协议(0x0806). arp_init()函数会被net/ipv4/af_inet.c中inet_init()调用,该子系统初始化函数除了调用arp部分初始化函数,还会调用协议簇中其他协议的初始化,如ipv4的协议注册即上层基本协议的初始化在该函数中完成。ipv4 协议类型定义如下:

static struct packet_type ip_packet_type = {
    .type = cpu_to_be16(ETH_P_IP),
    .func = ip_rcv,
};

以太网II封装中协议INNA占用2个字节,Linux协议栈中利用hash表来组织协议句柄,hash表长为16. 至此L2标准转发完成。

Previous5.SystemTap instrumentNext6.stap script

Last updated 4 years ago

Was this helpful?