美文网首页
linux 网络协议栈1--从中断到上送协议栈

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

作者: 苏苏林 | 来源:发表于2020-10-10 15:47 被阅读0次

    注: 内核代码是 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--从中断到上送协议栈

          本文链接:https://www.haomeiwen.com/subject/vtkjpktx.html