linux初探

首页

应用服务器

Linux技巧

中文文档

Linux初级

服务器源代码

命令详解

Linux技术应用

Linux安全应用

Linux业界新闻

UniX技术文章

Linux编程与内核

Linux数据库

Linux服务器

Linux安装指导

Linux论坛


首页>>Linux编程与内核>>

热门文章

·C/C++中多维数组指针作为函数
·教你如何使用 C++Builder 制
·C++ Builder中保持控件的位置
·C++Builder中动态更改自定义
·拓展网页技术之C++在网页设计
·C++Builder创建基于Internet
·C++Builder在WIN2000环境下编
·用C++ Builder为计算机增加启
·C++/VC++ 语言编程的疑难问题
·C/C++语言void及void指针深层

推荐文章

Linux内核接收以太帧的处理程序


1. 前言

  以太头中除了6字节目的MAC地址、6字节源MAC地址外,还有两字节的以太帧类型值,如IPv4为0x0800,ARP为0x0806等,网卡驱动收到以太帧后通过接口函数netif_receive_skb()(netif_rx实际最后也是调用netif_receive_skb)交到上层,而这个接口函数就完成对以太帧类型的区分,交到不同的协议处理程序。如果想自己编写某一以太类型帧的处理程序,需要自己添加相应的代码。以下为Linux内核2.6代码。

  2. 数据结构

  每种协议都要定义一个packet_type结构,引导进入相关的协议数据处理函数,所有节点组成一个链表(HASH链表)。

QUOTE:
/* include/linux/netdevice.h */

struct packet_type {

__be16 type; /* This is really htons(ether_type). */

struct net_device *dev; /* NULL is wildcarded here */

int (*func) (struct sk_buff *,

struct net_device *,

struct packet_type *,

struct net_device *);

void *af_packet_priv;

struct list_head list;

};

  参数说明:

  type:以太帧类型,16位。

  dev:所附着的网卡设备,如果为NULL则匹配全部网卡。

  func:协议入口接收处理函数。

  af_packet_priv:协议私有数据。

  list:链表扣。

  一般各协议的packet_type结构都是静态存在,初始化时只提供type和func两个参数就可以了,每个协议在初始化时都要将此结构加入到系统类型链表中。

  3. 处理函数

  3.1 添加节点
QUOTE:
/* net/core/dev.c */

/**

* dev_add_pack - add packet handler

* @pt: packet type declaration

*

* Add a protocol handler to the networking stack. The passed &packet_type

* is linked into kernel lists and may not be freed until it has been

* removed from the kernel lists.

*

* This call does not sleep therefore it can not

* guarantee all CPU's that are in middle of receiving packets

* will see the new packet type (until the next received packet).

*/

void dev_add_pack(struct packet_type *pt)

{

int hash;

spin_lock_bh(&ptype_lock);

// 如果类型是全部以太类型,则节点链接到ptype_all链

if (pt->type == htons(ETH_P_ALL)) {

netdev_nit++;

list_add_rcu(&pt->list, &ptype_all);

} else {

// 根据协议类型取个HASH,共15个HASH链表

hash = ntohs(pt->type) & 15;

// 将节点链接到HASH链表中,list_add_rcu是加了smp_wmb()保护的list_add链表操作

list_add_rcu(&pt->list, &ptype_base[hash]);

}

spin_unlock_bh(&ptype_lock);

}

  3.2 删除节点
QUOTE:
/**

* __dev_remove_pack - remove packet handler

* @pt: packet type declaration

*

* Remove a protocol handler that was previously added to the kernel

* protocol handlers by dev_add_pack(). The passed &packet_type is removed

* from the kernel lists and can be freed or reused once this function

* returns.

*

* The packet type might still be in use by receivers

* and must not be freed until after all the CPU's have gone

* through a quiescent state.

*/

void __dev_remove_pack(struct packet_type *pt)

