Chinaunix首页 | 论坛 | 博客
  • 博客访问: 230362
  • 博文数量: 48
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 412
  • 用 户 组: 普通用户
  • 注册时间: 2013-04-24 10:27
个人简介

Continuous pursuit technical details

文章分类

全部博文(48)

文章存档

2014年(1)

2013年(47)

分类: 网络与安全

2013-04-24 15:58:14

接收网络数据包的过程,从数据包到达网卡的物理接口开始,然后由网卡的驱动程序交给网络协议栈,最后经过协议栈的一层层处理之后交给应用程序。大致上是这样的过程,但实际上有更多的细节。本文中主要介绍第一个和第二个步骤。

我们本文中依然以一个Realtek 8139网卡为例(驱动程序为/drivers/net/8139too.c)。请注意在内核代码中receive都是用rx简写的。

(1)接收网络数据包过程之注册与激活软中断

在生成net_device对象及初始化的函数rtl8139_init_one中已经初始化dev->open方法为rtl8139_open函数(在本系列文章2:初始化中的net_device对象中已经介绍,点这里查看)。在rtl8139_open函数(这个函数在网卡启动时被调用)中注册了一个中断函数rtl8139_interrupt:

retval = request_irq (dev->irq, rtl8139_interrupt, SA_SHIRQ, dev->name, dev);

所以只要当网卡开启后(状态为up),当网络数据包到达时,都会产生一个硬件中断(这不同于后面的软中断)。这个硬件中断由内核调用中断处理程序rtl8139_interrupt函数处理。这个函数比较重要,网卡发送或者接收数据时内核都会调用这个函数处理中断,而中断的类型是根据网卡状态寄存器的不同而确定的。本文中仅涉及接收数据的中断,因此只给出了接收的代码:

static irqreturn_t rtl8139_interrupt (int irq, void *dev_instance, struct pt_regs *regs)

{

if (status & RxAckBits){

if (netif_rx_schedule_prep(dev))

__netif_rx_schedule (dev);

}

}

主要函数为__netif_rx_schedule(函数名意为:network interface receive schedule,即网络接口接收调度),因为当网卡接收到数据包之后,马上告知CPU在合适的时间去启动调度程序,轮询(poll)网卡。

请注意:Linux接收网络数据包实际上有两种方式。

(a)中断。每个数据包到达都会产生一个中断,然后由内核调用中断处理程序处理。

(b)NAPI(New API)。Linux内核2.6版本之后加入的新机制,核心方法是:不采用中断的方式读取数据,而代之以首先采用中断唤醒数据接收的服务程序,然后以POLL的方法来轮询数据。

因此本文中只介绍NAPI的接收方式。我们不再详细介绍这种机制,网上可找到比较多的资料,可以参考IBM的技术文章:NAPI 技术在 Linux 网络驱动上的应用和完善。

__netif_rx_schedule函数的定义如下:

static inline void __netif_rx_schedule(struct net_device *dev)

{

local_irq_save(flags);//disable interrupt

//Add interface to tail of rx poll list

list_add_tail(&dev->poll_list, &__get_cpu_var(softnet_data).poll_list);

//activate network rx softirq

__raise_softirq_irqoff(NET_RX_SOFTIRQ);

local_irq_restore(flags);

}

这个函数最核心的就是三步:

(a)local_irq_save:禁用中断

(b)list_add_tail:将设备添加到softnet_data的poll_list中。

(c)激活一个软中断NET_RX_SOFTIRQ。

======================================

说到这里我们必须介绍一个关键数据结构softnet_data,每个CPU都拥有一个这样的网络数据队列(所以函数中使用了__get_cpu_var函数取得),定义如下:

struct softnet_data

{

int throttle; /*为 1 表示当前队列的数据包被禁止*/

int cng_level; /*表示当前处理器的数据包处理拥塞程度*/

int avg_blog; /*某个处理器的平均拥塞度*/

struct sk_buff_head input_pkt_queue; /*接收缓冲区的sk_buff队列*/

struct list_head poll_list; /*POLL设备队列头*/

struct net_device output_queue; /*网络设备发送队列的队列头*/

struct sk_buff completion_queue; /*完成发送的数据包等待释放的队列*/

struct net_device backlog_dev; /*表示当前参与POLL处理的网络设备*/

};

