网络处理的软中断机制分析
2010-08-04 19:07
239 查看
http://blog.csdn.net/joshua_yu/archive/2006/01/27/589451.aspx
内核默认软中断机制分析(
process_backlog
)
首先需要介绍的就是
netif_rx
(在
net/core/dev.c
中定义)函数,这个函数在网卡驱动程序与
linux
内核之间建立了一道桥梁,将网卡接收上来的数据包(
sk_buff
形式)插入内核维护的接收缓冲
区队列当中:
int
netif_rx(struct sk_buff *skb)
{
int this_cpu = smp_processor_id();
struct softnet_data *queue;
unsigned long flags;
if (skb->stamp.tv_sec == 0)
do_gettimeofday(&skb->stamp);
/*
获取当前处理
CPU
的接收数据包缓冲区队列指针。系统为
每一个
CPU
都维
护一个独立的列表,这样可以避免共享访问互斥问题。
*/
queue = &softnet_data[this_cpu];
local_irq_save(flags);
netdev_rx_stat[this_cpu].total++;
/*
这里判断当前输入队列
的长度是否超过预定义的一个值,如果没有超过,则向下执行。
如果当前队列的长度大
于
0
,则将
sk_buff
插入队列,并且返回,注意这
里并没有调用
__cpu_raise_softirq
产生一个软中断,而较老的内核版本当中,插入队列以后立刻调用这个函数产生软中断。
另外,如果队列长度为
0
,则需要调用
netif_rx_schedule
及后续的
__netif_rx_schedule
函
数将当前网络设备加入
softnet_data
的轮询列表(
poll_list
)当中,这个列表维护所有网络设备的列表,当系统软中断处理函数运行时,逐个检索处于
poll_list
中的设备,然后调用设备
的
dev->poll
方法处理输入数据包队列中的数据。
将设备加入
poll_list
列表当中后,返回
enqueue
标记处继续将
sk_buff
加入输入数据包队列中,然后
返回。
*/
if (queue->input_pkt_queue.qlen <=
netdev_max_backlog) {
if (queue->input_pkt_queue.qlen) {
if (queue->throttle)
goto drop;
enqueue:
dev_hold(skb->dev);
/*
将当前
sk_buff
插入
input_pkt_queue
队列的尾部,即刻返回。
*/
__skb_queue_tail(&queue->input_pkt_queue,skb);
local_irq_restore(flags);
return queue->cng_level;
}
if (queue->throttle) {
queue->throttle = 0;
}
netif_rx_schedule(&queue->blog_dev);
goto enqueue;
}
if (queue->throttle == 0) {
queue->throttle = 1;
netdev_rx_stat[this_cpu].throttled++;
}
drop:
netdev_rx_stat[this_cpu].dropped++;
local_irq_restore(flags);
kfree_skb(skb);
return NET_RX_DROP;
}
从上面的分析可以知道,
netif_rx
函数主要负责将数据包插入内核队列中,并触发软中断,这一点与较早的版本是不同的,那么软中断是在
什么地方触发的呢?
以前的章节介绍过,硬件中断是在
irq.c
的
do_IRQ
函数中调用
handle_IRQ_event
函数,进而调用相应硬件驱动程序的中断处理函数实现的。在
do_IRQ
函数执行完硬件处理函数以后,接
着就会调用
do_softirq
函数执行软中断,并且根据软中断号在
softirq_vec
数组中查找相应中断的
action
方法,对于
NET_RX_SOFTIRQ
类型的软中断来说,系统将其
action
注册为
net_rx_action
,这样我们就进入了
net_rx_action
函数当中:
static
void net_rx_action(struct softirq_action *h)
{
int this_cpu = smp_processor_id();
struct softnet_data *queue =
&softnet_data[this_cpu];
unsigned long start_time = jiffies;
int budget = netdev_max_backlog;
(系统每次从队列中取
300
个
sk_buff
处理)
br_read_lock(BR_NETPROTO_LOCK);
local_irq_disable();
/*
在这里循环判断当前轮
询列表是否为空。如果不为空,则进入软中断处理过程。
*/
while (!list_empty(&queue->poll_list)) {
struct net_device *dev;
if (budget <= 0 || jiffies -
start_time > 1)
goto
softnet_break;
local_irq_enable();
/*
从轮询
列表中取出当前的设备
dev
指针,接着为
dev
调用
poll
方法,这是设备初始化过程中已经定义好的方法,如果设备未能自己实现该函数,则系统默认注册为
process_backlog
。
poll
函数执行成功返回
0
,失败返回-
1
。这里逻辑判断是,
dev->quota
必须大于
0
,而
poll
函数执行成功,则可以继续,直到所有
的设备都查询一遍为止。
*/
dev =
list_entry(queue->poll_list.next, struct net_device, poll_list);
if (dev->quota <= 0 ||
dev->poll(dev, &budget)) {
/*
这里的
poll
函数是
netdevice
结构的一个函数指针,对于不同的网卡驱动程序,我们可以根据自己的情况定义
poll
方法的实现(
e1000
网卡驱动程序就自己实现了一个
poll
方法,详情后面来分析),而系统默
认提供一个方法。
*/
local_irq_disable();
list_del(&dev->poll_list);
list_add_tail(&dev->poll_list,
&queue->poll_list);
if (dev->quota < 0)
dev->quota +=
dev->weight;
else
dev->quota =
dev->weight;
} else {
dev_put(dev);
local_irq_disable();
}
}
local_irq_enable();
br_read_unlock(BR_NETPROTO_LOCK);
return;
softnet_break:
netdev_rx_stat[this_cpu].time_squeeze++;
/*
触发软中断处理,等待
下一次调用本函数。
*/
__cpu_raise_softirq(this_cpu, NET_RX_SOFTIRQ);
local_irq_enable();
br_read_unlock(BR_NETPROTO_LOCK);
}
软中断处理函数
net_rx_action
实际上就是调用各个网络设备的
poll
方法处理数据包的,一般的讲,
poll
默认为
process_backlog
(在
net/core/dev..c
中定义):
static
int process_backlog(struct net_device *backlog_dev, int *budget)
{
int work = 0;
int quota = min(backlog_dev->quota, *budget);
int this_cpu = smp_processor_id();
struct softnet_data *queue =
&softnet_data[this_cpu];
unsigned long start_time = jiffies;
for (;;) {
struct sk_buff *skb;
struct net_device *dev;
local_irq_disable();
/*
从输入
缓冲区队列中取出
sk_buff
,调用
netif_receive_skb
函数将
sk_buff
交给上层协议进行处理。这里就是循环调用
__skb_dequeue
取出
skb
,直到所有的
skb
处理完毕为止。
*/
skb =
__skb_dequeue(&queue->input_pkt_queue);
if (skb == NULL)
goto job_done;
local_irq_enable();
dev = skb->dev;
netif_receive_skb(skb);
dev_put(dev);
work++;
if (work >= quota || jiffies -
start_time > 1)
break;
}
}
接下来看一下
sk_buff
是如何被递交到上层协议进行处理的,只是通过调用
netif_receive_skb
(在
net/core/dev.c
中定义)函数
实现的:
int
netif_receive_skb(struct sk_buff *skb)
{
struct packet_type *ptype, *pt_prev;
int ret = NET_RX_DROP;
unsigned short type = skb->protocol;
/*
给每个网络数据包打上
时间戳。
*/
if (skb->stamp.tv_sec == 0)
do_gettimeofday(&skb->stamp);
skb_bond(skb);
pt_prev = NULL;
/*
上层的每个协议在其初
始化的过程中会调用
dev_add_pack
函数将自己的
packet_type
结构加入到
ptye_all
列表当中,其中
packet_type
结构中定义了该协议的处理方法,对于
ip
协议来说,
func
方法就注册为
ip_rcv
。另外,一般协议
packet_type
结构的
dev
字段设为
NULL
,所以下面的
ptype->dev
就为
NULL
。
另外,如果我们需要增
加自己的协议,则需要创建一个
packet_type
结构,用我们自己的协议处理函数填充该结构的
func
方法,并且调用
dev_add_pack
函数将我们自己的协议加入
ptype_all
数组当中。
*/
for (ptype = ptype_all; ptype; ptype =
ptype->next) {
/*
这里每
一种协议在定义其
packet_type
结构时都设置接收这种
数据包
协议类型的设备指针,如果设置为
NULL
,则可以从
任何设备接收数据包。
这里针对协议类型为
ETH_P_ALL
的情况进行处理,对于
IP
协议来说,类型定义为
ETH_P_IP
,因此不在这里处理。
*/
if (!ptype->dev || ptype->dev ==
skb->dev) {
if (pt_prev) {
if (!pt_prev->data) {
ret =
deliver_to_old_ones(pt_prev, skb, 0);
} else {
atomic_inc(&skb->users);
ret =
pt_prev->func(skb, skb->dev, pt_prev);
}
}
pt_prev = ptype;
}
}
/*
这里针对各种协议进行处理,
IP
包的类型为
ETH_P_IP
,因此在这里处理。
*/
for
(ptype=ptype_base[ntohs(type)&15];ptype;ptype=ptype->next) {
if (ptype->type == type &&
(!ptype->dev
|| ptype->dev == skb->dev)) {
if (pt_prev) {
if (!pt_prev->data) {
ret =
deliver_to_old_ones(pt_prev, skb, 0);
} else {
atomic_inc(&skb->users);
ret =
pt_prev->func(skb, skb->dev, pt_prev);
}
}
pt_prev = ptype;
}
}
if (pt_prev) {
if (!pt_prev->data) {
ret = deliver_to_old_ones(pt_prev,
skb, 1);
} else {
ret = pt_prev->func(skb,
skb->dev, pt_prev);
}
} else {
kfree_skb(skb);
/* Jamal, now you will not able to escape
explaining
* me how you were
going to use this. :-)
*/
ret = NET_RX_DROP;
}
return ret;
}
在软中断处理函数当中,我们根据数据包的类型,调用相应的底层数据处理函数,对于
IP
包来说,就是调用
ip_rcv
函数并且进一步向上层协议递交
和处理。至此,内核的软中断的主要过程已经结束了。
接下来我们分析一下
IP
层以上的网络协议栈是如何进一步处理数据的,详细的说明文档见《
网络数据接收的协议处理过程分析
》。
内核默认软中断机制分析(
process_backlog
)
首先需要介绍的就是
netif_rx
(在
net/core/dev.c
中定义)函数,这个函数在网卡驱动程序与
linux
内核之间建立了一道桥梁,将网卡接收上来的数据包(
sk_buff
形式)插入内核维护的接收缓冲
区队列当中:
int
netif_rx(struct sk_buff *skb)
{
int this_cpu = smp_processor_id();
struct softnet_data *queue;
unsigned long flags;
if (skb->stamp.tv_sec == 0)
do_gettimeofday(&skb->stamp);
/*
获取当前处理
CPU
的接收数据包缓冲区队列指针。系统为
每一个
CPU
都维
护一个独立的列表,这样可以避免共享访问互斥问题。
*/
queue = &softnet_data[this_cpu];
local_irq_save(flags);
netdev_rx_stat[this_cpu].total++;
/*
这里判断当前输入队列
的长度是否超过预定义的一个值,如果没有超过,则向下执行。
如果当前队列的长度大
于
0
,则将
sk_buff
插入队列,并且返回,注意这
里并没有调用
__cpu_raise_softirq
产生一个软中断,而较老的内核版本当中,插入队列以后立刻调用这个函数产生软中断。
另外,如果队列长度为
0
,则需要调用
netif_rx_schedule
及后续的
__netif_rx_schedule
函
数将当前网络设备加入
softnet_data
的轮询列表(
poll_list
)当中,这个列表维护所有网络设备的列表,当系统软中断处理函数运行时,逐个检索处于
poll_list
中的设备,然后调用设备
的
dev->poll
方法处理输入数据包队列中的数据。
将设备加入
poll_list
列表当中后,返回
enqueue
标记处继续将
sk_buff
加入输入数据包队列中,然后
返回。
*/
if (queue->input_pkt_queue.qlen <=
netdev_max_backlog) {
if (queue->input_pkt_queue.qlen) {
if (queue->throttle)
goto drop;
enqueue:
dev_hold(skb->dev);
/*
将当前
sk_buff
插入
input_pkt_queue
队列的尾部,即刻返回。
*/
__skb_queue_tail(&queue->input_pkt_queue,skb);
local_irq_restore(flags);
return queue->cng_level;
}
if (queue->throttle) {
queue->throttle = 0;
}
netif_rx_schedule(&queue->blog_dev);
goto enqueue;
}
if (queue->throttle == 0) {
queue->throttle = 1;
netdev_rx_stat[this_cpu].throttled++;
}
drop:
netdev_rx_stat[this_cpu].dropped++;
local_irq_restore(flags);
kfree_skb(skb);
return NET_RX_DROP;
}
从上面的分析可以知道,
netif_rx
函数主要负责将数据包插入内核队列中,并触发软中断,这一点与较早的版本是不同的,那么软中断是在
什么地方触发的呢?
以前的章节介绍过,硬件中断是在
irq.c
的
do_IRQ
函数中调用
handle_IRQ_event
函数,进而调用相应硬件驱动程序的中断处理函数实现的。在
do_IRQ
函数执行完硬件处理函数以后,接
着就会调用
do_softirq
函数执行软中断,并且根据软中断号在
softirq_vec
数组中查找相应中断的
action
方法,对于
NET_RX_SOFTIRQ
类型的软中断来说,系统将其
action
注册为
net_rx_action
,这样我们就进入了
net_rx_action
函数当中:
static
void net_rx_action(struct softirq_action *h)
{
int this_cpu = smp_processor_id();
struct softnet_data *queue =
&softnet_data[this_cpu];
unsigned long start_time = jiffies;
int budget = netdev_max_backlog;
(系统每次从队列中取
300
个
sk_buff
处理)
br_read_lock(BR_NETPROTO_LOCK);
local_irq_disable();
/*
在这里循环判断当前轮
询列表是否为空。如果不为空,则进入软中断处理过程。
*/
while (!list_empty(&queue->poll_list)) {
struct net_device *dev;
if (budget <= 0 || jiffies -
start_time > 1)
goto
softnet_break;
local_irq_enable();
/*
从轮询
列表中取出当前的设备
dev
指针,接着为
dev
调用
poll
方法,这是设备初始化过程中已经定义好的方法,如果设备未能自己实现该函数,则系统默认注册为
process_backlog
。
poll
函数执行成功返回
0
,失败返回-
1
。这里逻辑判断是,
dev->quota
必须大于
0
,而
poll
函数执行成功,则可以继续,直到所有
的设备都查询一遍为止。
*/
dev =
list_entry(queue->poll_list.next, struct net_device, poll_list);
if (dev->quota <= 0 ||
dev->poll(dev, &budget)) {
/*
这里的
poll
函数是
netdevice
结构的一个函数指针,对于不同的网卡驱动程序,我们可以根据自己的情况定义
poll
方法的实现(
e1000
网卡驱动程序就自己实现了一个
poll
方法,详情后面来分析),而系统默
认提供一个方法。
*/
local_irq_disable();
list_del(&dev->poll_list);
list_add_tail(&dev->poll_list,
&queue->poll_list);
if (dev->quota < 0)
dev->quota +=
dev->weight;
else
dev->quota =
dev->weight;
} else {
dev_put(dev);
local_irq_disable();
}
}
local_irq_enable();
br_read_unlock(BR_NETPROTO_LOCK);
return;
softnet_break:
netdev_rx_stat[this_cpu].time_squeeze++;
/*
触发软中断处理,等待
下一次调用本函数。
*/
__cpu_raise_softirq(this_cpu, NET_RX_SOFTIRQ);
local_irq_enable();
br_read_unlock(BR_NETPROTO_LOCK);
}
软中断处理函数
net_rx_action
实际上就是调用各个网络设备的
poll
方法处理数据包的,一般的讲,
poll
默认为
process_backlog
(在
net/core/dev..c
中定义):
static
int process_backlog(struct net_device *backlog_dev, int *budget)
{
int work = 0;
int quota = min(backlog_dev->quota, *budget);
int this_cpu = smp_processor_id();
struct softnet_data *queue =
&softnet_data[this_cpu];
unsigned long start_time = jiffies;
for (;;) {
struct sk_buff *skb;
struct net_device *dev;
local_irq_disable();
/*
从输入
缓冲区队列中取出
sk_buff
,调用
netif_receive_skb
函数将
sk_buff
交给上层协议进行处理。这里就是循环调用
__skb_dequeue
取出
skb
,直到所有的
skb
处理完毕为止。
*/
skb =
__skb_dequeue(&queue->input_pkt_queue);
if (skb == NULL)
goto job_done;
local_irq_enable();
dev = skb->dev;
netif_receive_skb(skb);
dev_put(dev);
work++;
if (work >= quota || jiffies -
start_time > 1)
break;
}
}
接下来看一下
sk_buff
是如何被递交到上层协议进行处理的,只是通过调用
netif_receive_skb
(在
net/core/dev.c
中定义)函数
实现的:
int
netif_receive_skb(struct sk_buff *skb)
{
struct packet_type *ptype, *pt_prev;
int ret = NET_RX_DROP;
unsigned short type = skb->protocol;
/*
给每个网络数据包打上
时间戳。
*/
if (skb->stamp.tv_sec == 0)
do_gettimeofday(&skb->stamp);
skb_bond(skb);
pt_prev = NULL;
/*
上层的每个协议在其初
始化的过程中会调用
dev_add_pack
函数将自己的
packet_type
结构加入到
ptye_all
列表当中,其中
packet_type
结构中定义了该协议的处理方法,对于
ip
协议来说,
func
方法就注册为
ip_rcv
。另外,一般协议
packet_type
结构的
dev
字段设为
NULL
,所以下面的
ptype->dev
就为
NULL
。
另外,如果我们需要增
加自己的协议,则需要创建一个
packet_type
结构,用我们自己的协议处理函数填充该结构的
func
方法,并且调用
dev_add_pack
函数将我们自己的协议加入
ptype_all
数组当中。
*/
for (ptype = ptype_all; ptype; ptype =
ptype->next) {
/*
这里每
一种协议在定义其
packet_type
结构时都设置接收这种
数据包
协议类型的设备指针,如果设置为
NULL
,则可以从
任何设备接收数据包。
这里针对协议类型为
ETH_P_ALL
的情况进行处理,对于
IP
协议来说,类型定义为
ETH_P_IP
,因此不在这里处理。
*/
if (!ptype->dev || ptype->dev ==
skb->dev) {
if (pt_prev) {
if (!pt_prev->data) {
ret =
deliver_to_old_ones(pt_prev, skb, 0);
} else {
atomic_inc(&skb->users);
ret =
pt_prev->func(skb, skb->dev, pt_prev);
}
}
pt_prev = ptype;
}
}
/*
这里针对各种协议进行处理,
IP
包的类型为
ETH_P_IP
,因此在这里处理。
*/
for
(ptype=ptype_base[ntohs(type)&15];ptype;ptype=ptype->next) {
if (ptype->type == type &&
(!ptype->dev
|| ptype->dev == skb->dev)) {
if (pt_prev) {
if (!pt_prev->data) {
ret =
deliver_to_old_ones(pt_prev, skb, 0);
} else {
atomic_inc(&skb->users);
ret =
pt_prev->func(skb, skb->dev, pt_prev);
}
}
pt_prev = ptype;
}
}
if (pt_prev) {
if (!pt_prev->data) {
ret = deliver_to_old_ones(pt_prev,
skb, 1);
} else {
ret = pt_prev->func(skb,
skb->dev, pt_prev);
}
} else {
kfree_skb(skb);
/* Jamal, now you will not able to escape
explaining
* me how you were
going to use this. :-)
*/
ret = NET_RX_DROP;
}
return ret;
}
在软中断处理函数当中,我们根据数据包的类型,调用相应的底层数据处理函数,对于
IP
包来说,就是调用
ip_rcv
函数并且进一步向上层协议递交
和处理。至此,内核的软中断的主要过程已经结束了。
接下来我们分析一下
IP
层以上的网络协议栈是如何进一步处理数据的,详细的说明文档见《
网络数据接收的协议处理过程分析
》。
相关文章推荐
- 网络处理的软中断机制分析
- 网络处理的软中断机制分析
- (原创)网络处理的软中断机制分析
- 网络处理的软中断机制分析
- linux内核分析--浅析中断处理机制解析
- linux中断处理下文:软中断tasklet机制分析
- linux中断处理下文:软中断tasklet机制分析
- Android应用程序键盘(Keyboard)消息处理机制分析
- Android应用程序键盘(Keyboard)消息处理机制分析(20)
- Android应用程序消息处理机制(Looper、Handler)分析
- Linux中断机制之一:硬件处理
- Android中Handler的消息处理机制以及源码分析
- android 从源代码分析Android-Universal-Image-Loader的缓存处理机制
- 分析MIDP低级事件处理机制
- 中断处理的tasklet(小任务)机制和workqueue(工作队列)机制
- Hanlde消息处理机制的复习——网络图片下载
- android的窗口机制分析------事件处理
- linux网络协议栈分析笔记5-IP层的处理1
- [Django架构流程分析]请求处理机制其一:进入Django前的准备
- 网络瘫痪分析处理(午饭的一个例子)