IP/TCP 协议栈解析 —- 初始化

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创建

喜欢 0

这篇文章还没有评论

发表评论