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得了。这个会下回分解。