PF_RING模块初始化(五)

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_DEVICESpf_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 Mode3种模式,012,具体这里就不说了,可以看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;

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值