【Linux5.4】【TUN】代码学习记录(9)–tun_chr_read_iter
用户调用read函数读取tun设备数据时,tun_chr_read_iter被调用。
tun_chr_read_iter
对tun_do_read进行封装
static ssize_t tun_chr_read_iter(struct kiocb *iocb, struct iov_iter *to)
{
struct file *file = iocb->ki_filp;
struct tun_file *tfile = file->private_data;
struct tun_struct *tun = tun_get(tfile);
ssize_t len = iov_iter_count(to), ret;
if (!tun)
return -EBADFD;
ret = tun_do_read(tun, tfile, to, file->f_flags & O_NONBLOCK, NULL);
ret = min_t(ssize_t, ret, len);
if (ret > 0)
iocb->ki_pos = ret;
tun_put(tun);
return ret;
}
tun_do_read
tun_do_read内主要做两件事:①接收网络传来的skb数据,②将①中的数据交给用户。
如果tun_do_read是由tun_chr_read_iter调用的,那么参数ptr是NULL,所以需要tun_ring_recv获取ptr指针,再由struct sk_buff *skb = ptr得到skb,①完成。
调用tun_put_user将数据传入用户空间,②完成。
这里需要注意的是,在tfile中有一个tx_ring,是一个生产消费模型使用的“环形内存”,由生产者放入数据,消费者(也就是tun_ring_recv函数)读取数据,那么这个生产者按逻辑讲应该是内核的网络协议栈,网络协议栈收到物理网卡传来的数据,处理后如果要交付给tun设备,就会将数据放入相应的tfile中的tx_ring里。
static ssize_t tun_do_read(struct tun_struct *tun, struct tun_file *tfile,
struct iov_iter *to,
int noblock, void *ptr)
{
ssize_t ret;
int err;
tun_debug(KERN_INFO, tun, "tun_do_read\n");
if (!iov_iter_count(to)) {
tun_ptr_free(ptr);
return 0;
}
if (!ptr) {
/* Read frames from ring */
ptr = tun_ring_recv(tfile, noblock, &err);
if (!ptr)
return err;
}
if (tun_is_xdp_frame(ptr)) {
struct xdp_frame *xdpf = tun_ptr_to_xdp(ptr);
ret = tun_put_user_xdp(tun, tfile, xdpf, to);
xdp_return_frame(xdpf);
} else {
struct sk_buff *skb = ptr;
ret = tun_put_user(tun, tfile, skb, to);
if (unlikely(ret < 0))
kfree_skb(skb);
else
consume_skb(skb);
}
return ret;
}
tun_put_user
tun_put_user较长,可以只看关键部分:
上传数据到用户空间实际是由skb_copy_datagram_iter完成的。
/* Put packet to the user space buffer */
static ssize_t tun_put_user(struct tun_struct *tun,
struct tun_file *tfile,
struct sk_buff *skb,
struct iov_iter *iter)
{
...
skb_copy_datagram_iter(skb, vlan_offset, iter, skb->len - vlan_offset);
...
}