Linux内核协议栈(附1)inet_create函数注释

本文解析了inet_create函数,详细介绍了如何创建一个INET类型的套接字,包括查找协议、权限检查、内存分配等步骤。

/*
 *	Create an inet socket.------->建立一个inet类型的套接字
 */
//sock
//__sock_create
static int inet_create(struct net *net, struct socket *sock, int protocol)
{
	struct sock *sk;				//INET地址族为上层提供的一个抽象
	struct inet_protosw *answer;	//协议的socket接口
	struct inet_sock *inet;			//代表INET地址族的 socket
	struct proto *answer_prot;
	unsigned char answer_flags;
	char answer_no_check;
	int try_loading_module = 0;
	int err;

	if (unlikely(!inet_ehash_secret))
		if (sock->type != SOCK_RAW && sock->type != SOCK_DGRAM)
			build_ehash_secret();

	sock->state = SS_UNCONNECTED;

	/* Look for the requested type/protocol pair. */
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) {//--------------<SOCK_RAW,IPPROTO_IP>会走这里,但是不会break,导致	err = -EPROTONOSUPPORT;执行
			if (protocol != IPPROTO_IP)
				break;
		} else {
			/* Check for the two wild cases. */
			
			//在ifconfig 应用程序中走这个分支
			if (IPPROTO_IP == protocol) {
				protocol = answer->protocol;
				break;
			}
			//在ping 这种应用(后面章节会介绍)中走这个分支,传入的protocol 为ICMP(1),而且,如果你使用<SOCK_RAW, OSPF(89)>的组合时也会走这个分支,即使用IP 层数据传输。
			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;
	}

	/**/inet_init
	err = -EPERM;
	if (answer->capability > 0 && !capable(answer->capability))
		goto out_rcu_unlock;

	err = -EAFNOSUPPORT;
	if (!inet_netns_ok(net, protocol))
		goto out_rcu_unlock;



	sock->ops = answer->ops;//特定协议的操作函数集,声明在net.h
	
	answer_prot = answer->prot;//INET层协议描述块
	
	answer_no_check = answer->no_check;/**/inetsw_array
	answer_flags = answer->flags;
	rcu_read_unlock();

	WARN_ON(answer_prot->slab == NULL);

	err = -ENOBUFS;
	sk = sk_alloc(net, PF_INET, GFP_KERNEL, answer_prot);//-------->对应于tcp,是从tcp cache中分配的,构建对应网络层的sock,
	if (sk == NULL)
		goto out;

	err = 0;
	sk->sk_no_check = answer_no_check;
	if (INET_PROTOSW_REUSE & answer_flags)
		sk->sk_reuse = 1;

	inet = inet_sk(sk);//强制转换为inet_sock----->对于tcp,tcp_sock包含inet_connection_sock --> inet_connection_sock包含inet_sock --> inet_sock包含sock,而分配是从协议高速cache中分配的
	inet->is_icsk = (INET_PROTOSW_ICSK & answer_flags) != 0;

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

	if (ipv4_config.no_pmtu_disc)
		inet->pmtudisc = IP_PMTUDISC_DONT;
	else
		inet->pmtudisc = IP_PMTUDISC_WANT;

	inet->id = 0;

	sock_init_data(sock, sk);

	sk->sk_destruct	   = inet_sock_destruct;
	sk->sk_protocol	   = protocol;//-------------对于ping,IPPROTO_ICMP
	sk->sk_backlog_rcv = sk->sk_prot->backlog_rcv;

	inet->uc_ttl	= -1;
	inet->mc_loop	= 1;
	inet->mc_ttl	= 1;
	inet->mc_all	= 1;
	inet->mc_index	= 0;
	inet->mc_list	= NULL;

	sk_refcnt_debug_inc(sk);

	if (inet->num) {								//建立socket的时候是没有的,所以不会执行
		/* It assumes that any protocol which allows
		 * the user to assign a number at socket
		 * creation time automatically
		 * shares.
		 */
		inet->sport = htons(inet->num);
		/* Add to protocol hash chains. */ inetsw_array
		sk->sk_prot->hash(sk);//----------------------->将sk添加到相应协议的哈希表中,在udp中该函数是不能被调用的
	}
	//执行协议相关的初始化函数
	if (sk->sk_prot->init) {
		err = sk->sk_prot->init(sk); /**/tcp_v4_init_sock tcp_prot inetsw_array
		if (err)
			sk_common_release(sk);
	}
out:
	return err;
out_rcu_unlock:
	rcu_read_unlock();
	goto out;
}


