网络内核收包流程
*二层处理
一般来说,网卡或者二层设备的驱动程序的会调用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 空间。
阅读(2135) | 评论(0) | 转发(0) |