TCP/IP协议栈初始化(一) 长江之源

时间:2022-09-13 19:48:55

从开始的开始说起

TCP/IP协议栈的工作离不开数据结构的支撑。如果说TCP/IP协议栈构成了这庞大的网络世界,那么底层的数据结构就是这个世界的基石。那么这些基石都是如何从一个字节一个字节组织起来的呢?要从它们被创造的时候开始说起。一想到这么牛的协议居然是从一个全0的内存里构建起来的,想去源头看看的冲动,就像是站到了长江的源头,俯瞰整个长江流域的感觉。一切开始的开始,自然就是系统启动的时刻了。走吧,去源头看看去。

Linux 2.6.24内核里的协议栈是从net/ipv4/af_inet.c中的static int __init inet_init(void)函数开始的。系统启动内核引导阶段中,一系列初始化函数被调用,inet_init就是此时执行的。为了方便阅读与排版,我把源码中的空格和注释都去掉了。协议栈的整个初始化的过程也是围绕着这个函数展开的。

1355 static int __init inet_init(void)
1356 {
1357 struct sk_buff *dummy_skb;
1358 struct inet_protosw *q;
1359 struct list_head *r;
1360 int rc = -EINVAL;
1364 rc = proto_register(&tcp_prot, 1);
1365 if (rc)
1366 goto out;
1368 rc = proto_register(&udp_prot, 1);
1369 if (rc)
1370 goto out_unregister_tcp_proto;
1372 rc = proto_register(&raw_prot, 1);
1373 if (rc)
1374 goto out_unregister_udp_proto;
1380 (void)sock_register(&inet_family_ops);
1386 if (inet_add_protocol(&icmp_protocol, IPPROTO_ICMP) <0)
1387 printk(KERN_CRIT "inet_init Cannot add ICMPprotocol\n");
1388 if (inet_add_protocol(&udp_protocol, IPPROTO_UDP) <0)
1389 printk(KERN_CRIT "inet_init Cannot add UDPprotocol\n");
1390 if (inet_add_protocol(&tcp_protocol, IPPROTO_TCP) <0)
1391 printk(KERN_CRIT "inet_init Cannot add TCPprotocol\n");
1392 #ifdef CONFIG_IP_MULTICAST
1393 if (inet_add_protocol(&igmp_protocol, IPPROTO_IGMP) <0)
1394 printk(KERN_CRIT "inet_init Cannot add IGMPprotocol\n");
1395 #endif
1398 for (r = &inetsw[0]; r < &inetsw[SOCK_MAX]; ++r)
1399 INIT_LIST_HEAD(r);
1401 for (q = inetsw_array; q <&inetsw_array[INETSW_ARRAY_LEN]; ++q)
1402 inet_register_protosw(q);
1408 arp_init();
1414 ip_init();
1416 tcp_v4_init(&inet_family_ops);
1419 tcp_init();
1422 udplite4_register();
1428 icmp_init(&inet_family_ops);
1433 #if defined(CONFIG_IP_MROUTE)
1434 ip_mr_init();
1435 #endif
1440 if (init_ipv4_mibs())
1441 printk(KERN_CRIT "inet_init Cannot init ipv4mibs\n");
1443 ipv4_proc_init();
1445 ipfrag_init();
1447 dev_add_pack(&ip_packet_type);
1449 rc = 0;
11457 }


1355 static 说明这个函数只在这个文件中可见,_init 说明它是初始化函数,初始化完成后,它就从内存中消失了。inet 开头说明它是属于IPv4 Internet 协议,APUE 中说,AF_INET 表示IPv4 Internet AF_INET6 表示IPv6 Internet

1357 定义了一个structsk_buff的结构指针。sk_buff是网络栈中数据的传递者。一般来说,此时协议栈还没有准备好,还不可能发送网络数据,此时出现sk_buff应该不是为了向外部发送数据。用途是什么,暂时还不明白,dummy(傻乎乎的)的意思似乎是在暗指这个包可能什么也不会做。

1358-1359 struct list *r 这个变量只是一个内核常见的双向链表,没有什么好的。而struct inet_protosw*q这个数据结构值得说一下。因为后面的故事里,它是关键角色。来自include/net/protocol.h头文件。内核注释说它是用来为IP层注册socket的接口,也就是说这个数据结构是socketIP之间连接者,等等,那么传输层的协议呢,怎么不见了?其实这里的socket已经包含了传输层的协议了。不信可以看代码:

69 struct inet_protosw {
70 struct list_head list;
73 unsigned short type; /* This is the 2nd argument tosocket(2). */
74 unsigned short protocol; /* This is the L4 protocol number. */
76 struct proto *prot;
77 const struct proto_ops *ops;
79 int capability;
83 char no_check; /* checksum on rcv/xmit/none? */
84 unsigned char flags; /* See INET_PROTOSW_* below. */
85 };

70 又来一个list,说明structinet_protosw也是按照双向链表管理的。

73 socket系统调用的第2个参数,用来指明socket的类型,APUE中说Unix中一共有4种,分别是SOCK_DGRAM,SOCK_RAW, SOCK_STREAM,SOCK_SEQPACKET。但目前在IPv4中只支持前三种,分别表示无连接报文(UDP),原始socket(直接传递IP包数据),面向连接的流socketTCP)。

74 协议编号,同一类型的socket支持多种协议时,用来指定一个特定的协议。一般为0,采用默认的协议,如SOCK_STREAM的默认协议就是TCP协议。

76 又是一个结构体。表示与socket关联的网络协议块,作为structinet_protosw成员来实现接口的功能。后面可以发现这个成员会被赋值为前面提到的tcp_prot。也就是这样把传输层协议包含在内的。

77 协议的特定函数的集合,里面定义了与76proto很相似的函数集,目前还不明白为什么要这样定义。

79 用来衡量一个socket能力的值。至少注释里这么写,目前还不知道有什么意义。

83用来标识,是否由协议来完成校验和的工作。有的设备有硬件生成校验和,当然如果没有的话,linux就勉为其难,用软件来实现,当然速度会慢一些。

84 同样标识变量。用来指示协议的属性,端口是否可复用,是否是永久的等。随后遇到细说。

1360 返回码,rc为return code缩写

1364-1374 用函数注册了3个协议。分别是tcp_prot,udp_prot,raw_prot。为什么在前面说目前只支持三种类型的socket呢,现在看到了吧!注意到,这三个可是主要的控制层协议啊。别的不管既然学习协议栈了,tcp_prot肯定是要去看看的。跟着源码去看了下,tcp_prot定义在tcp_ipv4.c文件中。下次再说了。