注册 登录  
 加关注
查看详情
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

小可尼の博客

Linux后端的技术窝窝

 
 
 

日志

 
 

inetpeer.c与inetpeer.h  

2013-04-23 22:38:56|  分类: linux内核开发 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |
出处:http://tech.ddvip.com/2009-09/1252575146131970.html

首先来看 long-living ip peer information.

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

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

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


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

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

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

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

  3 开启gc定时器.


///内存限制 
extern int inet_peer_threshold; 
 
///cache 
static struct kmem_cache *peer_cachep __read_mostly; 
///定时器.可以看到它的处理函数是peer_check_expire,我们后面会介绍这个函数. 
static DEFINE_TIMER(peer_periodic_timer, peer_check_expire, 0, 0); 
///相应的读写锁. 
static DEFINE_RWLOCK(peer_pool_lock); 
void __init inet_initpeers(void) 
{ 
 struct sysinfo si; 
 
 /* Use the straight interface to information about memory. */ 
 si_meminfo(&si); 
 
///上面是取得系统的一些信息,我们这里主要用到的就是内存信息,因此这里通过总内存大小,来对inet_peer_threshold进行赋值. 
 if (si.totalram <= (32768*1024)/PAGE_SIZE) 
 inet_peer_threshold >>= 1; /* max pool size about 1MB on IA32 */ 
 if (si.totalram <= (16384*1024)/PAGE_SIZE) 
 inet_peer_threshold >>= 1; /* about 512KB */ 
 if (si.totalram <= (8192*1024)/PAGE_SIZE) 
 inet_peer_threshold >>= 2; /* about 128KB */ 
 
///create一个cache. 
 peer_cachep = kmem_cache_create("inet_peer_cache", 
  sizeof(struct inet_peer), 
  0, SLAB_HWCACHE_ALIGN|SLAB_PANIC, 
  NULL); 
///初始化定时器. 
 peer_periodic_timer.expires = jiffies 
 + net_random() % inet_peer_gc_maxtime 
 + inet_peer_gc_maxtime; 
 add_timer(&peer_periodic_timer); 
}
peer系统的核心函数就是inet_getpeer,它是提供给其他子系统的接口,它封装了lookup函数,而loopup函数只是很简单的avl树的查找函数.

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

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


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

struct inet_peer *inet_getpeer(__be32 daddr, int create) 
{ 
 struct inet_peer *p, *n; 
 struct inet_peer **stack[PEER_MAXDEPTH], ***stackptr; 
 
///查找是否存在这个peer节点. 
 read_lock_bh(&peer_pool_lock); 
 p = lookup(daddr, NULL); 
///存在的话引用计数加1. 
 if (p != peer_avl_empty) 
 atomic_inc(&p->refcnt); 
 read_unlock_bh(&peer_pool_lock); 
 
 if (p != peer_avl_empty) { 
///如果这个节点在unused中,则从unused中移除它.并返回 
 unlink_from_unused(p); 
 return p; 
 } 
 
///如果create参数为null,则返回null. 
 if (!create) 
 return NULL; 
 
///开始创建一个新的peer节点. 
 n = kmem_cache_alloc(peer_cachep, GFP_ATOMIC); 
 if (n == NULL) 
 return NULL; 
 n->v4daddr = daddr; 
 atomic_set(&n->refcnt, 1); 
 atomic_set(&n->rid, 0); 
///得到合适的包id. 
 n->ip_id_count = secure_ip_id(daddr); 
 n->tcp_ts_stamp = 0; 
 
 write_lock_bh(&peer_pool_lock); 
 /* Check if an entry has suddenly appeared. */ 
 p = lookup(daddr, stack); 
 if (p != peer_avl_empty) 
 goto out_free; 
 
///加入到avl树. 
 link_to_pool(n); 
///初始化它的unused链表. 
 INIT_LIST_HEAD(&n->unused); 
 peer_total++; 
 write_unlock_bh(&peer_pool_lock); 
///如果此时内存超过限制,则remove掉链表头的元素(也就是LRU算法了,后面我们会分析cleanup_once这个函数. 
 if (peer_total >= inet_peer_threshold) 
 /* Remove one less-recently-used entry. */ 
 cleanup_once(0); 
 
