- 论坛徽章:
- 0
|
内核版本:2.6.12
[color="Red"]一、网卡驱动程序
当然,网卡驱动程序完成了从网卡接收数据的第一部份工作,以以3com501 的驱动
linux/drivers/net/3c501.c为例(因为它排在了第一个):
设备初始化函数中,依次调用:
[Copy to clipboard]
[ - ]
CODE:int init_module(void)
->el1_probe()
-->el1_probe1()
先向内核申请注册一个以太设备,并设定设备的open函数为:
[Copy to clipboard]
[ - ]
CODE:dev->open = &el_open;(line 316)
在el_open函数中,注册了自己的中断处理程序:(line 350)
[Copy to clipboard]
[ - ]
CODE:if ((retval = request_irq(dev->irq, &el_interrupt, 0, dev->name, dev)))
el_interruupt
为中断处理程序,中断实际上是一个电信号,网卡收到数据包后,就触发这个电信号给中断控制器的某个引脚,中断控制器向CPU某引脚发送相应的信号,CPU
检测到此信号后,进入内存中操作系统预设的位置的代码,即中断处理程序入口点,这样,操作系统设用do_IRQ()处理中断,该中断函数根据中断号和
dev_id,确定中断处理函数,对于我们的例子,也就是调用对应中断函数el_interrupt(),它判断网卡传递的信号,如果是接收数据,于是读
网卡寄存器……XXXXXXXXX
完成读取读取后,调用接收函数:(p654)
[Copy to clipboard]
[ - ]
CODE: else if (rxsr & RX_GOOD)
{
/*
* Receive worked.
*/
el_receive(dev);
}
参数dev 就是本身网卡的设备描述,el_receive()函数最重要的工作就是分配缓存:
[Copy to clipboard]
[ - ]
CODE:skb = dev_alloc_skb(pkt_len+2);(line724)
并关联设备:
[Copy to clipboard]
[ - ]
CODE:skb->dev = dev;(line740)
然后调用:
[Copy to clipboard]
[ - ]
CODE:netif_rx(skb);(line748)
netif_rx是网络栈向驱动程序提供的接收数据包的接口,对于驱动程序的编写者而言,只需要知道这个函数即可,不过我们要分析整个接收流程,还要来进一步剖析它。
PS:网卡驱动的整个实现比较复杂,我这里只是做了简单的描述,对于没有网络驱动编写经验的朋友,可以参考《Linux设备驱动程序》第十七章网卡驱动方面的内容,九贱对作者提供的例子做了进一步的脚注:
http://bbs.chinaunix.net/viewthread.php?tid=845422&highlight=独孤九贱
[color="Red"]二、下半部
因为中断处理程序本身的一些局限性,为了提高其效率,Linux将中断处理分为两部份:上半部(中断处理程序)和下半部。
一些时间任务紧迫,与硬件相关的任务,被放在了中断处理程序中,而另外一些可以“稍微延迟”的任务被放在了下半部中。例如,响应网卡中断,读取接收数据,由网卡中断处理程序完成,而对数据包的接收的进一步处理,就由下半部来完成。
Linux中下半部的完成,有许多种实现,对于网络栈来说,采用了“软中断”机制。也就是说,当从网卡接收到数据后,网络子系统触发软中断,网卡中断处理程序就返回去继续它的工作了,而内核会在尽可能快的时间内,调用网络子系统的软中断函数,进一步处理数据包。
关于下半部和软中断的介绍,《Linux内核设计与实现》第二版第七章中,有详细的介绍。
[color="Red"]三、队列
由于中断处理的接收函数netif_rx同软中断函数不是连续处理的。也就是说,netif_rx不是等上层协议栈处理完一个数据包后,再返回处理另一个
数据包,所以,在netif_rx与软中断函数中间,自然需要一个缓冲区:netif_rx往里面写,而软中断函数从里边取。所以,从性能的角度考虑,建
立这样一个缓冲区,是非常必要的。Linux实现这一缓冲的算法是队列。
Linux用struct softnet_data结构描述了队列:
[Copy to clipboard]
[ - ]
CODE:struct softnet_data
{
int throttle;
int cng_level;
int avg_blog;
struct sk_buff_head input_pkt_queue;
struct list_head poll_list;
struct net_device *output_queue;
struct sk_buff *completion_queue;
struct net_device backlog_dev; /* Sorry. 8) */
};
throttle 用于拥塞控制,当拥塞发生时,throttle将被设置,后续进入的数据包将被丢弃;
cng_level描述一个拥塞级别,由netif_rx函数返回。
input_pkt_queue成员即为包的输入队列,在后面的分析中,我们可以看它网络子系统,如果进行入队和出队操作;
poll_list维护一个设备列表,特别的,我们并不需要针对每一个物理设备进行特别处理,比如,eth0接收一个包,eth1也接收一个包,eth0
->eth1,再分别调用它们的处理函数poll,从队列中取出数据包并送住上层。而且采用了一个通用伪设备backlog_dev(struct
softnet_data的最后一个成员),来进行处理,它的poll函数是process_backlog,在后面队列初始化中,我们会看到。
特别地,队列是一个per_cpu变量,也就是说,第一个CPU都有一个:
[Copy to clipboard]
[ - ]
CODE:DECLARE_PER_CPU(struct softnet_data,softnet_data);(net/core/dev.c line:576)
这样,每一次,都可以通过:
[Copy to clipboard]
[ - ]
CODE: for (i = 0; i
来遍历每一个CPU的队列,并处理,或者是:
[Copy to clipboard]
[ - ]
CODE:struct softnet_data *queue;
queue = &__get_cpu_var(softnet_data);
取得当前CPU的队列。
[color="Red"]四、上层协议的处理
最后一个要讨论的问题是,如何把相应的协议送往相应的上层栈,如IP协议交由tcp/ip栈,arp协议交由arp处理程序……
以太网帧头有一个类型字段,最简单的办法莫过于
switch(type)
case 0x0800
do_ip();
case 0x0806:
do_arp();
……
呵呵,当然Linux不会用我的笨办法了
内核用ruct packet_type结构来描述每一类协议:
[Copy to clipboard]
[ - ]
CODE: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 *);
void *af_packet_priv;
struct list_head list;
};
type即为协议类型值,func函数指针为对应的包处理句柄,list成员用于维护协议链表。
以ip协议(net/ipv4/af_inet.c)为例:
[Copy to clipboard]
[ - ]
CODE:static struct packet_type ip_packet_type = {
.type = __constant_htons(ETH_P_IP),
.func = ip_rcv,
};
对于IP协议来讲,类型是ETH_P_IP,接收处理函数是ip_rcv。接下来,就是要把ip_packet_type添加进协议链表中去:
[Copy to clipboard]
[ - ]
CODE:inet_init(void)(line:1012)
-> ip_init();
void __init ip_init(void)
{
dev_add_pack(&ip_packet_type);
……
}
void dev_add_pack(struct packet_type *pt)
{
int hash;
spin_lock_bh(&ptype_lock);
if (pt->type == htons(ETH_P_ALL)) {
netdev_nit++;
list_add_rcu(&pt->list, &ptype_all);
} else {
hash = ntohs(pt->type) & 15;
list_add_rcu(&pt->list, &ptype_base[hash]);
}
spin_unlock_bh(&ptype_lock);
}
可见,最后添加进的是一个以ptype_bash数组为首的链表,用协议值& 15,来取hash值,定位数组入口。
OK,有了这些基础,我们就可以来分析netif_rx函数了!!
未完,待续……
[ 本帖最后由 独孤九贱 于 2006-11-14 16:01 编辑 ]
您对本贴的看法:
鲜花[0]
臭蛋[0]
__________________________________
擦掉眼泪,抚平伤口,站起来重建家园,一定要坚强!!!
空间积分可以换礼品了!
|
有奖跟帖:服务器节能,奖50-100元图书
|
致电800-858-2903,了解DELL如何为你量身订制笔记本
|
送2G U盘
![]()
flw
![]()
广告杀手-老法王
侠客
![]()
![]()
UID:14893
注册:2002-8-12
最后登录:
2008-09-05
帖子:
20110
精华:
13
可用积分:15469
(大富大贵)
信誉积分:925
空间积分:0
(白手起家)
专家积分:0 (本版)
15469
-->
状态:[color="#00ff00"]...在线...
[
个人空间
]
[
短信
]
[
博客
]
![]()
2楼
发表于 2006-11-14 16:15
支持九贱发文!
您对本贴的看法:
鲜花[0]
臭蛋[0]
__________________________________
data Maybe a = Nothing
| Just a
---
如何知道一个变量是什么类型?
如何知道分配的内存有多大?
如何知道 select 的 fd_set 里哪个句柄是无效的?
如何知道指针是不是有效的?
如何通过文件句柄得到文件名?
……
如何知道我昨晚把袜子脱哪儿了?
空间积分可以换礼品了!
|
有奖跟帖:服务器节能,奖50-100元图书
|
致电800-858-2903,了解DELL如何为你量身订制笔记本
|
送2G U盘
独孤九贱
![]()
(九贱)
天使
![]()
![]()
UID:83191
注册:2003-8-12
最后登录:
2008-09-04
帖子:
1158
精华:
13
可用积分:1336
(家境小康)
信誉积分:100
空间积分:0
(白手起家)
专家积分:0 (本版)
1336
-->
来自:山城重庆
状态:[color="#999999"]...离线...
[
个人空间
]
[
短信
]
[
博客
]
![]()
3楼
发表于 2006-11-14 16:22
多谢楼上的支持,我也是初看内核栈,以前都一直在看netfilter,写出来是为了和大家一起交流学习,水平有限,错误,不详之处众多,众人拾材火焰高呀!
[color="red"]一、网络子系统的初始化
实始化是在net/core/dev.c的init 函数中完成的
[Copy to clipboard]
[ - ]
CODE:static int __init net_dev_init(void)
{
int i, rc = -ENOMEM;
BUG_ON(!dev_boot_phase);
net_random_init();
if (dev_proc_init())
goto out;
if (netdev_sysfs_init())
goto out;
/*ptype_all与ptype_base类似,不过它用于混杂模式*/
INIT_LIST_HEAD(&ptype_all);
/*初始化hash链表首部,我们前面已经讲过它的链表的建立了,现在才看到初始化,先后顺序有问题*/
for (i = 0; i input_pkt_queue);
/*初始化各个成员变量*/
queue->throttle = 0;
queue->cng_level = 0;
queue->avg_blog = 10; /* arbitrary non-zero */
queue->completion_queue = NULL;
/*初始化设备列表*/
INIT_LIST_HEAD(&queue->poll_list);
/*设置backlog_dev设备的状态,“我已经可以接收数据了”*/
set_bit(__LINK_STATE_START, &queue->backlog_dev.state);
queue->backlog_dev.weight = weight_p;
/*这一步很重要,指明backlog_dev的poll函数为process_backlog*/
queue->backlog_dev.poll = process_backlog;
atomic_set(&queue->backlog_dev.refcnt, 1);
}
#ifdef OFFLINE_SAMPLE
samp_timer.expires = jiffies + (10 * HZ);
add_timer(&samp_timer);
#endif
dev_boot_phase = 0;
/*接收和发送的软中断,在这里安装*/
open_softirq(NET_TX_SOFTIRQ, net_tx_action, NULL);
open_softirq(NET_RX_SOFTIRQ, net_rx_action, NULL);
hotcpu_notifier(dev_cpu_callback, 0);
dst_init();
dev_mcast_init();
rc = 0;
out:
return rc;
}
[Copy to clipboard]
[ - ]
CODE:二、netif_rx
netif_rx有两个很重要的工作:
1、把backlog_dev加入进设备列表;
2、把从网卡驱动接收来的数据添加进接收包队列中;
[Copy to clipboard]
[ - ]
CODE:int netif_rx(struct sk_buff *skb)
{
int this_cpu;
struct softnet_data *queue;
unsigned long flags;
/* if netpoll wants it, pretend we never saw it */
if (netpoll_rx(skb))
return NET_RX_DROP;
/*如果没有设置接收的时间,更新之*/
if (!skb->stamp.tv_sec)
net_timestamp(&skb->stamp);
/*
* The code is rearranged so that the path is the most
* short when CPU is congested, but is still operating.
*/
local_irq_save(flags);
this_cpu = smp_processor_id();
/*取得当前CPU的队列*/
queue = &__get_cpu_var(softnet_data);
/*接收计数器累加*/
__get_cpu_var(netdev_rx_stat).total++;
/*队列总数没有超过定义的最大值*/
if (queue->input_pkt_queue.qlen input_pkt_queue.qlen) {
if (queue->throttle)
goto drop;
enqueue:
dev_hold(skb->dev);
/*将数据包入队,并累加队列计算器*/
__skb_queue_tail(&queue->input_pkt_queue, skb);
#ifndef OFFLINE_SAMPLE
get_sample_stats(this_cpu);
#endif
local_irq_restore(flags);
return queue->cng_level;
}
if (queue->throttle)
queue->throttle = 0;
/*将backlog_dev设备添加进设备列表,并进一步入理*/
netif_rx_schedule(&queue->backlog_dev);
goto enqueue;
}
if (!queue->throttle) {
queue->throttle = 1;
__get_cpu_var(netdev_rx_stat).throttled++;
}
drop:
/*丢弃数据包,先累加计数器,再释放skb*/
__get_cpu_var(netdev_rx_stat).dropped++;
local_irq_restore(flags);
kfree_skb(skb);
return NET_RX_DROP;
}
[color="Red"]三、netif_rx_schedule
[Copy to clipboard]
[ - ]
CODE:static inline void netif_rx_schedule(struct net_device *dev)
{
if (netif_rx_schedule_prep(dev))
__netif_rx_schedule(dev);
}
netif_rx_schedule函数只是一个包裹函数,它将主动权交到了__netif_rx_schedule手中:
[Copy to clipboard]
[ - ]
CODE:static inline void __netif_rx_schedule(struct net_device *dev)
{
unsigned long flags;
local_irq_save(flags);
dev_hold(dev);
/*添加设备进列表*/
list_add_tail(&dev->poll_list, &__get_cpu_var(softnet_data).poll_list);
if (dev->quota quota += dev->weight;
else
dev->quota = dev->weight;
/*触发接收软中断*/
__raise_softirq_irqoff(NET_RX_SOFTIRQ);
local_irq_restore(flags);
}
接收处理函数触发软中断,负责“下半部”处理的软中断函数net_rx_action函数将会被执行。
未完,待续……
[ 本帖最后由 独孤九贱 于 2006-11-14 16:25 编辑 ]
您对本贴的看法:
鲜花[0]
臭蛋[0]
__________________________________
擦掉眼泪,抚平伤口,站起来重建家园,一定要坚强!!!
空间积分可以换礼品了!
|
有奖跟帖:服务器节能,奖50-100元图书
|
致电800-858-2903,了解DELL如何为你量身订制笔记本
|
送2G U盘
独孤九贱
![]()
(九贱)
天使
![]()
![]()
UID:83191
注册:2003-8-12
最后登录:
2008-09-04
帖子:
1158
精华:
13
可用积分:1336
(家境小康)
信誉积分:100
空间积分:0
(白手起家)
专家积分:0 (本版)
1336
-->
来自:山城重庆
状态:[color="#999999"]...离线...
[
个人空间
]
[
短信
]
[
博客
]
![]()
4楼
发表于 2006-11-14 16:50
[Copy to clipboard]
[ - ]
CODE:一、接收的软中断函数net_rx_action
[Copy to clipboard]
[ - ]
CODE:static void net_rx_action(struct softirq_action *h)
{
/*取得当前CPU上的队列*/
struct softnet_data *queue = &__get_cpu_var(softnet_data);
unsigned long start_time = jiffies;
/*netdev_max_backlog是一个sysctl常量,它决定了最大的排队等候的数据包*/
int budget = netdev_max_backlog;
local_irq_disable();
/*处理每一个设备上接收到的数据包,直接遍历完所有的设备,其中之一,就是netif_rx函数添加进的伪设备:backlog_dev*/
while (!list_empty(&queue->poll_list)) {
struct net_device *dev;
if (budget 1)
goto softnet_break;
local_irq_enable();
/*取得每一个设备*/
dev = list_entry(queue->poll_list.next,
struct net_device, poll_list);
netpoll_poll_lock(dev);
/*调用设备的poll函数*/
if (dev->quota poll(dev, &budget)) {
netpoll_poll_unlock(dev);
local_irq_disable();
list_del(&dev->poll_list);
list_add_tail(&dev->poll_list, &queue->poll_list);
if (dev->quota quota += dev->weight;
else
dev->quota = dev->weight;
} else {
netpoll_poll_unlock(dev);
dev_put(dev);
local_irq_disable();
}
}
out:
local_irq_enable();
return;
softnet_break:
__get_cpu_var(netdev_rx_stat).time_squeeze++;
__raise_softirq_irqoff(NET_RX_SOFTIRQ);
goto out;
}
对于软中断函数,我们主要关心的是:
遍历设备列表,取得每一个设备的poll函数,在netif_rx函数中,添加进队列的是back_log设备,它的poll函数是process_backlog。
[color="Red"]二、process_backlog
[Copy to clipboard]
[ - ]
CODE:static int process_backlog(struct net_device *backlog_dev, int *budget)
{
int work = 0;
int quota = min(backlog_dev->quota, *budget);
/*同样地,取得当前CPU的队列*/
struct softnet_data *queue = &__get_cpu_var(softnet_data);
unsigned long start_time = jiffies;
backlog_dev->weight = weight_p;
/*这个循环中,将队列中的数据包逐一出队*/
for (;;) {
struct sk_buff *skb;
struct net_device *dev;
/*数据包出队,当然,为了保护队列不至于混乱,必须关闭中断*/
local_irq_disable();
skb = __skb_dequeue(&queue->input_pkt_queue);
if (!skb)
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;
}
backlog_dev->quota -= work;
*budget -= work;
return -1;
job_done:
backlog_dev->quota -= work;
*budget -= work;
list_del(&backlog_dev->poll_list);
smp_mb__before_clear_bit();
netif_poll_enable(backlog_dev);
if (queue->throttle)
queue->throttle = 0;
local_irq_enable();
return 0;
}
[color="Red"]三、netif_receive_skb
netif_receive_skb函数最重要的工作,就是把数据包交给上层协议栈:
int netif_receive_skb(struct sk_buff *skb)
{
/*ptype和pt_prev用来遍历packet_type结构的链表,我们前面讨论过上层协议的封装与注册*/
struct packet_type *ptype, *pt_prev;
int ret = NET_RX_DROP;
unsigned short type;
/* 处理NAPI的情况*/
if (skb->dev->poll && netpoll_rx(skb))
return NET_RX_DROP;
if (!skb->stamp.tv_sec)
net_timestamp(&skb->stamp);
skb_bond(skb);
/*接收计数器累加*/
__get_cpu_var(netdev_rx_stat).total++;
/*更新传输层头和网络层头指针*/
skb->h.raw = skb->nh.raw = skb->data;
skb->mac_len = skb->nh.raw - skb->mac.raw;
pt_prev = NULL;
rcu_read_lock();
#ifdef CONFIG_NET_CLS_ACT
if (skb->tc_verd & TC_NCLS) {
skb->tc_verd = CLR_TC_NCLS(skb->tc_verd);
goto ncls;
}
#endif
/*处理混杂模式的情况*/
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;
}
}
#ifdef CONFIG_NET_CLS_ACT
if (pt_prev) {
ret = deliver_skb(skb, pt_prev);
pt_prev = NULL; /* noone else should process this after*/
} else {
skb->tc_verd = SET_TC_OK2MUNGE(skb->tc_verd);
}
ret = ing_filter(skb);
if (ret == TC_ACT_SHOT || (ret == TC_ACT_STOLEN)) {
kfree_skb(skb);
goto out;
}
skb->tc_verd = 0;
ncls:
#endif
handle_diverter(skb);
/*进入网桥*/
if (handle_bridge(&skb, &pt_prev, &ret))
goto out;
/*取得上层协议类型,这个类型值,是在网卡驱动中设置的*/
type = skb->protocol;
/*遍历每一个注册协议,调用deliver_skb函数来调用协议封装的处理函数*/
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;
}
}
if (pt_prev) {
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;
}
out:
rcu_read_unlock();
return ret;
}
对于IP协议而言,早已注册的处理函数是ip_recv……
未完,待续!
本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u2/72457/showart_1165037.html |
|