tun驱动之read
创始人
2024-06-01 20:36:59

从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为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,超过末尾位置后,均被重置为开始的位置。

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

相关内容

热门资讯

工信部部长李乐成:加快建立未来... 加快建设以先进制造业为骨干的现代化产业体系,将从哪些方面发力?工业和信息化部党组书记、部长李乐成在接...
国投智能股价涨5%,南方基金旗... 1月12日,国投智能涨5%,截至发稿,报15.95元/股,成交3.72亿元,换手率2.78%,总市值...
广元市前锋区开展“甜蜜莓好・田... 近日,在广元市前锋区委社工部、观塘镇人民政府的支持下,兴民社工联合前锋观新路社区以“服务创新型”特色...
诚迈科技股价涨5.02%,南方... 1月12日,诚迈科技涨5.02%,截至发稿,报50.42元/股,成交5.09亿元,换手率4.74%,...
步科股份股价涨5.08%,永赢... 1月12日,步科股份涨5.08%,截至发稿,报154.60元/股,成交5.02亿元,换手率4.14%...