分类
协议栈分析

TCP socket 创建

IPV4的TCP socket 创建和其他IPV4 socket 创建在调用inet_create之前流程都是一样的,直到在inet_create函数中,才会调用4层协议特有的init函数。
创建socket的系统调用始于SYSCALL_DEFINE3

SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol)
{
        return __sys_socket(family, type, protocol); //work function family 3层协议类型。type套接字类型,protocol 四层协议类型。
}

其中__sys_socket函数的作用主要是创建socket,并给socket分配文件描述符

int __sys_socket(int family, int type, int protocol)
{
        int retval;
        struct socket *sock;
        int flags;

        /* Check the SOCK_* constants for consistency.  */
        BUILD_BUG_ON(SOCK_CLOEXEC != O_CLOEXEC);
        BUILD_BUG_ON((SOCK_MAX | SOCK_TYPE_MASK) != SOCK_TYPE_MASK);
        BUILD_BUG_ON(SOCK_CLOEXEC & SOCK_TYPE_MASK);
        BUILD_BUG_ON(SOCK_NONBLOCK & SOCK_TYPE_MASK);

        flags = type & ~SOCK_TYPE_MASK;  //type除了传递socket类型还可以传递socket flags
        if (flags & ~(SOCK_CLOEXEC | SOCK_NONBLOCK))
                return -EINVAL;
        type &= SOCK_TYPE_MASK;

        if (SOCK_NONBLOCK != O_NONBLOCK && (flags & SOCK_NONBLOCK))
                flags = (flags & ~SOCK_NONBLOCK) | O_NONBLOCK;

        retval = sock_create(family, type, protocol, &sock); //创建socket 主函数
        if (retval < 0)
                return retval;

        return sock_map_fd(sock, flags & (O_CLOEXEC | O_NONBLOCK)); //将socket映射成一个文件描述符。通过文件的读写操作来操作套接字。
}

sock_create函数稍微复杂 后续分解。先分解 sock_map_fd函数

struct file *sock_alloc_file(struct socket *sock, int flags, const char *dname)
{
        struct file *file;

        if (!dname)
                dname = sock->sk ? sock->sk->sk_prot_creator->name : "";

        file = alloc_file_pseudo(SOCK_INODE(sock), sock_mnt, dname,
                                O_RDWR | (flags & O_NONBLOCK),
                                &socket_file_ops); //创建文件结构,操作函数是socket_file_ops
        if (IS_ERR(file)) {
                sock_release(sock);
                return file;
        }

        sock->file = file;  //sock file 结构为该文件
        file->private_data = sock; //file的private_data指向sock。后续读写文件实质上操作 这个socket。具体查看socket_file_ops对应的函数。
        return file;
}
EXPORT_SYMBOL(sock_alloc_file);

static int sock_map_fd(struct socket *sock, int flags)
{
        struct file *newfile;
        int fd = get_unused_fd_flags(flags); // 获取空闲的文件描述符
        if (unlikely(fd < 0)) {
                sock_release(sock);
                return fd;
        }

        newfile = sock_alloc_file(sock, flags, NULL); //创建文件结构,上面的函数
        if (likely(!IS_ERR(newfile))) {
                fd_install(fd, newfile);  //将文件描述符和文件关联
                return fd;   //返回文件描述符
        }

        put_unused_fd(fd);
        return PTR_ERR(newfile);
}

sock_create函数最后调用__sock_create函数创建socket。__sock_create函数做了一些检查,创建 socket结构后,查找三层协议。最后调用三层协议的create函数,这个函数的过程简单,这里就不分析,对应IPV4 TCP套接字最后调用的是inet_create函数。
inet_create函数较长,前面部分主要是根据类型和协议查找四层协议的inet_protosw结构。下面列出该函数的关键代码

        sock->ops = answer->ops;            //type的操作函数
        answer_prot = answer->prot;         //proto结构体,创建sk使用
        answer_flags = answer->flags;       //四层协议的flags
        rcu_read_unlock();

        WARN_ON(!answer_prot->slab);

        err = -ENOBUFS;
        sk = sk_alloc(net, PF_INET, GFP_KERNEL, answer_prot, kern); //创建sk,这个函数很简单,就是创建sk,并将answer_prot赋值到sk的sk_prot变量中。
        if (!sk)
                goto out;
......
        if (sk->sk_prot->init) {  //判断四层协议是否有init函数
                err = sk->sk_prot->init(sk); //调用init函数,TCP中调用的是tcp_v4_init_sock函数 
                if (err) {
                        sk_common_release(sk);
                        goto out;
                }
        }

