tun驱动之read

从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》。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值