msn: [email protected]
来源:http://yfydz.cublog.cn
1. 前言 Linux2.6内核中网络SNMP统计数据的处理方式和2.4相比有了比较大的变化,本文介绍2.6下的SNMP信息的操作过程。 各种协议的SNMP统计方法相同,只是参数有所不同,为篇幅简单,使用UDP的统计信息来说明,因为UDP协议的SNMP统计信息较少,以下内核代码版本为2.6.17.11。 2. 数据结构 MIB库单位项基本结构: SNMP MIB单位项结构简单,就是一个名称字符串和相应的数组索引值: /* include/linux/snmp.h */ struct snmp_mib { char *name; int entry; }; 下面是为方便代码编辑而定义的两个宏: #define SNMP_MIB_ITEM(_name,_entry) { \ .name = _name, \ .entry = _entry, \ } #define SNMP_MIB_SENTINEL { \ .name = NULL, \ .entry = 0, \ } 实例,UDP协议SNMP统计表: static const struct snmp_mib snmp4_udp_list[] = { SNMP_MIB_ITEM("InDatagrams", UDP_MIB_INDATAGRAMS), SNMP_MIB_ITEM("NoPorts", UDP_MIB_NOPORTS), SNMP_MIB_ITEM("InErrors", UDP_MIB_INERRORS), SNMP_MIB_ITEM("OutDatagrams", UDP_MIB_OUTDATAGRAMS), SNMP_MIB_SENTINEL }; 其中各项UDP索引宏定义为: enum { UDP_MIB_NUM = 0, UDP_MIB_INDATAGRAMS, /* InDatagrams */ UDP_MIB_NOPORTS, /* NoPorts */ UDP_MIB_INERRORS, /* InErrors */ UDP_MIB_OUTDATAGRAMS, /* OutDatagrams */ __UDP_MIB_MAX }; UDP的SNMP MIB库定义,就是一个保存各统计项的数组,struct snmp_mib结构中的索引项就是索引这个数组的值: /* include/net/snmp.h */ #define UDP_MIB_MAX __UDP_MIB_MAX struct udp_mib { unsigned long mibs[UDP_MIB_MAX]; } __SNMP_MIB_ALIGN__; 对于不同的协议,需要统计的参数和数量各自不同,但定义格式都是一样的,其实写程序时麻烦就在定义这块,定义完后操作都很简单了。 3. SNMP统计操作 3.1 以下宏用来定义各协议的SNMP统计信息 /* include/net/snmp.h */ #define DEFINE_SNMP_STAT(type, name) \ __typeof__(type) *name[2] #define DECLARE_SNMP_STAT(type, name) \ extern __typeof__(type) *name[2] #define SNMP_STAT_BHPTR(name) (name[0]) #define SNMP_STAT_USRPTR(name) (name[1]) 3.2 SNMP统计操作 以下各宏定义了安全地进行SNMP信息增减操作,保证的多CPU情况下也正确: /* include/net/snmp.h */ #define SNMP_INC_STATS_BH(mib, field) \ (per_cpu_ptr(mib[0], raw_smp_processor_id())->mibs[field]++) #define SNMP_INC_STATS_OFFSET_BH(mib, field, offset) \ (per_cpu_ptr(mib[0], raw_smp_processor_id())->mibs[field + (offset)]++) #define SNMP_INC_STATS_USER(mib, field) \ (per_cpu_ptr(mib[1], raw_smp_processor_id())->mibs[field]++) #define SNMP_INC_STATS(mib, field) \ (per_cpu_ptr(mib[!in_softirq()], raw_smp_processor_id())->mibs[field]++) #define SNMP_DEC_STATS(mib, field) \ (per_cpu_ptr(mib[!in_softirq()], raw_smp_processor_id())->mibs[field]--) #define SNMP_ADD_STATS_BH(mib, field, addend) \ (per_cpu_ptr(mib[0], raw_smp_processor_id())->mibs[field] += addend) #define SNMP_ADD_STATS_USER(mib, field, addend) \ (per_cpu_ptr(mib[1], raw_smp_processor_id())->mibs[field] += addend) 3.3 UDP协议SNMP定义 UDP统计定义: /* net/ipv4/udp.h */ DECLARE_SNMP_STAT(struct udp_mib, udp_statistics); UDP各参数增加定义,通常SNMP统计信息是只增不减的,所以只定义了INC操作而没有DEC操作: /* net/ipv4/udp.h */ #define UDP_INC_STATS(field) SNMP_INC_STATS(udp_statistics, field) #define UDP_INC_STATS_BH(field) SNMP_INC_STATS_BH(udp_statistics, field) #define UDP_INC_STATS_USER(field) SNMP_INC_STATS_USER(udp_statistics, field) 在程序中就直接使用上述宏来增加SNMP统计量即可。 内存分配: DEFINE_SNMP_STAT等宏定义的是指针,因此要提前进行内存分配: /* net/ipv4/af_inet.c */ static int __init init_ipv4_mibs(void) { ...... udp_statistics[0] = alloc_percpu(struct udp_mib); udp_statistics[1] = alloc_percpu(struct udp_mib); ...... 4. SNMP参数显示 一般协议的SNMP参数可通过/proc/net/snmp文件获取,自己也可以定义新的文件获取自己协议的SNMP信息,显示过程就是普通的/proc只读文件显示过程,现在在2.6中通常使用seq流来显示: /* net/ipv4/proc.c */ ...... // 打印参数名称 seq_puts(seq, "\nUdp:"); for (i = 0; snmp4_udp_list[i].name != NULL; i++) seq_printf(seq, " %s", snmp4_udp_list[i].name); // 打印参数具体数值 seq_puts(seq, "\nUdp:"); for (i = 0; snmp4_udp_list[i].name != NULL; i++) seq_printf(seq, " %lu", fold_field((void **) udp_statistics, snmp4_udp_list[i].entry)); ...... 值得注意的是具体参数值不是直接从数组取,而是通过fold_field()函数来读取的,实际上是将各CPU的bottom half和user部分所有统计值之和: /* net/ipv4/proc.c */ static unsigned long fold_field(void *mib[], int offt) { unsigned long res = 0; int i; for_each_possible_cpu(i) { res += *(((unsigned long *) per_cpu_ptr(mib[0], i)) + offt); res += *(((unsigned long *) per_cpu_ptr(mib[1], i)) + offt); } return res; } 5. 2.4内核处理方式简介 在2.4内核中,和2.6区别重点在于协议的MIB库定义,定义过程是非常直接的,而2.6中通过宏隐藏的多CPU处理的细节: /* 2.4.26, include/net/udp.h */ extern struct udp_mib udp_statistics[NR_CPUS*2]; struct udp_mib { unsigned long UdpInDatagrams; unsigned long UdpNoPorts; unsigned long UdpInErrors; unsigned long UdpOutDatagrams; unsigned long __pad[0]; } ____cacheline_aligned; 其他宏操作定义形式上就都是一样的了。 6. 结论 2.6的SNMP操作和2.4最大区别就是掩盖了多CPU处理的细节,在定义时不用带多CPU的相关参数。