tcp 是4层协议,向上通过socket与用户态程序交互,向下通过接口函数与3层协议通讯(ip,ipv6)。可能由于历史原因,tcp协议栈和ip协议栈在目录上做的不是很模块化,其实在ipv4目录下的tcp文件很大一部分代码都是和ipv6的tcp共享的。而且很奇怪的是,4层协议栈和3层协议栈之间完全可以做成模块结构,但是tcp和ip在初始化的时候代码完全杂糅在一起。在这一方面,ipv6的初始化反而更清晰。
IP初始函数
由于ip协议对应的tcp协议栈和ip协议初始化代码没有分离,因此,tcp协议的初始化函数在ip 初始化函数里面。函数是 net/ipv4/af_inet.c中的inet_init函数。
static int __init inet_init(void)
{
struct inet_protosw *q;
struct list_head *r;
int rc = -EINVAL;
sock_skb_cb_check_size(sizeof(struct inet_skb_parm));
rc = proto_register(&tcp_prot, 1);//这个函数做一些初始化,创建memcache,并注册到socket的一个全局表中,后续看在主要的发包流程中不会用到这个全局表。看上去这个表主要是在proc文件中显示当前系统中有多少个socket协议
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;
rc = proto_register(&ping_prot, 1);
if (rc)
goto out_unregister_raw_proto;
/*
* Tell SOCKET that we are alive...
*/
(void)sock_register(&inet_family_ops);//注册ip协议族,socket协同调用的时候指定的domain。就是在选协议族。
#ifdef CONFIG_SYSCTL
ip_static_sysctl_init();
#endif
/*
* Add all the base protocols.
*/
if (inet_add_protocol(&icmp_protocol, IPPROTO_ICMP) < 0)
pr_crit("%s: Cannot add ICMP protocol\n", __func__);
if (inet_add_protocol(&udp_protocol, IPPROTO_UDP) < 0)
pr_crit("%s: Cannot add UDP protocol\n", __func__);
if (inet_add_protocol(&tcp_protocol, IPPROTO_TCP) < 0) //注册tcp收包结构,结构在下文贴出
pr_crit("%s: Cannot add TCP protocol\n", __func__);
#ifdef CONFIG_IP_MULTICAST
if (inet_add_protocol(&igmp_protocol, IPPROTO_IGMP) < 0)
pr_crit("%s: Cannot add IGMP protocol\n", __func__);
#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); //注册四层协议结构。用户态程序创建socket第二个和第三个参数 type 和protol,就是用来筛选这个结构的。
tcp 接受ip层数据接口
static struct net_protocol tcp_protocol = {
.early_demux = tcp_v4_early_demux,
.early_demux_handler = tcp_v4_early_demux,
.handler = tcp_v4_rcv, //这个是正常流程的入口函数
.err_handler = tcp_v4_err, //这个是出错是入口函数
.no_policy = 1,
.netns_ok = 1,
.icmp_strict_tag_validation = 1,
};
tcp inetsw_array结构
{
.type = SOCK_STREAM, //流类型
.protocol = IPPROTO_TCP, //协议类型
.prot = &tcp_prot, // 协议的结构
.ops = &inet_stream_ops,// 流操作函数
.flags = INET_PROTOSW_PERMANENT |
INET_PROTOSW_ICSK,
},
在linux中,对四层协议栈又做了一次分类,同类型的协议抽象了一层操作函数,然后在操作函数中在调用各个具体协议的操作函数。当前分类一共有以下几种
enum sock_type {
SOCK_STREAM = 1, //流类型,有链接
SOCK_DGRAM = 2, //数据包包类型无连接
SOCK_RAW = 3, //raw socket类型直接发包
SOCK_RDM = 4, //可靠连接类型,这个类型貌似就是RDM
SOCK_SEQPACKET = 5, //顺序包类型
SOCK_DCCP = 6, //数据包拥塞控制类型
SOCK_PACKET = 10, //直接从dev获取包的类型,linux特有。
};
tcp prot 结构,这个结构 太过复杂,现在只是搞懂了几个用的函数在哪里调用。剩下的需要慢慢搞懂。
struct proto tcp_prot = {
.name = "TCP",
.owner = THIS_MODULE,
.close = tcp_close,
.pre_connect = tcp_v4_pre_connect, //创建连接前调用
.connect = tcp_v4_connect, //创建连接时调用,syn包
.disconnect = tcp_disconnect, //断开连接时调用
.accept = inet_csk_accept, //接受连接时调用
.ioctl = tcp_ioctl, //ioctl 低啊用
.init = tcp_v4_init_sock, //创建socket时调用
.destroy = tcp_v4_destroy_sock,
.shutdown = tcp_shutdown,
.setsockopt = tcp_setsockopt, //设置opt调用
.getsockopt = tcp_getsockopt, //获取opt调用
.keepalive = tcp_set_keepalive,
.recvmsg = tcp_recvmsg,
.sendmsg = tcp_sendmsg,
.sendpage = tcp_sendpage,
.backlog_rcv = tcp_v4_do_rcv,
.release_cb = tcp_release_cb,
.hash = inet_hash,
.unhash = inet_unhash,
.get_port = inet_csk_get_port,
.enter_memory_pressure = tcp_enter_memory_pressure,
.leave_memory_pressure = tcp_leave_memory_pressure,
.stream_memory_free = tcp_stream_memory_free,
.sockets_allocated = &tcp_sockets_allocated,
.orphan_count = &tcp_orphan_count,
.memory_allocated = &tcp_memory_allocated,
.memory_pressure = &tcp_memory_pressure,
.sysctl_mem = sysctl_tcp_mem,
.sysctl_wmem_offset = offsetof(struct net, ipv4.sysctl_tcp_wmem),
.sysctl_rmem_offset = offsetof(struct net, ipv4.sysctl_tcp_rmem),
.max_header = MAX_TCP_HEADER,
.obj_size = sizeof(struct tcp_sock), //tcp sock,这个sock里面套了一堆结构,第一个结构是就是sock。注意内核在这做了三次抽象 sock , connection_sock最后是tcp_sock.通过这三次抽象减少重复代码。面向对象啊
.slab_flags = SLAB_TYPESAFE_BY_RCU,
.twsk_prot = &tcp_timewait_sock_ops,
.rsk_prot = &tcp_request_sock_ops,
.h.hashinfo = &tcp_hashinfo,
.no_autobind = true,
#ifdef CONFIG_COMPAT
.compat_setsockopt = compat_tcp_setsockopt,
.compat_getsockopt = compat_tcp_getsockopt,
#endif
.diag_destroy = tcp_abort,
};
EXPORT_SYMBOL(tcp_prot);
inet_add_protocol函数,很简单就是讲四层协议的收包结构注册到三层协议中,方便调用。使用cmpxchg
inet_register_protosw 函数,注册AF_INET协议栈的协议类型,这个函数也很简单,首先通过根据类型分类,然后在同类型的链表中查找是否有重名显现如果么有则注册。
proto_register 这个函数,跟进去看了一下,主要是做了一些memcache的创建。然后注册到一个全局链表中。它注册的那个链表后续貌似没有用到的地方看代码只有在proc文件显示的时候中用到。
到这里初始化就完成了。下一章将分析tcp socket创建