从tun驱动读取的数据,最终来源于用户空间通过write写入的数据,如下所示:
inti fd = socket(); int f = open("/dev/net/tun", O_RDWR)
write(fd, buf, len); --> 协议栈 --> tun驱动 --> read(f, buf, len);
比如tun驱动对应的网段是10.8.0.0/24,向此网段发送的socket数据,最终会到达tun驱动中,然后通过read,读取这些数据。首先需要知道,发送的数据,是如何一步步到tun驱动的。
tcp的发送数据,经过上图中的处理后,最后到达链路层,我们这次从dev_queue_xmit开始分析。
一 从协议栈到tun驱动
struct netdev_queue *netdev_pick_tx(struct net_device *dev,
struct sk_buff *skb,
void *accel_priv)
{
int queue_index = 0;
// 对于tun设备,如果设置了IFF_MULTI_QUEUE标记,则real_num_tx_queues为MAX_TAP_QUEUES
// 否则real_num_tx_queues为1
if (dev->real_num_tx_queues != 1) {
const struct net_device_ops *ops = dev->netdev_ops;
if (ops->ndo_select_queue)
queue_index = ops->ndo_select_queue(dev, skb, accel_priv,
__netdev_pick_tx);
else
queue_index = __netdev_pick_tx(dev, skb);
if (!accel_priv)
queue_index = netdev_cap_txqueue(dev, queue_index);
}
skb_set_queue_mapping(skb, queue_index); // skb->queue_mapping = queue_index
return netdev_get_tx_queue(dev, queue_index); // dev->_tx[queue_index]
}
static int __dev_queue_xmit(struct sk_buff *skb, void *accel_priv)
{
struct net_device *dev = skb->dev;
struct netdev_queue *txq;
struct Qdisc *q;
// 选择发送队列
txq = netdev_pick_tx(dev, skb, accel_priv);
// qdisc用于拥堵处理
q = rcu_dereference_bh(txq->qdisc);
if (q->enqueue) { // 定义了 enqueue
// 进入带有拥塞控制的处理
rc = __dev_xmit_skb(skb, q, dev, txq);
goto out;
}
// 没有qdisc,无法进行拥塞控制,如loopback
if (dev->flags & IFF_UP) {
int cpu = smp_processor_id(); /* ok because BHs are off */
if (txq->xmit_lock_owner != cpu) {
if (!netif_xmit_stopped(txq)) { // 如果txq不是stop状态
skb = dev_hard_start_xmit(skb, dev, txq, &rc);
__this_cpu_dec(xmit_recursion);
}
}
}
}
调用netdev_pick_tx,选择发送的队列。我们这次分析的tun驱动,没有设置IFF_MULTI_QUEUE标记,只有一个发送队列,并且不支持拥塞处理。因此在__dev_queue_xmit中,调用dev_hard_start_xmit继续处理。经过如下几步处理:
dev_hard_start_xmit --> xmit_one --> netdev_start_xmit。
static inline netdev_tx_t netdev_start_xmit(struct sk_buff *skb, struct net_device *dev,
struct netdev_queue *txq, bool more)
{
const struct net_device_ops *ops = dev->netdev_ops;
int rc;
rc = __netdev_start_xmit(ops, skb, dev, more);
if (rc == NETDEV_TX_OK)
txq_trans_update(txq);
return rc;
}
netdev_start_xmit中的ops,即tun驱动中的tap_netdev_ops。
static inline netdev_tx_t __netdev_start_xmit(const struct net_device_ops *ops,
struct sk_buff *skb, struct net_device *dev,
bool more)
{
skb->xmit_more = more ? 1 : 0;
return ops->ndo_start_xmit(skb, dev);
}
调用ops->ndo_start_xmit,即tun_net_xmit。
static netdev_tx_t tun_net_xmit(struct sk_buff *skb, struct net_device *dev)
{
if (skb_array_produce(&tfile->tx_array, skb))
goto drop;
// 唤醒等待的进程
tfile->socket.sk->sk_data_ready(tfile->socket.sk);
}
tun_net_xmit中最重要的操作是调用skb_array_produce将skb添加到一个ptr_ring结构上。
上图展示了可以保存6个指针的ptr_ring存取数据的情况。producer指向下一个可以保存数据的位置,每放入一个数据,其向后移动一个位置;consumer_head指向下一个读取数据的位置,该位置为NULL,表示无数据可读。producer和consumer_head,超过末尾位置后,均被重置为开始的位置。
二 从tun驱动到用户空间
下面开始分析,从驱动文件中读取数据时的逻辑,即:
int f = open("/dev/net/tun", O_RDWR);
read(f, buf, len);
read最终的执行函数是tun_chr_read_iter,tun_chr_read_iter --> tun_do_read --> tun_ring_recv。
static ssize_t tun_chr_read_iter(struct kiocb *iocb, struct iov_iter *to)
{
ret = tun_do_read(tun, tfile, to, file->f_flags & O_NONBLOCK, NULL);
}
static ssize_t tun_do_read(struct tun_struct *tun, struct tun_file *tfile,
struct iov_iter *to,
int noblock, struct sk_buff *skb)
{
if (!skb) {
/* Read frames from ring */
skb = tun_ring_recv(tfile, noblock, &err);
if (!skb)
return err;
}
ret = tun_put_user(tun, tfile, skb, to);
}
static struct sk_buff *tun_ring_recv(struct tun_file *tfile, int noblock,
int *err)
{
DECLARE_WAITQUEUE(wait, current);
skb = skb_array_consume(&tfile->tx_array);
if (skb)
goto out;
add_wait_queue(&tfile->wq.wait, &wait);
current->state = TASK_INTERRUPTIBLE;
while (1) {
skb = skb_array_consume(&tfile->tx_array);
if (skb)
break;
if (signal_pending(current)) {
error = -ERESTARTSYS;
break;
}
if (tfile->socket.sk->sk_shutdown & RCV_SHUTDOWN) {
error = -EFAULT;
break;
}
schedule();
}
current->state = TASK_RUNNING;
remove_wait_queue(&tfile->wq.wait, &wait);
out:
return skb;
}
tun_ring_recv中调用skb_array_consume从ptr_ring列表中获取一条数据,如果获取成功,函数返回,并最后将数据返回给用户空间;如果取不到数据,则定义wait对象,将wait对象连接到tfile->wq.wait上,进程休眠,等待数据的到来。
休眠的进程,什么时候会被唤醒呢,答案是在tun_net_xmit中,skb_array_produce执行成功后,调用tfile->socket.sk->sk_data_ready唤醒的。tfile->socket.sk->sk_data_ready对应的是sock_def_readable,是在tun_chr_open--> sock_init_data中设置的。
sock_def_readable的逻辑,可以看下《网络篇之epoll》。