{

struct list_head *head;

struct packet_type *pt1;

spin_lock_bh(&ptype_lock);

// 根据协议类型找是在ptype_all表还是某一HASH链表中

if (pt->type == htons(ETH_P_ALL)) {

netdev_nit--;

head = &ptype_all;

} else

head = &ptype_base[ntohs(pt->type) & 15];

// 直接用地址比对进行查找,而不是类型,因为同一个类型也可能有多个节点

list_for_each_entry(pt1, head, list) {

if (pt == pt1) {

list_del_rcu(&pt->list);

goto out;

}

}

printk(KERN_WARNING "dev_remove_pack: %p not found.\n", pt);

out:

spin_unlock_bh(&ptype_lock);

}

/**

* dev_remove_pack - remove packet handler

* @pt: packet type declaration

*

* Remove a protocol handler that was previously added to the kernel

* protocol handlers by dev_add_pack(). The passed &packet_type is removed

* from the kernel lists and can be freed or reused once this function

* returns.

*

* This call sleeps to guarantee that no CPU is looking at the packet

* type after return.

*/

// 只是__dev_remove_pack()的包裹函数

void dev_remove_pack(struct packet_type *pt)

{

__dev_remove_pack(pt);


synchronize_net();

}

  4. 实例

  4.1 IP
QUOTE:
/* net/ipv4/af_inet.c */

static struct packet_type ip_packet_type = {

.type = __constant_htons(ETH_P_IP),

.func = ip_rcv, // IP接收数据的入口点

};

static int __init inet_init(void)

{

......

dev_add_pack(&ip_packet_type);

......

  由于IP协议部分不能作为内核模块,所以是没有卸载函数的,没必要调用dev_remove_pack()函数。

  4.2 8021q vlan
QUOTE:
/* net/8021q/vlan.c */

static struct packet_type vlan_packet_type = {

.type = __constant_htons(ETH_P_8021Q),

.func = vlan_skb_recv, /* VLAN receive method */

};

......

static int __init vlan_proto_init(void)

{

......

dev_add_pack(&vlan_packet_type);

......


static void __exit vlan_cleanup_module(void)

{

......

dev_remove_pack(&vlan_packet_type);

......

  由于VLAN可为模块方式存在,所以在模块清除函数中要调用dev_remove_pack()。

  5. 网络接收

  网卡驱动收到数据包构造出skb后,通过接口函数netif_receive_skb()传递到上层进行协议处理分配。
QUOTE:
/* net/core/dev.c */

int netif_receive_skb(struct sk_buff *skb)

{

......

// 先查处理所有以太类型的链表各节点

list_for_each_entry_rcu(ptype, &ptype_all, list) {

if (!ptype->dev || ptype->dev == skb->dev) {

if (pt_prev)

ret = deliver_skb(skb, pt_prev, orig_dev);

pt_prev = ptype;

}

}

......

// 再查指定协议的HASH链表

list_for_each_entry_rcu(ptype, &ptype_base[ntohs(type)&15], list) {

if (ptype->type == type &&

(!ptype->dev || ptype->dev == skb->dev)) {

if (pt_prev)

ret = deliver_skb(skb, pt_prev, orig_dev);

pt_prev = ptype;

}

}

......


// 该函数就是调用个协议的接收函数处理该skb包,进入第三层网络层处理

static __inline__ int deliver_skb(struct sk_buff *skb,

struct packet_type *pt_prev,

struct net_device *orig_dev)

{

atomic_inc(&skb->users);

return pt_prev->func(skb, skb->dev, pt_prev, orig_dev);

}

  6. 结论

  通过链表挂接方式,Linux内核可以很容易的添加各种协议的接收处理函数。

  数据流程:

  网卡驱动--->netif_rx()--->netif_receive_skb()->deliver_skb()->packet_type.func

相关文章:

·说说在fedora上使用ctags和cscope看代码
·(IPC)信号量的问题
·Linux下信号与进程控制解析
·Linux内核模块函数调用及命名空间
·通用的优化Linux的六大妙招
·编译内核过程中出现的问题
·揭示恶意的Linux内核模块是如何工作
·解读和分析Linux核心源码的两种方法
·Linux中GCC的应用介绍

Copyright@2005 www.linuxGoo.com All Right Reserved