一、中文注释
这两个函数是Linux内核网络子系统中负责发起网络包(sk_buff结构)传输的代码。下面我将用中文对这两个函数做一个简单的注释:
/**
* __dev_queue_xmit - 发送一个buffer
* @skb: 要发送的buffer
* @sb_dev: 子设备,用于层2转发离线处理
*
* 将buffer排队以发送到网络设备。调用此函数之前,调用者必须设置好设备和优先级,并构建好buffer。
* 这个函数可以在中断上下文中调用。
*
* 如果失败,则返回一个负的errno错误码。成功返回并不保证帧一定会被发送,因为可能会因为拥塞或流量整形而被丢弃。
*
* 注意,这个方法也可以返回队列规则(queue disciplines)的错误,
* 包括NET_XMIT_DROP,它是一个正值。所以,错误也可能是正值。
*
* 不管返回的值是什么,skb都会被消耗,所以当前很难尝试重新发送到这个方法。
* (如果你小心的话,在发送前,你可以增加引用计数以持有引用进行重试。)
*
* 调用此方法时,中断必须是启用的。这是因为BH(Bottom Half,底半部)启用代码必须在IRQ启用的情况下,
* 不会造成死锁。
*/
static int __dev_queue_xmit(struct sk_buff *skb, struct net_device *sb_dev)
{
// 省略代码中部分注释和实现...
// 如果网络设备并没有队列(例如环回设备,隧道),则直接发送
if (dev->flags & IFF_UP) {
int cpu = smp_processor_id(); // 当前CPU编号
// 如果还没有线程持有发送锁
if (txq->xmit_lock_owner != cpu) {
// 省略了关于递归发送检测和发送限制的代码...
skb = validate_xmit_skb(skb, dev, &again);
if (!skb)
goto out;
HARD_TX_LOCK(dev, txq, cpu); // 获取硬件发送锁
// 如果可以发送(没有被停止)
if (!netif_xmit_stopped(txq)) {
// 尝试发送数据包
skb = dev_hard_start_xmit(skb, dev, txq, &rc);
// 如果发送完成
if (dev_xmit_complete(rc)) {
HARD_TX_UNLOCK(dev, txq); // 解锁
goto out;
}
}
HARD_TX_UNLOCK(dev, txq); // 解锁
// 如果虚拟设备请求排队发送数据包,打印告警信息
} else {
// 如果检测到递归发送,打印告警信息
}
}
rc = -ENETDOWN; // 网络设备不可用
rcu_read_unlock_bh(); // 读解锁
atomic_long_inc(&dev->tx_dropped); // 增加丢包计数
kfree_skb_list(skb); // 释放skb
return rc;
out:
rcu_read_unlock_bh(); // 读解锁
return rc;
}
/**
* dev_queue_xmit - 发送一个buffer
* @skb: 要发送的buffer
*
* 对__dev_queue_xmit函数的简单封装,不指定子设备。
*/
int dev_queue_xmit(struct sk_buff *skb)
{
return __dev_queue_xmit(skb, NULL);
}
EXPORT_SYMBOL(dev_queue_xmit); // 导出符号,使得其他模块可以调用
这两个函数涉及到Linux内核网络发送路径的处理细节,包括选择设备队列、处理网络设备的发送锁、检查设备队列是否被停止、调用真正的发送函数、错误处理和资源清理等。它们体现了操作系统内核在网络通信方面的复杂逻辑和对并发控制的处理。
二、中文讲解
这两个函数是Linux内核网络堆栈中负责处理网络包发送的函数。
__dev_queue_xmit
函数是一个静态函数,它的作用是将一个网络数据包(即socket buffer,简称skb)放入网络设备队列中以便传输出去。它的参数包括待发送的数据包`skb`和用于二层转发卸载的从属设备`sb_dev`。
函数内部步骤如下:
1. 通过`skb_reset_mac_header`重置数据包中的MAC头部信息。
2. 如果数据包需要软件时间戳,则添加这个时间戳。
3. 获取RCU读锁,这是因为可能会访问共享数据而不希望被其他CPU更改。
4. 更新数据包的优先级,并初始化包的传输长度信息。
5. 如果设置有特定的流量控制,会执行一些流量控制的处理。
6. 如果网络设备标志表明它不需要数据包的目的信息,则直接丢弃这部分信息。
7. 根据数据包和设备信息,选择合适的网络设备队列。
8. 如果设备有队列调度器,则尝试将数据包加入队列。
9. 如果设备不需要队列,比如是软件设备(例如回环设备或者隧道),则尝试直接传输数据包。
10. 若设备正常运作,使用硬启动传输函数`dev_hard_start_xmit`尝试传输数据包;如果设备已经关闭或发送失败,则将其丢弃,并增加丢包计数器。
11. 结束前释放之前获取的RCU读锁。
dev_queue_xmit
函数是一个可导出的简单封装函数,用于调用`__dev_queue_xmit`函数。它只需要一个参数,即要发送的数据包`skb`,因为它默认不需要处理从属设备`sb_dev`。这个函数会被内核中其他模块调用,以此来发送网络数据。
注意的是,`__dev_queue_xmit`函数可以在中断上下文中调用,并且即使发送操作报告成功,也无法保证数据包一定会被传输出去,因为可能会因为网络拥堵或流量整形策略被丢弃。函数返回值可能是负的错误码,也可能是正的,例如`NET_XMIT_DROP`表示包被丢弃。一旦调用该函数发送数据包,数据包的内存就被消费了,即使发送失败也不会尝试重传。此外,在调用此函数时,必须保证中断是开启的,以避免死锁。