Linux协议栈函数调用流程
普通网络驱动程序中必须要调用的函数是eth_type_trans(略),然后向上递交sk_buff时调用netif_rx()(net/core/dev.c).其函数中主要几行 __skb_queue_tail(&queue->input_pkt_queue, skb);添加skb到接受队列中 netif_rx_schedule(&queue->backlog_dev); 开启接受软中断处理. struct softnet_data * queue 在net_dev_init()(dev.c)中初始化.其中有: open_softirq(NET_TX_SOFTIRQ, net_tx_action, NULL); //发送sk_buff软中断 open_softirq(NET_RX_SOFTIRQ, net_rx_action, NULL); //接受sk_buff软中断 [进入软中断] 首先来看net_rx_action(), 其中最重要一行 if (dev->quota <= 0 || dev->poll(dev, &budget)) dev->poll()处理skb的递交, 此回掉函数也是在net_dev_init()中初始化,其中queue->backlog_dev.poll = process_backlog;初始化了这个回掉函数. process_backlog 函数循环递交队列中所有的skb. for(;;) { ...... skb = __skb_dequeue(&queue->input_pkt_queue); ...... netif_receive_skb(skb); ...... } 接下来 netif_receive_skb 进一步处理skb的递交,我们来看. ...... skb->h.raw = skb->nh.raw = skb->data; //初始化 sk_buff 中的 ip或其他协议的头 skb->mac_len = skb->nh.raw - skb->mac.raw; ...... if (handle_bridge(&skb, &pt_prev, &ret, orig_dev)) //处理桥接数据,具体参考 bridge 实现. goto out; ...... list_for_each_entry_rcu(ptype, &ptype_base[ntohs(type)&15], list) { if (ptype->type == type && (!ptype->dev || ptype->dev == skb->dev)) { if (pt_prev) ret = deliver_skb(skb, pt_prev, orig_dev); //向上层递交 pt_prev = ptype; } } 这是最主要的递交过程,根据协议类型向上层协议层递交 skb. 关于NAPI 你需要实现自己的poll而不是默认的process_backlog, 在你自己的poll函数中你需要调用netif_rx_schedule 而不是一般的 netif_rx,现在明白其实你自己实现的poll就是在软中断中被调用,代替netif_rx中给你的默认poll. 下面进入具体协议过程: static struct packet_type ip_packet_type = { // ip 类型 af_inet.c .type = __constant_htons(ETH_P_IP), .func = ip_rcv, .gso_send_check = inet_gso_send_check, .gso_segment = inet_gso_segment, }; static struct packet_type arp_packet_type = { // arp 类型 arp.c .type = __constant_htons(ETH_P_ARP), .func = arp_rcv, }; 其他略,自己看去. 所有类型通过函数 dev_add_pack 注册到 ptype_base. ip_rcv 就是开始 ip 层协议解析的开始 . ip_rcv 中有 return NF_HOOK(PF_INET, NF_IP_PRE_ROUTING, skb, dev, NULL, ip_rcv_finish); 如果核心编译了netfilter,就会调用相关的hook函数,具体参考netfilter实现说明。现在我们关心的是 ip_rcv_finish. 其中最重要的是 ip_route_input 和 dst_input, dst_input 中主要是 err = skb->dst->input(skb); 那现在我们知道要在 ip_route_input 中查找或创造路由表,也就是找到skb->dst 然后初始化着指针,那么最后会调用dst的input函数. 那接下来主要看 ip_route_input 函数: 函数中主要调用 ip_route_input_slow: 如果数据报是本地的: ...... rth->u.dst.input= ip_local_deliver; ...... 如果数据报需要被转发: ...... ip_mkroute_input -> __mkroute_input 中有 rth->u.dst.input = ip_forward; ...... 其他可能还有 ip_mr_input 处理多播等函数. 路由表和转发等其他过程不是讨论的范围,下面我们看 ip_local_deliver. 首先检测 ip 是否被分片如果是调用 ip_defrag IP碎片重组函数,然后 return NF_HOOK(PF_INET, NF_IP_LOCAL_IN, skb, skb->dev, NULL, ip_local_deliver_finish); 调用了NF_IP_LOCAL_IN处的hook函数进行处理. 然后调用 ip_local_deliver_finish. 其中 ...... __skb_pull(skb, ihl); //剥去IP头 skb->h.raw = skb->data; //指向传输层协议 ...... int protocol = skb->nh.iph->protocol; ...... hash = protocol & (MAX_INET_PROTOS - 1); ...... if ((ipprot = rcu_dereference(inet_protos[hash])) != NULL) { ...... ret = ipprot->handler(skb); ...... else { ...... icmp_send(skb, ICMP_DEST_UNREACH, ICMP_PROT_UNREACH, 0); //发送icmp不可达 ...... } ...... 现在我们的重点转移到 inet_protos 和 ipprot->handler中了. struct net_protocol { // include/net/protocal.h int (*handler)(struct sk_buff *skb); void (*err_handler)(struct sk_buff *skb, u32 info); int (*gso_send_check)(struct sk_buff *skb); struct sk_buff *(*gso_segment)(struct sk_buff *skb, int features); int no_policy; }; 现在我们看看谁都进行注册了. 我又在 af_inet.c 文件的 inet_init() 中看到 if (inet_add_protocol(&icmp_protocol, IPPROTO_ICMP) < 0) printk(KERN_CRIT "inet_init: Cannot add ICMP protocol\n"); if (inet_add_protocol(&udp_protocol, IPPROTO_UDP) < 0) printk(KERN_CRIT "inet_init: Cannot add UDP protocol\n"); if (inet_add_protocol(&tcp_protocol, IPPROTO_TCP) < 0) printk(KERN_CRIT "inet_init: Cannot add TCP protocol\n"); #ifdef CONFIG_IP_MULTICAST if (inet_add_protocol(&igmp_protocol, IPPROTO_IGMP) < 0) printk(KERN_CRIT "inet_init: Cannot add IGMP protocol\n"); #endif 这些协议也在此文件中被初始化 #ifdef CONFIG_IP_MULTICAST static struct net_protocol igmp_protocol = { .handler = igmp_rcv, }; #endif static struct net_protocol tcp_protocol = { .handler = tcp_v4_rcv, .err_handler = tcp_v4_err, .gso_send_check = tcp_v4_gso_send_check, .gso_segment = tcp_tso_segment, .no_policy = 1, }; static struct net_protocol udp_protocol = { .handler = udp_rcv, .err_handler = udp_err, .no_policy = 1, }; static struct net_protocol icmp_protocol = { .handler = icmp_rcv, }; 我们来个最难的tcp然后在往下看,那么就是 tcp_v4_rcv了,我们现在进入了传输层. ret = tcp_v4_do_rcv(sk, skb); -> tcp_rcv_state_process(sk, skb, skb->h.th, skb->len)) -> tcp_data_queue(sk, skb); 最后数据会被 __skb_queue_tail(&sk->sk_receive_queue, skb); 看放到了sk的接受队列中. 上面看的流程主要展示了一个带数据的数据报被接收的过程,意味着tcp已经established,其他的tcp状态处理很复杂不是本文描述范围,呵呵. 注意:下面是用户读取数据时,在系统调用中的过程了,已经不是软中断了. 当用户读取数据时,系统调用函数 sys_socketcall. 根据 call 不管是 sys_recv, sys_recvfrom 还是 sys_recvmsg 都会调用到 sock_recvmsg -> __sock_recvmsg -> sock->ops->recvmsg(iocb, sock, msg, size, flags); struct socket * sock->ops 谁初始化的呐 ? af_inet.c 中又找到 static struct inet_protosw inetsw_array[] = { { .type = SOCK_STREAM, .protocol = IPPROTO_TCP, .prot = &tcp_prot, //sock inet socket (struct sock) .ops = &inet_stream_ops, //socket 伯克利 socket (struct socket) .capability = -1, .no_check = 0, .flags = INET_PROTOSW_PERMANENT | INET_PROTOSW_ICSK, }, ...... } 显然上面是传输层协议数组中的tcp操作据柄初始化,还有其他协议并没有列出.先往下看在inet_init()中还有一行是(void)sock_register(&inet_family_ops); 这又是什么呐 ? static struct net_proto_family inet_family_ops = { .family = PF_INET, .create = inet_create, .owner = THIS_MODULE, }; 其中实现了inet_create函数,也就是当你调用 socket() 系统调用时内核会调用到实现的inet_create函数,family是PF_INET是针对所有PF_INET协议族的create函数,该函数会创建一个struct sock 数据结构保存在 struct socket结构中. inet_create函数中 ...... sock->ops = answer->ops; // socket操作据柄 answer_prot = answer->prot; // sock 操作据柄 ...... sk = sk_alloc(PF_INET, GFP_KERNEL, answer_prot, 1); ...... 所以上面 sock->ops->recvmsg 调用到了伯克利 socket. const struct proto_ops inet_stream_ops = { //af_inet.c ...... .recvmsg = sock_common_recvmsg, ...... }; sock_common_recvmsg实现是 struct sock *sk = sock->sk; ...... err = sk->sk_prot->recvmsg(iocb, sk, msg, size, flags & MSG_DONTWAIT, flags & ~MSG_DONTWAIT, &addr_len); ...... 有调用了inet socket的 recvmsg. struct proto tcp_prot = { //tcp_ipv4.c ...... .recvmsg = tcp_recvmsg, ...... }; 在tcp_recvmsg中 skb = skb_peek(&sk->sk_receive_queue);从接收队列中读取了相应的skb然后拷贝到用户地址空间中 err = skb_copy_datagram_iovec(skb, offset, msg->msg_iov, used); 整个数据接受流程就全部完成了.
posted on 2013-08-27 14:11 SuperKing 阅读(1552) 评论(0) 编辑 收藏 举报