本文先大致阐述系统协议栈初始化过程,然后剖析数据包的接收和发送通道过程,在文章最后着重梳理其过程及通道结构区别。
源码版本:Linux kernel 1.2.13;工具:Source Insight 3.5
下图为网络协议栈初始化程序流程框架
本篇幅将根据上图来介绍系统网络协议栈的初始化过程。
先从init/main.c 文件出发,在执行了一系列涉及到具体处理器架构初始化代码之后,最终将进入到 init/main.c 中的start_kernel 函数执行。该函数内部将调用各个初始化程序,我们这里只关注网络栈的初始化过程:系统网络栈初始化总入口函数即为sock_init 函数,网络栈的初始化将从该函数出发完成。
sock_init 函数定义在 net/socket.c 中,其调用:
1> proto_init 函数:进行协议实现模块初始化;
2> dev_init 函数:进行网络设备驱动层模块初始化;
3> 初始化专门用于网络处理的下半部分执行函数:net_bh,并使能;
4> 将由变量pops指向的域操作函数集合(如INET域,UNIX域)数组清零,在此后的网络栈初始化中会对其进行正确的初始化。
前面的学习我们了解到,不同的域对应不同的域操作函数。 //系统网络栈初始化 void sock_init(void) { int i; printk("Swansea University Computer Society NET3.019\n"); /* * Initialize all address (protocol) families. */ //pops指向的域操作函数集合(如INET域,UNIX域等)数组清零, //此后的网络栈初始化中会对其进行正确的初始化 for (i = 0; i < NPROTO; ++i) pops[i] = NULL; /* * Initialize the protocols module. */ //协议实现模块初始化 proto_init(); #ifdef CONFIG_NET /* * Initialize the DEV module. */ //网络设备驱动层模块初始化 dev_init(); /* * And the bottom half handler */ //初始化专门用于网络处理的下本部分执行函数:net_bh,并使能。 bh_base[NET_BH].routine= net_bh; enable_bh(NET_BH); #endif }
好,接下来看看 proto_init 函数,该函数定义在 net/socket.c 中,其主要完成一下工作:
遍历由 protocols 全局变量(net/protocols.c)指向的域初始化函数集,进行各域的初始化。INET域就初始化为 inet_proto_init,其将在 proto_init 函数被调用以进行 INET 的初始化,UNIX 域的初始化函数为 unix_proto_init。当然还涉及其余域的初始化。
//协议实现模块初始化 void proto_init(void) { extern struct net_proto protocols[]; /* Network protocols */ struct net_proto *pro; /* Kick all configured protocols. */ //遍历由protocols全局变量指向的域初始化函数集, //进行各域的初始化 pro = protocols; while (pro->name != NULL) { (*pro->init_func)(pro); pro++; } /* We\'re all done... */ }
dev_init 函数则是对系统中存在的所有网络设备进行初始化操作,其实际上是由具体硬件驱动程序进行初始化,函数实现是内部调用 device 结构中 init 字段指向的函数完成各自特定硬件设备的初始化工作,如果初始化失败,该设备将从系统设备列表中(dev_base指向)删除。 /* * Initialize the DEV module. At boot time this walks the device list and * unhooks any devices that fail to initialise (normally hardware not * present) and leaves us with a valid list of present and active devices. * * The PCMCIA code may need to change this a little, and add a pair * of register_inet_device() unregister_inet_device() calls. This will be * needed for ethernet as modules support. */ void dev_init(void) { struct device *dev, *dev2; /* * Add the devices. * If the call to dev->init fails, the dev is removed * from the chain disconnecting the device until the * next reboot. */ dev2 = NULL; for (dev = dev_base; dev != NULL; dev=dev->next) { if (dev->init && dev->init(dev)) { /* * It failed to come up. Unhook it. */ if (dev2 == NULL) dev_base = dev->next; else dev2->next = dev->next; } else { dev2 = dev; } } }
回到proto_init 函数,前面说到该函数的功能是进行各域的初始化。这里我们着重介绍INET域的初始化。
初始化INET域,则是通过调用 inet_proto_init 函数来实现的,参见注释(net/inet/af_inet.c) /* * Called by socket.c on kernel startup. */ //INET域初始化 void inet_proto_init(struct net_proto *pro) { struct inet_protocol *p; int i; printk("Swansea University Computer Society TCP/IP for NET3.019\n"); /* * Tell SOCKET that we are alive... */ //注册一个域操作集 (void) sock_register(inet_proto_ops.family, &inet_proto_ops); seq_offset = CURRENT_TIME*250; /* * Add all the protocols. */ //初始化INET下所有协议类型 //这些变量都是proto结构体类型,不同协议有对应的proto变量 //proto结构体内包含操作函数集,套接字散列表,以及使用标志字段等 for(i = 0; i < SOCK_ARRAY_SIZE; i++) { tcp_prot.sock_array[i] = NULL; udp_prot.sock_array[i] = NULL; raw_prot.sock_array[i] = NULL; } tcp_prot.inuse = 0; tcp_prot.highestinuse = 0; udp_prot.inuse = 0; udp_prot.highestinuse = 0; raw_prot.inuse = 0; raw_prot.highestinuse = 0; printk("IP Protocols: "); //下面进行传输层和网络层之间的衔接:网络层处理函数结果本层的处理后,将查询inet_protos数组, //匹配合适的传输层协议,调用其对应inet_protocol结构中注册的接收函数 //具体是通过将由inet_protocol_base全局变量指向的inet_protocol结构队列中的元素 //散列到inet_protos哈希表中,从而被网络层使用。该操作通过调用inet_add_protocol函数实现 for(p = inet_protocol_base; p != NULL;) { struct inet_protocol *tmp = (struct inet_protocol *) p->next; inet_add_protocol(p); printk("%s%s",p->name,tmp?", ":"\n"); p = tmp; } /* * Set the ARP module up */ //ARP协议初始化函数 arp_init(); /* * Set the IP module up */ //IP协议初始化函数 ip_init(); }
上面的 inet_add_protocol 函数进行传输层和网络层之间的衔接,将由 inet_protocol_base 指向的链表中 inet_protocol 结构插入到由 inet_protos 表示的数组中,从而被网络层使用。 void inet_add_protocol(struct inet_protocol *prot) { unsigned char hash; struct inet_protocol *p2; hash = prot->protocol & (MAX_INET_PROTOS - 1); prot ->next = inet_protos[hash]; inet_protos[hash] = prot; prot->copy = 0; /* Set the copy bit if we need to. */ p2 = (struct inet_protocol *) prot->next; while(p2 != NULL) { if (p2->protocol == prot->protocol) { prot->copy = 1; break; } p2 = (struct inet_protocol *) prot->next; } }
在 inet_proto_init 函数内部我们还可以看到,该函数最后调用了 arp_init 函数和 ip_init 函数分别进行ARP 协议初始化函数和IP协议初始化函数。
arp_init 函数: //ARP协议初始化函数 //ARP协议是网络层协议,为了从链路层接收数据包,其必须定义个packet_type结构 void arp_init (void) { /* Register the packet type */ arp_packet_type.type=htons(ETH_P_ARP); //向链路层模块注册 dev_add_pack(&arp_packet_type); /* Start with the regular checks for expired arp entries. */ //添加定时器 add_timer(&arp_timer); /* Register for device down reports */ //注册时间通知句柄函数,监听网络设备状态变化事件 register_netdevice_notifier(&arp_dev_notifier); }
该函数主要完成注册工作,同时注册事件通知句柄函数,监听网络设备状态变化事件,从而对ARP缓存进行及时更新,以维护ARP缓存内容的有效性。