现在的位置: 首页 > 综合 > 正文

linux下ip协议(V4)的实现(五)

2014年03月21日 ⁄ 综合 ⁄ 共 5529字 ⁄ 字号 评论关闭

这次主要介绍一些ip层管理以及统计相关的东西. 

首先来看 long-living ip peer information. 

我们知道ip协议是无状态的协议.这里内核为了提升性能.为每个目的ip地址(换句话说,也就是和本机进行通信过的主机)保存了一些信息. 

peer子系统一般是被tcp,或者routing子系统所使用. 

这个信息的数据结构是inet_peer,它是一棵avl树,每个节点的key就是一个ip地址.由于是avl树,因此每次搜索都是O(lg n): 

Java代码  收藏代码
  1. struct inet_peer  
  2. {  
  3.     ///avl树的左子树和右子树  
  4.     struct inet_peer    *avl_left, *avl_right;  
  5. ///远端peer的ip地址  
  6.     __be32          v4daddr;    /* peer's address */  
  7. ///树的高度  
  8.     __u16           avl_height;  
  9. ///下一个使用这个peer的包id(我们的包id的选择,就是基于这个域,也就是每次通过传入ip地址,从而得到当前应使用的id(通过inet_getid函数)).  
  10.     __u16           ip_id_count;    /* IP ID for the next packet */  
  11. ///这个链表包含了所有定时器到期的peer(由于peer初始化的时候内存大小有限制,因此我们就需要定时将在给定时间内没有使用的peer放到这个链表中).这里只有当它的引用计数为0时,才会最终从unused中移除.  
  12.     struct list_head    unused;  
  13. ///当这个inet_peer元素被加入到unused链表中(通过inet_putpeer)的时间.  
  14.     __u32           dtime;      /* the time of last use of not 
  15.  
  16. ///引用计数                      * referenced entries */  
  17.     atomic_t        refcnt;  
  18. ///帧结束的计数器.  
  19.     atomic_t        rid;        /* Frag reception counter */  
  20. ///下面这两个是被tcp使用来管理时间戳的.  
  21.     __u32           tcp_ts;  
  22.     unsigned long       tcp_ts_stamp;  
  23. };  



peer子系统的初始化是在inet_initpeers中进行的,它是被ipv4协议的初始化函数ip_init调用的.这个函数的主要任务有三个: 

1 allocate一个将要保存inet_peer数据的cache. 

2 定义能被inet_peer所使用的最大内存限制. 

3 开启gc定时器. 

Java代码  收藏代码
  1. ///内存限制  
  2. extern int inet_peer_threshold;  
  3.   
  4. ///cache  
  5. static struct kmem_cache *peer_cachep __read_mostly;  
  6. ///定时器.可以看到它的处理函数是peer_check_expire,我们后面会介绍这个函数.  
  7. static DEFINE_TIMER(peer_periodic_timer, peer_check_expire, 00);  
  8. ///相应的读写锁.  
  9. static DEFINE_RWLOCK(peer_pool_lock);  
  10. void __init inet_initpeers(void)  
  11. {  
  12.     struct sysinfo si;  
  13.   
  14.     /* Use the straight interface to information about memory. */  
  15.     si_meminfo(&si);  
  16.   
  17. ///上面是取得系统的一些信息,我们这里主要用到的就是内存信息,因此这里通过总内存大小,来对inet_peer_threshold进行赋值.  
  18.     if (si.totalram <= (32768*1024)/PAGE_SIZE)  
  19.         inet_peer_threshold >>= 1/* max pool size about 1MB on IA32 */  
  20.     if (si.totalram <= (16384*1024)/PAGE_SIZE)  
  21.         inet_peer_threshold >>= 1/* about 512KB */  
  22.     if (si.totalram <= (8192*1024)/PAGE_SIZE)  
  23.         inet_peer_threshold >>= 2/* about 128KB */  
  24.   
  25. ///create一个cache.  
  26.     peer_cachep = kmem_cache_create("inet_peer_cache",  
  27.             sizeof(struct inet_peer),  
  28.             0, SLAB_HWCACHE_ALIGN|SLAB_PANIC,  
  29.             NULL);  
  30. ///初始化定时器.  
  31.     peer_periodic_timer.expires = jiffies  
  32.         + net_random() % inet_peer_gc_maxtime  
  33.         + inet_peer_gc_maxtime;  
  34.     add_timer(&peer_periodic_timer);  
  35. }  



peer系统的核心函数就是inet_getpeer,它是提供给其他子系统的接口,它封装了lookup函数,而loopup函数只是很简单的avl树的查找函数. 

而 inet_getpeer函数它通过传入的key(也就是ip地址)和一个flag(比如赋值为create),可以做到,当查找失败后,能创建一个新的树的拣点,并初始化ip包 的id(使用id模块的secure_ip_id)来初始化. 

首先来看它的调用图,然后我们再分析整个函数: 


 

