Chinaunix首页 | 论坛 | 博客
  • 博客访问: 356126
  • 博文数量: 167
  • 博客积分: 2867
  • 博客等级: 少校
  • 技术积分: 1306
  • 用 户 组: 普通用户
  • 注册时间: 2010-05-12 00:08
文章分类

全部博文(167)

文章存档

2017年(10)

2016年(5)

2015年(9)

2014年(10)

2013年(5)

2012年(17)

2011年(110)

2010年(1)

我的朋友

分类: LINUX

2016-08-31 20:54:41

网络内核收包流程

*二层处理
一般来说,网卡或者二层设备的驱动程序的会调用netif_receive_skb 函数,举个例子,现在驱动大多使用napi的方式收包,netif_receive_skb 一般会作为 napi 的poll函数 :

netif_napi_add()--->napi->poll--->netif_receive_skb


napi 的poll函数是做为网络收包中断处理函数的下半部被调用,当有数据包到达的时候,会先触发一个中断
先进行必要的收包处理,然后转入下半部,就是poll,对数据包进行轮询,收到包后,把包交给netif_receive_skb。

netif_receive_skb--->__netif_receive_skb--->deliver_skb--->pt_prev->func();
           //这里pt_prev 是一个packet_type类型三层协议类型指针,通过dev_add_pack增加到内核的
           //全局链表ptype_base里面,根据特定的条件判断,调用某一个func,这里,比如 
           //ip_packet_type, 对应的ip_rcv

*三层处理
static struct packet_type ip_packet_type __read_mostly = {
        .type = cpu_to_be16(ETH_P_IP), 
        .func = ip_rcv,
};

skb 从 ip_rcv  进入三层

ip_rcv--->ip_rcv_finish--->ip_route_input_noref--->ip_route_input_mc
                                  |                                     --->ip_route_input_slow
                                  |                                     //在这里根据是否多播调用不同的函数,两个函数都会
                                  |                                     //设置rth->dst.input= ip_local_deliver;
                                  |                                     //skb_dst_set(skb, &rth->dst);
                                  |--->dst_input(skb)--->skb_dst(skb)->input(skb)--->ip_local_deliver
 

ip_local_deliver--->ip_local_deliver_finish--->
                            //在这个函数里面,会根据ip包头的protocol类型,选择相应的接收函数对包进行接收:
                            int protocol = ip_hdr(skb)->protocol; 
                           ipprot = rcu_dereference(inet_protos[protocol]);
                           //inet_protos[] 是一个protocol数组,里面存放着所支持的四层protocol
                           //icmp_protocol,udp_protocol,tcp_protocol,igmp_protocol 四个最基础的
                           ...
                           ret = ipprot->handler(skb);
                           //这里的handler 对应着具体协议的接收函数,比如 
                           //.handler =      udp_rcv, 就是udp的接收函数,数据包由此从三层传入四层处理。

struct net_protocol {
        void                    (*early_demux)(struct sk_buff *skb);
        int                     (*handler)(struct sk_buff *skb);
        void                    (*err_handler)(struct sk_buff *skb, u32 info);
        unsigned int            no_policy:1,
                                netns_ok:1;
};

static const struct net_protocol udp_protocol = {
        .handler =      udp_rcv,
        .err_handler =  udp_err,
        .no_policy =    1,
        .netns_ok =     1,
};

static const struct net_protocol tcp_protocol = {
        .early_demux    =       tcp_v4_early_demux,
        .handler        =       tcp_v4_rcv,
        .err_handler    =       tcp_v4_err,
        .no_policy      =       1,
        .netns_ok       =       1,
};
*四层处理
数据包传到四层后是如何处理的?理论上来讲,四层协议拿到包以后,会根据目的端口号进行hash,来找到对应的sock 结构,每一个sock 会有一个sk_receive_queue, 接收到的skb包被挂载到相应sock 的sk_receive_queue 队列上。每一个sock 对应一个socket,sock 和socket 在内核中是两个不同的数据结构。sock是和具体协议相关的,而socket是协议无关的,主要关联一个inode,用户在userspace 打开一个
socket的时候,会绑定一个和这个socket 关联的inode以及一个fd,应用程序通过控制fd来控制socket。前面说过,一个socket 关联一个sock,sock收到的数据包会放入sk_receive_queue 队列里面,这样,应用程序就可以从sk_receive_queue里面读取数据了。最终收数据包结束。


还以udp 收包为例子,数据包进入udp_rcv函数后:

udp_rcv--->__udp4_lib_rcv--->__udp4_lib_lookup_skb--->udp_queue_rcv_skb--->
__udp_queue_rcv_skb--->sock_queue_rcv_skb
                                       
                                       
                                        //根据上述调用链,到sock_queue_rcv_skb 中 
                                       
                                       //
struct sk_buff_head *list = &sk->sk_receive_queue;从而把skb 插入到
                                       
                                       //链表
sk->sk_receive_queue


*应用层收包

    应用层收包,会通过lib库调到内核暴露出来的API接口,对于收包来说,一般来说会调到recvmsg函数

对应udp接口就是udp_recvmsg


udp_recvmsg--->__skb_recv_datagram--->//这个函数会从sk的skb队列里面取出skb
           
                                                                 //struct sk_buff_head *queue = &sk->sk_receive_queue;
                                                                 
                                                                 //skb_queue_walk(queue, skb),取到一个skb后,返回

                   ---->skb_copy_datagram_iovec(skb, sizeof(struct udphdr), msg->msg_iov, copied);

                          //从取到的skb里,把数据拷贝到用户的msg buffer 空间。



   

                                           




阅读(2122) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~