网络数据帧的代码流程

1.net/core/dev.c
    在net_dev_init中初始化两个链表:ptype_base和ptype_all
    struct list_head ptype_base[PTYPE_HASH_SIZE] __read_mostly;
    struct list_head ptype_all __read_mostly;   /* Taps */
    INIT_LIST_HEAD(&ptype_all);
    for (i = 0; i < PTYPE_HASH_SIZE; i++)
        INIT_LIST_HEAD(&ptype_base[i]);

2.net/ipv4/af_inet.c
    static struct packet_type ip_packet_type __read_mostly = {
        .type = cpu_to_be16(ETH_P_IP),
        .func = ip_rcv,
        .list_func = ip_list_rcv,
    };
    在inet_init中初始化ip协议packet_type结构体根据ETH_P_IP类型挂载全局的ptype_base链表中
    dev_add_pack(&ip_packet_type);
        dev_add_pack在---net/core/dev.c中定义
            ptype_head
                if (pt->type == htons(ETH_P_ALL)) 放入ptype_all
                else 放入ptype_base中

netif_receive_skb函数中根据收到数据包的类型为索引来遍历ptype_base数组,找到挂接的ip协议类型,然后就能把数据包交给注册的L3回调函数:ip_rcv来处理了netif_receive_skb的主要作用体现在两个遍历链表的操作中,其中之一为遍历ptype_all双向链表,这些为注册到内核的一些sniffer,将上传给这些sniffer,另一个就是遍历ptype_base hash表,这个就是具体的协议类型
当eth1接收到一个IP数据包时,它首先分别发送一份副本给每个ptype_all链表中的packet_type,它们都由package_rcv处理,然后在根据HASH值,再遍历另一个HASH表时,发送一份给
类型为ETH_P_IP的类型,它由ip_rcv处理。如果这个链表中还注册有其他IP层的协议,它也会同时发送一个副本给它.

3.net/core/dev.c
netif_receive_skb                ---根据ptype_base的hash表,来选择网络层处理函数
    netif_receive_skb_internal
        enqueue_to_backlog        if (static_branch_unlikely(&rps_needed)) ---将数据包skb放入接收队列:input_pkt_queue中
        __netif_receive_skb                    ---上送协议栈
            __netif_receive_skb_one_core
                __netif_receive_skb_core                           
                    if (likely(!deliver_exact)) {deliver_ptype_list_skb(skb, &pt_prev, orig_dev, type, &ptype_base[ntohs(type) & PTYPE_HASH_MASK]);}
                    *ppt_prev = pt_prev;  ---就是ptype_base中的ip_packet_type(.func = ip_rcv),根据skb->protocol,协议类型获取对应数组元素
                ret = INDIRECT_CALL_INET(pt_prev->func, ipv6_rcv, ip_rcv, skb, skb->dev, pt_prev, orig_dev);

4.net/ipv4/ip_input.c
ip_rcv                                                                
    ip_rcv_core
    ip_rcv_finish
        l3mdev_ip_rcv
        ip_rcv_finish_core                                                ---实现skb与rth->dst关联
            ip_route_input_noref                ---net/ipv4/route.c            查找路由表,其结果会决定报文的去向
                ip_route_input_rcu                ---net/ipv4/route.c
                    ip_route_input_slow            ---net/ipv4/route.c
        dst_input                                ---include/net/dst.h
            return skb_dst(skb)->input(skb);    ---include/net/dst.h
                skb_dst                            ---include/linux/skbuff.h (return (struct dst_entry *)(skb->_skb_refdst & SKB_DST_PTRMASK);)
                |    struct dst_entry            ---include/net/dst.h
                |
                |
struct rtable {    |                                ---include/net/route.h
    struct dst_entry    dst;                        ---ip_local_deliver
    ...
};
 
5.net/ipv4/route.c
ip_route_input_rcu
    ip_route_input_slow
        设置路由:res->type == RTN_BROADCAST --> ip_mkroute_input
            ip_mkroute_input
                __mkroute_input
                    rt_dst_alloc设置:rt->dst.output和rt->dst.input
                    rth->dst.input = ip_forward; 覆盖rt->dst.input
                    skb_dst_set(skb, &rth->dst);
        本地路由:res->type == RTN_LOCAL --> rt_dst_alloc
            rt_dst_alloc    设置路由表的出口函数和入口函数:rt->dst.output和rt->dst.input
                rt->dst.input = ip_local_deliver;  --- 本地包
        skb_dst_set(skb, &rth->dst);  --> 设置skb和rth下的dst关联起来
            
ip_local_deliver                           
    ip_defrag    使得IP分片重组                                                        ---net/ipv4/ip_fragment.c
    ip_local_deliver_finish    (netfilter:NF_INET_LOCAL_IN)                            ---net/ipv4/ip_input.c
        ip_protocol_deliver_rcu                                                        ---net/ipv4/ip_input.c
            ipprot = rcu_dereference(inet_protos[protocol]);
                static struct net_protocol tcp_protocol = {            ---net/ipv4/af_inet.c 在static int __init inet_init(void)中使用inet_add_protocol注册
                    .handler    =   tcp_v4_rcv,
                }
            ret = INDIRECT_CALL_2(ipprot->handler, tcp_v4_rcv, udp_rcv, skb);        ---net/ipv4/ip_input.c      
            tcp_v4_rcv                                                                ---net/ipv4/tcp_ipv4.c
                th = (const struct tcphdr *)skb->data;//tcp header
                iph = ip_hdr(skb);//获取ip header
                tcp_v4_do_rcv                   接收数据                              ---net/ipv4/tcp_ipv4.c
                    tcp_rcv_established           ESTABLISH状态的数据包处理       ---net/ipv4/tcp_input.c  
