Linux邻居协议 学习笔记 之四 通用邻居项创建、查找、删除等相关

上节主要是分析了通用邻居层邻居项的垃圾回收机制,这一节主要是分析邻居项的创建、查找、删除等相关的函数,这一节只是介绍函数功能,而没有涉及状态机、通用邻居层的架构等。比如邻居项删除函数neigh_destroy,而这个函数主要是通过垃圾回收机制的调用才会执行删除操作;而对于邻居项创建函数neigh_create,在arp协议下,则在路由缓存与邻居项绑定时会触发调用neigh_create创建邻居项,或者接收到arp reply或者arp request数据包时会触发调用neigh_create创建邻居项。本节只是分析这些函数的实现流程,而不分析这些函数在arp协议中或者其他邻居协议中被其他逻辑子层调用的过。

首先介绍neigh_create函数,分析如下

1、调用neigh_alloc,申请一个邻居项缓存

2、设置邻居项的primary_key、dev值,并增加dev的引用计数

3、调用邻居表的constructor,初始化邻居表协议相关的参数

4、如果存在n->parms->neigh_setup,则调用neigh_setup进行初始化(对于arp,该函数

为NULL)

5、设置confirmed时间

6、如果创建的邻居项总数超过了邻居表定义的最大值,则调用neigh_hash_grow扩充

邻居项的hash表容量

7、在将创建的邻居项插入到邻居项hash表之前,需要再次查找hash表里的邻居项:

a)如果该邻居项已经存在hash表里,则增加对该邻居项的引用计数,并释放已

申请的邻居项缓存

b)如果该邻居项还没有在hash表里,则将该邻居项插入的hash表里。

struct neighbour*neigh_create(struct neigh_table *tbl, const void *pkey,

struct net_device *dev)

{

u32 hash_val;

int key_len = tbl->key_len;

int error;

struct neighbour *n1, *rc, *n =neigh_alloc(tbl);

if (!n) {

rc = ERR_PTR(-ENOBUFS);

goto out;

}

memcpy(n->primary_key, pkey, key_len);

n->dev = dev;

dev_hold(dev);

/* Protocol specific setup. */

if (tbl->constructor && (error = tbl->constructor(n)) < 0) {

rc = ERR_PTR(error);

goto out_neigh_release;

}

/* Device specific setup. */

if (n->parms->neigh_setup&&

(error = n->parms->neigh_setup(n)) < 0) {

rc = ERR_PTR(error);

goto out_neigh_release;

}

n->confirmed = jiffies -(n->parms->base_reachable_time << 1);

write_lock_bh(&tbl->lock);

if (atomic_read(&tbl->entries)> (tbl->hash_mask + 1))

neigh_hash_grow(tbl,(tbl->hash_mask + 1) << 1);

hash_val = tbl->hash(pkey, dev) &tbl->hash_mask;

if (n->parms->dead) {

rc = ERR_PTR(-EINVAL);

goto out_tbl_unlock;

}

for (n1 = tbl->hash_buckets[hash_val];n1; n1 = n1->next) {

if (dev == n1->dev &&!memcmp(n1->primary_key, pkey, key_len)) {

neigh_hold(n1);

rc = n1;

goto out_tbl_unlock;

}

}

n->next =tbl->hash_buckets[hash_val];

tbl->hash_buckets[hash_val] = n;

n->dead = 0;

neigh_hold(n);

write_unlock_bh(&tbl->lock);

NEIGH_PRINTK2("neigh %p iscreated.\n", n);

rc = n;

out:

return rc;

out_tbl_unlock:

write_unlock_bh(&tbl->lock);

out_neigh_release:

neigh_release(n);

goto out;

}

由于该函数调用了neigh_alloc,下面我们分析函数neigh_alloc

从邻居表的slab缓存里,申请一个邻居项,并进行通用邻居层的初始化

static structneighbour *neigh_alloc(struct neigh_table *tbl)

