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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM