linux 网络协议栈1--从中断到上送协议栈

注: 内核代码是 4.9 版本

协议栈从报文接收说起,报文接收从网卡驱动说起。

两种方式,NAPI 和 非NAPI。

NAPI(New API) 是Linux内核针对网络数据传输做出的一个优化措施。
其目的是在大量数据传输时, 在收到硬件中断后,通过poll方式将传输过来的数据包统一处理, 通过禁止网络设备中断以减少硬件中断数量((Interrupt Mitigation),从而实现更高的数据传输。

其中要点:

1、硬件中断后开始处理报文。中断处理函数只是触发软中断准备接收报文;

2、软中断中通过pool方式处理报文。通过轮训的方式一次性处理多个报文;

3、禁止网络设备中断以减少硬件中断数量。同上,在软中断处理函数中,将禁止中断,处理完成后,开启中断,这样一次中断处理多个报文。

先从NAPI方式说起

以 Inter 的 e1000 的驱动为例,e1000在加载驱动并做设备初始化时会调用 e1000_probe 函数,完成设备的部分初始化工作,重要的是设备的napi结构 和 poll函数是在这个函数中设置的:

static int e1000_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
{
    struct net_device *netdev;
    struct e1000_adapter *adapter;
    struct e1000_hw *hw;
......

    // 为网卡创建网络设备对象 net_device 结构,并完成组册
    netdev = alloc_etherdev(sizeof(struct e1000_adapter));
    if (!netdev)
        goto err_alloc_etherdev;

    SET_NETDEV_DEV(netdev, &pdev->dev);
        // 设置网卡私有数据
    pci_set_drvdata(pdev, netdev);
    adapter = netdev_priv(netdev);
    adapter->netdev = netdev;
    adapter->pdev = pdev;
    adapter->msg_enable = netif_msg_init(debug, DEFAULT_MSG_ENABLE);
    adapter->bars = bars;
    adapter->need_ioport = need_ioport;

    hw = &adapter->hw;
    hw->back = adapter;

    err = -EIO;
    // 映射寄存器IO区域(后面拷贝报文)
    hw->hw_addr = pci_ioremap_bar(pdev, BAR_0);
    if (!hw->hw_addr)
        goto err_ioremap;

    ......
    // 挂载网络设备操作接口
    netdev->netdev_ops = &e1000_netdev_ops;
    e1000_set_ethtool_ops(netdev);
    netdev->watchdog_timeo = 5 * HZ;
        // 初始化并挂载设备的NAPI接口,e1000_clean 是其poll函数,软中断中调用处理报文
        // adapter->napi 挂到 netdev->napi_list
    netif_napi_add(netdev, &adapter->napi, e1000_clean, 64);

    strncpy(netdev->name, pci_name(pdev), sizeof(netdev->name) - 1);

    ......
}

还有个重要的函数是e1000的网卡中断处理函数e1000_intr,是在上面 e1000_netdev_ops 中的ndo_open函数(网卡UP后会调用)中,调用的 e1000_request_irq注册的。

然后是NAPI的接收流程。
在网卡中断之前,数据包到达网卡之后,就通过DMA直接将数据从网卡拷贝到内存的环形缓冲区了,成为 ring buffer,和非NAPI不同。

中断处理函数,如果没有在运行的NAPI任务,调度一个新的NAPI任务,会调用通用的NAPI处理函数,__napi_schedule,将设备的napi 挂载到当前CPU的 softnet_data 的待轮训设备列表poll_list中,并触发软中断。
napi_schedule_prep 中会检查并设置 napi_struct的 NAPI_STATE_SCHED位,并在流程结束后(软中断处理完报文)调用 __napi_complete,清楚状态位。

static irqreturn_t e1000_intr(int irq, void *data)
{
    struct net_device *netdev = data;
    struct e1000_adapter *adapter = netdev_priv(netdev);
    struct e1000_hw *hw = &adapter->hw;
......
        // 如果没有在运行的NAPI任务,调度一个新的NAPI任务
    if (likely(napi_schedule_prep(&adapter->napi))) {
        adapter->total_tx_bytes = 0;
        adapter->total_tx_packets = 0;
        adapter->total_rx_bytes = 0;
        adapter->total_rx_packets = 0;
                // 调用通用的NAPI处理函数
        __napi_schedule(&adapter->napi);
    } else {
        /* this really should not happen! if it does it is basically a
         * bug, but not a hard error, so enable ints and continue
         */
        if (!test_bit(__E1000_DOWN, &adapter->flags))
            e1000_irq_enable(adapter);
    }

    return IRQ_HANDLED;
}

void __napi_schedule(struct napi_struct *n)
{
    unsigned long flags;

    local_irq_save(flags);
    ____napi_schedule(this_cpu_ptr(&softnet_data), n);
    local_irq_restore(flags);
}

static inline void ____napi_schedule(struct softnet_data *sd,
                     struct napi_struct *napi)
{
        //  将设备的napi 挂载带了 sd 的poll_list中。
    list_add_tail(&napi->poll_list, &sd->poll_list);
       //  触发软中断
    __raise_softirq_irqoff(NET_RX_SOFTIRQ);
}

软中断接收处理函数是net_rx_action,在网络设备模块初始化时注册。

net_dev_init:

    open_softirq(NET_TX_SOFTIRQ, net_tx_action);
    open_softirq(NET_RX_SOFTIRQ, net_rx_action);

内核ksoftirqd%d(%d对应CPU的ID)内核线程用于处理CPU上的软中断。内核在初始化的时候,会在每个CPU上启动一个这样的内核线程用来处理每个CPU上的软中断。
最终会调用到上面注册的收发报的软中断处理函数。

// 注册
static struct smp_hotplug_thread softirq_threads = {
    .store          = &ksoftirqd,
    .thread_should_run  = ksoftirqd_should_run,
    .thread_fn      = run_ksoftirqd,
    .thread_comm        = "ksoftirqd/%u",
};

static __init int spawn_ksoftirqd(void)
{
    register_cpu_notifier(&cpu_nfb);

    BUG_ON(smpboot_register_percpu_thread(&softirq_threads));

    return 0;
}
early_initcall(spawn_ksoftirqd);
// ksoftirqd 处理函数
static void run_ksoftirqd(unsigned int cpu)
{
    local_irq_disable();
    if (local_softirq_pending()) {
        /*
         * We can safely run softirq on inline stack, as we are not deep
         * in the task stack here.
         */
        __do_softirq();
        local_irq_enable();
        cond_resched_rcu_qs();
        return;
    }
    local_irq_enable();
}

asmlinkage __visible void __softirq_entry __do_softirq(void)
{
    ...
    while ((softirq_bit = ffs(pending))) {
        unsigned int vec_nr;
        int prev_count;

        h += softirq_bit - 1;

        vec_nr = h - softirq_vec;
        ...
        h->action(h);
        ...
        h++;
        pending >>= softirq_bit;
    }
    ...
}

收包软中断处理函数,最终调用设备poll 函数处理报文。

static __latent_entropy void net_rx_action(struct softirq_action *h)
{
    struct softnet_data *sd = this_cpu_ptr(&softnet_data);
    unsigned long time_limit = jiffies + 2;
    int budget = netdev_budget;
    LIST_HEAD(list);
    LIST_HEAD(repoll);

    local_irq_disable();
    list_splice_init(&sd->poll_list, &list);
    local_irq_enable();

    for (;;) {
        struct napi_struct *n;

        if (list_empty(&list)) {
            if (!sd_has_rps_ipi_waiting(sd) && list_empty(&repoll))
                return;
            break;
        }
                // 这里始终取第一个,处理完成摘除节点处理是在napi_poll中,着实让我找了一会,代码不对称影响阅读。
        n = list_first_entry(&list, struct napi_struct, poll_list);
                // napi_poll 主要是为了调用设备注册的poll 函数,如果报文未处理完(通过poll 函数的配额判断)会通过repoll记录这个设备的napi结构,后面再挂到sd中。
        budget -= napi_poll(n, &repoll);

        /* If softirq window is exhausted then punt.
         * Allow this to run for 2 jiffies since which will allow
         * an average latency of 1.5/HZ.
         */
                // budget 是每次软中断执行的配额,配额用尽或者poll函数执行的时间超过2个tick,结束处理
        if (unlikely(budget <= 0 ||
                 time_after_eq(jiffies, time_limit))) {
            sd->time_squeeze++;
            break;
        }
    }

    __kfree_skb_flush();
    local_irq_disable();
        //  把这个napi重新加到sd->poll_list头部,等待下次软中断再次poll
    list_splice_tail_init(&sd->poll_list, &list);
    list_splice_tail(&repoll, &list);
    list_splice(&list, &sd->poll_list);
    if (!list_empty(&sd->poll_list))
               // 存在未处理完的情况,再次触发软中断,等待下次处理
        __raise_softirq_irqoff(NET_RX_SOFTIRQ);
    net_rps_action_and_irq_enable(sd);
}


static int napi_poll(struct napi_struct *n, struct list_head *repoll)
{
    void *have;
    int work, weight;
        // 摘节点
    list_del_init(&n->poll_list);

    have = netpoll_poll_lock(n);
        // poll 函数的配额
    weight = n->weight;

    /* This NAPI_STATE_SCHED test is for avoiding a race
     * with netpoll's poll_napi().  Only the entity which
     * obtains the lock and sees NAPI_STATE_SCHED set will
     * actually make the ->poll() call.  Therefore we avoid
     * accidentally calling ->poll() when NAPI is not scheduled.
     */
    work = 0;
    if (test_bit(NAPI_STATE_SCHED, &n->state)) {
                // 调用设备的poll 函数处理报文
        work = n->poll(n, weight);
        trace_napi_poll(n, work, weight);
    }

    WARN_ON_ONCE(work > weight);
        // 配额未用尽,结束处理
    if (likely(work < weight))
        goto out_unlock;

    /* Drivers must not modify the NAPI state if they
     * consume the entire weight.  In such cases this code
     * still "owns" the NAPI instance and therefore can
     * move the instance around on the list at-will.
     */
    if (unlikely(napi_disable_pending(n))) {
        napi_complete(n);
        goto out_unlock;
    }

    if (n->gro_list) {
        /* flush too old packets
         * If HZ < 1000, flush all packets.
         */
        napi_gro_flush(n, HZ >= 1000);
    }

    /* Some drivers may have called napi_schedule
     * prior to exhausting their budget.
     */
    if (unlikely(!list_empty(&n->poll_list))) {
        pr_warn_once("%s: Budget exhausted after napi rescheduled\n",
                 n->dev ? n->dev->name : "backlog");
        goto out_unlock;
    }
        // 配额用尽,说明存在报文未处理完,记录,等待下次软中断处理。
    list_add_tail(&n->poll_list, repoll);

out_unlock:
    netpoll_poll_unlock(have);

    return work;
}

设备的poll 函数,e1000_clean。adapter->clean_rx 是在e1000_open中挂载的e1000_clean_rx_irq,它循环处理设备ring buf中的报文,并调用 e1000_receive_skb处理。

static int e1000_clean(struct napi_struct *napi, int budget)
{
    struct e1000_adapter *adapter = container_of(napi, struct e1000_adapter,
                             napi);
    int tx_clean_complete = 0, work_done = 0;

    tx_clean_complete = e1000_clean_tx_irq(adapter, &adapter->tx_ring[0]);

    adapter->clean_rx(adapter, &adapter->rx_ring[0], &work_done, budget);

    if (!tx_clean_complete)
        work_done = budget;

    /* If budget not fully consumed, exit the polling mode */
    if (work_done < budget) {
        if (likely(adapter->itr_setting & 3))
            e1000_set_itr(adapter);
        napi_complete_done(napi, work_done);
        if (!test_bit(__E1000_DOWN, &adapter->flags))
            e1000_irq_enable(adapter);
    }

    return work_done;
}

e1000_receive_skb 解析eth头,获取上次协议类型,以及设置 skb->pkt_type,然后调用napi_gro_receive ,在开启GRO的情况下尝试走GRO接收,否者将数据上送协议栈。
GRO(generic receive offload)主要思想就是,组合一些类似的数据包(基于一些数据域)为一个大的数据包(一个skb),然后feed给协议栈,这里主要是利用Scatter-gather IO,也就是skb的struct skb_shared_info域来合并数据包。

static void e1000_receive_skb(struct e1000_adapter *adapter, u8 status,
                  __le16 vlan, struct sk_buff *skb)
{
        // 解析eth头,获取上次协议类型,以及设置 skb->pkt_type
    skb->protocol = eth_type_trans(skb, adapter->netdev);

    if (status & E1000_RXD_STAT_VP) {
        u16 vid = le16_to_cpu(vlan) & E1000_RXD_SPC_VLAN_MASK;

        __vlan_hwaccel_put_tag(skb, htons(ETH_P_8021Q), vid);
    }
    napi_gro_receive(&adapter->napi, skb);
}

gro_result_t napi_gro_receive(struct napi_struct *napi, struct sk_buff *skb)
{
    skb_mark_napi_id(skb, napi);
    trace_napi_gro_receive_entry(skb);

    skb_gro_reset_offset(skb);
        
    return napi_skb_finish(dev_gro_receive(napi, skb), skb);
}
EXPORT_SYMBOL(napi_gro_receive);

// 根据 dev_gro_receive 的返回结果处理报文
static gro_result_t napi_skb_finish(gro_result_t ret, struct sk_buff *skb)
{
    switch (ret) {
        // 不支持GRO,送入协议栈
    case GRO_NORMAL:
        if (netif_receive_skb_internal(skb))
            ret = GRO_DROP;
        break;
         // skb被合并(数据区),skb可以释放
    case GRO_DROP:
        kfree_skb(skb);
        break;
    case GRO_MERGED_FREE:
        /*skb数据被合并入其它skb(数据区),或合并后发送,skb可以释放。 */ 
        if (NAPI_GRO_CB(skb)->free == NAPI_GRO_FREE_STOLEN_HEAD)
            napi_skb_free_stolen_head(skb);
        else
            __kfree_skb(skb);
        break;
        // 报文已经被保存,但没做合并,skb被接管不需要释放
    case GRO_HELD:
    case GRO_MERGED:
        break;
    }

    return ret;
}

最后送入协议栈的处理都是调用的 netif_receive_skb_internal,如果配置了RPS,会走RPS接收流程,选中CPU后,走一遍非NAPI收包流程,不做详细说。否者,调用__netif_receive_skb 上送上次协议栈。
RPS全称Receive packet Steering,用于在软件层面实现报文在多个CPU之间的负载均衡以及提高报文处理的缓存命中率,和它类似的还有一个RFS(Receive Flow Steering)rps和rfs出现的原因主要有以下两个:
1、 对于多队列网卡,网卡硬件接收队列与cpu核数在数量上不匹配导致报文在cpu之间分配不均。
2、 对于单队列网卡,rps和rfs可以在软件层面将报文平均分配到多个cpu上。

static int netif_receive_skb_internal(struct sk_buff *skb)
{
    int ret;

    net_timestamp_check(netdev_tstamp_prequeue, skb);

    if (skb_defer_rx_timestamp(skb))
        return NET_RX_SUCCESS;

    rcu_read_lock();

#ifdef CONFIG_RPS
    if (static_key_false(&rps_needed)) {
        struct rps_dev_flow voidflow, *rflow = &voidflow;
        // 根据报文以及入接口信息获取CPU以及rps_dev_flow
        int cpu = get_rps_cpu(skb->dev, skb, &rflow);

        if (cpu >= 0) {
            /* 这个是非NAPI中断收包的流程,这里选中了CPU之后,将报文放入softnet_data的input_pkt_queue队列中,
              softnet_data是每CPU的私有数据对象,它自带一个napi_struct成员backlog,函数enqueue_to_backlog
              就是将backlog加入到softnet_data的待轮训设备列表中,并触发软中断,在软中断处理函数net_rx_action()
              中,同样会调用backlog代表的CPU共用轮训设备的poll函数,为process_backlog(net_dev_init中初始化),
              该函数会调用__netif_receive_skb()函数将报文送到上层协议栈处理。
            */
            ret = enqueue_to_backlog(skb, cpu, &rflow->last_qtail);
            rcu_read_unlock();
            return ret;
        }
    }
#endif
    ret = __netif_receive_skb(skb);
    rcu_read_unlock();
    return ret;
}

static int enqueue_to_backlog(struct sk_buff *skb, int cpu,
                  unsigned int *qtail)
{
    struct softnet_data *sd;
    unsigned long flags;
    unsigned int qlen;

    sd = &per_cpu(softnet_data, cpu);

    local_irq_save(flags);

    rps_lock(sd);
    if (!netif_running(skb->dev))
        goto drop;
    qlen = skb_queue_len(&sd->input_pkt_queue);
    if (qlen <= netdev_max_backlog && !skb_flow_limit(skb, qlen)) {
        if (qlen) {
enqueue:
            // 报文挂载到input_pkt_queue中
            __skb_queue_tail(&sd->input_pkt_queue, skb);
            input_queue_tail_incr_save(sd, qtail);
            rps_unlock(sd);
            local_irq_restore(flags);
            return NET_RX_SUCCESS;
        }

        /* Schedule NAPI for backlog device
         * We can use non atomic operation since we own the queue lock
         */
        // 测试释放已经有调度实例
        if (!__test_and_set_bit(NAPI_STATE_SCHED, &sd->backlog.state)) {
            if (!rps_ipi_queued(sd))
                // 这里将sd 自己的napi_struct 挂到了待轮训列表,设备无关的,共用的。
                ____napi_schedule(sd, &sd->backlog);
        }
        goto enqueue;
    }

drop:
    sd->dropped++;
    rps_unlock(sd);

    local_irq_restore(flags);

    atomic_long_inc(&skb->dev->rx_dropped);
    kfree_skb(skb);
    return NET_RX_DROP;
}

后面就是根据上层协议类型,调用对应的协议接收处理函数处理报文,算是正式进入上层协议栈了。无论是否是能了RPS,最终都会调用__netif_receive_skb 调用__netif_receive_skb_core,主要做:
1、ptype_all处理,例如抓包程序、raw socket等;
2、如果存在vlan头,做vlan报文的处理(vlan_do_receive),解析vlan信息,根据vlanid查找vlan子接口,找到后,替换skb->dev返回another_round处重新处理报文,相当于vlan子接口接收到了报文,应该支持多层vlan,QinQ的情况下多次走这个流程;
3、特殊设备接口处理,例如OVS、linux bridge等;
4、ptype_base处理,交给协议栈处理,例如ip、arp等;

static int __netif_receive_skb_core(struct sk_buff *skb, bool pfmemalloc)
{
    struct packet_type *ptype, *pt_prev;
    rx_handler_func_t *rx_handler;
    struct net_device *orig_dev;
    bool deliver_exact = false;
    int ret = NET_RX_DROP;
    __be16 type;

    net_timestamp_check(!netdev_tstamp_prequeue, skb);

    trace_netif_receive_skb(skb);

    orig_dev = skb->dev;

    skb_reset_network_header(skb);
    if (!skb_transport_header_was_set(skb))
        skb_reset_transport_header(skb);
    skb_reset_mac_len(skb);

    pt_prev = NULL;

another_round:
    // 送上层协议栈前设置iif
    skb->skb_iif = skb->dev->ifindex;

    __this_cpu_inc(softnet_data.processed);

    if (skb->protocol == cpu_to_be16(ETH_P_8021Q) ||
        skb->protocol == cpu_to_be16(ETH_P_8021AD)) {
        // 这里解析了vlan头,再skb中记录vlan id,上层协议类型,并下移skb->data
        skb = skb_vlan_untag(skb);
        if (unlikely(!skb))
            goto out;
    }

#ifdef CONFIG_NET_CLS_ACT
    if (skb->tc_verd & TC_NCLS) {
        skb->tc_verd = CLR_TC_NCLS(skb->tc_verd);
        goto ncls;
    }
#endif

    if (pfmemalloc)
        goto skip_taps;
    // ptype_all,如tcpdump,所有包都会调用注册的handle处理
    list_for_each_entry_rcu(ptype, &ptype_all, list) {
        if (pt_prev)
            ret = deliver_skb(skb, pt_prev, orig_dev);
        pt_prev = ptype;
    }
        // 协议区分全局和设备指定的
    list_for_each_entry_rcu(ptype, &skb->dev->ptype_all, list) {
        if (pt_prev)
            ret = deliver_skb(skb, pt_prev, orig_dev);
        pt_prev = ptype;
    }

skip_taps:
#ifdef CONFIG_NET_INGRESS
    if (static_key_false(&ingress_needed)) {
        skb = sch_handle_ingress(skb, &pt_prev, &ret, orig_dev);
        if (!skb)
            goto out;

        if (nf_ingress(skb, &pt_prev, &ret, orig_dev) < 0)
            goto out;
    }
#endif
#ifdef CONFIG_NET_CLS_ACT
    skb->tc_verd = 0;
ncls:
#endif
    if (pfmemalloc && !skb_pfmemalloc_protocol(skb))
        goto drop;

    if (skb_vlan_tag_present(skb)) {
        if (pt_prev) {
            ret = deliver_skb(skb, pt_prev, orig_dev);
            pt_prev = NULL;
        }
        // 这里主要做的事情是更新skb->dev为vlan id对应的设备(vlan子接口),然后再走一遍接收处理。
        if (vlan_do_receive(&skb))
            goto another_round;
        else if (unlikely(!skb))
            goto out;
    }
    /*
    bridge、ovs的接口,都会走到。
    如果一个dev被添加到一个bridge(做为bridge的一个接口),这个接口设备的rx_handler将被设置为,
    br_handle_frame函数这是在br_add_if函数中设置的,而br_add_if (net/bridge/br_if.c)是在向
    网桥设备上添加接口时设置的。进入br_handle_frame也就进入了bridge的逻辑代码。*/
    rx_handler = rcu_dereference(skb->dev->rx_handler);
    if (rx_handler) {
        if (pt_prev) {
            ret = deliver_skb(skb, pt_prev, orig_dev);
            pt_prev = NULL;
        }
        switch (rx_handler(&skb)) {
        case RX_HANDLER_CONSUMED:  // 报文已经被消费,结束处理
            ret = NET_RX_SUCCESS;
            goto out;
        case RX_HANDLER_ANOTHER:  // skb->dev 被修改,重新走一次
            goto another_round;
        case RX_HANDLER_EXACT: /* 精确传递到ptype->dev == skb->dev */
            deliver_exact = true;
        case RX_HANDLER_PASS:
            break;
        default:
            BUG();
        }
    }

    if (unlikely(skb_vlan_tag_present(skb))) {
        // 还有vlan标记,说明找不到vlanid对应的设备,存在vlanid,则判定是到其他设备的包
        if (skb_vlan_tag_get_id(skb))
            skb->pkt_type = PACKET_OTHERHOST;
        /* Note: we might in the future use prio bits
         * and set skb->priority like in vlan_do_receive()
         * For the time being, just ignore Priority Code Point
         */
        skb->vlan_tci = 0;
    }

    type = skb->protocol;

    /* deliver only exact match when indicated */
    /* 设置三层协议,下面提交都是按照解析三层协议提交的,调用最终的三层协议注册的handler,如ip_rcv */
    if (likely(!deliver_exact)) {
        deliver_ptype_list_skb(skb, &pt_prev, orig_dev, type,
                       &ptype_base[ntohs(type) &
                           PTYPE_HASH_MASK]);
    }

    deliver_ptype_list_skb(skb, &pt_prev, orig_dev, type,
                   &orig_dev->ptype_specific);

    if (unlikely(skb->dev != orig_dev)) {
        deliver_ptype_list_skb(skb, &pt_prev, orig_dev, type,
                       &skb->dev->ptype_specific);
    }

    if (pt_prev) {
        if (unlikely(skb_orphan_frags(skb, GFP_ATOMIC)))
            goto drop;
        else
            ret = pt_prev->func(skb, skb->dev, pt_prev, orig_dev);
    } else {
drop:
        if (!deliver_exact)
            atomic_long_inc(&skb->dev->rx_dropped);
        else
            atomic_long_inc(&skb->dev->rx_nohandler);
        kfree_skb(skb);
        /* Jamal, now you will not able to escape explaining
         * me how you were going to use this. :-)
         */
        ret = NET_RX_DROP;
    }

out:
    return ret;
}

static inline int deliver_skb(struct sk_buff *skb,
                  struct packet_type *pt_prev,
                  struct net_device *orig_dev)
{
    if (unlikely(skb_orphan_frags(skb, GFP_ATOMIC)))
        return -ENOMEM;
    atomic_inc(&skb->users);
    return pt_prev->func(skb, skb->dev, pt_prev, orig_dev);
}

各个协议的处理函数,是在各个协议模块初始化的时候注册的,如下面ipv4协议,注册的ip_rcv处理函数。

static struct packet_type ip_packet_type __read_mostly = {

        .type = cpu_to_be16(ETH_P_IP),

        .func = ip_rcv,

};

static int __init inet_init(void)

{

         ...

         dev_add_pack(&ip_packet_type);

         ...

}

非NAPI方式

非NAPI方式,一般流程是(eth口):
1、设备驱动程序调用netdev_alloc_skb 分配sk_buf,并完成数据拷贝;
2、调用eth_type_trans 解析eth头,设置上层协议类型和 pkt_type
3、调用netif_rx --> netif_rx_internal -->enqueue_to_backlog -->___napi_schedule流程,将报文挂载到softnet_data的input_pkt_queue,将 softnet_data的 napi_struct结构的backlog 挂载到 softnet_data 的待轮训设备列表中,触发软中断;
4、软中断中调用 softnet_data的backlog poll函数,处理input_pkt_queue中的报文,即process_backlog.

可以看到,这里的软中断之前,报文已经拷贝input_pkt_queue中,而当软中断开始运行时,input_pkt_queue中可能已经有不同网卡的报文了,process_backlog作为一个CPU 通用的poll函数处理不同网卡的报文。

static int process_backlog(struct napi_struct *napi, int quota)
{
   struct softnet_data *sd = container_of(napi, struct softnet_data, backlog);
   bool again = true;
   int work = 0;

   /* Check if we have pending ipi, its better to send them now,
    * not waiting net_rx_action() end.
    */
   if (sd_has_rps_ipi_waiting(sd)) {
       local_irq_disable();
       net_rps_action_and_irq_enable(sd);
   }

   napi->weight = weight_p;
   while (again) {
       struct sk_buff *skb;
       // 最重要的,出队,调用__netif_receive_skb 送协议栈,同上面NAPI方式。
       while ((skb = __skb_dequeue(&sd->process_queue))) {
           rcu_read_lock();
           __netif_receive_skb(skb);
           rcu_read_unlock();
           input_queue_head_incr(sd);
           if (++work >= quota)
               return work;

       }

       local_irq_disable();
       rps_lock(sd);
       if (skb_queue_empty(&sd->input_pkt_queue)) {
           /*
            * Inline a custom version of __napi_complete().
            * only current cpu owns and manipulates this napi,
            * and NAPI_STATE_SCHED is the only possible flag set
            * on backlog.
            * We can use a plain write instead of clear_bit(),
            * and we dont need an smp_mb() memory barrier.
            */
           napi->state = 0;
           again = false;
       } else {
           skb_queue_splice_tail_init(&sd->input_pkt_queue,
                          &sd->process_queue);
       }
       rps_unlock(sd);
       local_irq_enable();
   }

   return work;
}

你可能感兴趣的:(linux 网络协议栈1--从中断到上送协议栈)