{

struct neighbour *n = NULL;

unsigned long now = jiffies;

int entries;

entries =atomic_inc_return(&tbl->entries) – 1;

if (entries >= tbl->gc_thresh3 ||

(entries >= tbl->gc_thresh2 &&

time_after(now, tbl->last_flush + 5 * HZ))) {

if (!neigh_forced_gc(tbl)&&

entries >= tbl->gc_thresh3)

goto out_entries;

}

n =kmem_cache_zalloc(tbl->kmem_cachep, GFP_ATOMIC);

if (!n)

goto out_entries;

skb_queue_head_init(&n->arp_queue);

rwlock_init(&n->lock);

n->updated = n->used = now;

n->nud_state = NUD_NONE;

n->output = neigh_blackhole;

n->parms =neigh_parms_clone(&tbl->parms);//直接从邻居表中克隆

setup_timer(&n->timer,neigh_timer_handler, (unsigned long)n);//创建定时器

NEIGH_CACHE_STAT_INC(tbl, allocs);

n->tbl = tbl;

atomic_set(&n->refcnt, 1);

n->dead = 1;

out:

return n;

out_entries:

atomic_dec(&tbl->entries);

goto out;

}

下面分析通用邻居项的查找函数,主要是有neigh_lookup、neigh_lookup_nodev、__neigh_lookup、__neigh_lookup_errno

函数neigh_lookup的功能是根据关键字net_device与pkey在邻居表里查找一个邻居项

1、根据关键字net_device与pkey,计算hash值

2、通过hash值,在邻居项hash数组中,找到指定的hash链表

3、遍历指定的hash链表,比较net_device与pkey值,查找符合条件的邻居项。

struct neighbour*neigh_lookup(struct neigh_table *tbl, const void *pkey,

struct net_device *dev)

{

struct neighbour *n;

int key_len = tbl->key_len;

u32 hash_val;

NEIGH_CACHE_STAT_INC(tbl, lookups);

read_lock_bh(&tbl->lock);

hash_val = tbl->hash(pkey, dev);

for (n = tbl->hash_buckets[hash_val& tbl->hash_mask]; n; n = n->next) {

if (dev == n->dev &&!memcmp(n->primary_key, pkey, key_len)) {

neigh_hold(n);

NEIGH_CACHE_STAT_INC(tbl,hits);

break;

}

}

read_unlock_bh(&tbl->lock);

return n;

}

函数neigh_lookup_nodev功能是根据关键字net与pkey在邻居表里查找一个邻居项

该函数的功能与neigh_lookup类似

struct neighbour*neigh_lookup_nodev(struct neigh_table *tbl, struct net *net,

const void *pkey)

{

struct neighbour *n;

int key_len = tbl->key_len;

u32 hash_val;

NEIGH_CACHE_STAT_INC(tbl, lookups);

read_lock_bh(&tbl->lock);

hash_val = tbl->hash(pkey, NULL);

for (n = tbl->hash_buckets[hash_val& tbl->hash_mask]; n; n = n->next) {

if (!memcmp(n->primary_key,pkey, key_len) &&

net_eq(dev_net(n->dev), net)) {

neigh_hold(n);

NEIGH_CACHE_STAT_INC(tbl,hits);

break;

}

}

read_unlock_bh(&tbl->lock);

return n;

}

函数__neigh_lookup的功能是同时实现查找邻居项与决定是否创建邻居项,该函数通常被其他协议子层调用。

static inlinestruct neighbour *

__neigh_lookup(structneigh_table *tbl, const void *pkey, struct net_device *dev, int creat)

{

struct neighbour *n = neigh_lookup(tbl,pkey, dev);

if (n || !creat)

return n;

n = neigh_create(tbl, pkey, dev);

return IS_ERR(n) ? NULL : n;

}

该函数与函数__neigh_lookup相比,其在查找不到邻居项的情况下,即会默认创建一个新的邻居项。

static inlinestruct neighbour *

__neigh_lookup_errno(structneigh_table *tbl, const void *pkey,

struct net_device *dev)

{

struct neighbour *n = neigh_lookup(tbl,pkey, dev);

if (n)

return n;

return neigh_create(tbl, pkey, dev);

}

下面分析一下邻居项hash表容量的扩容函数neigh_hash_grow

功能:邻居项散列表的扩容

1、为邻居表申请一个hash bucket数组指针,

2、然后将原hash bucket中所有的邻居项,重新计算hash值后,存放在新的hash链表

3、释放原hash bucket数组所占用的缓存。

static voidneigh_hash_grow(struct neigh_table *tbl, unsigned long new_entries)

