PF_RING模块初始化(五)
原文链接:http://blog.csdn.net/smilestone_322/article/details/8451630
Linux2.6的内核后对模块机制更加重视了,因为模块可以很方便的添加到内核,也可以很方便的从内核移除,对于驱动程序来说是一个很方便的事情,当需要该模块的时候采用insmod插入到内核,不需要时可以采用rmmod从内核很方便的删除,这样可以避免内核由于外设驱动程序的增多还不断庞大,linux和windows的一个区别就是linux的内核和应用程序是可以定制的,这样用户可以根据自己的需要进行配置,减少不必要的东西,加快内核的效率,提高运行的速度,当然这样比较windows来说,对用户的水平要求更高了,必须安装一个软件,有许多依赖,一般的用户还搞不定。
模块要能很方便的添加到内核和从内核很方便的删除,就必须有初始化的函数和退出的函数,就好像我们main函数及windows的DriverEntry函数一样,初始化函数就是第一个运行的函数,我们讲到内核,首先必须讲解初始化的函数,也可以说是一个宏吧。Linux调用insmod时,其实通过一个宏module_init进行模块的初始化,而rmmod通过调用module_exit从内核将模块删除。一般模块必须包含的头文件如下:
#include <linux/init.h>
#include <linux/module.h>
包含init.h的目的是指定初始化和清除函数,包含module.h就是包含装载模块需要的符号和函数的定义。
下面开始讲解PF_RING的模块初始化函数及退出函数吧,还是回到源码,以源码说话,讲解的更加清楚。
PF_RING的初始化函数就是ring_init,源码如下:
staticint __init ring_init(void)
{
static structnet_device any_dev, none_dev;
int i;
#if(LINUX_VERSION_CODE> KERNEL_VERSION(2,6,11))
int rc;
#endif
// PF_RING只有在2.6.11以后的内核才能正常使用
printk("[PF_RING] Welcome to PF_RING %s($Revision: %s$)\n"
"(C) 2004-11 L.Deri <deri@ntop.org>\n",
RING_VERSION, SVN_REV);
//printk为内核打印语句函数,相当于应用程序中的printf函数一样;
#if(LINUX_VERSION_CODE> KERNEL_VERSION(2,6,11))
if((rc = proto_register(&ring_proto, 0))!= 0)
return(rc);
#endif
// 如果内核版本大于2.6.11的话,调用proto_register函数,该函数作用是注册PF_RING新协议,下面的INIT_LIST_HEAD的作用是初始化4个链表;
INIT_LIST_HEAD(&ring_table);
INIT_LIST_HEAD(&ring_cluster_list);
INIT_LIST_HEAD(&ring_aware_device_list);
INIT_LIST_HEAD(&ring_dna_devices_list);
for(i = 0; i < MAX_NUM_DEVICES; i++)
INIT_LIST_HEAD(&device_ring_list[i]);
/* MAX_NUM_DEVICES在pf_ring.h中定义,其值256,
INIT_LIST_HEAD(&device_ring_list[i])的作用是初始化256个链表,在看看device_ring_list这个结构,同样在pf_ring.h定义,其实它就是一个数组,定义如下:
/*For each device, pf_ring keeps a list of the number of
available ring socketslots. So that a caller knows in advance whether
there are slots available(for rings bound to such device)
that can potentially hostthe packet
*/
static struct list_head device_ring_list[MAX_NUM_DEVICES];
*/
init_ring_readers();
/* init_ring_readers函数定义如下:
inline voidinit_ring_readers(void) {
atomic_set(&num_ring_readers, 0);
atomic_set(&ring_stop, 0);
}
*/
memset(&any_dev, 0, sizeof(any_dev));
//将any_dev结构的内存置为0
strcpy(any_dev.name, "any");
any_dev.ifindex = MAX_NUM_IFIDX-1,any_dev.type = ARPHRD_ETHER;
memset(&any_device_element, 0,sizeof(any_device_element));
/*
any_device_element结构体的定义如下:
typedef struct {
pfring_device_typedevice_type; /* Device Type */
struct net_device *dev;
struct proc_dir_entry*proc_entry;
/* Hardware Filters */
struct {
u_int16_t num_filters;
hw_filtering_device_handler filter_handlers;
} hw_filters;
struct list_headdevice_list;
} ring_device_element;
static ring_device_element any_device_element, none_device_element;
*/
any_device_element.dev = &any_dev,any_device_element.device_type = standard_nic_family;
memset(&none_dev, 0, sizeof(none_dev));
strcpy(none_dev.name, "none");
none_dev.ifindex = MAX_NUM_IFIDX-2;
memset(&none_device_element, 0,sizeof(none_device_element));
none_device_element.dev = &none_dev,none_device_element.device_type = standard_nic_family;
//创建/proc/net/pfring目录
ring_proc_init();
/*
有必要好好研究下ring_proc_init,看怎样创建/proc目录呢,还是先看这个函数的源码吧。
static void ring_proc_init(void)
{
ring_proc_dir =proc_mkdir("pf_ring",
#if(LINUX_VERSION_CODE >=KERNEL_VERSION(2,6,24))
init_net. //如果版本大于2.6.24
#endif
proc_net);
//上面的proc_mkdir的作用是建立一个pf_ring的目录,/proc/net/pfring
if(ring_proc_dir) {
#if(LINUX_VERSION_CODE <KERNEL_VERSION(2,6,30))
ring_proc_dir->owner = THIS_MODULE; //如果版本大于2.6.30
#endif
ring_proc_dev_dir = proc_mkdir(PROC_DEV, ring_proc_dir);
/*proc_mkdir的函数原型是怎样的呢,linux没有像vc中的msdn,只要借助google搜一下,struct proc_dir_entry*proc_mkdir(const char *name, struct proc_dir_entry *parent);
:类似于mkdir()函数,name是目录名,parent是要创建的目录的父目录名(若parent = NULL则创建在/proc目录下)。是不是创建一个目录/proc/net/pfring/dev,有待验证。
*/
ring_proc = create_proc_read_entry(PROC_INFO, 0 /* read-only */,
ring_proc_dir,
ring_proc_get_info, NULL);
/*
structproc_dir_entry*create_proc_read_entry(constchar*name,mode_tmode,structproc_dir_entry*base,read_proc_t*read_proc,void*data);
说明:
name : 要创建的文件名;
mode : 文件掩码,为 0 则按照系统默认的掩码创建文件。
base : 指定该文件所在的目录,如果为 NULL,则文件被创建在 /proc根目录下。
read_proc : 实现该文件的 read_proc函数。也就是说,当我们读取 "name"这个文件时(如 cat /proc/myproc_name),读取请求会通过这个函数发送到驱动模块,然后在函数里处理的数据会写到 myproc_name文件中。
data : 内核忽略此参数,但会把它当作参数传递给 read_proc这个自定义函数。
函数功能:在ring_proc_dir目录下创建一个PROC_INFO的文件
对照函数实参和形参可以看出,该函数放在这里的作用了,当我们cat/proc/net/pfring/info时,通过ring_proc_get_info函数将请求发送给驱动模块,然后将得到的结果保存在info文件中。
和create_proc_read_entry类似的函数还有一个create_proc_entry函数,该函数的作用同样用来建立/proc文件,它比create_proc_read_entry()更为底层,它的原型如下:
structproc_dir_entry*create_proc_entry (constchar*name,mode_tmode,structproc_dir_entry*parent);
它只有3个参数,和create_proc_read_entry()一样,分别是name:文件名字,mode:掩码,parent:父目录;
*/
ring_proc_plugins_info =
create_proc_read_entry(PROC_PLUGINS_INFO, 0 /* read-only*/,
ring_proc_dir,
ring_proc_get_plugin_info, NULL);
if(!ring_proc || !ring_proc_plugins_info)
printk("[PF_RING] unable to register proc file\n");
else {
#if(LINUX_VERSION_CODE <KERNEL_VERSION(2,6,30))
ring_proc->owner = THIS_MODULE;
ring_proc_plugins_info->owner =THIS_MODULE;
#endif
printk("[PF_RING] registered/proc/net/pf_ring/\n");
}
} else
printk("[PF_RING] unable to create /proc/net/pf_ring\n");
}
/*********************************** */
*/
// ring_proc_init();函数分析完了,从分析中可以看出该函数的作用是创建/proc/net/pfring/目录。
//注册新协议sock
sock_register(&ring_family_ops);
/*饭要一口一口的吃,程序要一行一行的看,下面轮到解析sock_register函数了,呵呵,这个函数的作用是注册新协议sock,先看看这个函数的源码:首先解释一下实参:
static structnet_proto_family ring_family_ops = {
.family = PF_RING,
.create = ring_create,
.owner = THIS_MODULE,
};
在include/linux/socket.h中找到这个结构体的定义;
struct net_proto_family {
int family; // family为域编号,在本文中指的是PF_RING
int (*create)(struct socket *sock, int protocol); //内核实现socket系统调用的函数
short authentication;
short encryption;
short encrypt_net;
struct module *owner; //
};
当用户创建PF_RING时, 如我们在Pfring.c文件中讲解pfring_open_consumer时,讲到创建了一个socket,然后bind,即 ring->fd = socket(PF_RING, SOCK_RAW, htons(ETH_P_ALL));
ring_create函数通过调用sock_create进行创建,sock_create调用__sock_create。__sock_create要创建一个struct socket,这是一个普通BSD socket的结构体,其定义如下:
struct socket {
socket_state state;
unsigned long flags;
struct proto_ops *ops;
struct fasync_struct *fasync_list;
struct file *file;
struct sock *sk;
wait_queue_head_t wait;
short type;
};
sock_register函数的源码在/net/socket.c中,定义如下:
int sock_register(const structnet_proto_family *ops)
{
interr;
if(ops->family >= NPROTO) {
printk(KERN_CRIT"protocol %d >= NPROTO(%d)\n", ops->family,
NPROTO);
return-ENOBUFS;
}
spin_lock(&net_family_lock);
if(net_families[ops->family])
err= -EEXIST;
else{
net_families[ops->family]= ops; //注册一个协议PF_RING
err= 0;
}
spin_unlock(&net_family_lock);
printk(KERN_INFO“NET: Registered protocol family %d\n”, ops->family);
returnerr;
}
思考题:为什么应用空间调用socket时,ring_create会自动调用?
*/
/*注册通知链表 ,将ring_netdev_notifier按照优先级插入到链表 netdev_chain中,这样因为每一个协议都调用register_netdevice_notifier函数注册了一个ring_netdev_notifier的结构体,而netdev_chain的作用就是当外界调用相应的ioctl的时候通知这个链表上所有相关的设备(notifier_call函数);
*/
register_netdevice_notifier(&ring_netdev_notifier);
//ring_netdev_notifier的定义如下:
/* static struct notifier_block ring_netdev_notifier = {
.notifier_call =ring_notifier, //pf_ring.c中有函数源码
};
struct notifier_block类型,这个struct是在include/linux/notifier.h里面的:
struct notifier_block
{
int (*notifier_call)(struct notifier_block *self, unsigned long, void *);
struct notifier_block *next;
int priority;
};
当系统调用notifier_call时,就会调用ring_notifier函数;
而register_netdevice_notifier函数在net/core/dev.c里面,是这样的:
int register_netdevice_notifier(struct notifier_block *nb)
{
return notifier_chain_register(&netdev_chain, nb);
}
而 notifier_chain_register函数在include/linux/notifier.h里面,是这样的:
extern __inline__ intnotifier_chain_register(struct notifier_block **list, struct notifier_block *n)
{
while(*list)
{
if(n->priority > (*list)->priority) //优先级判断
break;
list= &((*list)->next);
}
n->next = *list;
*list=n;
return 0;
}
这个函数的作用是将按照n的优先级将n插入到链表中的对应位置;
*/
/* Sanity check */
if(transparent_mode > driver2pf_ring_non_transparent)
transparent_mode = standard_linux_path;
//工作模式有3种,感兴趣可以去看pf_ring的文档
typedef enum {
standard_linux_path =0, /* Business as usual */
driver2pf_ring_transparent= 1, /* Packets are still delivered to the kernel */
driver2pf_ring_non_transparent= 2 /* Packets not delivered to the kernel */
} direct2pf_ring;
printk("[PF_RING] Min # ring slots%d\n", min_num_slots);
printk("[PF_RING] Slot version %d\n",
RING_FLOWSLOT_VERSION);
printk("[PF_RING] Capture TX %s\n",
enable_tx_capture ? "Yes [RX+TX]" :"No [RX only]");
printk("[PF_RING] Transparent Mode%d\n",
transparent_mode);
printk("[PF_RING] IP Defragment %s\n",
enable_ip_defrag ? "Yes" :"No");
printk("[PF_RING] Initializedcorrectly\n");
/*
上面这一连串的printk函数的作用是打印PF_RING的一些信息,比如是槽位总数,我的版本为4096个槽位,Slot版本号,是否使能enable_tx_capture,如果使能的话,能够同时捕获发送数据包和接收数据包,Transparent Mode有3种模式,0,1,2,具体这里就不说了,可以看PF_RING的文档,里面讲的很详细了。Ip Defragment,是否支持ip分段。
*/
register_device_handler();
/*
void register_device_handler(void) {
if(transparent_mode !=standard_linux_path) return;
/*如果不是第一种模式,直接返回,所以register_device_handler()第2种模式和第3种模式时,这个函数是不会调用的*/
prot_hook.func =packet_rcv; //packet 包接收hook函数,只在第一种模式下有用;
prot_hook.type =htons(ETH_P_ALL);
dev_add_pack(&prot_hook); //将prot_hook的结构插入链表;
}
SOCK_PACKET的实现就是用dev_add_pack();dev_add_pack函数在/net/core/dev.c中定义;
void dev_add_pack(struct packet_type *pt)
{
int hash;
spin_lock_bh(&ptype_lock);
if (pt->type ==htons(ETH_P_ALL))
list_add_rcu(&pt->list,&ptype_all);
else {
hash =ntohs(pt->type) & PTYPE_HASH_MASK;
list_add_rcu(&pt->list, &ptype_base[hash]);
}
spin_unlock_bh(&ptype_lock);
}
EXPORT_SYMBOL(dev_add_pack);
在goolge上搜list_add_rcu的函数原型定义如下(include/list.h),版本不是我的2.6.35,估先分析下下面这个的作用,就是将一个在链表中插入一个新的节点:
static inline void __list_add_rcu(structlist_head * new,
struct list_head * prev, struct list_head * next)
{
new->next = next;
new->prev = prev;
smp_wmb(); //执行完前面2句才能执行后面两句
next->prev = new;
prev->next = new;
}
既然和我的内核不一致,那么没办法,只有在2.6.35中搜这个函数了。在include/linux/rculist.h
18 static inline void __list_add_rcu(struct list_head *new,
19 struct list_head *prev, struct list_head *next)
20 {
21 new->next = next;
22 new->prev = prev;
23 rcu_assign_pointer(prev->next, new);
24 next->prev = new;
25 }
//
43 static inline void list_add_rcu(struct list_head *new, struct list_head *head)
44 {
45 __list_add_rcu(new, head, head->next);
46 }
函数的功能还是一样的,将new节点插入到链表中,list_add_rcu的作用的是在链表头插入一个新的节点。
*/
pfring_enabled = 1; //pfring使能
return 0;
}