TCP协议在socket中的初始化

TCP

TCP通过校验和、序列号、确认应答、重发控制、连接管理和窗口控制实现可靠传输。

TCP通过确认应答ACK来实现有保障的数据传输,但是由于各种原因,目标主机可能无法收到ACK信号,导致源主机不停重发数据。为此,引入序列号与确认信号相结合,实现有效的重发控制。

作为面向连接的协议,TCP在数据通信前,通过TCP首部发送一个SYN包作为建立连接的请求等待确认应答。一个连接的建立与断开至少要来回发送7个包才能完成。其中建立连接三次握手,需要发送3个包。切断连接四次挥手,需要发送4个包。

LINUX & TCP

下图展示了LINUX中TCP客户机和服务器交互时调用的基本套接字函数。


(图源:UNIX网络编程-第三版)

一个进程要进行网络读写,第一个执行的就是socket(),它定义在socket.h文件中:

#include <sys/socket.h>
int socket(int family, int type, int protocol);

其中family指定协议类型簇(IPv4、IPv6、Unix域协议、路由套接字、密钥套接字),type指定套接字类型,protocol指定协议的类型常量(TCP、UDP、SCTP)。socket()函数返回套接字描述符。

跟踪调用栈


(图源:https://www.jianshu.com/p/5d82a685b5b6)

启动qemuOS,打开GDB调试,连接到远程。在__sys_socket()函数处打上断点,追踪函数调用栈:

在gdb中根据情况交替使用nextstep命令,追踪程序运行。函数调用了__sock_create()实际执行socket创建任务。对__socket_create()进行追踪。

__sock_create()函数中

sock_alloc()

__socket_create()中,又调用了sock_alloc()函数将socket分配给一个结构体变量sock
这里,sock的类型为struct socket*,在linux源码目录下使用正则表达式find -name "*.h" | xargs grep "strcut socket {" -rn找到该结构体定义在./linux-5.0.1/include/linux/net.h中,如下:

struct socket {
	socket_state		state;
	short			type;
	unsigned long		flags;
	struct socket_wq	*wq;
	struct file		*file;
	struct sock		*sk;
	const struct proto_ops	*ops;
};

state是socket的状态,type是类型,flags是标志位,eq是等待队列,file是指针列表。sock是一个重要的结构体,同理可以找到结构体sock的定义,根据注释描述,它表示一个网络层的socket。

new_inode_pseudo()

接着调用了new_inode_pseudo()函数,在相应的文件系统中创建一个inode。其代码位于./linux-5.0.1/fs/inode.c中:

struct inode *new_inode_pseudo(struct super_block *sb)
{
	struct inode *inode = alloc_inode(sb);

	if (inode) {
		spin_lock(&inode->i_lock);
		inode->i_state = 0;
		spin_unlock(&inode->i_lock);
		INIT_LIST_HEAD(&inode->i_sb_list);
	}
	return inode;
}

inode为socket文件系统中的节点。inode和socket对象是连结在一起的,可以根据inode取得socket对象。分配inode后,应用程序可以通过文件描述符对socket进行读写访问。

协议簇初始化socket

继续往下,有一条语句:

pf = rcu_dereference(net_families[family]);

用于查找内核初始化时注册的协议簇。当传入的family字段存在于协议簇中时,又继续执行一条语句:

err = pf->create(net, sock, protocol, kern);

这一步调用的是inet_create()函数,它位于./net/ipv4/Af_inet.c中。

inet_create()

这个函数主要完成四个工作:
1 将socket的state设置为SS_UNCONNECTED

sock->state = SS_UNCONNECTED;

2 根据type字段找到对应套接字类型

lookup_protocol:
	err = -ESOCKTNOSUPPORT;
	rcu_read_lock();
	list_for_each_entry_rcu(answer, &inetsw[sock->type], list) {

		err = 0;
		/* Check the non-wild match. */
		if (protocol == answer->protocol) {
			if (protocol != IPPROTO_IP)
				break;
		} else {
			/* Check for the two wild cases. */
			if (IPPROTO_IP == protocol) {
				protocol = answer->protocol;
				break;
			}
			if (IPPROTO_IP == answer->protocol)
				break;
		}
		err = -EPROTONOSUPPORT;
	}

	if (unlikely(err)) {
		if (try_loading_module < 2) {
			rcu_read_unlock();
			/*
			 * Be more specific, e.g. net-pf-2-proto-132-type-1
			 * (net-pf-PF_INET-proto-IPPROTO_SCTP-type-SOCK_STREAM)
			 */
			if (++try_loading_module == 1)
				request_module("net-pf-%d-proto-%d-type-%d",
					       PF_INET, protocol, sock->type);
			/*
			 * Fall back to generic, e.g. net-pf-2-proto-132
			 * (net-pf-PF_INET-proto-IPPROTO_SCTP)
			 */
			else
				request_module("net-pf-%d-proto-%d",
					       PF_INET, protocol);
			goto lookup_protocol;
		} else
			goto out_rcu_unlock;
	}

	err = -EPERM;
	if (sock->type == SOCK_RAW && !kern &&
	    !ns_capable(net->user_ns, CAP_NET_RAW))
		goto out_rcu_unlock;

	sock->ops = answer->ops;
	answer_prot = answer->prot;
	answer_flags = answer->flags;
	rcu_read_unlock();

3 初始化socket的sk

sk = sk_alloc(net, PF_INET, GFP_KERNEL, answer_prot, kern);
if (!sk)
    goto out;

err = 0;
if (INET_PROTOSW_REUSE & answer_flags)
    sk->sk_reuse = SK_CAN_REUSE;

4 建立socket与sock之间的关系

inet = inet_sk(sk);
inet->is_icsk = (INET_PROTOSW_ICSK & answer_flags) != 0;

inet->nodefrag = 0;

if (SOCK_RAW == sock->type) {
    inet->inet_num = protocol;
    if (IPPROTO_RAW == protocol)
        inet->hdrincl = 1;
}

if (net->ipv4.sysctl_ip_no_pmtu_disc)
    inet->pmtudisc = IP_PMTUDISC_DONT;
else
    inet->pmtudisc = IP_PMTUDISC_WANT;

inet->inet_id = 0;

sock_init_data(sock, sk);

5 使用具体协议初始化socket
这一步通过hook机制(回调函数)实现。

回到__sys_socket()

在函数的最后使用sock_map_fd(),将初始化好的socket与文件系统关联,对于TCP和其他协议来说,区别在于socket结构体中family字段的不同:

return sock_map_fd(sock, flags & (O_CLOEXEC | O_NONBLOCK));

参考文献

[1] https://www.jianshu.com/p/5d82a685b5b6

posted @ 2019-12-26 15:17  Mr-Tiger  阅读(2246)  评论(0编辑  收藏  举报