大致说明一下这个数据结构的意义。某个网卡产生中断之后,内核就把这个网卡挂载到轮询列表(poll_list)中。一个CPU会轮询自己的列表中的每一个网卡,看看它们是不是有新的数据包可以处理。我们需要先用一个比喻说明这个数据结构与轮询的关系:网卡就是佃户,CPU就是地主。佃户有自己种的粮食(网络数据包),但地主家也有粮仓(softnet_data)。地主要收粮的时候,就会挨家挨户的去催佃户交粮,放到自己的粮仓里。

=======================================

(2)接收网络数据包的软中断处理

我们知道:激活软中断之后,并不是马上会被处理的。只有当遇到软中断的检查点时,系统才会调用相应的软中断处理函数。

所有的网络接收数据包的软中断处理函数都是net_rx_action。这个函数的详细注释可以看IBM的那篇技术文章。其核心语句就是一个轮询的函数:

dev->poll

就调用了相应设备的poll函数。也就是说,当CPU处理软中断时,才去轮询网卡,把数据放入softnet_data中。

下面是整个中断和轮询过程的一个示意图:


下面我们解释一下poll函数具体干了什么事情。

而我们知道,在Realtek 8139网卡的net_device对象中我们已经注册了一个poll函数:

dev->poll = rtl8139_poll

那么一次poll就表示从网卡缓冲区取出一定量的数据。而rtl8139_poll函数中调用的主要函数就是rtl8139_rx函数。这个函数是完成从网卡取数据,分配skb缓冲区的核心函数。其核心代码如下:

static int rtl8139_rx(struct net_device *dev, struct rtl8139_private *tp, int budget)

{

skb = dev_alloc_skb (pkt_size + 2);

eth_copy_and_sum (skb, &rx_ring[ring_offset + 4], pkt_size, 0);//memcpy

skb->protocol = eth_type_trans (skb, dev);

netif_receive_skb (skb);

}

工作主要分为4部分:

(a)给sk_buff数据结构(skb)分配空间。

(b)从网卡的环形缓冲区rx_ring中拷贝出网络数据包放到sk_buff对象skb中。这个函数实质上就是一个memcpy函数。

(c)在skb中标识其协议为以太网帧。

(d)调用netif_receice_skb函数。

netif_receive_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);

pt_prev = ptype;

}

}

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);

pt_prev = ptype;

}

}

两个循环分别遍历了两个链表:ptype_all和ptype_base。前者是内核中注册的sniffer,后者则是注册到内核协议栈中的网络协议类型。如果skb中的协议类型type与ptype_base中的类型一致,那么使用deliver_skb函数发送给这个协议一份,定义如下:

static __inline__ int deliver_skb(struct sk_buff *skb, struct packet_type *pt_prev)

{

atomic_inc(&skb->users);

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

}

这个函数只是一个封装函数,实际上调用了每个packet type结构中注册的处理函数func。

struct packet_type {

unsigned short type;

struct net_device *dev;

int (*func) (struct sk_buff *,

struct net_device *, struct packet_type *);

void *af_packet_priv;

struct list_head list;

};

例如:IP包类型的处理函数就是ip_rcv(定义在/net/ipv4/ip_output.c文件中),定义如下:

static struct packet_type ip_packet_type = {

.type = __constant_htons(ETH_P_IP),

.func = ip_rcv,

};

这个包的类型是在ip_init协议初始化时添加到全局的ptype_base哈希数组中的:

void __init ip_init(void)

{

dev_add_pack(&ip_packet_type);

}

这就是接收网络数据包的全过程。

阅读(1837) | 评论(0) | 转发(0) |
0

上一篇:没有了

下一篇:TCP为啥分包,怎样重组

给主人留下些什么吧!~~