<think>我们正在分析`inet_getpeer`函数,它是Linux内核中用于管理IPv4协议对端(peer)信息的关键函数,主要用于实现TCP协议中的某些特性(如TIME_WAIT状态的快速回收)。该函数位于`net/ipv4/inetpeer.c`文件中。 ### 一、`inet_getpeer`函数的作用 `inet_getpeer`函数的主要作用是获取或创建一个对端(peer)信息结构体(`struct inet_peer`)。这个结构体用于存储与特定IPv4地址相关的信息,例如: - 路径MTU(PMTU) - 用于TCP TIME_WAIT状态回收的计数器 - 用于IP分片重组的ID生成器 在TCP连接中,为了快速回收TIME_WAIT状态,内核需要记录每个对端(peer)的信息,而`inet_getpeer`就是用来获取这些信息的。 ### 二、实现原理 `inet_getpeer`函数的核心是维护一个AVL树(平衡二叉树),该树以对端的IPv4地址(和可选的地址族标识)为键值。函数首先尝试在树中查找指定的对端,如果找到则返回对应的`inet_peer`结构体;如果没有找到,则创建一个新的节点并插入到树中。 #### 1. 查找过程 查找过程使用AVL树的标准查找算法。由于AVL树是平衡的,查找操作的时间复杂度为O(log n)。 #### 2. 创建新节点 如果查找失败,则创建一个新的`inet_peer`结构体,并初始化其字段,例如: - 设置对端地址(v4或v6) - 初始化引用计数(refcnt) - 初始化各种协议相关的字段(如TCP的tw计数器) #### 3. 插入节点 新节点被创建后,需要插入到AVL树中。插入后,AVL树可能会失去平衡,因此需要进行旋转操作以恢复平衡。 #### 4. 引用计数管理 `inet_getpeer`返回的`inet_peer`结构体具有引用计数(refcnt),调用者在使用完毕后必须通过`inet_putpeer`函数减少引用计数。当引用计数降为0时,该结构体不会被立即释放,而是被放入一个垃圾回收列表,由内核线程在适当的时候回收。 ### 三、关键代码分析 以下是`inet_getpeer`函数的简化代码(基于Linux 5.x内核): ```c struct inet_peer *inet_getpeer(struct inetpeer_addr *daddr, int create) { struct inet_peer *p, *n; struct inet_peer __rcu **stack[PEER_MAXDEPTH], ***stackptr; // 1. 在AVL树中查找 stackptr = stack; p = lookup(daddr, stack, &stackptr); if (p != peer_avl_empty) { // 找到节点,增加引用计数并返回 refcount_inc(&p->refcnt); return p; } if (!create) return NULL; // 2. 创建新节点 n = kmem_cache_alloc(peer_cachep, GFP_ATOMIC); if (!n) return NULL; n->daddr = *daddr; refcount_set(&n->refcnt, 1); // 初始引用计数为1 atomic_set(&n->rid, 0); atomic_set(&n->ip_id_count, (daddr->family == AF_INET) ? secure_ip_id(daddr->addr.a4) : secure_ipv6_id(daddr->addr.a6)); // 3. 插入新节点到AVL树 link_to_pool(n); // 4. 平衡AVL树 while (stackptr > stack) { stackptr--; inet_peer_avl_rebalance(stackptr, n); } return n; } ``` ### 四、关键点说明 1. **查找函数`lookup`**:该函数在AVL树中查找指定地址的节点,并记录查找路径(通过stack数组)。 2. **节点创建**:使用`kmem_cache_alloc`从专用的slab缓存(peer_cachep)中分配内存,初始化关键字段。 3. **插入与平衡**:`link_to_pool`将新节点插入到树中,然后通过`inet_peer_avl_rebalance`沿着查找路径回溯,进行必要的旋转操作以保持树的平衡。 4. **引用计数**:新节点初始引用计数为1,返回给调用者。调用者使用完后必须调用`inet_putpeer`减少引用计数。 ### 五、性能考虑 - 由于使用了AVL树,查找和插入操作的时间复杂度都是O(log n),效率较高。 - 节点分配使用GFP_ATOMIC标志,因此可以在中断上下文中调用(但需注意内存分配失败的可能性)。 - 垃圾回收机制避免了频繁的内存分配和释放,提高了性能。 ### 六、应用场景 `inet_getpeer`主要用于TCP协议中,尤其是在处理TIME_WAIT状态时。例如,在`tcp_conn_request`(处理SYN报文)和`tcp_v4_conn_request`中,会调用该函数来获取或创建对端信息,以便更新TIME_WAIT状态的相关计数器。 ### 总结 `inet_getpeer`函数Linux内核中管理对端信息的重要接口,通过AVL树高效地存储和检索对端数据。它支持TCP等协议的特性实现,如TIME_WAIT状态的快速回收,从而提升网络性能。 §§相关问题§§ 1. Linux内核中AVL树是如何实现的? 2. `inet_putpeer`函数的具体实现和作用是什么? 3. TCP协议中如何利用`inet_getpeer`实现TIME_WAIT状态的快速回收? 4. 对端信息结构体`struct inet_peer`包含哪些重要字段?</think>### Linux内核中`inet_getpeer`函数实现详解 `inet_getpeer`是Linux内核网络协议栈中用于管理IPv4对端(peer)信息的关键函数,主要用于实现高效的对端状态管理。以下是其实现原理和作用的详细分析: #### 一、功能与作用 1. **核心功能**:获取或创建IPv4对端结构体(`struct inet_peer`) 2. **主要作用**: - 为每个唯一IP地址维护对端信息 - 实现TCP协议中的TIME_WAIT状态快速回收机制 - 管理IP分片ID生成器,避免分片ID冲突 - 存储路径MTU(PMTU)信息[^1] #### 二、实现原理 1. **数据结构**: - 使用AVL树(平衡二叉树)存储对端信息 - 树节点为`struct inet_peer`: ```c struct inet_peer { struct inet_peer __rcu *avl_left, *avl_right; __be32 v4daddr; // IPv4地址 u16 avl_height; // 树高度 u32 dtime; // 最后使用时间 refcount_t refcnt; // 引用计数 // ... 其他协议特定字段 }; ``` 2. **核心流程**: ```c struct inet_peer *inet_getpeer(struct inetpeer_addr *daddr, int create) { struct inet_peer *p, *n; struct inet_peer **stack[PEER_MAXDEPTH], ***stackptr; // 1. 在AVL树中查找现有节点 stackptr = stack; p = lookup(daddr, stack, &stackptr); if (p != peer_avl_empty) { // 找到现有节点,增加引用计数 refcount_inc(&p->refcnt); return p; } if (!create) return NULL; // 2. 创建新节点 n = kmem_cache_alloc(peer_cachep, GFP_ATOMIC); if (!n) return NULL; // 3. 初始化新节点 n->v4daddr = daddr->addr.a4; refcount_set(&n->refcnt, 1); atomic_set(&n->rid, 0); atomic_set(&n->ip_id_count, secure_ip_id(n->v4daddr)); // 4. 插入AVL树并平衡 link_to_pool(n); while (stackptr > stack) { stackptr--; inet_peer_avl_rebalance(*stackptr, stackptr); } return n; } ``` #### 三、关键技术点 1. **AVL树管理**: - 使用自平衡二叉树确保O(log n)的查找效率 - 插入节点后自动进行树旋转平衡操作 ```c static void inet_peer_avl_rebalance(struct inet_peer **stack[], struct inet_peer ***stackend) { while (stackend > stack) { struct inet_peer *node = *--stackend; struct inet_peer **nodep = *stackend; int lh, rh; lh = node_height(node->avl_left); rh = node_height(node->avl_right); if (lh - rh > 1) { // 左子树不平衡 if (node_height(node->avl_left->avl_left) >= node_height(node->avl_left->avl_right)) node = avl_rotate_right(node); // 右旋 else node = avl_rotate_left_right(node); // 左右旋 } // ... 其他平衡情况处理 *nodep = node; } } ``` 2. **引用计数机制**: - 通过`refcount_t`实现安全引用管理 - 使用`inet_putpeer()`释放引用: ```c void inet_putpeer(struct inet_peer *p) { if (refcount_dec_and_test(&p->refcnt)) call_rcu(&p->rcu, inetpeer_free_rcu); } ``` 3. **内存管理**: - 使用`kmem_cache_alloc`从专用slab分配器分配内存 - 通过RCU机制安全释放内存[^3] #### 四、应用场景 1. **TCP TIME_WAIT状态管理**: ```c // net/ipv4/tcp_minisocks.c void tcp_time_wait(struct sock *sk, int state, int timeo) { struct inet_peer *peer = inet_getpeer(sk->sk_daddr); if (peer) { peer->tcp_ts_stamp = jiffies; // ... TIME_WAIT状态处理 inet_putpeer(peer); } } ``` 2. **IP分片ID生成**: ```c // net/ipv4/ip_output.c void ip_select_ident_segs(struct net *net, struct sk_buff *skb, ...) { struct inet_peer *peer; peer = inet_getpeer(iph->daddr, 1); id = atomic_inc_return(&peer->ip_id_count); iph->id = htons(id); inet_putpeer(peer); } ``` #### 五、性能优化 1. **无锁读取**:通过RCU机制实现读操作无锁化 2. **延迟释放**:使用`call_rcu`延迟释放内存,减少锁争用 3. **缓存局部性**:AVL树结构优化CPU缓存命中率 #### 六、使用注意事项 1. **引用管理**:必须成对使用`inet_getpeer()`和`inet_putpeer()` 2. **原子上下文**:可在中断上下文调用(使用GFP_ATOMIC标志) 3. **内存限制**:通过`/proc/sys/net/ipv4/inet_peer_threshold`控制最大节点数 ### 总结 `inet_getpeer`是Linux内核网络协议栈的核心组件,通过AVL树高效管理对端信息,支持TCP状态管理和IP分片等关键功能。其设计结合了RCU无锁读取、引用计数和延迟释放等机制,在保证数据一致性的同时实现高性能[^1][^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值