 return n; 
 
out_free: 
........................................ 
}

接下来我们来看clean_once这个函数.这个函数不仅会被inet_getpeer调用,还会被peer_periodic_timer调用:
static int cleanup_once(unsigned long ttl) 
{ 
 struct inet_peer *p = NULL; 
 
 /* Remove the first entry from the list of unused nodes. */ 
 spin_lock_bh(&inet_peer_unused_lock); 
 if (!list_empty(&unused_peers)) { 
 __u32 delta; 
 
 p = list_first_entry(&unused_peers, struct inet_peer, unused); 
 
///计算出这个peer最后一次被使用(也就是操作引用计数)到当前过去了多久. 
 delta = (__u32)jiffies - p->dtime; 
 
///如果这个时间小于传进来的ttl,就不进行任何操作.直接返回(这个ttl也就表示一个在unused链表中的元素在删除前,需要等待多久).而我们上面的inet_getpeer中,传进来的是0,这就会直接删除掉第一个peer. 
 if (delta < ttl) { 
  /* Do not prune fresh entries. */ 
  spin_unlock_bh(&inet_peer_unused_lock); 
  return -1; 
 } 
 
 list_del_init(&p->unused); 
///引用计数-1. 
 atomic_inc(&p->refcnt); 
 } 
 spin_unlock_bh(&inet_peer_unused_lock); 
 
 if (p == NULL) 
 /* It means that the total number of USED entries has 
  * grown over inet_peer_threshold. It shouldn't really 
  * happen because of entry limits in route cache. */ 
 return -1; 
///这个函数就简单介绍一下,先会判断p的引用计数,如果引用计数为1,则说明可以从avl树中删除它,然后将它彻底free掉.当引用技术不为1,则会将它直接加入到unused链表中(这里要注意,它并没有从avl树中删除). 
 unlink_from_pool(p); 
 return 0; 
}

接下来来看定时器处理函数:
static void peer_check_expire(unsigned long dummy) 
{ 
 unsigned long now = jiffies; 
 int ttl; 
 
///如果内存过大则将ttl设置为最小. 
 if (peer_total >= inet_peer_threshold) 
 ttl = inet_peer_minttl; 
 else 
///其实也就是根据使用的内存peer_total,来设置ttl. 
 ttl = inet_peer_maxttl 
  - (inet_peer_maxttl - inet_peer_minttl) / HZ * 
   peer_total / inet_peer_threshold * HZ; 
 while (!cleanup_once(ttl)) { 
 if (jiffies != now) 
  break; 
 } 
 
///这里要注意,我们的定时器时间也是根据当前使用的内存peer_total来进行调节的. 
 if (peer_total >= inet_peer_threshold) 
 peer_periodic_timer.expires = jiffies + inet_peer_gc_mintime; 
 else 
 peer_periodic_timer.expires = jiffies 
  + inet_peer_gc_maxtime 
  - (inet_peer_gc_maxtime - inet_peer_gc_mintime) / HZ * 
  peer_total / inet_peer_threshold * HZ; 
 add_timer(&peer_periodic_timer); 
}

然后我们来看下ip头的id域在内核中的实现(也就是ip包的id的选择).

  内核中实现这个的函数是__ip_select_ident,一般我们调用,都是调用它的包装函数ip_select_ident,这个函数只不过是判断了下DF位(主要是为了处理win95的bug),然后调用__ip_select_ident.

  我们来看实现:


void __ip_select_ident(struct iphdr *iph, struct dst_entry *dst, int more) 
{ 
 struct rtable *rt = (struct rtable *) dst; 
 
 if (rt) { 
///如果peer为空,则调用rt_bind_peer新创建一个peer. 
 if (rt->peer == NULL) 
  rt_bind_peer(rt, 1); 
 
 /* If peer is attached to destination, it is never detached, 
   so that we need not to grab a lock to dereference it. 
  */ 
 if (rt->peer) { 
///取得当前的peer的id(也就是ip_id_count域).这里要注意调用inet_getid后,ip_id_count域会自动增长), 
  iph->id = htons(inet_getid(rt->peer, more)); 
  return; 
 } 
 } else 
 printk(KERN_DEBUG "rt_bind_peer(0) @%pn", 
     __builtin_return_address(0)); 
 
///如果peer创建失败,则调用ip_select_fb_ident 
 ip_select_fb_ident(iph); 
} 
 