{

struct neighbour **new_hash, **old_hash;

unsigned int i, new_hash_mask,old_entries;

NEIGH_CACHE_STAT_INC(tbl, hash_grows);

BUG_ON(!is_power_of_2(new_entries));

new_hash = neigh_hash_alloc(new_entries);

if (!new_hash)

return;

old_entries = tbl->hash_mask + 1;

new_hash_mask = new_entries – 1;

old_hash = tbl->hash_buckets;

get_random_bytes(&tbl->hash_rnd,sizeof(tbl->hash_rnd));

for (i = 0; i < old_entries; i++) {

struct neighbour *n, *next;

for (n = old_hash[i]; n; n = next){

unsigned int hash_val =tbl->hash(n->primary_key, n->dev);

hash_val &=new_hash_mask;

next = n->next;

n->next =new_hash[hash_val];

new_hash[hash_val] = n;

}

}

tbl->hash_buckets = new_hash;

tbl->hash_mask = new_hash_mask;

neigh_hash_free(old_hash, old_entries);

}

下面分析邻居项的删除函数neigh_destroy

1、判断该邻居项是否可以删除 dead==1,则可以删除

2、删除该邻居项的定时器

3、对于可以删除的邻居项, 将邻居项所关联的所有二层缓存头的hh_output设置

为neigh_blackhole,即直接丢弃数据包

4、调用skb_queue_purge,丢弃队列中所有待发送的数据包

5、取消对net_dev、neigh_parms的引用

6、最后调用kmem_cache_free,将邻居项的缓存释放给邻居表的slab缓存中。

voidneigh_destroy(struct neighbour *neigh)

{

struct hh_cache *hh;

NEIGH_CACHE_STAT_INC(neigh->tbl,destroys);

if (!neigh->dead) {

printk(KERN_WARNING

"Destroying alive neighbour%p\n", neigh);

dump_stack();

return;

}

if (neigh_del_timer(neigh))

printk(KERN_WARNING"Impossible event.\n");

while ((hh = neigh->hh) != NULL) {

neigh->hh = hh->hh_next;

hh->hh_next = NULL;

write_seqlock_bh(&hh->hh_lock);

hh->hh_output =neigh_blackhole;

write_sequnlock_bh(&hh->hh_lock);

if(atomic_dec_and_test(&hh->hh_refcnt))

kfree(hh);

}

skb_queue_purge(&neigh->arp_queue);

dev_put(neigh->dev);

neigh_parms_put(neigh->parms);

NEIGH_PRINTK2("neigh %p isdestroyed.\n", neigh);

atomic_dec(&neigh->tbl->entries);

kmem_cache_free(neigh->tbl->kmem_cachep,neigh);

}

函数neigh_release是neigh_destroy的包裹函数,增加了对neigh->refcnt的判断。

如果邻居项的引用计数为1时,则调用neigh_destroy释放该邻居项所对应的缓存

static inlinevoid neigh_release(struct neighbour *neigh)

{

if(atomic_dec_and_test(&neigh->refcnt))

neigh_destroy(neigh);

}

下面分析一下一些小函数

neigh_clone,该函数只是增加对邻居项的引用,而并不clone一个邻居项

static inlinestruct neighbour * neigh_clone(struct neighbour *neigh)

{

if (neigh)

atomic_inc(&neigh->refcnt);

return neigh;

}

设置邻居项的confirmed值为当前时间

static inlinevoid neigh_confirm(struct neighbour *neigh)

{

if (neigh)

neigh->confirmed = jiffies;

}

/*

从hh_cache中拷贝二层头部,并调用hh_cache->output输出skb

*/

static inlineint neigh_hh_output(struct hh_cache *hh, struct sk_buff *skb)

{

unsigned seq;

int hh_len;

do {

int hh_alen;

seq =read_seqbegin(&hh->hh_lock);

hh_len = hh->hh_len;

hh_alen = HH_DATA_ALIGN(hh_len);

memcpy(skb->data – hh_alen,hh->hh_data, hh_alen);

} while(read_seqretry(&hh->hh_lock, seq));

skb_push(skb, hh_len);

return hh->hh_output(skb);

}

游手好闲会使人心智生锈

Linux邻居协议 学习笔记 之四 通用邻居项创建、查找、删除等相关

相关文章:

你感兴趣的文章:

标签云: