tun驱动之write

news2024/10/1 19:23:27

tun的write执行类型下面的代码

int fd = open("/dev/net/tun", O_RDWR)
write(fd, buf, len);


首先要明确一点,向tun驱动写的数据,最后会进入网络协议栈,相当于外部的数据通过网卡进入网络协议栈。所以写入tun驱动的数据,会放到cpu中的input_pkt_queue队列。



一 软中断启动


要想把数据发送将清除,软中断是绕不过去的。因为tun驱动,没有对应的硬件设备,所以不会产生硬中断。
软中断的初始化函数是spawn_ksoftirqd,在函数中注册了软中断处理线程softirq_threads。

static struct smp_hotplug_thread softirq_threads = {
    .store            = &ksoftirqd,
    .thread_should_run    = ksoftirqd_should_run,
    .thread_fn        = run_ksoftirqd,
    .thread_comm        = "ksoftirqd/%u",
};

static __init int spawn_ksoftirqd(void)
{
    cpuhp_setup_state_nocalls(CPUHP_SOFTIRQ_DEAD, "softirq:dead", NULL,
                  takeover_tasklets);
    BUG_ON(smpboot_register_percpu_thread(&softirq_threads));

    return 0;
}


在smpboot_register_percpu_thread中调用了smpboot_register_percpu_thread_cpumask:

int smpboot_register_percpu_thread_cpumask(struct smp_hotplug_thread *plug_thread,
                       const struct cpumask *cpumask)
{
    unsigned int cpu;
    int ret = 0;

    if (!alloc_cpumask_var(&plug_thread->cpumask, GFP_KERNEL))
        return -ENOMEM;
    cpumask_copy(plug_thread->cpumask, cpumask);

    get_online_cpus();
    mutex_lock(&smpboot_threads_lock);
    for_each_online_cpu(cpu) {
        ret = __smpboot_create_thread(plug_thread, cpu);
        if (ret) {
            smpboot_destroy_threads(plug_thread);
            free_cpumask_var(plug_thread->cpumask);
            goto out;
        }
        if (cpumask_test_cpu(cpu, cpumask))
            smpboot_unpark_thread(plug_thread, cpu);
    }
    list_add(&plug_thread->list, &hotplug_threads);
out:
    mutex_unlock(&smpboot_threads_lock);
    put_online_cpus();
    return ret;
}


遍历系统中的每个cpu,调用__smpboot_create_thread创建软中断处理线程:

static int
__smpboot_create_thread(struct smp_hotplug_thread *ht, unsigned int cpu)
{
    struct task_struct *tsk = *per_cpu_ptr(ht->store, cpu);
    struct smpboot_thread_data *td;

    td = kzalloc_node(sizeof(*td), GFP_KERNEL, cpu_to_node(cpu));
    if (!td)
        return -ENOMEM;
    td->cpu = cpu;
    td->ht = ht;

    tsk = kthread_create_on_cpu(smpboot_thread_fn, td, cpu,
                    ht->thread_comm);

    return 0;
}


构造smpboot_thread_data对象,并作为线程处理函数smpboot_thread_fn的参数。

