一、 设备轮询机制的基本思想
所谓的设备轮询机制实际上就是利用网卡驱动程序提供的NAPI机制加快网卡处理数据包的速度,因为在大流量的网络环境当中,标准的网卡中断加上逐层的数据拷贝和系统调用会占用大量的CPU资源,而真正用于处理这些数据的资源却很少。
一个基本的想法是对于大流量网络,如果发现一个DMA传输中断(这表明一个网络数据通过DMA通道到达了DMA缓冲区),则首先关闭网卡的中断模式,而对于随后的数据全部采用轮询方式进行接收,这样大大降低了网卡的中断次数,如果轮询发现没有数据包可收或者已经接收了一定数量的数据包,则打开网卡的中断模式,依次类推。
这种方法被证明在某种情况下能够大大提高网络处理能力,但是在某种情况下会降低处理能力,因此并不是普遍适用的好的处理方法。另外,如果内核网络层数据包的处理方式仍然采用标准的方法(逐层拷贝并且产生一次系统调用,libpcap库就是采用这种标准方式),那么总体效率还是很低。必须考虑采用其它的优化措施降低网络层传输的内存拷贝次数及避免频繁的系统调用。
为了达到上述的目标,提出了基于PF_RING套接字的设备轮询机制,另外还可以采用内核补丁RTIRQ,即实时中断机制。
二、 PF_RING套接字的实现
PF_RING套接字是作者为了减少网络层传输中的内存拷贝即避免频繁的系统调用而设计的一种新的套接字类型,这种套接字采用模块方式动态加载。
为了能够使得内核支持这种新的套接字类型,必须使用特定的内核补丁,该补丁增加了两个文件ring.h即ring_packet.c,分别定义了使用ring套接字的各种数据结构以及ring套接字的定义及处理函数。
该模块采用模块方式加载,模块初始化函数ring_init()在ring_packet.c中定义:
static int __init ring_init(void)
{
ring_table = NULL;
sock_register(&ring_family_ops);
set_ring_handler(my_ring_handler);
return 0;
}
该函数调用sock_register()函数将PF_RING套接字协议族(Linux自身提供多种套接字协议族,比如INET,UNIX域套接字,APPLETALK,X.25等,每一种套接字协议族都由一个net_proto_family结构描述,该结构的关键成员是协议族序号以及create()方法,用来创建一个此种类型的套接字)注册到系统的全局套接字协议族数组net_family中,以便用户层调用sock()函数创建PF_RING套接字时,系统能够从net_family数组中找到相应的记录和create()方法创建这种套接字。
PF_RING套接字的create()方法注册为ring_create()函数:
static int ring_create(struct socket *sock, int protocol) {
struct sock *sk;
struct ring_opt *pfr;
int err;
/*如果想创建PF_RING类型的套接字,必须拥有ROOT权限,并且套接字类型是SOCK_RAW,并且必须接收所有的以太网数据类型*/
if(!capable(CAP_NET_ADMIN))
return -EPERM;
if(sock->type != SOCK_RAW)
return -ESOCKTNOSUPPORT;
if(protocol != htons(ETH_P_ALL))
return -EPROTONOSUPPORT;
err = -ENOMEM;
/*分配一个BSD套接字,并且将套接字家族类型赋值为PF_RING*/
sk = sk_alloc(PF_RING, GFP_KERNEL, 1
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0))
, NULL
#endif
);
if (sk == NULL)
goto out;
/*套接字操作符集合,这个集合定义了套接字的各种操作函数,包括connect,bind,mmap,poll等,实际的使用当中就是调用这些函数完成套接字的各种操作的*/
sock->ops = &ring_ops;
sock_init_data(sock, sk);
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0))
sk_set_owner(sk, THIS_MODULE);
#endif
err = -ENOMEM;
/*这里利用sock结构中提供的一个协议私有数据sk_protinfo,这段数据用
struct ring_opt {
<