找回密码
 注册
搜索
热搜: 回贴
微赢网络技术论坛 门户 服务器 Linux/BSD 查看内容

2.4内核数据报的发送过程学习笔记---从协议栈到驱动程序

2009-12-20 13:53| 发布者: admin| 查看: 111| 评论: 0|原作者: 慕容紫英

如题所示,此篇学习笔记记录的是数据报的发送过程,关于接收的相关过程,另一篇笔记有总结.以下总结了从网络协议到网卡数据报传输的具体过程,以下研究的代码来自于2.4.
无论是ip还是arp协议,当有数据要发送的时候都会调用dev_queue_xmit函数,也就是说,dev_queue_xmit是驱动程序对上面的接口.

/*
*功能:发送一个skb
*主要执行步骤:
*1,检查skb是否为碎片,检查device是否能处理skb的碎片操作.
*2,确定检验和已经被正确计算.
*3,发送skb.此时有两种情况,一种情况是有Qos层,调用qdisc_run(执行过程中真正的调用函数是qdisc_restart),另一种情况是直接调用hard_queue_xmit(如虚设备)
* 3.1:根据出队规则选择一个skb发送出去,注意此时的skb不一定就是传递进来的skb,因为要考虑到出队规则.(第一种情况)
* 3.2:直接调用hard_queue_xmit将数据报发送出去.(如虚拟设备)
*注意:此时发送一个skb所需要的东西都已经知道了,比如说,skb->dev为outgoing device,即选择传输的网络接口.
*/
int dev_queue_xmit(struct sk_buff *skb)
{
struct net_device *dev = skb->dev;
struct Qdisc *q;

/*首先检查skb_shinfo(skb)->frag_list是否有值,如果有,但是网络设备不支持skb的碎片列表,
*则通过skb_linearize把这些碎片重组到一个完整的skb中.
*/
if (skb_shinfo(skb)->frag_list &&
!(dev->features&NETIF_F_FRAGLIST) &&
skb_linearize(skb, GFP_ATOMIC) != 0) {
kfree_skb(skb);
return -ENOMEM;
}
if (skb_shinfo(skb)->nr_frags &&
(!(dev->features&NETIF_F_SG) || illegal_highdma(dev, skb)) &&
skb_linearize(skb, GFP_ATOMIC) != 0) {
kfree_skb(skb);
return -ENOMEM;
}
/* 如果数据包未做检验和,并且设备对该协议不支持检验和计算,则在
* 此处计算检验和.
*/
if (skb->ip_summed == CHECKSUM_HW &&
(!(dev->features&(NETIF_F_HW_CSUM|NETIF_F_NO_CSUM)) &&
(!(dev->features&NETIF_F_IP_CSUM) ||
skb->protocol != htons(ETH_P_IP)))) {
if ((skb = skb_checksum_help(skb)) == NULL)
return -ENOMEM;
}
/* Grab device queue */
spin_lock_bh(&dev->queue_lock);
/*net_device的成员qdisk是一个发送队列,缓冲等待网络设备进行发送的skb*/
q = dev->qdisc;
//如果队列存在,入队,一般情况下为实设备,有自己的发送队列,如果是虚设备,则一般没有自己的发送队列.
if (q->enqueue) {
int ret = q->enqueue(skb, q);
/*根据出队规则,挑选一个skb发送出去*/
qdisc_run(dev);
spin_unlock_bh(&dev->queue_lock);
/*返回状态信息*/
return ret == NET_XMIT_BYPASS ? NET_XMIT_SUCCESS : ret;
}

/*如执行到此步,则表明网络接口没有自己的发送队列,一般为虚设备的情况,如loopback,ip隧道等*/
if (dev->flags&IFF_UP) {
int cpu = smp_processor_id();
if (dev->xmit_lock_owner != cpu) {
spin_unlock(&dev->queue_lock);
spin_lock(&dev->xmit_lock);
dev->xmit_lock_owner = cpu;
if (!netif_queue_stopped(dev)) {
//如果ptype_all中有成员,则先发给其中注册的处理函数
if (netdev_nit)
dev_queue_xmit_nit(skb,dev);

//dev->hard_start_xmit对应实际设备驱动程序的发送函数
if (dev->hard_start_xmit(skb, dev) == 0) {
dev->xmit_lock_owner = -1;
spin_unlock_bh(&dev->xmit_lock);
return 0;
}
}
/*如果device的发送队列被禁止*/
dev->xmit_lock_owner = -1;
spin_unlock_bh(&dev->xmit_lock);
if (net_ratelimit())
printk(KERN_CRIT "Virtual device %s asks to queue packet!\n", dev->name);
kfree_skb(skb);
return -ENETDOWN;
} else {
/* Recursion is detected! It is possible, unfortunately */
if (net_ratelimit())
printk(KERN_CRIT "Dead loop on virtual device %s, fix it urgently!\n", dev->name);
}
}
spin_unlock_bh(&dev->queue_lock);
/*如果设备没有被打开*/
kfree_skb(skb);
return -ENETDOWN;
}
由上面的注释可以看到,dev_queue_xmit会遇到两种情况,一种情况是有traffic control层(QoS层),一种是直接调用设备驱动程序的发送函数hard_start_xmit(比如一般的虚拟设备都是如此)前种情况的路线是:dev_queue_xmit-->qdisc_run-->qdisc_restart(dev),后者的路线是:dev_queue_xmit-->hard_start_xmit.
qdisc的源代码为:
static inline void qdisc_run(struct net_device *dev)
{
while (!netif_queue_stopped(dev) &&
qdisc_restart(dev)0)
/* NOTHING */;
}
可以看出,qdisc_run只是qdisc_restart的包裹函数,此函数过滤了发送队列被禁止的dev,然后调用qdisc_restart.
/*函数作用:当device有自己的发送队列时的发送函数.
*此函数涉及到的两种锁:
* dev->queue_lock: 是device发送队列queue的锁,
* dev->xmit_lock: 是driver 的发送程序hard_start_xmit的锁.
*
*函数的执行路线:qdisc_restart---->sniffer(如果有)---->dev_hard_xmit.
*
*返回值:
* 0 : 发送队列为空时.
* 1 : 队列非空,但由于某种原因没能发送数据,比如不能获得dev的发送队列的锁dev->xmit_lock.
* -1: 发送成功时.
*/
int qdisc_restart(struct net_device *dev)
{
/*dev->qdisc为dev的发送队列*/
struct Qdisc *q = dev->qdisc;
struct sk_buff *skb;
/* 根据一定的规则,挑选一个skb发送出去,此处为体现有traffic control时的区别*/
if ((skb = q->dequeue(q)) != NULL) {
if (spin_trylock(&dev->xmit_lock)) {
/* Remember that the driver is grabbed by us. */
dev->xmit_lock_owner = smp_processor_id();
/* And release queue */
spin_unlock(&dev->queue_lock);
if (!netif_queue_stopped(dev)) {
/*如果有注册的sniffer,则发送个各个sniffer,当sniffer注册时,netdev_nit会相应的加1,所以netdev_nit代表了sniffer的数量*/
if (netdev_nit)
dev_queue_xmit_nit(skb, dev);
if (dev->hard_start_xmit(skb, dev) == 0) {
dev->xmit_lock_owner = -1;
spin_unlock(&dev->xmit_lock);
spin_lock(&dev->queue_lock);
return -1;
}
}
/* Release the driver */
dev->xmit_lock_owner = -1;
spin_unlock(&dev->xmit_lock);
spin_lock(&dev->queue_lock);
q = dev->qdisc;
}
/*如果没得到device的发送函数的锁,则说明此device已经被别的cpu调用在发送数据*/
else {
/* So, someone grabbed the driver. */
/* It may be transient configuration error,
when hard_start_xmit() recurses. We detect
it by checking xmit owner and drop the
packet when deadloop is detected.
*/
/*如果device的发送函数锁为本cpu所有,但却还忙,则free掉sk_buff*/
if (dev->xmit_lock_owner == smp_processor_id()) {
kfree_skb(skb);
if (net_ratelimit())
printk(KERN_DEBUG "Dead loop on netdevice %s, fix it urgently!\n", dev->name);
return -1;
}
/*更新冲突状态信息*/
netdev_rx_stat[smp_processor_id()].cpu_collision ;
}
/* Device kicked us out :(
This is possible in three cases:
0. driver is locked
1. fastroute is enabled
2. device cannot determine busy state
before start of transmission (f.e. dialout)
3. device is buggy (ppp)
*/
/*走到此,应该是没发送成功,则把skb重新放回到队列中,然后调用net0f_schedule(dev)再次试图将其通过dev发送出去*/
q->ops->requeue(skb, q);
netif_schedule(dev);
return 1;
}
/*程序走到这时,说明队列为空,q.qlen应该是0*/
return q->q.qlen;
}
可以看到,调用了qdisk_restart函数的一般都是device有自己的发送队列的情况,此时在出队列函数dequeue处体现了traffic control的作用.
由代码可知,在发送失败时,会将skb重新放入队列中,然后调用netif_schedule(dev)将其重新发送.下面来看看netif_schedule的源代码.
static inline void netif_schedule(struct net_device *dev)
{
if (!test_bit(__LINK_STATE_XOFF, &dev->state))
__netif_schedule(dev);
}
可以看到,netif_schedule是__netif_schedule(dev)的包裹函数.
/*
*函数作用:把参数dev加入到softnet_data中的output_queue链表首,优先调度,然后触发发送软中断,调用net_tx_action.
*/
static inline void __netif_schedule(struct net_device *dev)
{
if (!test_and_set_bit(__LINK_STATE_SCHED, &dev->state)) {
unsigned long flags;
int cpu = smp_processor_id();
local_irq_save(flags);
dev->next_sched = softnet_data[cpu].output_queue;
softnet_data[cpu].output_queue = dev;
cpu_raise_softirq(cpu, NET_TX_SOFTIRQ);
local_irq_restore(flags);
}
}
如注释所示,此函数功能很简单,把参数传进来的dev放到softnet_data[cpu].output_queue的链表首,优先等待调度,然后触发发送软中断,调用相应的发送处理函数,即net_tx_action.读到此,产生了两个问题,就是说,发送成功后,直接返回-1,由于某种原因不能发送skb时才会有这些操作,也就是说,只有在因为某种原因不能发送skb时,需要将数据报重新放回队列,然后把dev放在output_queue的链表首,此时才触发发送软中断,不是每次发送数据时都触发发送软中断?还有,我记得当数据报发送数据包成功后会把skb加入到completion_queue中,但是怎么没看到相关代码?这两个问题先放下,看看读完net_tx_action的代码后能不能找到答案.
/*
*net_tx_action为发送软中断的处理函数.
*函数作用:
* 1 释放softnet_data[cpu].completion_queue中发送成功的sk_buff.
* 2 调度softnet_data[cpu].output_queue中的device发送数据.
*/
static void net_tx_action(struct softirq_action *h)
{
int cpu = smp_processor_id();
if (softnet_data[cpu].completion_queue) {
struct sk_buff *clist;

/*因为net_tx_action不是在中断的环境中运行的,所以驱动程序可以在任意时候不顾net_tx_action的执行而向completion_queue中添加数据,
所以此处要禁止中断,因为禁止中断时间越短越好,所以先用本地变量clist指向softnet_data[cpu].completion_queue,然后将softnet_data[cpu].completion_queue
设置为NULL,开中断,由于本地变量是不能被外界访问的,所以之后再慢慢的处理skb*/
local_irq_disable();
clist = softnet_data[cpu].completion_queue;
softnet_data[cpu].completion_queue = NULL;
local_irq_enable();
while (clist != NULL) {
struct sk_buff *skb = clist;
clist = clist->next;
BUG_TRAP(atomic_read(&skb->users) == 0);
__kfree_skb(skb);
}
}
if (softnet_data[cpu].output_queue) {
struct net_device *head;

/*禁止中断的原因同上*/
local_irq_disable();
head = softnet_data[cpu].output_queue;
softnet_data[cpu].output_queue = NULL;
local_irq_enable();
while (head != NULL) {
struct net_device *dev = head;
head = head->next_sched;
smp_mb__before_clear_bit();
/*调用output_queue中的device进行发送数据*/
clear_bit(__LINK_STATE_SCHED, &dev->state);

/*在调用一个device发送数据时,应先获得此设备的发送队列的锁*/
if (spin_trylock(&dev->queue_lock)) {
qdisc_run(dev);
spin_unlock(&dev->queue_lock);
}
/*如果得不到此设备的发送队列的锁,此时可能有另外的cpu在用到此设备发送数据,则重新调度此设备发送数据*/
else {
netif_schedule(dev);
}
}
}
}
此函数完成两件事,如上面代码注释可知: 1,释放softnet_data[cpu].completion_queue中发送成功的sk_buff. 2,调度softnet_data[cpu].output_queue中的device发送其发送队列中的数据.突然想起在以前读ldd2的时候看见过如下一句话:网络接口在两种可能的事件下中断处理器:新数据包的到达,或者外发数据包的传输已经完成.好像想起了点什么,觉得应该是这样:当数据报成功传送完之后,把其加入到completion_queue中的代码应该是在网卡驱动中具体实现的,传输成功后,会调用net_tx_action来处理completion_queue中发送成功的数据报,然后在调度output_queue中的device传送其发送队列中的数据包.读到这时,最少两种情况会调用net_tx_action,第一种情况是在由于某种原因不能发送skb时(比如不能获得dev的发送队列的锁dev->xmit_lock),或者在网卡驱动程序成功发送完数据包后,会产生中断,然后执行发送软中断处理函数.这样理解可以理解通,不知道实事上是不是这样,如果我想错了,知道正确答案的朋友,希望能给予及时指正,不胜感激.
这里要知道,跟接收数据包时的原理一样,一般不会将所有事情都在中断环境中解决,所以当一个网卡发送完数据包后,并不是直接的在中断环境下释放skb资源,而是将其放入completion_queue中,这块也只是指针的指向,而不是数据包的拷贝,然后调用发送软中断来处理数据包.当驱动程序传输完数据包后,会调用dev_kfree_skb_irq来释放sk_buff.
static inline void dev_kfree_skb_irq(struct sk_buff *skb)
{
if (atomic_dec_and_test(&skb->users)) {
struct softnet_data *sd;
unsigned long flags;

local_irq_save(flags);
sd = &_ _get_cpu_var(softnet_data);
skb->next = sd->completion_queue;
sd->completion_queue = skb;
raise_softirq_irqoff(NET_TX_SOFTIRQ);
local_irq_restore(flags);
}
}
此处可以看出来,此函数只是把skb放入completion_queue的链表头,真正的释放工作在发送软中断net_tx_action中完成.
ok,分析到这,基本上算是通了.下面是自己在研究以上代码时随手记的一些零散知识点,有看到这篇笔记的朋友们可以直接pass了,自己不舍得扔掉,于是贴在此.
数据包的发送过程和接收过程大体上类似:
1,NET_TX_SOFTIRQ:发送软中断 NET_RX_SOFTIRQ:接收软中断 ; net_tx_action:发送软中断处理函数 net_rx_action:接收软中断处理函数 ; dev_queue_xmit netif_rx ; output_queue:NAPI和non-NAPI都用poll_list:只有non_NAPI用.
2,当一个device被调用以用来接收数据时,其device的__LINK_STATE_RX_SCHED被标记.当一个device被调用以用来发送数据时,其device的__LINK_STATR_SCHED被标记.
3,当一个device正在被调用时,为什么要使能或禁止队列的传输功能呢?
一个原因是,一个device可能暂时用光其存储空间,所以会导致传输失败.
解决办法:当驱动程序获知devicedevice没有足够的space来存储一个数据包时,调用netif_stop_queue禁止传输.相反,当驱动程序获知device有足够的space来存储一个skb时,则调用netif_start_queue使能传输功能.
4,netif_wake_queue = netif_start_queue netif_schedule
5,当传输一个数据时.当以下条件发生时,需要把数据包重新入队.
1)发送队列被禁止(netif_queue_stopped(dev) is true)
2)另一个cpu正持有此device driver的lock.
3)传输失败.
6,dev_kfree_skb:释放skb ; dev_kfree_skb_irq:仅仅把skb放入到softnet_data的completion_queue中,然后调用发送软中断,让net_tx_action完成真正的释放工作.
由于本人刚开始研究网络的底层实现不长时间,所以难免有理解错误的地方,如果本篇笔记存在错误,希望看到的达人们给予指正,不胜感激.





最新评论

QQ|小黑屋|最新主题|手机版|微赢网络技术论坛 ( 苏ICP备08020429号 )

GMT+8, 2024-9-29 21:26 , Processed in 0.250888 second(s), 12 queries , Gzip On, MemCache On.

Powered by Discuz! X3.5

© 2001-2023 Discuz! Team.

返回顶部