tcp_v4_init_sock函数只是对tcp_init_sock函数进行封装,并赋值icsk_af_ops为ipv4特有的操作。这个结构主要是tcp在ipv4协议中的一些特定操作(发包 socket option 创建ip头等 ),ipv6中也有一个类似的结构。
tcp_init_sock函数才是tcp socket 初始化的核心函数。代码如下

void tcp_init_sock(struct sock *sk)
{
        struct inet_connection_sock *icsk = inet_csk(sk); //初始化 icsk 套接字
        struct tcp_sock *tp = tcp_sk(sk);   //初始化tcp套接字

        tp->out_of_order_queue = RB_ROOT;
        sk->tcp_rtx_queue = RB_ROOT;
        tcp_init_xmit_timers(sk);    //初始化tcp套接字定时器,retran delay_ack keepalive等 
        INIT_LIST_HEAD(&tp->tsq_node);
        INIT_LIST_HEAD(&tp->tsorted_sent_queue);

        icsk->icsk_rto = TCP_TIMEOUT_INIT;
        tp->mdev_us = jiffies_to_usecs(TCP_TIMEOUT_INIT);
        minmax_reset(&tp->rtt_min, tcp_jiffies32, ~0U);

        /* So many TCP implementations out there (incorrectly) count the
         * initial SYN frame in their delayed-ACK and congestion control
         * algorithms that we must have the following bandaid to talk
         * efficiently to them.  -DaveM
         */
        tp->snd_cwnd = TCP_INIT_CWND; //tcp默认发送窗口 居然是10 好小 

        /* There's a bubble in the pipe until at least the first ACK. */
        tp->app_limited = ~0U;

        /* See draft-stevens-tcpca-spec-01 for discussion of the
         * initialization of these values.
         */
        tp->snd_ssthresh = TCP_INFINITE_SSTHRESH;
        tp->snd_cwnd_clamp = ~0;
        tp->mss_cache = TCP_MSS_DEFAULT;

        tp->reordering = sock_net(sk)->ipv4.sysctl_tcp_reordering;
        tcp_assign_congestion_control(sk);  //这个是拥塞算法的初始化函数

        tp->tsoffset = 0;
        tp->rack.reo_wnd_steps = 1;

        sk->sk_state = TCP_CLOSE;       //创建socket的初始状态 TCP_CLOSE状态

        sk->sk_write_space = sk_stream_write_space; //唤醒阻塞在socket上的写进程
        sock_set_flag(sk, SOCK_USE_WRITE_QUEUE);

        icsk->icsk_sync_mss = tcp_sync_mss;  //协商 mss 的函数 通过pmtu获取貌似

        sk->sk_sndbuf = sock_net(sk)->ipv4.sysctl_tcp_wmem[1]; //这个应该是发送buf
        sk->sk_rcvbuf = sock_net(sk)->ipv4.sysctl_tcp_rmem[1]; //这个应该是接受buf

        sk_sockets_allocated_inc(sk);  //貌似是做统计用的
        sk->sk_route_forced_caps = NETIF_F_GSO; //暂时不知道干嘛用的 ,大概跟GSO有关系
}
EXPORT_SYMBOL(tcp_init_sock); 

tcp_assign_congestion_control函数是拥塞控制算法的初始化函数,这个里面会选取默认的拥塞控制算法并初始化。代码如下

void tcp_assign_congestion_control(struct sock *sk)
{
        struct net *net = sock_net(sk);
        struct inet_connection_sock *icsk = inet_csk(sk);
        const struct tcp_congestion_ops *ca;

        rcu_read_lock();
        ca = rcu_dereference(net->ipv4.tcp_congestion_control);
        if (unlikely(!try_module_get(ca->owner)))
                ca = &tcp_reno;
        icsk->icsk_ca_ops = ca;   //直接赋值默认拥塞控制算法的结构
        rcu_read_unlock();

        memset(icsk->icsk_ca_priv, 0, sizeof(icsk->icsk_ca_priv));
        if (ca->flags & TCP_CONG_NEEDS_ECN)
                INET_ECN_xmit(sk);
        else
                INET_ECN_dontxmit(sk);
}

到这里,TCP socket 的创建就完事,接下来,有两种用法,作为客户端进行connect。 作为服务器端 bind 本地端口后进行listen。 connect很有意思的地方在于,刚发出去syn包,本地就认为这个链接是establish得了。这个会下回分解。

发表评论

电子邮件地址不会被公开。 必填项已用*标注