static int smpboot_thread_fn(void *data)
{
    struct smpboot_thread_data *td = data;
    struct smp_hotplug_thread *ht = td->ht;

    while (1) {
        set_current_state(TASK_INTERRUPTIBLE);
        preempt_disable();

        /* Check for state change setup */
        switch (td->status) {
        if (!ht->thread_should_run(td->cpu)) {
            preempt_enable_no_resched();
            schedule();
        } else {
            __set_current_state(TASK_RUNNING);
            preempt_enable();
            ht->thread_fn(td->cpu);
        }
    }
}


ht即softirq_threads,判断当前是否有软中断,如果有,则执行ht->thread_fn,即run_ksoftirqd。run_ksoftirqd中调用了__do_softirq。

asmlinkage __visible void __softirq_entry __do_softirq(void)
{
    struct softirq_action *h;
    __u32 pending;
    int softirq_bit;

    pending = local_softirq_pending(); // 保存软中断掩码
    account_irq_enter_time(current);

    __local_bh_disable_ip(_RET_IP_, SOFTIRQ_OFFSET);

restart:
    /* Reset the pending bitmask before enabling irqs */
    // 清除软中断标志,必须在local_irq_enable前清除
    set_softirq_pending(0);

    // 强开中断
    local_irq_enable();

    h = softirq_vec;

    while ((softirq_bit = ffs(pending))) {
        unsigned int vec_nr;
        h += softirq_bit - 1;

        vec_nr = h - softirq_vec;

        trace_softirq_entry(vec_nr);
        h->action(h);

        h++;
        pending >>= softirq_bit;
    }

    rcu_bh_qs();
    local_irq_disable(); // 强关中断
}


softirq_vec是softirq_action类型的数组,保存了系统中注册的软中断的处理函数。pending是按位标识了softirq_vec哪个类型的软中断被触发。如果该类型的软中断被触发,调用其处理函数。软中断的处理函数是在哪注册的呢,继续往下看。

二 网络模块初始化


网络模块的初始化函数是net_dev_init。

static int __init net_dev_init(void)
{
    int i, rc = -ENOMEM;

    INIT_LIST_HEAD(&ptype_all);

    for_each_possible_cpu(i) {
        struct work_struct *flush = per_cpu_ptr(&flush_works, i);
        struct softnet_data *sd = &per_cpu(softnet_data, i);

        INIT_WORK(flush, flush_backlog);

        skb_queue_head_init(&sd->input_pkt_queue);
        skb_queue_head_init(&sd->process_queue);
        INIT_LIST_HEAD(&sd->poll_list);
        sd->output_queue_tailp = &sd->output_queue;

        sd->backlog.poll = process_backlog;
        sd->backlog.weight = weight_p;
    }

    open_softirq(NET_TX_SOFTIRQ, net_tx_action);
    open_softirq(NET_RX_SOFTIRQ, net_rx_action);

    return rc;
}


每cpu变量sd保存了发送和接收的网络数据包,其中从其他机器接收的包,放到了input_pkt_queue。
关注下process_backlog,如果驱动不支持napi,则会用到process_backlog。

调用open_softirq注册接收数据的软中段NET_RX_SOFTIRQ的处理函数net_rx_action。

void open_softirq(int nr, void (*action)(struct softirq_action *))
{
	softirq_vec[nr].action = action;
}

open_softirq的处理也相当简单,将该类型的软中断处理函数,放到softirq_vec数组的相应位置。

三 软中断处理函数net_rx_action

static __latent_entropy void net_rx_action(struct softirq_action *h)
{
    struct softnet_data *sd = this_cpu_ptr(&softnet_data);
    
    LIST_HEAD(list);
    LIST_HEAD(repoll);

    local_irq_disable();
    // 将poll_list列表中的数据,移到到list列表
    list_splice_init(&sd->poll_list, &list);
    local_irq_enable();

    for (;;) {
        struct napi_struct *n;

        // 获取到相应的napi_struct
        n = list_first_entry(&list, struct napi_struct, poll_list);
        budget -= napi_poll(n, &repoll);
    }

    local_irq_disable();
}

poll_list的数据从哪来的呢, 后面会解答这个疑惑。此次讲解的tun驱动,不支持napi,for循环中获取到的struct napi_struct n为sd->backlog,又是为什么。也会在后面解答。

static int process_backlog(struct napi_struct *napi, int quota)
{
	struct softnet_data *sd = container_of(napi, struct softnet_data, backlog);
	bool again = true;
	int work = 0;

	napi->weight = dev_rx_weight;
	while (again) {
		struct sk_buff *skb;

		while ((skb = __skb_dequeue(&sd->process_queue))) {
			rcu_read_lock();
			__netif_receive_skb(skb);
			rcu_read_unlock();
			input_queue_head_incr(sd);
			if (++work >= quota)
				return work;

		}

		local_irq_disable();
		rps_lock(sd);
		if (skb_queue_empty(&sd->input_pkt_queue)) {
			napi->state = 0;
			again = false;
		} else {
			skb_queue_splice_tail_init(&sd->input_pkt_queue,
						   &sd->process_queue);
		}
		rps_unlock(sd);
		local_irq_enable();
	}

	return work;
}

process_backlog首先处理 process_queue列表中的数据,如果process_queue列表为空,则将input_pkt_queue列表中的数据移到process_queue。调用__netif_receive_skb开始网络协议栈的处理。

四 tun驱动write逻辑

向tun驱动文件中写数据,会走到tun_chr_write_iter-->tun_get_user,在tun_get_user中,根据用户空间传入的数据,组装skb。

static ssize_t tun_get_user(struct tun_struct *tun, struct tun_file *tfile,
			    void *msg_control, struct iov_iter *from,
			    int noblock, bool more)
{
	if (frags) {
	} else if (tfile->napi_enabled) { // 如果tun支持napi
		struct sk_buff_head *queue = &tfile->sk.sk_write_queue;
		int queue_len;

		spin_lock_bh(&queue->lock);
		__skb_queue_tail(queue, skb);
		queue_len = skb_queue_len(queue);
		spin_unlock(&queue->lock);

		if (!more || queue_len > NAPI_POLL_WEIGHT)
			napi_schedule(&tfile->napi);

		local_bh_enable();
	} else if (!IS_ENABLED(CONFIG_4KSTACKS)) {
		tun_rx_batched(tun, tfile, skb, more);
	} else {
		// 将数据传输到IP层
		netif_rx_ni(skb);
	}
}

需要说明一点的是,如果tun支持napi,则走第二个分支。我们这次分析,tun不支持napi,所以走最后一个分支。

然道调用netif_rx_ni-->netif_rx_internal-->enqueue_to_backlog。

static inline void ____napi_schedule(struct softnet_data *sd,
				     struct napi_struct *napi)
{
	list_add_tail(&napi->poll_list, &sd->poll_list);
	__raise_softirq_irqoff(NET_RX_SOFTIRQ); // 标记软件中断
}


static int enqueue_to_backlog(struct sk_buff *skb, int cpu,
			      unsigned int *qtail)
{
	struct softnet_data *sd;
	unsigned int qlen;

	// 获取per cpu数据
	sd = &per_cpu(softnet_data, cpu);

	qlen = skb_queue_len(&sd->input_pkt_queue);
	// 数量小于netdev_max_backlog
	if (qlen <= netdev_max_backlog && !skb_flow_limit(skb, qlen)) { // 判断队列是否超过最大值
		// 如果队列不空,说明当前已经标记过软件中断
		if (qlen) {
enqueue:
			// 把skb添加到input_pkt_queue队列
			__skb_queue_tail(&sd->input_pkt_queue, skb);
			input_queue_tail_incr_save(sd, qtail);
			rps_unlock(sd);
			return NET_RX_SUCCESS;
		}

		if (!__test_and_set_bit(NAPI_STATE_SCHED, &sd->backlog.state)) {
			// 如果NAPI_STATE_SCHED标志没有设置,表示当前没有软中断在处理数据包
			// 将backlog添加到poll_list中,backlog也就是初始化process_backlog
			if (!rps_ipi_queued(sd))
				____napi_schedule(sd, &sd->backlog);
		}
		goto enqueue;
	}
}

在 enqueue_to_backlog,如果qlen不为0,说明当前已经标记了软中断,将skb添加到input_pkt_queue列表中。如果qlen为0,将。backlog添加到poll_list上,然道标记软中断。

数据添加到input_pkt_queue列表后,就会在NET_RX_SOFTIRQ的中断处理函数net_rx_action中处理。

最后,有必要再回顾下net_rx_action。

static __latent_entropy void net_rx_action(struct softirq_action *h)
{
    struct softnet_data *sd = this_cpu_ptr(&softnet_data);
    
    LIST_HEAD(list);
    LIST_HEAD(repoll);

    local_irq_disable();
    // sd->poll_list中连接的是sd->backlog->poll_list
    list_splice_init(&sd->poll_list, &list);
    local_irq_enable();

    for (;;) {
        struct napi_struct *n;

        // 获取到相应的napi_struct,即sd->backlog
        n = list_first_entry(&list, struct napi_struct, poll_list);
        budget -= napi_poll(n, &repoll);
    }

    local_irq_disable();
}

将sd->poll_list连接的内容,移到到list列表上,遍历list链表,调用napi_poll。

static int napi_poll(struct napi_struct *n, struct list_head *repoll)
{
	if (test_bit(NAPI_STATE_SCHED, &n->state)) {
		work = n->poll(n, weight);
	}
}

调用n->poll,即sd->backlog->poll,也就是process_backlog。 在process_backlog中开始进入协议栈的处理。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/397938.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

LSTM网络:一种强大的时序数据建模工具

❤️觉得内容不错的话&#xff0c;欢迎点赞收藏加关注&#x1f60a;&#x1f60a;&#x1f60a;&#xff0c;后续会继续输入更多优质内容❤️&#x1f449;有问题欢迎大家加关注私戳或者评论&#xff08;包括但不限于NLP算法相关&#xff0c;linux学习相关&#xff0c;读研读博…

32位Ubuntu系统安装visual studio code

Step.01 下载vscode安装包 vscode自1.36版本后停止支持32位linux系统&#xff0c;所以要使用<1.36版本。1.33版本下载地址&#xff1a; Visual Studio Code March 2019See what is new in the Visual Studio Code March 2019 Release (1.33)https://code.visualstudio.com…

nvm的使用

nvm工具 nvm是什么nvm下载与安装nvm的基本使用 1、nvm介绍 1.1、基于node的开发 在介绍nvm之前&#xff0c;先介绍下前端开发中关于node的使用。目前前端不管是基于vue或者react框架的开发&#xff0c;都是基于node环境下&#xff0c;进行包的管理与开发的。而不同项目组&a…

work-notes(23):结合typora、git、gitee实现云存储笔记完成的操作过程

时间&#xff1a;2023-03-07 文章目录摘要一、下载 typora二、安装 Git三、创建连接远程仓库四、使用 Git 上传到远程仓库五、到gitee上查看总结摘要 由于很想找一个好用&#xff0c;又有云存储的笔记软件。之前用过 有道笔记&#xff08;还行&#xff0c;量大了难找&#xff…

「MySQL进阶」为什么MySQL用B+树做索引而不用二叉查找树、平衡二叉树、B树

「MySQL进阶」为什么MySQL用B树做索引而不用二叉查找树、平衡二叉树、B树 文章目录「MySQL进阶」为什么MySQL用B树做索引而不用二叉查找树、平衡二叉树、B树一、概述二、二叉查找树三、平衡二叉树四、B树五、B树六、聚集索引和非聚集索引七、利用聚集索引和非聚集索引查找数据利…

剑指 Offer 67 把字符串转换成整数

摘要 面试题67. 把字符串转换成整数 一、字符串解析 根据题意&#xff0c;有以下四种字符需要考虑&#xff1a; 首部空格&#xff1a; 删除之即可&#xff1b;符号位&#xff1a;三种情况&#xff0c;即 , − , 无符号"&#xff1b;新建一个变量保存符号位&#xff0…

螯合剂p-SCN-Bn-TCMC,282097-63-6,双功能配体化合物应用于光学成像应用

p-SCN-Bn-TCMC 反应特点&#xff1a;p-SCN-Bn-TCMC属于双功能配体是螯合剂&#xff0c;也具有共价连接到生物靶向载体&#xff08;如抗体、肽和蛋白质&#xff09;的反应位点。应用于核医学、MRI和光学成像应用。西安凯新生物科技有限公司供应的杂环化合物及其衍生物可制作为具…

消息队列理解

为什么使用消息队列 使⽤消息队列主要是为了&#xff1a; 减少响应所需时间和削峰。降低系统耦合性&#xff08;解耦/提升系统可扩展性&#xff09;。 当我们不使⽤消息队列的时候&#xff0c;所有的⽤户的请求会直接落到服务器&#xff0c;然后通过数据库或者 缓存响应。假…

GPU是什么

近期ChatGPT十分火爆&#xff0c;随之而来的是M国开始禁售高端GPU显卡。M国想通过禁售GPU显卡的方式阻挡中国在AI领域的发展。 GPU是什么&#xff1f;GPU&#xff08;英语&#xff1a;Graphics Processing Unit&#xff0c;缩写&#xff1a;GPU&#xff09;是显卡的“大脑”&am…

给比特币“雕花” 增值还是累赘?

比特币网络也能发NFT了&#xff0c;大玩家快速入场。3月6日&#xff0c;Yuga Labs开启了TwelveFold拍卖会&#xff0c;该项目是Yuga Labs在比特币区块链网络上发行的首个NFT合集&#xff0c;内含300个艺术品。 在没有智能合约的比特币网络造NFT&#xff0c;没那么友好。但Web3…

Jmeter+Ant+Jenkins自动化搭建之报告优化

平台简介一个完整的接口自动化测试平台需要支持接口的自动执行&#xff0c;自动生成测试报告&#xff0c;以及持续集成。Jmeter支持接口的测试&#xff0c;Ant支持自动构建&#xff0c;而Jenkins支持持续集成&#xff0c;所以三者组合在一起可以构成一个功能完善的接口自动化测…

概率论与数理统计相关知识

本博客为《概率论与数理统计&#xff0d;&#xff0d;茆诗松&#xff08;第二版&#xff09;》阅读笔记&#xff0c;目的是查漏补缺前置知识数学符号连乘符号&#xff1a;&#xff1b;总和符号&#xff1a;&#xff1b;“任意”符号&#xff1a;∀&#xff1b;“存在”符号&…

IDEA项目中配置Maven镜像源(下载源)

目录前言一、IDEA中Maven的位置二、修改Maven的配置文件2.1 配置文件2.2 修改镜像源三、在IDEA中使配置文件生效四、配置文件和本地仓库迁移前言 在使用IDEA搭建项目的过程中&#xff0c;我们发现框架的jar包下载非常缓慢&#xff0c;这是因为国内访问Maven仓库速度较低&#…

构建GRE隧道打通不同云商的云主机内网

文章目录1. 环境介绍2 GRE隧道搭建2.1 华为云 GRE 隧道安装2.2 阿里云 GRE 隧道安装3. 设置安全组4. 验证GRE隧道4.1 在华为云上 ping 阿里云云主机内网IP4.2 在阿里云上 ping 华为云云主机内网IP5. 总结1. 环境介绍 华为云上有三台云主机&#xff0c;内网 CIDR 是 192.168.0.0…

TensoRT8.4_cuda11.6 sampleOnnxMNIST运行生成

1、版本信息 win10电脑环境&#xff1a; TensorRT:8.4.1.5CUDA: 11.6VS: 2019 环境安装成功后&#xff0c;使用sampleOnnxMNIST测试 2、VS2019环境配置 用vs打开sampleOnnxMNIST项目&#xff0c;位置在 D:\TensorRT-8.4.1.5\samples\sampleOnnxMNIST &#xff08;1&#xf…

创建SpringBoot工程详细步骤

new新建一个项目选择Spring Initializr, 然后配置一下地址, 可以如下图使用阿里云的,(因为国外的Spring官网可能不稳定) 下面这三个地址(选一个)能用的用上就行 https://start.spring.io(默认) https://start.springboot.io https://start.aliyun.com 然后 然后点击Finish…

HarmonyOS/OpenHarmony应用开发-dataUriUtils的使用

模块导入接口详情 dataUriUtils.getId getId(uri: string): number 获取附加到给定uri的路径组件末尾的ID。 参数&#xff1a; 名称 类型 必填 描述 uri string 是 指示要从中获取ID的uri对象。 dataUriUtils.attachId attachId(uri: string, id: number): string …

上班三年,薪资还赶不上应届程序员的一半奖金?

工资的鸿沟&#xff0c;始于社会分工的出现和细化。打工人行走职场&#xff0c;你是否也经历过&#xff1a;卷也卷不赢&#xff0c;躺也躺不平的45人生&#xff01;不同打工人分工提升了社会生产的效率&#xff0c;也加速了社会财富的积累&#xff0c;更提高了人们的收入水平。…

Zookeeper特性和节点数据类型详解

什么是ZK&#xff1f; zk,分布式应用协调框架&#xff0c;Apache Hadoop的一个子项目&#xff0c;解决分布式应用中遇到的数据管理问题。 可以理解为存储少量数据基于内存的数据库。两大核心&#xff1a;文件系统存储结构 和 监听通知机制。 文件系统存储结构 文件目录以 / …

Pytorch深度学习与入门实战

Pytorch深度学习入门与实战Pytorch简介Pytorch特点PyTorch安装环境要求PyTorch兼容的Python版本搭建开发环境下载Miniconda![下载miniconda](https://img-blog.csdnimg.cn/adace1a2f7ae476aa883b53203477c92.pnPytorch官网地址GPU版本安装检查显卡驱动依赖库安装机器学习基础与…