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

tun驱动之read相关推荐

  1. tun驱动之tun_init

    tun驱动的初始化方法是tun_init. static int __init tun_init(void) {int ret = 0;pr_info("%s, %s\n", DR ...

  2. tun驱动之open

    tun驱动对应的设备文件是:/dev/net/tun,其详细信息如下: crw-rw-rw- 1 root root 10, 200 2月  26 08:05 tun 主次设备号的定义如下: #def ...

  3. linux tap 源码分析,tun/tap 驱动源码分析

    此驱动运行时可设置tun模式和tap模式,tun模式能取到IP数据包,无法获得ARP数据,而tap模式取到的是以太包,可以得到链路层以上的一切数据包. 由于项目需要使用tun驱动,而又不想不求甚解,从 ...

  4. Tun/Tap接口指导

    转载来源: 本文来自博客园,作者:charlieroro,转载请注明原文链接:https://www.cnblogs.com/charlieroro/p/13497340.html ========= ...

  5. TUN/TAP 学习总结(三) —— Windows TUN demo

    这个和Linux 的TUN demo一样,添加一条静态路由指定TUN设备,demo 程序从TUN读取报文,简单处理ICMP报文,然后送回协议栈,从而使ping命令成功执行. 与Linux 不同,win ...

  6. TCP/IP网络的一些问题(路由/协议/linux的实现)

    1.linux的虚拟网卡-tun linux的虚拟网卡驱动可以配置为两种模式,一种是点对点的tun模式,一种是以太网tap模式,实质上tun模式中从虚拟网卡出来的是ip数据报,也就是三层数据,而tap ...

  7. vmware的vmnet-概念的解说

    vmware本身实现了一个很不错的虚拟网络-vmnet,这个虚拟网络完全可以脱离vmware而存在,我个人十分看好这个虚拟网络架构,它实质上是在内核实现的一个虚拟的链路层网络,至于它还有没有其它的组网 ...

  8. Linux虚拟化KVM-Qemu分析(九)之virtio设备

    目录 1. 概述 2. 流程分析 3. tap创建 - 网卡后端设备 4. virtio-net创建 4.1 数据结构 4.2 流程分析 4.2.1 class_init 4.2.2 instance ...

  9. 超猛tuntap虚拟网卡实现超猛UDP隧道

    TUN/TAP虚拟网卡在25Gbps物理网卡的环境下可以接近25Gbps的转发能力吗? 答案当然是可以. 实现简单到让你怀疑人生! 首先看一个图: 上周末写了点代码,这周贴上去: https://gi ...

最新文章

  1. new和make的区别
  2. 换肤的css,换肤功能,css文件中准备三套颜色
  3. OPPO Reno7红丝绒新年版开售:精致虎头标识+金色镜头保护圈
  4. OpenProj打开不了或者提示Failed to load Java VM Library的错误的解决方案
  5. zznu 1914 asd的甩锅计划
  6. html5 画猫全过程svg入门
  7. 【seaborn】jointplot 改变图片长宽比,非方形
  8. 截屏、文字提取一气呵成,超实用 OCR 开源小工具
  9. arctanx麦克劳林公式推导过程_三角函数的求导过程
  10. Python中timestamp时间戳和日期时间的转换
  11. 新媒体工作者必备常识
  12. Linux篇19多线程第三部分
  13. error C2448: 'Unknown' : function-style initializer appears to be a function definition
  14. 享元模式--大量的飞龙
  15. 深度学习——核心思想
  16. Docker images导出和导入
  17. 机器人与控制器的关联
  18. Redis 过期策略和淘汰策略
  19. 最长公共子序列问题——LCS算法
  20. 记一次羞羞的事情。。。

热门文章

  1. 无线通信模块定点传输-点对点的具体传输应用
  2. java记录-String、StringBuilder和StringBuffer
  3. Mapguide配置心得
  4. ubuntu下制作window启动盘(官方)
  5. Java踩坑记录-00001 BeanCreationException
  6. 【Argoverse 1 Motion Forecasting Dataset】轨迹预测数据集简介
  7. vue之原生上传图片并压缩图片大小(1)
  8. 罗技无线鼠标响应缓慢
  9. java工具类 - word内容文本替换
  10. IDS--入侵检测系统的学习