这里只有一个要注意的就是我们检测peer是否存在,检测了两次,这是我们在第二次得到锁之前与第一次释放锁之后,这段时间内有可能一个新的peer被加入. 

Java代码  收藏代码
  1. struct inet_peer *inet_getpeer(__be32 daddr, int create)  
  2. {  
  3.     struct inet_peer *p, *n;  
  4.     struct inet_peer **stack[PEER_MAXDEPTH], ***stackptr;  
  5.   
  6. ///查找是否存在这个peer节点.  
  7.     read_lock_bh(&peer_pool_lock);  
  8.     p = lookup(daddr, NULL);  
  9. ///存在的话引用计数加1.  
  10.     if (p != peer_avl_empty)  
  11.         atomic_inc(&p->refcnt);  
  12.     read_unlock_bh(&peer_pool_lock);  
  13.   
  14.     if (p != peer_avl_empty) {  
  15. ///如果这个节点在unused中,则从unused中移除它.并返回  
  16.         unlink_from_unused(p);  
  17.         return p;  
  18.     }  
  19.   
  20. ///如果create参数为null,则返回null.  
  21.     if (!create)  
  22.         return NULL;  
  23.   
  24. ///开始创建一个新的peer节点.  
  25.     n = kmem_cache_alloc(peer_cachep, GFP_ATOMIC);  
  26.     if (n == NULL)  
  27.         return NULL;  
  28.     n->v4daddr = daddr;  
  29.     atomic_set(&n->refcnt, 1);  
  30.     atomic_set(&n->rid, 0);  
  31. ///得到合适的包id.  
  32.     n->ip_id_count = secure_ip_id(daddr);  
  33.     n->tcp_ts_stamp = 0;  
  34.   
  35.     write_lock_bh(&peer_pool_lock);  
  36.     /* Check if an entry has suddenly appeared. */  
  37.     p = lookup(daddr, stack);  
  38.     if (p != peer_avl_empty)  
  39.         goto out_free;  
  40.   
  41. ///加入到avl树.  
  42.     link_to_pool(n);  
  43. ///初始化它的unused链表.  
  44.     INIT_LIST_HEAD(&n->unused);  
  45.     peer_total++;  
  46.     write_unlock_bh(&peer_pool_lock);  
  47. ///如果此时内存超过限制,则remove掉链表头的元素(也就是LRU算法了,后面我们会分析cleanup_once这个函数.  
  48.     if (peer_total >= inet_peer_threshold)  
  49.         /* Remove one less-recently-used entry. */  
  50.         cleanup_once(0);  
  51.   
  52.     return n;  
  53.   
  54. out_free:  
  55. ........................................  
  56. }  



接下来我们来看clean_once这个函数.这个函数不仅会被inet_getpeer调用,还会被peer_periodic_timer调用: 

Java代码  收藏代码
  1. static int cleanup_once(unsigned long ttl)  
  2. {  
  3.     struct inet_peer *p = NULL;  
  4.   
  5.     /* Remove the first entry from the list of unused nodes. */  
  6.     spin_lock_bh(&inet_peer_unused_lock);  
  7.     if (!list_empty(&unused_peers)) {  
  8.         __u32 delta;  
  9.   
  10.         p = list_first_entry(&unused_peers, struct inet_peer, unused);  
  11.   
  12. ///计算出这个peer最后一次被使用(也就是操作引用计数)到当前过去了多久.  
  13.         delta = (__u32)jiffies - p->dtime;  
  14.   
  15. ///如果这个时间小于传进来的ttl,就不进行任何操作.直接返回(这个ttl也就表示一个在unused链表中的元素在删除前,需要等待多久).而我们上面的inet_getpeer中,传进来的是0,这就会直接删除掉第一个peer.  
  16.         if (delta < ttl) {  
  17.             /* Do not prune fresh entries. */  
  18.             spin_unlock_bh(&inet_peer_unused_lock);  
  19.             return -1;  
  20.         }  
  21.   
  22.         list_del_init(&p->unused);  
  23. ///引用计数-1.  
  24.         atomic_inc(&p->refcnt);  
  25.     }  
  26.     spin_unlock_bh(&inet_peer_unused_lock);  
  27.   
  28.     if (p == NULL)  
  29.         /* It means that the total number of USED entries has 
  30.          * grown over inet_peer_threshold.  It shouldn't really 
  31.          * happen because of entry limits in route cache. */  
  32.         return -1;  
  33. ///这个函数就简单介绍一下,先会判断p的引用计数,如果引用计数为1,则说明可以从avl树中删除它,然后将它彻底free掉.当引用技术不为1,则会将它直接加入到unused链表中(这里要注意,它并没有从avl树中删除).  
  34.     unlink_from_pool(p);  
  35.     return 0;  
  36. }  




接下来来看定时器处理函数: 

Java代码  收藏代码
  1. static void peer_check_expire(unsigned long dummy)  

抱歉!评论已关闭.