上一篇文章中介绍了socket系统的初始化,下面开始介绍目前应用最广泛的一个协议族:ipv4(或者叫af_inet)的初始化。ipv4作为一个内嵌模块,位于net/ipv4目录下,入口点为inet_init()[net/ipv4/af_inet.c:1512],该函数写的很整齐,方便了我们这些读者。先上代码:


  1. static int __init inet_init(void
  2.     struct sk_buff *dummy_skb; 
  3.     struct inet_protosw *q; 
  4.     struct list_head *r; 
  5.     int rc = -EINVAL; 
  6.     BUILD_BUG_ON(sizeof(struct inet_skb_parm) > sizeof(dummy_skb->cb)); 
  7.     rc = proto_register(&tcp_prot, 1); 
  8.     if (rc) 
  9.         goto out; 
  10.     rc = proto_register(&udp_prot, 1); 
  11.     if (rc) 
  12.         goto out_unregister_tcp_proto; 
  13.     rc = proto_register(&raw_prot, 1); 
  14.     if (rc) 
  15.         goto out_unregister_udp_proto; 
  16.     /* 
  17.      *  Tell SOCKET that we are alive... 
  18.      */ 
  19.     (void)sock_register(&inet_family_ops); 
  20. #ifdef CONFIG_SYSCTL 
  21.     ip_static_sysctl_init(); 
  22. #endif 
  23.     /* 
  24.      *  Add all the base protocols. 
  25.      */ 
  26.     if (inet_add_protocol(&icmp_protocol, IPPROTO_ICMP) < 0) 
  27.         printk(KERN_CRIT "inet_init: Cannot add ICMP protocol\n"); 
  28.     if (inet_add_protocol(&udp_protocol, IPPROTO_UDP) < 0) 
  29.         printk(KERN_CRIT "inet_init: Cannot add UDP protocol\n"); 
  30.     if (inet_add_protocol(&tcp_protocol, IPPROTO_TCP) < 0) 
  31.         printk(KERN_CRIT "inet_init: Cannot add TCP protocol\n"); 
  32. #ifdef CONFIG_IP_MULTICAST 
  33.     if (inet_add_protocol(&igmp_protocol, IPPROTO_IGMP) < 0) 
  34.         printk(KERN_CRIT "inet_init: Cannot add IGMP protocol\n"); 
  35. #endif 
  36.     /* Register the socket-side information for inet_create. */ 
  37.     for (r = &inetsw[0]; r < &inetsw[SOCK_MAX]; ++r) 
  38.         INIT_LIST_HEAD(r); 
  39.     for (q = inetsw_array; q < &inetsw_array[INETSW_ARRAY_LEN]; ++q) 
  40.         inet_register_protosw(q); 
  41.     /* 
  42.      *  Set the ARP module up 
  43.      */ 
  44.     arp_init(); 
  45.     /* 
  46.      *  Set the IP module up 
  47.      */ 
  48.     ip_init(); 
  49.     tcp_v4_init(); 
  50.     /* Setup TCP slab cache for open requests. */ 
  51.     tcp_init(); 
  52.     /* Setup UDP memory threshold */ 
  53.     udp_init(); 
  54.     /* Add UDP-Lite (RFC 3828) */ 
  55.     udplite4_register(); 
  56.     /* 
  57.      *  Set the ICMP layer up 
  58.      */ 
  59.     if (icmp_init() < 0) 
  60.         panic("Failed to create the ICMP control socket.\n"); 
  61.     /* 
  62.      *  Initialise the multicast router 
  63.      */ 
  64. #if defined(CONFIG_IP_MROUTE) 
  65.     if (ip_mr_init()) 
  66.         printk(KERN_CRIT "inet_init: Cannot init ipv4 mroute\n"); 
  67. #endif 
  68.     /* 
  69.      *  Initialise per-cpu ipv4 mibs 
  70.      */ 
  71.     if (init_ipv4_mibs()) 
  72.         printk(KERN_CRIT "inet_init: Cannot init ipv4 mibs\n"); 
  73.     ipv4_proc_init(); 
  74.     ipfrag_init(); 
  75.     dev_add_pack(&ip_packet_type); 
  76.     rc = 0; 
  77. out: 
  78.     return rc; 
  79. out_unregister_udp_proto: 
  80.     proto_unregister(&udp_prot); 
  81. out_unregister_tcp_proto: 
  82.     proto_unregister(&tcp_prot); 
  83.     goto out; 
  84. 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。之后做了什么呢?


  1. 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的声明有些奇怪,下面我们来看一下


  1. 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,这个结构体很简单:


  1. /* This is used to register socket interfaces for IP protocols.  */      
  2. struct inet_protosw {                                             
  3.         struct list_head list;                                   
  4.                                                                   
  5.         /* These two fields form the lookup key.  */                     
  6.         unsigned short   type;     /* This is the 2nd argument to socket(2). */
  7.         unsigned short   protocol; /* This is the L4 protocol number.  */
  8.                                                    
  9.         struct proto     *prot;                                   
  10.         const struct proto_ops *ops;           
  11.                                                                         
  12.         int              capability; /* Which (if any) capability do 
  13.                                       * we need to use this socket  
  14.                                       * interface?            
  15.                                       */                               
  16.         char             no_check;   /* checksum on rcv/xmit/none? */     
  17.         unsigned char    flags;      /* See INET_PROTOSW_* below.  */     
  18. }; 

成员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方法入手,是它引出了所有后面的故事。