上一篇文章中介绍了socket系统的初始化,下面开始介绍目前应用最广泛的一个协议族:ipv4(或者叫af_inet)的初始化。ipv4作为一个内嵌模块,位于net/ipv4目录下,入口点为inet_init()[net/ipv4/af_inet.c:1512],该函数写的很整齐,方便了我们这些读者。先上代码:
static int __init inet_init(void) { struct sk_buff *dummy_skb; struct inet_protosw *q; struct list_head *r; int rc = -EINVAL; BUILD_BUG_ON(sizeof(struct inet_skb_parm) > sizeof(dummy_skb->cb)); rc = proto_register(&tcp_prot, 1); if (rc) goto out; rc = proto_register(&udp_prot, 1); if (rc) goto out_unregister_tcp_proto; rc = proto_register(&raw_prot, 1); if (rc) goto out_unregister_udp_proto; /* * Tell SOCKET that we are alive... */ (void)sock_register(&inet_family_ops); #ifdef CONFIG_SYSCTL ip_static_sysctl_init(); #endif /* * Add all the base protocols. */ 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 /* Register the socket-side information for inet_create. */ for (r = &inetsw[0]; r < &inetsw[SOCK_MAX]; ++r) INIT_LIST_HEAD(r); for (q = inetsw_array; q < &inetsw_array[INETSW_ARRAY_LEN]; ++q) inet_register_protosw(q); /* * Set the ARP module up */ arp_init(); /* * Set the IP module up */ ip_init(); tcp_v4_init(); /* Setup TCP slab cache for open requests. */ tcp_init(); /* Setup UDP memory threshold */ udp_init(); /* Add UDP-Lite (RFC 3828) */ udplite4_register(); /* * Set the ICMP layer up */ if (icmp_init() < 0) panic("Failed to create the ICMP control socket./n"); /* * Initialise the multicast router */ #if defined(CONFIG_IP_MROUTE) if (ip_mr_init()) printk(KERN_CRIT "inet_init: Cannot init ipv4 mroute/n"); #endif /* * Initialise per-cpu ipv4 mibs */ if (init_ipv4_mibs()) printk(KERN_CRIT "inet_init: Cannot init ipv4 mibs/n"); ipv4_proc_init(); ipfrag_init(); dev_add_pack(&ip_packet_type); rc = 0; out: return rc; out_unregister_udp_proto: proto_unregister(&udp_prot); out_unregister_tcp_proto: proto_unregister(&tcp_prot); goto out; } fs_initcall(inet_init);
下面分四部分对其进行解读。
1. 首先是三个proto_register(),分别注册了tcp_prot,udp_prot,raw_prot三个协议(struct proto[include/net/sock.h:594])(这三个对象在后面的初始化过程中还会涉及到,可以比较一下出现这两次的目的何在),跟到proto_register()[net/core/sock.c:2041]中看一下,首先是一个大的if,为这个proto分配了几个必要的slab。之后做了什么呢?
list_add(&prot->node, &proto_list);
只是把它加到了全局变量proto_list[net/sock/sock.c:1937]中,这个链表在内核里又起什么作用呢?因为proto_list是静态全局变量,也就是说只有在该文件中才能被访问到,所以不妨在该文件中对其搜索一下:经过搜索发现,proto_list除了在proto_register和proto_unregister中被使用到,就只有在proc_fs中被使用,也就是输出目前proto_list链表中所有节点的状态。
2. sock_register()[net/socket.c:2151]。该函数的实现也很简单:向net_families[net/socket.c:150]中注册一个新的协议族(struct net_proto_family[include/linux/net.h:192])。协议族是linux网络模块划分的最大单位,每个协议族都被分配了一个协议族号(在include/linux/socket.h:158~232中定义),从该文件中可以看出目前最大的协议族号只有35,即AF_PHONET。既然协议族的数量很少,那么net_families就采用了一种最高效的数据结构:数组。35个net_proto_family的空间都是预分配的,只有在注册时才会向该协议族自己的位置上增加对应的family,create和owner。
到此为止,af_inet对上层的初始化操作基本完成,在此可以看到一个网络协议(确切的说是协议族)模块初始化的一般流程。两个步骤:proto_register()然后sock_register()。先注册proto,然后注册net_proto_family。每个协议族至少包含一个协议,如协议族netlink(net/netlink/af_netlink)就只有一个协议netlink。
下面的初始化操作就和上层没有多大关系了,主要是af_inet内部协议的初始化。
3. inet_add_protocol()[net/ipv4/protocol.c:53]。向af_inet注册一个协议,注意此时的注册的协议为struct net_protocol[include/net/protocol.h:36]。inet_add_protocol()的实现很简单,只是将协议设置到静态变量inet_protos[net/ipv4/protocol.c:46]数组中。inet_protos的声明有些奇怪,下面我们来看一下
struct net_protocol *inet_protos[MAX_INET_PROTOS] ____cacheline_aligned_in_smp;
后面加了一个____cacheline_aligned_in_smp,根据它的名字可以看出,它的作用是在多处理器环境下对齐cacheline,而cacheline又是什么东西呢?可以参考http://blog.csdn.net/pennyliang/archive/2010/10/20/5953939.aspx,一位牛人的blog,这篇文章大致介绍了一下为什么cacheline aligned对齐能够在多处理器环境下加速。
inet_protos在include/net/protocol.h:98中被extern,那这样的话我们只有对整个内核进行grep了(也可以使用网上的lxr,但只恐找不全或内核版本不匹配),经过grep,可以确定该变量只在net/ipv4/目录下的af_inet.c,icmp.c,ip_input.c,protocol.c四个文件中被使用,且前三个文件中只是使用rcu_dereference()对数组中的某一个协议进行引用,协议的增加和删除都在protocol.c中。
TODO:至于这些协议是怎么被上层调用到的,目前还不清楚。
现在请关注该函数的第二个参数:protocol_num。这是af_inet中协议的协议号,这些协议号都是向一个名叫iana[http://www.iana.org]的组织申请的,而且这个组织不光分配af_inet的协议号,af_inet的协议也是由他们制定的,制定完善后的协议会变成RFC发布到ietf[http://www.ietf.org]。iana还拥有全球域名分配和IP地址分配的大权。已注册协议号可以在http://www.iana.org/assignments/protocol-numbers/protocol-numbers.xml中看到。
4. inet_register_protosw()[net/ipv4/af_inet.c:967]。向inetsw[net/ipv4/af_inet.c:124]注册一个struct inet_protosw,这个结构体很简单:
/* This is used to register socket interfaces for IP protocols. */ struct inet_protosw { struct list_head list; /* These two fields form the lookup key. */ unsigned short type; /* This is the 2nd argument to socket(2). */ unsigned short protocol; /* This is the L4 protocol number. */ struct proto *prot; const struct proto_ops *ops; int capability; /* Which (if any) capability do * we need to use this socket * interface? */ char no_check; /* checksum on rcv/xmit/none? */ unsigned char flags; /* See INET_PROTOSW_* below. */ };
成员type和protocol分别指代socket类型,协议号。socket类型即BSD socket中标准的几个类型,像SOCK_STREAM,SOCK_DGRAM,SOCK_RAW等等;协议号就是第三节中所提到的af_inet的协议号。
下面两个成员是两组函数接口:prot和ops。我们可以发现,prot就是在第一节proto_register()中注册的协议,可见,这层协议是给上层使用的;ops这一套函数接口在之后就会发现它位于prot下方。
现在可以明白struct inet_protosw的作用了:它的目的就是承上启下,起到衔接BSD socket接口和af_inet协议族接口的作用。
再来看inetsw这个全局变量,它又是做什么作用的呢?grep看一下,很明显,除了在inet_register_protosw注册的使用使用到该变量以外,就只有在inet_create()[net/ipv4/af_inet.c:265]函数中使用了。我们现在只要知道,inet_create()是af_inet在创建socket时候的总入口点(下一章会对其进行详细剖析),那这就很明白了,inetsw就是为inet_create服务,在创建socket的时候方便它找到协议接口,正如inetsw声明时所说:"The inetsw table contains everything that inet_create needs to build a new socket."
说到inetsw,就不得不提及inetsw_array[net/ipv4/af_inet.c:930],inetsw就是根据它初始化的,可以看到这里定义了TCP,UDP和IP三个协议的inet_protosw类型。值得关注的是IP的protocol字段,它设置的IPPROTO_IP宏的值为0,我们再到iana查一下这个协议号,iana上显示这个协议是"IPv6 Hop-by-Hop Option"!为什么会不一致呢?下面说一下我的理解:
我们知道在TCP/IP协议中,IP位于L3网络层,TCP,UDP位于L4传输层,BSD socket所用到的protocol应该是指L4的协议,那IP协议当然不在其中。但BSD socket的SOCK_RAW类型该如何实现呢?所以就有了这个IPPROTO_IP,在注释中有这样对它的一个形容:"wild card",直译是百搭牌,可以指代任何类型的协议。实现SOCK_RAW的难度太大,只有把这张牌打出来了...百搭的体现可以在后面对inet_create()的剖析中找到。SOCK_RAW这种类型目前我还没有怎么接触,不好做评价。
接下来调用的几个初始化函数都是和具体的protocol相关的,介绍到具体的protocol时再具体释义。下一章我们会从af_inet协议族的inet_create方法入手,是它引出了所有后面的故事。