UDP提供了无连接通信,且不对传送数据包进行可靠性保证,不用对数据进行超时重传,也不用对数据进行应答(ACK),适合于一次传输少量数据,UDP传输的可靠性由应用层负责. 这里主要分析UDP 传输控制块(sock)的管理, port获取, udp查找传输控制块.
1. UDP sock管理
sock在内核中表示了通信双方的所有信息,接收和发送数据,都要找到对应的sock, 为了加快查找速度,udp实现了两个hash 表。
hash 表初始化
void __init udp_table_init(struct udp_table *table, const char *name)
{
unsigned int i;
/*分配分配hash表结构udp_hslot,这里乘以2,表示 分配两种类型的hash表*/
table->hash = alloc_large_system_hash(name,2 * sizeof(struct udp_hslot),uhash_entries,21,0,&table->log,&table->mask,UDP_HTABLE_SIZE_MIN,64 * 1024);
/*第二类hash表内存,mask表示hash表的个数*/
table->hash2 = table->hash + (table->mask + 1);
/*初始化对应的hash 表 */
for (i = 0; i <= table->mask; i++) {
INIT_HLIST_NULLS_HEAD(&table->hash[i].head, i);
table->hash[i].count = 0;
spin_lock_init(&table->hash[i].lock);
}
for (i = 0; i <= table->mask; i++) {
INIT_HLIST_NULLS_HEAD(&table->hash2[i].head, i);
table->hash2[i].count = 0;
spin_lock_init(&table->hash2[i].lock);
}
}
目前内核中log=11,mask=2048-1 ; 也即每种hash表有2048个hash表
udp 根据port 去进行hash运算
hash布局如下:
2 sock hash运算
在用户空间进场调用bind接口时,如指定port,则直接根据port找到对应的hash表,如果port==0,则随机选取一个port,并进行hash运行
int udp_lib_get_port(struct sock *sk, unsigned short snum,
int (*saddr_comp)(const struct sock *sk1,
const struct sock *sk2),
unsigned int hash2_nulladdr)
{
struct udp_hslot *hslot, *hslot2;
struct udp_table *udptable = sk->sk_prot->h.udp_table;
int error = 1;
struct net *net = sock_net(sk);
if (!snum) { /*处理port=0的情况 */
int low, high, remaining;
unsigned int rand;
unsigned short first, last;
DECLARE_BITMAP(bitmap, PORTS_PER_CHAIN);
/* 获取系统端口的范围*/
inet_get_local_port_range(net, &low, &high);
remaining = (high - low) + 1;
/*获取随机数,这样可以获取到随机的port */
rand = prandom_u32();
first = reciprocal_scale(rand, remaining) + low;
/*把rand变成hash表大小的奇数倍,这样可以遍历hash表中的所有port,并且不会产生连续的port ,可以结合上面hash表布局图来看*/
rand = (rand | 1) * (udptable->mask + 1);
last = first + udptable->mask + 1;
do{ /*获取第一个hash表 */
hslot = udp_hashslot(udptable, net, first);
/*用于port是否使用的标记 */
bitmap_zero(bitmap, PORTS_PER_CHAIN);
spin_lock_bh(&hslot->lock);
/*标记hash表中的port是否被使用,一个bit对应一个port */
udp_lib_lport_inuse(net, snum, hslot, bitmap, sk,
saddr_comp, udptable->log);
snum = first;
do {
if (low <= snum && snum <= high &&
!test_bit(snum >> udptable->log, bitmap) &&
!inet_is_local_reserved_port(net, snum))
goto found;
snum += rand; /*如果snum被使用,则获取下一个port,这里加上奇数倍hash表大小,可以遍历hash表中的所有port,直到snum回到first */
} while (snum != first);
spin_unlock_bh(&hslot->lock);
} while (++first != last);/ *进行下一个hast表的遍历 */
goto fail;
} else { /*这里处理指定port的类型,一般用于服务端 */
hslot = udp_hashslot(udptable, net, snum);
spin_lock_bh(&hslot->lock);
if (hslot->count > 10) {/*如果hslot的个数大于10,则优先在slot2中查找,这样可以加快速度 */
int exist;
/*下面对slot2的遍历查找,与上面类似 */
unsigned int slot2 = udp_sk(sk)->udp_portaddr_hash ^ snum;
slot2 &= udptable->mask;
hash2_nulladdr &= udptable->mask;
hslot2 = udp_hashslot2(udptable, slot2);
if (hslot->count < hslot2->count)
goto scan_primary_hash;
exist = udp_lib_lport_inuse2(net, snum, hslot2,/*判读snum释放被使用 */
sk, saddr_comp);
if (!exist && (hash2_nulladdr != slot2)) {
hslot2 = udp_hashslot2(udptable, hash2_nulladdr);
exist = udp_lib_lport_inuse2(net, snum, hslot2,
sk, saddr_comp);
}
if (exist)
goto fail_unlock;
else
goto found;
}
scan_primary_hash:
if (udp_lib_lport_inuse(net, snum, hslot, NULL, sk,
saddr_comp, 0))
goto fail_unlock;
}
found: /* 当找到一个port后,把对应的sock,添加到hast表*/
inet_sk(sk)->inet_num = snum;
udp_sk(sk)->udp_port_hash = snum;
udp_sk(sk)->udp_portaddr_hash ^= snum;
if (sk_unhashed(sk)) {
sk_nulls_add_node_rcu(sk, &hslot->head);
hslot->count++;
sock_prot_inuse_add(sock_net(sk), sk->sk_prot, 1);
hslot2 = udp_hashslot2(udptable, udp_sk(sk)->udp_portaddr_hash);
spin_lock(&hslot2->lock);
hlist_nulls_add_head_rcu(&udp_sk(sk)->udp_portaddr_node,
&hslot2->head);
hslot2->count++;
spin_unlock(&hslot2->lock);}
3. sock查找
当ip层接收到数据后,根据源IP,目的IP, 目的port去查找对应的sock,实现函数为__udp4_lib_lookup
sock同时加入了两个hash表,查找时,如果hash1的个数大于10,则优先从hash2中查找,只要弄懂hash过程,从hash中查找则相对简单。