从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开始分析。
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为1if (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);elsequeue_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_indexreturn 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,无法进行拥塞控制,如loopbackif (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,超过末尾位置后,均被重置为开始的位置。
下面开始分析,从驱动文件中读取数据时的逻辑,即:
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》。
上一篇:C语言经典例题-字符数组