---快路径                eaten = tcp_queue_rcv(sk, skb, &fragstolen);    ---数据加入接收列队,添加数据到 sk_receive_queue 中
                        tcp_data_ready(sk);
                        
---慢路径                tcp_data_queue                                              ---net/ipv4/tcp_input.c
                            tcp_data_ready if (!sock_flag(sk, SOCK_DEAD))            ---net/ipv4/tcp_input.c
                                sk->sk_data_ready(sk);                                 ---net/ipv4/tcp_input.c


inet_create                                                            net/ipv4/af_inet.c,在socket的pf->create时调用
    sock_init_data                                                    ---net/core/sock.c                                    
        sk->sk_data_ready   =   sock_def_readable;                    ---net/core/sock.c    
            sock_def_readable                                当软中断上收到数据包时会通过调 sk_data_ready 函数指针 来唤醒在 sock 上等待的进程        
                rcu_dereference                                获取wait queue
                if (skwq_has_sleeper(wq))                    有进程在此socket的等待队列
                    wake_up_interruptible_sync_poll            唤醒等待队列上的进程
                        __wake_up_sync_key
                            __wake_up_common_lock
                                __wake_up_common
                                    ret = curr->func(curr, mode, wake_flags, key); 此回调函数唤醒,在初始化的时候有定义DEFINE_WAIT_FUNC(wait, woken_wake_function);
                                    
inet_recvmsg                                                                        ---net/ipv4/af_inet.c
    err = INDIRECT_CALL_2(sk->sk_prot->recvmsg, tcp_recvmsg, udp_recvmsg, sk, msg, size, flags & MSG_DONTWAIT, flags & ~MSG_DONTWAIT, &addr_len);
    tcp_recvmsg                                                                        ---net/ipv4/tcp.c
        skb_queue_walk(&sk->sk_receive_queue, skb                    来遍历接收队列接收数据
        sk_wait_data                                                等待队列处理    ---net/core/sock.c 
            DEFINE_WAIT_FUNC(wait, woken_wake_function);            
                .private    = current,                                把当前进程描述符current关联到其.private成员上
                .func       = function,                                注册回调函数function(woken_wake_function)
                .entry      = LIST_HEAD_INIT((name).entry),         定义了一个等待队列wait
            add_wait_queue(sk_sleep(sk), &wait);                    把新定义的等待队列项wait,插入到sock对象的等待队列下
                sk_sleep                                            获取sock对象下的等待队列列表头wait_queue_head_t
            rc = sk_wait_event(sk, timeo, skb_peek_tail(&sk->sk_receive_queue) != skb, &wait);    进程将进入睡眠状态

        
inet_add_protocol
    cmpxchg
         cmpxchg(ptr, o, n)宏的作用就一目了然了。 如果 ptr 值和 old相等, 则将new赋值给ptr且返回old, 否则返回new

struct proto tcp_prot = {                
    tcp_v4_do_rcv
        tcp_rcv_established
        
6.net/core/dev.c    
netif_rx
    netif_rx_internal
        enqueue_to_backlog                    ---将数据包skb放入接收队列:input_pkt_queue中 
             __skb_queue_tail(&sd->input_pkt_queue, skb);     --> 将skb添加到CPU的输入队列:input_pkt_queue
                
7.net/core/dev.c    
net_dev_init        
    skb_queue_head_init(&sd->input_pkt_queue);
    open_softirq(NET_RX_SOFTIRQ, net_rx_action);
        net_rx_action
        
8.net/core/dev.c
netif_rx_ni
    do_softirq
         
网络设备子系统的初始化:net_dev_init
    1. 在proc文件系统上创建此设备相关的目录,调用:dev_proc_init
    2. 在sys目录下创建网络设备的目录,调用:netdev_kobject_init
    3. 为每个CPU创建一个struct softnet_data sd变量
    
从网卡收包到上送协议栈的两种模式:
1) 一种是传统的中断模式:即收到一个数据包,执行一次中断处理函数,在此函数中分配skb,替换有数据的skb(DMA已经将数据拷贝到初始化的skb),调用netif_rx将有
    数据的skb放在percpu的队列上(如果开启了RPS,这个队列有可能是本地cpu的,也有可能是其他cpu的),最后激活软中断。之后的软中断处理函数net_rx_action中调用poll函数
    process_backlog(如果将skb放在其他cpu队列上了,还需要通过ipi激活其他cpu的软中断),处理percpu队列上的数据包,上送协议栈__netif_receive_skb。中断模式会触发很多中断,
    影响性能。
2) napi模式,这种模式下,一次中断可以poll收多个数据包(配额64)。具体的为收到一个中断,执行中断处理函数(比如ixgbe_msix_clean_rings),在此函数中只是激活软中断,并不处
    理skb,在之后的软中断处理函数net_rx_action中调用驱动注册的poll函数,比如ixgbe_poll,来收包,上送协议栈netif_receive_skb_internal(如果开启了RPS,就会按照non-napi的
    处理方式,将skb放在percpu的队列上,这个队列有可能是本地cpu的,也有可能是其他cpu的),再经过软中断处理才会将skb上送协议栈__netif_receive_skb。


虚拟网络设备中:老式的napi的poll函数就是process_backlog函数:
报文接收过程中的三个成员:
1)poll_list,网络设备dev的队列。其中的设备接收到了报文,需要被处理;
2) input_pkt_queue,skb报文结构的队里,保存已接收并需要被处理的报文;
3) struct napi_struct *napi,网络设备

内核在中断下半部中调用process_backlog函数处理数据包,其中使用process_queue处理队列,来做input_pkt_queue接收队列的中间桥梁,使在处理数据包时不必关闭本地CPU的中断。
而对input_pkt_queue队列的处理又做到了精简,提高了效率,关闭中断的时间缩短了

  • 39
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值