工作笔记-----FreeRTOS中的lwIP网络任务为什么会让出CPU

工作笔记-----FreeRTOS中的lwIP网络任务为什么会让出CPU

@@ Author: 明月清了个风
@@ Date: 2025.7.30
@@ Ps:最近接触了在FreeRTOS中使用lwIP实现的网络任务,但是在看项目代码的过程中出现了一些疑问——网络任务的优先级为所有任务中最高的,并且任务框架中没有延时,为什么会让出CPU,记录一下这个寻找答案的过程(我也不知道对不对)。

基于FreeRTOS的lwIP任务框架

当使用了操作系统时,就可以使用lwIP的NETCONN接口,并且lwIP也提供了统一的ethernetif.c供用户使用,使用该接口的网络任务的框架如下(删除了项目中的处理逻辑,只留下了基本的框架):

static void EthThread(void *arg)
{struct netconn *conn;err_t err, accept_err;struct netbuf *buf;void *data;u16_t len;LWIP_UNUSED_ARG(arg);/* Create a new connection identifier. */conn = netconn_new(NETCONN_TCP);if (conn != NULL){  /* Bind connection to well known port number 7. */err = netconn_bind(conn, NULL, PORT);if (err == ERR_OK){/* Tell connection to go into listening mode. */netconn_listen(conn);while (1) {/* Grab new connection. */accept_err = netconn_accept(conn, &newconn);/* Process the new connection. */if (accept_err == ERR_OK) {while (netconn_recv(newconn, &buf) == ERR_OK) {do {netbuf_data(buf, &data, &len);// 数据处理逻辑} while (netbuf_next(buf) >= 0);netbuf_delete(buf);}netconn_close(newconn);netconn_delete(newconn);}}}else{netconn_delete(newconn);}} 
}

当然在这之前需要初始化lwIP,这里的代码应该都是差不多的

uint8_t IP_ADDRESS[4];
uint8_t NETMASK_ADDRESS[4];
uint8_t GATEWAY_ADDRESS[4];struct netif gnetif;  	// lwIP提供的网络接口结构体
ip4_addr_t ipaddr;  	// IP地址
ip4_addr_t netmask; 	// 子网掩码
ip4_addr_t gw;			// 网关void lwip_init(void)
{// 需要对IP_ADDRESS,NETMASK_ADDRESS,GATEWAY_ADDRESS内容进行设置,举例如下IP_ADDRESS[0] = 192;IP_ADDRESS[1] = 168;IP_ADDRESS[2] = 110;IP_ADDRESS[3] = 110;NETMASK_ADDRESS[0] = 255;NETMASK_ADDRESS[1] = 255;NETMASK_ADDRESS[2] = 255;NETMASK_ADDRESS[3] = 0;GATEWAY_ADDRESS[0] = 192;GATEWAY_ADDRESS[1] = 168;GATEWAY_ADDRESS[2] = 110;GATEWAY_ADDRESS[3] = 1;tcpip_init( NULL, NULL); // 这里会创建tcpip线程以及邮箱IP4_ADDR(&ipaddr, IP_ADDRESS[0], IP_ADDRESS[1], IP_ADDRESS[2], IP_ADDRESS[3]);IP4_ADDR(&netmask, NETMASK_ADDRESS[0], NETMASK_ADDRESS[1] , NETMASK_ADDRESS[2], NETMASK_ADDRESS[3]);IP4_ADDR(&gw, GATEWAY_ADDRESS[0], GATEWAY_ADDRESS[1], GATEWAY_ADDRESS[2], GATEWAY_ADDRESS[3]);// 初始化网口,这里传入了ethernetif_init作为初始化函数netif_add(&gnetif, &ipaddr, &netmask, &gw, NULL, &ethernetif_init, &tcpip_input);netif_set_default(&gnetif);   //设置netif为默认网口if (netif_is_link_up(&gnetif)){netif_set_up(&gnetif);      //打开netif网口}else{netif_set_down(&gnetif);}
}

其中netif_add传入了ethernetif_init函数作为网口初始化函数,如果这里传入NULL就会报错

在这里插入图片描述

这个函数中进行了netif的部分结构体成员初始化,并在最后调用了low_level_init(netif);进行初始化

为什么会让出CPU

可以看到在任务函数中没有会导致任务阻塞让出CPU的函数或操作,比如等待信号量或者等待队列消息,那也意味着如果该任务优先级最高,那么就会永远占用。

那么为了看在哪里会阻塞这个任务,就要在while循环中去寻找

首先是netconn_accept()函数,这个函数用于获得一个新的连接,这个函数原型挺长的就不放出来了,关注中间的一段代码:

if (netconn_is_nonblocking(conn)) {if (sys_arch_mbox_tryfetch(&conn->acceptmbox, &accept_ptr) == SYS_ARCH_TIMEOUT) {API_MSG_VAR_FREE_ACCEPT(msg);NETCONN_MBOX_WAITING_DEC(conn);return ERR_WOULDBLOCK;}} else {
#if LWIP_SO_RCVTIMEOif (sys_arch_mbox_fetch(&conn->acceptmbox, &accept_ptr, conn->recv_timeout) == SYS_ARCH_TIMEOUT) {API_MSG_VAR_FREE_ACCEPT(msg);NETCONN_MBOX_WAITING_DEC(conn);return ERR_TIMEOUT;}
#elsesys_arch_mbox_fetch(&conn->acceptmbox, &accept_ptr, 0);
#endif /* LWIP_SO_RCVTIMEO*/}
分支1-----非阻塞

可以看到这个if调用了函数netconn_is_nonblocking(conn),进一步去看,这个函数为一个宏定义,原型如下:

/** Get the blocking status of netconn calls (@todo: write/send is missing) */
#define netconn_is_nonblocking(conn)        (((conn)->flags & NETCONN_FLAG_NON_BLOCKING) != 0)

注释的意思是获取当前连接的阻塞状态,通过判断connflags成员的NETCONN_FLAG_NON_BLOCKING位是否置位,进一步去看这个宏定义的注释就能发现意思是:这个连接是否应该避免阻塞?

/** Should this netconn avoid blocking? */
#define NETCONN_FLAG_NON_BLOCKING             0x02

也就是说,如果置位说明该连接在当前操作下应该避免阻塞那么上面的if (netconn_is_nonblocking(conn))为真,进入该判断内部逻辑,也就是调用sys_arch_mbox_tryfetch(&conn->acceptmbox, &accept_ptr),该函数原型如下

u32_t
sys_arch_mbox_tryfetch(sys_mbox_t *mbox, void **msg)
{BaseType_t ret;void *msg_dummy;LWIP_ASSERT("mbox != NULL", mbox != NULL);LWIP_ASSERT("mbox->mbx != NULL", mbox->mbx != NULL);if (!msg) {msg = &msg_dummy;}ret = xQueueReceive(mbox->mbx, &(*msg), 0);if (ret == errQUEUE_EMPTY) {*msg = NULL;return SYS_MBOX_EMPTY;}LWIP_ASSERT("mbox fetch failed", ret == pdTRUE);/* Old versions of lwIP required us to return the time waited.This is not the case any more. Just returning != SYS_ARCH_TIMEOUThere is enough. */return 1;
}

根据这个函数的命名和逻辑可以看出,该用于尝试去conn->acceptmbox接收消息ret = xQueueReceive(mbox->mbx, &(*msg), 0);,并且该操作是非阻塞的,最后一个接收延时参数为0,符合之前进入该函数体的判断条件.函数下面的注释含义如下:

旧版本的lwIP要求该函数返回等待的时间,但现在不是了,只要返回的数值不等于SYS_ARCH_TIMEOUT就行了

进一步地,我们看这个宏定义的值:

/** Return code for timeouts from sys_arch_mbox_fetch and sys_arch_sem_wait */
#define SYS_ARCH_TIMEOUT 0xffffffffUL
  • 返回值为1,符合注释,那么就会判断失败并继续执行netconn_accept()函数下面的部分,

  • 若在上面就返回了,也就是if (ret == errQUEUE_EMPTY)判断成功,这意味着xQueueReceive()函数接收消息失败(这是FreeRtos中的队列操作函数,接收成功返回1,接收失败返回0),将传进来保存接收消息的指针赋值为*msg = NULL,并且返回SYS_MBOX_EMPTY,该宏定义如下

    /** sys_mbox_tryfetch() returns SYS_MBOX_EMPTY if appropriate.* For now we use the same magic value, but we allow this to change in future.*/
    #define SYS_MBOX_EMPTY SYS_ARCH_TIMEOUT
    

    那么从这返回就会继续执行最上面的这段代码

    if (sys_arch_mbox_tryfetch(&conn->acceptmbox, &accept_ptr) == SYS_ARCH_TIMEOUT) {API_MSG_VAR_FREE_ACCEPT(msg);NETCONN_MBOX_WAITING_DEC(conn);return ERR_WOULDBLOCK;}
    

    其中API_MSG_VAR_FREE_ACCEPT(msg);是一个经过层层宏定义的函数,原型为memp_free,应该是和内存管理相关的,我也没细看了这边。

然后就会执行NETCONN_MBOX_WAITING_DEC(conn);函数,这和上面返回1之后的结果是一样的,最后都会到这个函数,这里我也没看了,因为已经知道为什么阻塞了,上面都是非阻塞的过程。那么另一边就是为什么会阻塞了,也就是这一段代码

#if LWIP_SO_RCVTIMEOif (sys_arch_mbox_fetch(&conn->acceptmbox, &accept_ptr, conn->recv_timeout) == SYS_ARCH_TIMEOUT) {API_MSG_VAR_FREE_ACCEPT(msg);NETCONN_MBOX_WAITING_DEC(conn);return ERR_TIMEOUT;}
#elsesys_arch_mbox_fetch(&conn->acceptmbox, &accept_ptr, 0);
#endif /* LWIP_SO_RCVTIMEO*/}

该宏定义原型如下:

#define LWIP_SO_RCVTIMEO        1    /* set to 1 to enable receive timeout for sockets/netconns and SO_RCVTIMEO processing */

通过这个宏定义的注释可以看出,只要开启这个宏定义就会允许sockets/netconns阻塞延时。

那么现在看另一分支的判断条件,也就是会调用sys_arch_mbox_fetch()函数,该函数原型如下,可以发现,如果conn->recv_timeout为0,那么该接收过程就会一直阻塞直到收到消息,如果不为0,那么就会按照设置的值进行对应的阻塞。


u32_t
sys_arch_mbox_fetch(sys_mbox_t *mbox, void **msg, u32_t timeout_ms)
{BaseType_t ret;void *msg_dummy;LWIP_ASSERT("mbox != NULL", mbox != NULL);LWIP_ASSERT("mbox->mbx != NULL", mbox->mbx != NULL);if (!msg) {msg = &msg_dummy;}if (!timeout_ms) {/* wait infinite */ret = xQueueReceive(mbox->mbx, &(*msg), portMAX_DELAY);LWIP_ASSERT("mbox fetch failed", ret == pdTRUE);} else {TickType_t timeout_ticks = timeout_ms / portTICK_RATE_MS;ret = xQueueReceive(mbox->mbx, &(*msg), timeout_ticks);if (ret == errQUEUE_EMPTY) {/* timed out */*msg = NULL;return SYS_ARCH_TIMEOUT;}LWIP_ASSERT("mbox fetch failed", ret == pdTRUE);}/* Old versions of lwIP required us to return the time waited.This is not the case any more. Just returning != SYS_ARCH_TIMEOUThere is enough. */return 1;
}

至此,就可以知道为什么任务函数中没有任何显式阻塞过程却能让出CPU了,关键在于LWIP_SO_RCVTIMEO宏定义,另外netconn->recv_timeout结构体成员在初始化时,也就是调用函数netconn_new()时会被自动初始化为0,因此只要开启对应宏定义就能让该任务阻塞了,当然阻塞的地方并不止这一个地方,while循环中和netconn有关的API应该都有对应的阻塞判断。

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

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

相关文章

在 CentOS 系统上安装 Docker

在 CentOS 系统上安装 Docker,可按以下步骤操作:一、卸载旧版本(如存在)bashsudo yum remove docker \docker-client \docker-client-latest \docker-common \docker-latest \docker-latest-logrotate \docker-logrotate \docker-…

【CVPR2025】FlowRAM:用区域感知与流匹配加速高精度机器人操作策略学习

文章目录FlowRAM:用区域感知与流匹配加速高精度机器人操作策略学习一、问题出在哪里?方法部分:从结构到机制,详解 FlowRAM 的内部设计逻辑1. 动态半径调度器:自适应注意力机制在 3D 感知中的实现2. 多模态编码器与序列…

图片查重从设计到实现(5)Milvus可视化工具

要通过网页(Web)访问和管理 Milvus 向量数据库,可以使用官方提供的 Milvus Web UI 工具,这是一款可视化管理界面,支持查看集合、向量数据、执行基本操作等功能。以下是具体的部署和访问方法: 一、部署 Milv…

Linux-awk与sed

文章目录一、AWK1. awk 是什么?2. awk 的基础语法2.1 选项2.2 模式2.3 动作3. awk 的内置变量4. 典型应用场景及示例4.1 打印特定列4.2 条件筛选4.3 使用正则表达式4.4 统计行数4.5 字段操作4.6 使用内置函数4.7 多文件处理4.8 使用自定义变量5. 高级应用&#xff1…

文件加密工具(勒索病毒加密方式)

语言:C# WPF功能:文件加/解密本程序不提供下载,该程序新手操作不当,可能会导致文件加密后无法解密问题,解密需要独立私钥private.key文件支持,没有私钥加密文件是无法被解密的。更新:2025年7月3…

IOC实现原理源码解析

Spring三级缓存流程图singletonObjects(一级缓存):缓存经过了完整生命周期的Bean;arlySingletonobjects(二级缓存):缓存未经过完整生命周期的Bean,如果某个Bean出现了循环依赖&#…

笔记本电脑磁盘维护指南:WIN11系统磁盘维护完全手册

1. 引言 在当今数字化时代,笔记本电脑已经成为我们工作、学习和娱乐不可或缺的重要工具。随着Windows 11操作系统的普及和应用,用户对于系统性能和稳定性的要求越来越高。然而,许多用户往往忽视了一个至关重要的方面——磁盘维护。磁盘作为计算机系统中负责数据存储和读取的…

李宏毅2025《机器学习》-第九讲:大型语言模型评测的困境与“古德哈特定律”**

摘要: 随着大型语言模型(LLM)的推理能力日益增强,如何公平、准确地评测其“智力”水平,成了一个极其棘手的问题。本文基于李宏毅教授的最新课程,深入探讨了当前LLM评测面临的困境。文章首先揭示了标准数学和…

Spring Boot集成Chaos Monkey:构建高韧性系统的故障注入实战指南

Spring Boot集成Chaos Monkey:构建高韧性系统的故障注入实战指南一、Chaos Engineering核心原理1.1 混沌工程价值矩阵1.2 Chaos Monkey核心攻击类型二、Spring Boot集成Chaos Monkey2.1 基础集成配置依赖引入配置文件 - application.yml2.2 高级攻击策略配置自定义攻…

AtCoder Beginner Contest 416(ABCDE)

A - Vacation Validation 翻译&#xff1a; 给你一个长度为 N 的字符串 S&#xff0c;它由 o 和 x 以及整数 L 和 R 组成。 请判断 S 中从第 L 个字符到第 R 个字符的所有字符是否都是 o。 思路&#xff1a; &#xff08;模拟&#xff09; 实现&#xff1a; #include<bits…

【AlphaFold3】网络架构篇(2)|Input Embedding 对输入进行特征嵌入

博主简介&#xff1a;努力学习的22级计算机科学与技术本科生一枚&#x1f338;博主主页&#xff1a; Yaoyao2024往期回顾&#xff1a;【AlphaFold3】网络架构篇&#xff08;1&#xff09;|概览预测算法每日一言&#x1f33c;: 去留无意&#xff0c;闲看庭前花开花落&#xff1b…

秋招Day20 - 微服务 - 概念

什么是微服务&#xff1f;将一个大型的单体项目分割成一个个可以独立开发和部署的小服务&#xff0c;服务之间松耦合&#xff0c;可以通过轻量级通信机制&#xff08;比如HTTP&#xff09;相互协作微服务带来了哪些挑战&#xff1f; 介绍一下一下Dubbo&#xff1f;Dubbo是一个高…

PyTorch 生态四件套:从图片、视频到文本、语音的“开箱即用”实践笔记

写在前面 当我们谈论 PyTorch 时&#xff0c;我们首先想到的是 torch.Tensor、nn.Module 和强大的自动求导系统。但 PyTorch 的力量远不止于此。为了让开发者能更高效地处理图像、文本、音频、视频等真实世界的复杂数据&#xff0c;PyTorch 建立了一个强大的官方生态系统。本文…

2023 年 NOI 最后一题题解

问题描述2023 年 NOI 最后一题是一道融合图论与动态规划的综合优化问题&#xff0c;聚焦于带时间窗约束的多路径规划。题目具体要求如下&#xff1a;给定一个有向图&#xff0c;其中节点代表城市&#xff0c;边代表交通路线。每条边具有三个属性&#xff1a;行驶时间、基础费用…

Android补全计划 TextView设置文字不同字体和颜色

1 富文本 1 java中动态加载文本 颜色 String strMsg "今天<font color\"#00ff00\">天气不错</font>"; tv_msg.setText(Html.fromHtml(strMsg));字体和颜色 String str2 "今天<font color\"#00ff00\"><big>天气不…

C语言:详解单链表与例题

C语言&#xff1a;详解单链表与例题 1.单链表的实现 2.例题&#xff1a;移除链表元素 1.单链表的实现 链表根据带头或不带头、单向或双向、循环或不循环分类为8种&#xff0c;最常用的是单链表和双向链表&#xff0c;单链表是 不带头单向不循环 链表。 链表由节点组成&#xff…

从0开始学习R语言--Day62--RE插补

对于会有多次测量值的数据&#xff0c;用普通的回归去插补&#xff0c;往往会忽略掉数据个体本身的特点&#xff0c;毕竟多次的测量值其实就代表了数据个体的不稳定性&#xff0c;存在额外的干扰。而RE的插补原理是结合个体本身的随机效应和群体的固体效应再加上截距进行插补的…

RESTful API开发指南:使用Spring Boot构建企业级接口

目录 1. 引言2. RESTful API基础概念3. Spring Boot环境搭建4. 项目结构设计5. 核心组件开发6. 数据库集成7. 安全认证8. 异常处理9. API文档生成10. 测试策略11. 部署与监控12. 最佳实践 1. 引言 在现代软件开发中&#xff0c;RESTful API已成为构建分布式系统和微服务架构…

从 Print 到 Debug:用 PyCharm 掌控复杂程序的调试之道

目录摘要调试工具窗口会话工具栏调试工具栏单步工具栏调试器选项卡调用栈帧&#xff08;Frames&#xff09;变量&#xff08;Variables&#xff09;&#x1f4a1; 表达式求值区域&#xff08;Evaluate expression field&#xff09;&#x1f5b1;️ 右键菜单&#xff08;Contex…

用于前列腺活检分级的分层视觉 Transformer:迈向弥合泛化差距|文献速递-医学影像算法文献分享

Title题目Hierarchical Vision Transformers for prostate biopsy grading: Towardsbridging the generalization gap用于前列腺活检分级的分层视觉 Transformer&#xff1a;迈向弥合泛化差距01文献速递介绍前列腺癌是全球男性中第二常见的确诊癌症&#xff0c;也是第五大致命癌…