static void ip_select_fb_ident(struct iphdr *iph) 
{ 
 static DEFINE_SPINLOCK(ip_fb_id_lock); 
 static u32 ip_fallback_id; 
 u32 salt; 
 
 spin_lock_bh(&ip_fb_id_lock); 
///由于无法得到peer,因此需要跳过peer子系统,直接取得id. 
 salt = secure_ip_id((__force __be32)ip_fallback_id ^ iph->daddr); 
 iph->id = htons(salt & 0xFFFF); 
 ip_fallback_id = salt; 
 spin_unlock_bh(&ip_fb_id_lock); 
} 

来看下ip层的统计信息的表示.

  这里的统计信息是通过per cpu的变量ip_statistics来表示的,这里要知道其实网络子系统很多地方都有一个统计信息,这些统计信息的初始化是通过ipv4_mib_init_net来做的.


来看下ip层的统计信息的表示.

  这里的统计信息是通过per cpu的变量ip_statistics来表示的,这里要知道其实网络子系统很多地方都有一个统计信息,这些统计信息的初始化是通过ipv4_mib_init_net来做的.


static __net_init int ipv4_mib_init_net(struct net *net) 
{ 
 
///我们看到有tcp层,ip层等等的统计信息.这里每个统计变量都是 per cpu的. 
 if (snmp_mib_init((void **)net->mib.tcp_statistics, 
   sizeof(struct tcp_mib)) < 0) 
 goto err_tcp_mib; 
 if (snmp_mib_init((void **)net->mib.ip_statistics, 
   sizeof(struct ipstats_mib)) < 0) 
 goto err_ip_mib; 
 if (snmp_mib_init((void **)net->mib.net_statistics, 
   sizeof(struct linux_mib)) < 0) 
 goto err_net_mib; 
 if (snmp_mib_init((void **)net->mib.udp_statistics, 
   sizeof(struct udp_mib)) < 0) 
 goto err_udp_mib; 
 if (snmp_mib_init((void **)net->mib.udplite_statistics, 
   sizeof(struct udp_mib)) < 0) 
 goto err_udplite_mib; 
 if (snmp_mib_init((void **)net->mib.icmp_statistics, 
   sizeof(struct icmp_mib)) < 0) 
 goto err_icmp_mib; 
 if (snmp_mib_init((void **)net->mib.icmpmsg_statistics, 
   sizeof(struct icmpmsg_mib)) < 0) 
 goto err_icmpmsg_mib; 
 
 tcp_mib_init(net); 
 return 0; 
........................... 
}

每个cpu所统计的信息就是通过他处理的中断所传递的包的信息.

  而它提供了几个宏来执行统计,这几个宏分为中端上下文外和内执行.


#define IP_INC_STATS(net, field) SNMP_INC_STATS((net)->mib.ip_statistics, field) 
 
//这2个都是在中断上下文外的 
#define IP_INC_STATS_BH(net, field) SNMP_INC_STATS_BH((net)->mib.ip_statistics, field) 
#define IP_ADD_STATS_BH(net, field, val) SNMP_ADD_STATS_BH((net)->mib.ip_statistics, field, val)

  最后看下,ip的一些配置工具的实现.

  这里只是简单的介绍下,具体的去看源码就好了.

  这里有4种途经来进行配置:

  1 ioctl

  这个主要是被ifconfig使用.对应内核就是netdev的do_ioctl函数.

  2 netlink

  主要被iproute2使用.比如RTMGRP_IPV4_IFADDR广播组,来通知用户空间,网络地址的改变.

  3 /proc文件系统.

  也就是/proc/sys/net/ipv4

  4 RAPP/BOOTP/DHCP

  这些也就是通过远程host来配置ip地址等.

  ip子系统还提供了一个inetaddr_chain通知链来通知内核其他子系统(比如路由子系统,以及nerfilter masquerading)ip配置的改变.


  评论这张
 
阅读(305)| 评论(0)
推荐 转载

历史上的今天

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2018