FreeRTOS源码分析六:vTaskDelay vs xTaskDelayUntil任务延时

系列文章目录

FreeRTOS源码分析一:task创建(RISCV架构)
FreeRTOS源码分析二:task启动(RISCV架构)
FreeRTOS源码分析三:列表数据结构
FreeRTOS源码分析四:时钟中断处理响应流程
FreeRTOS源码分析五:资源访问控制(一)


文章目录

  • 系列文章目录
  • 前言
  • 无符号溢出
  • tick 溢出的几种情况
  • vTaskDelayUntil
  • vTaskDelay
    • prvAddCurrentTaskToDelayedList
  • 附:一个数学证明
      • 1) “溢出 ⇒ wake < startTick ”
      • 2) “不溢出 ⇒ wake > startTick ”
  • 总结


前言

// vTaskDelay - 简单的相对延迟
void vTaskDelay( const TickType_t xTicksToDelay );// xTaskDelayUntil - 精确的绝对延迟
BaseType_t xTaskDelayUntil( TickType_t * const pxPreviousWakeTime,const TickType_t xTimeIncrement );

这两个 API 都可以使当前任务进入延迟,并延迟一定的时间。本文从实现层面介绍他们的区别。


无符号溢出

在 C 语言中,无符号整数unsigned intuint32_tTickType_t 等)是按模运算定义的。这一规则由 C 标准规定:

无符号整数运算的结果是对 2N2^N2N 取模的值,其中 NNN 是该类型的比特宽度。

例如:

  • uint8_t 范围是 0 ~ 255
  • 任何运算结果超出这个范围,就会自动对 256 取模
  • 这不是溢出错误,而是自然回绕(wrap-around)

当结果超过最大值时,从 0 重新开始计数:

uint8_t a = 250;
uint8_t b = a + 10; // 250 + 10 = 260
// 按 256 取模:260 - 256 = 4
// b == 4

对于 32 位 tick 计数器:

uint32_t tick = 0xFFFFFFFE; // 最大值前两步
tick++; // 0xFFFFFFFF
tick++; // 溢出后回到 0

  • FreeRTOS 的 TickType_t 通常是 无符号 32 位(范围 0 ~ 4,294,967,295)
  • 每次 tickCount++,溢出时自动从 0 重新开始
  • 不需要手动处理溢出运算,无符号加减法天生支持回绕
  • 比较逻辑需要自己设计,FreeRTOS 通过“双链表 + 溢出判断”来规避直接比较的问题

tick 溢出的几种情况

#define mainQUEUE_SEND_FREQUENCY_MS        pdMS_TO_TICKS( 1000 )/* Initialise xNextWakeTime - this only needs to be done once. */xNextWakeTime = xTaskGetTickCount();for( ; ; ){....../* Place this task in the blocked state until it is time to run again. */vTaskDelayUntil( &xNextWakeTime, mainQUEUE_SEND_FREQUENCY_MS );
}

这是 vTaskDelayUntil 的一般用法。我们一般要在某个位置获取当前 tickCount 记为 startTick,随后调用 vTaskDelayUntil 延迟当前任务从 startTick 的一段时间。

我们以三个变量代替三个时间:startTick 为 xTaskGetTickCount 调用时返回的 tickCount。nowTick 为用户调用 vTaskDelayUntil 时的 tickCount。而 tickDelay 为用户指定的延迟 tick 数。

这里会存在两个溢出的情况:

  • startTicknowTick 之间,系统 tick 已经溢出。这个表现为:nowTick < startTick
  • startTickstartTick + tickDelay 之间,系统 tick 会发生溢出。这个表现为:startTick + tickDelay < startTick
  • 那么,为什么溢出之后会表现为 startTick + tickDelay < startTick ?附中有明确的数学证明。

这里我们另外定义一个变量:wake = startTick + tickDelaywake 可能溢出或没有

那么,我们结合上面这两种情况,在先判断 从 startTicknowTick 之间,系统 tick 溢出情况之后再判断后面定时器的溢出情况,则有以下情况:

  • nowTick >= startTick 表明系统计数器未溢出,wake < startTick (wake 本身溢出了) 或 wake > nowTick >= startTick (wake 未溢出) 这两种情况都表明任务的待唤醒时间尚未抵达,任务需阻塞。若以上都不满足(等价于 wake >= startTickwake <= nowTick),说明“计划唤醒点已经过去”,不阻塞,立刻返回。
  • nowTick < startTick 表明系统计数器溢出,只有当 wake 也溢出且 wake > nowTick 时才会阻塞:条件是 (wake < startTick ) && (wake > nowTick)。直观解释:大家都已经跨到新一圈了,而且 wake 还在 nowTick 之后,才需要等;否则就是“错过了”或“在旧圈里”,不阻塞。

vTaskDelayUntil

这个时候来看 vTaskDelayUntil 的源码就非常清晰了。

xTaskDelayUntil 用于延迟任务从 *pxPreviousWakeTime 到 *pxPreviousWakeTime + xTimeIncrement 这段时间
@return pdTRUE:本次调用确实延迟了任务(进入延迟列表)。 pdFALSE:任务未延迟(唤醒点已过,立即返回)。

BaseType_t xTaskDelayUntil( TickType_t * const pxPreviousWakeTime,const TickType_t xTimeIncrement )
{TickType_t xTimeToWake;BaseType_t xAlreadyYielded, xShouldDelay = pdFALSE;/* 参数有效性检查 */configASSERT( pxPreviousWakeTime );configASSERT( ( xTimeIncrement > 0U ) );/* 挂起调度器,防止 tickCount 在计算期间发生变化 */vTaskSuspendAll();{/* 缓存当前系统 tick 计数(在本代码块中不会变化) */const TickType_t xConstTickCount = xTickCount;configASSERT( uxSchedulerSuspended == 1U );/* 计算下一次唤醒的时间点(无符号加法,可能会溢出) */xTimeToWake = *pxPreviousWakeTime + xTimeIncrement;/* ---- 溢出情况分析 ----* 如果当前 tickCount < 上一次的唤醒时间,说明 tickCount 已经溢出过。* 在这种情况下,我们只有在 “下一次唤醒时间也发生溢出且它仍大于当前 tickCount” 时才需要延迟。* 这样处理是因为这种情况等价于没有溢出。*/if( xConstTickCount < *pxPreviousWakeTime ){if( ( xTimeToWake < *pxPreviousWakeTime ) && ( xTimeToWake > xConstTickCount ) ){xShouldDelay = pdTRUE;}}else{/* ---- 未溢出情况 ----* 如果下一次唤醒时间溢出(xTimeToWake < prevWake),或* 下一次唤醒时间仍在未来(xTimeToWake > 当前 tickCount),则需要延迟。*/if( ( xTimeToWake < *pxPreviousWakeTime ) || ( xTimeToWake > xConstTickCount ) ){xShouldDelay = pdTRUE;}}/* 更新 pxPreviousWakeTime,为下一次调用做准备 */*pxPreviousWakeTime = xTimeToWake;if( xShouldDelay != pdFALSE ){/* 将当前任务加入延迟列表。* 这里需要的是“等待的时间”而不是“目标唤醒时间”,* 所以要减去当前 tickCount 得到阻塞时长。*/prvAddCurrentTaskToDelayedList( xTimeToWake - xConstTickCount, pdFALSE );}}/* 恢复调度器,如果期间有更高优先级任务就绪,这里可能会发生任务切换 */xAlreadyYielded = xTaskResumeAll();/* 若 ResumeAll 未触发切换(返回 pdFALSE),仍主动 yield 一次:*  1) 让同优先级任务获得公平的时间片/协作式让出;*  2) 在禁抢占或端口差异下,统一通过显式 yield 兑现切换。* 若此时系统中没有更高/同优先级可运行任务,此调用几乎为空操作。 */if( xAlreadyYielded == pdFALSE ){taskYIELD_WITHIN_API();}/* 返回本次调用是否真的延迟了任务 */return xShouldDelay;
}
  • 函数用于周期性任务的延时,保证任务唤醒的时间间隔固定,不受执行时间波动影响。
  • vTaskDelay() 不同,它基于绝对唤醒时间pxPreviousWakeTime)而非相对延迟。
  • 每次调用都会将 pxPreviousWakeTime 累加 xTimeIncrement,而不是更新为当前时间。这避免了周期漂移。
  • xTaskResumeAll() 会恢复调度器、处理挂起期间的就绪任务,并可能立即触发切换。
  • 若未触发切换(返回 pdFALSE),仍调用 taskYIELD_WITHIN_API()

vTaskDelay

vTaskDelay → 基于相对时间延迟任务,延迟是“从执行 vTaskDelay 开始算”,可能因执行时间累积产生周期漂移。

void vTaskDelay( const TickType_t xTicksToDelay )
{BaseType_t xAlreadyYielded = pdFALSE;/* 延时时间为 0 时,不阻塞任务,只是强制进行一次任务切换。 */if( xTicksToDelay > ( TickType_t ) 0U ){/* 挂起调度器,防止任务状态修改过程被调度打断。 */vTaskSuspendAll();{configASSERT( uxSchedulerSuspended == 1U );/* 当前任务不可能在事件列表中(它正运行着),因此直接将它* 从就绪列表移除,并加入延迟列表。 */prvAddCurrentTaskToDelayedList( xTicksToDelay, pdFALSE );}/* 恢复调度器:* - 将挂起期间的 pending ready 任务并入就绪列表。* - 处理挂起期间累积的 tick(xPendedTicks)。* - 检查是否需要立即任务切换,必要时发起切换。*/xAlreadyYielded = xTaskResumeAll();}/* 如果恢复调度器时没有触发切换(返回 pdFALSE),* 仍然调用一次 yield:*   - 兑现同优先级任务的时间片轮转。*   - 处理 xYieldPending 标志(中断中可能置位的“应切换”标志)。*/if( xAlreadyYielded == pdFALSE ){taskYIELD_WITHIN_API();}
}

prvAddCurrentTaskToDelayedList

prvAddCurrentTaskToDelayedList() 的主要功能是 把当前正在运行的任务移出就绪队列,并加入到合适的延时/挂起队列中,以实现延时阻塞功能(包括 Tick 溢出情况处理和无限期阻塞)。

static void prvAddCurrentTaskToDelayedList( TickType_t xTicksToWait,const BaseType_t xCanBlockIndefinitely )
{TickType_t xTimeToWake;const TickType_t xConstTickCount = xTickCount;  // 当前系统 Tick 值的快照List_t * const pxDelayedList = pxDelayedTaskList;           // 当前延时任务列表List_t * const pxOverflowDelayedList = pxOverflowDelayedTaskList; // Tick 溢出延时列表#if ( INCLUDE_xTaskAbortDelay == 1 ){/* 进入延时队列前,先清除任务的 ucDelayAborted 标志位* 用于检测任务离开阻塞态时,是否是被中止延时(Abort Delay)唤醒的 */pxCurrentTCB->ucDelayAborted = ( uint8_t ) pdFALSE;}#endif/* 1. 从就绪队列移除当前任务*    因为任务状态链表项在就绪队列和阻塞队列中是同一个 list item,*    所以必须先从就绪队列删除才能放到阻塞队列。 */if( uxListRemove( &( pxCurrentTCB->xStateListItem ) ) == ( UBaseType_t ) 0 ){/* 如果该优先级的就绪任务已经被清空,就更新优先级位图,* 表示该优先级上已无就绪任务 */portRESET_READY_PRIORITY( pxCurrentTCB->uxPriority, uxTopReadyPriority );}#if ( INCLUDE_vTaskSuspend == 1 ){/* 2. 判断是否是无限期阻塞(portMAX_DELAY 且允许无限阻塞) */if( ( xTicksToWait == portMAX_DELAY ) && ( xCanBlockIndefinitely != pdFALSE ) ){/* 直接加入挂起任务列表(xSuspendedTaskList),* 保证它不会因为时间到而被唤醒,必须手动唤醒。 */listINSERT_END( &xSuspendedTaskList, &( pxCurrentTCB->xStateListItem ) );}else{/* 3. 计算唤醒时间(可能会发生无符号溢出,但内核会处理) */xTimeToWake = xConstTickCount + xTicksToWait;/* 设置链表项的值为唤醒时间(用于延时队列的排序) */listSET_LIST_ITEM_VALUE( &( pxCurrentTCB->xStateListItem ), xTimeToWake );if( xTimeToWake < xConstTickCount ){/* 4. Tick 计数溢出* 如果唤醒时间比当前 Tick 值小,说明加法结果溢出,* 则把任务放入溢出延时队列。 */vListInsert( pxOverflowDelayedList, &( pxCurrentTCB->xStateListItem ) );}else{/* 5. Tick 未溢出* 直接加入当前延时队列(pxDelayedList),* 按唤醒时间升序插入。 */vListInsert( pxDelayedList, &( pxCurrentTCB->xStateListItem ) );/* 如果该任务的唤醒时间比当前系统记录的最早唤醒时间还早,* 更新 xNextTaskUnblockTime(优化 Tick 处理,减少无意义扫描)。 */if( xTimeToWake < xNextTaskUnblockTime ){xNextTaskUnblockTime = xTimeToWake;}}}}
}

这里我们回顾:FreeRTOS源码分析四:时钟中断处理响应流程 中提到,时钟中断会调用函数 xTaskIncrementTick,它会对系统 tick 加1,当检测到 tick 溢出时,会交换两个延时队列。如下所示:

BaseType_t xTaskIncrementTick( void )
{TCB_t * pxTCB;                          // 任务控制块指针TickType_t xItemValue;                  // 延迟列表项的值(唤醒时间)BaseType_t xSwitchRequired = pdFALSE;   // 是否需要任务切换标志/* 时钟递增应该在每个内核定时器事件上发生。* 如果调度器被挂起,则递增待处理的时钟计数。 */if( uxSchedulerSuspended == ( UBaseType_t ) 0U ){// === 调度器未被挂起的情况 ===/* 小优化:在此代码块中时钟计数不会改变 */const TickType_t xConstTickCount = xTickCount + ( TickType_t ) 1;/* 递增RTOS时钟,如果溢出到0则切换延迟和溢出延迟列表 */xTickCount = xConstTickCount;// 处理时钟计数器溢出的情况(从最大值回绕到0)if( xConstTickCount == ( TickType_t ) 0U ){taskSWITCH_DELAYED_LISTS();  // 切换延迟任务列表}....................................
}

而宏 taskSWITCH_DELAYED_LISTS 则会切换两个任务列表的角色:

#define taskSWITCH_DELAYED_LISTS()                                                \do {                                                                          \List_t * pxTemp;                                                          \\/* The delayed tasks list should be empty when the lists are switched. */ \configASSERT( ( listLIST_IS_EMPTY( pxDelayedTaskList ) ) );               \\pxTemp = pxDelayedTaskList;                                               \pxDelayedTaskList = pxOverflowDelayedTaskList;                            \pxOverflowDelayedTaskList = pxTemp;                                       \xNumOfOverflows = ( BaseType_t ) ( xNumOfOverflows + 1 );                 \prvResetNextTaskUnblockTime();                                            \} while( 0 )

恰与这里两个延时队列想对应,当用户延时会发生在 tick 溢出之后时,则加入溢出队列。而当溢出发生,则溢出队列就变为当前的延时队列。

这样上一轮“溢出延时链表”里的任务会自然地在新的 tick 空间里按照时间顺序等待唤醒。xNextTaskUnblockTime 被重置,用于后续快速判断“下一个到期点”。

附:一个数学证明

因为是无符号模 2N2^N2N 加法,而且 tickDelay > 0(源码里有 configASSERT( xTimeIncrement > 0U ))。设:

  • startTick ∈ [0, 2^N-1]
  • tickDelay ∈ [1, 2^N-1]
  • 真实和 S = startTick + tickDelay
  • 存回寄存器/变量的结果 wake = S mod 2^N

结论: 溢出当且仅当 wake < startTick 。证明分两步:

1) “溢出 ⇒ wake < startTick ”

若溢出,则 S ≥ 2^N,所以

wake=S−2N=startTick+tickDelay−2N=startTick−(2N−tickDelay).wake = S - 2^N = startTick + tickDelay - 2^N = startTick - (2^N - tickDelay). wake=S2N=startTick+tickDelay2N=startTick(2NtickDelay).

因为 tickDelay ≥ 1,故 (2^N - tickDelay) ≤ 2^N - 1 且至少为 1,于是

wake=startTick−(2N−tickDelay)⏟≥1≤startTick−1<startTick.wake = startTick - \underbrace{(2^N - tickDelay)}_{\ge 1} \le startTick - 1 \;<\; startTick . wake=startTick1(2NtickDelay)startTick1<startTick.

所以一旦溢出,wake 一定小于 startTick

2) “不溢出 ⇒ wake > startTick ”

若不溢出,则 S < 2^Nwake = S = startTick + tickDelay。又因 tickDelay ≥ 1

wake=startTick+tickDelay≥startTick+1>startTick.wake = startTick + tickDelay \ge startTick + 1 > startTick . wake=startTick+tickDelaystartTick+1>startTick.

(只有当 tickDelay = 0 才可能 wake = startTick ,但这被断言禁止了。)

因此,判断溢出最简单的办法就是:做完 wake = startTick + tickDelay 的无符号加法后,看 wake < startTick 是否成立。成立就说明发生了进位丢弃。


总结

完结撒花!!!

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

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

相关文章

Linux学习-应用软件编程(fread/fwrite,流定义相关接口)

freadsize_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream); 功能按块从文件读取数据&#xff0c;从文件中读 nmemb 个、每个 size 字节的元素&#xff0c;存入 ptr 指向的内存。参数- ptr &#xff1a;存储读取数据的内存首地址&#xff08;需提前分配足够…

IP分片(IP Fragmentation)

目录 一、核心概念:MTU与分片的必要性 二、IP分片的关键字段(IPv4头部) 三、分片与重组流程 1. 分片过程(发送端或中间路由器) 2. 重组过程(接收端) 四、IPv4与IPv6分片的差异 五、分片的潜在问题与风险 六、总结 一、传输效率降低,带宽开销增加 二、可靠性降低,数据丢…

高并发内存池 内存释放回收(6)

文章目录前言一、threadcache回收内存二、centralcache回收内存三、pagecache回收内存总结前言 Hello&#xff0c;我们继续乘胜追击   本篇难度较大&#xff0c;大家要好好学一下 一、threadcache回收内存 当某个线程申请的对象不用了&#xff0c;可以将其释放给 thread cac…

2438. 二的幂数组中查询范围内的乘积

2438. 二的幂数组中查询范围内的乘积 初始理解题目 首先&#xff0c;我们需要清楚地理解题目在说什么。题目给出一个正整数 n&#xff0c;要求我们构造一个数组 powers&#xff0c;这个数组满足以下条件&#xff1a; 元素性质​&#xff1a;数组中的每个元素都是 2 的幂。即…

【PyTorch学习笔记 - 01】 Tensors(张量)

最近项目需要优化一下目标检测网络&#xff0c;在这个过程中发现还是得增加对框架底层的掌握才可行。于是准备对pytorch的一些基本概念做一些再理解。参考PyTorch的wiki&#xff0c;对自己的学习过程做个记录。 Tensors 是一种特殊的数据结构&#xff0c;与数组和矩阵非常相似…

【C/C++】(struct test*)0->b 讲解

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 一、什么是结构体成员的偏移量&#xff1f; 二、为什么需要计算偏移量&#xff1f; 三、如何计算偏移量&#xff1f; 四、总结 一、什么是结构体成员的偏移量&#…

使用Pytest进行接口自动化测试(三)

&#xff08;一&#xff09;YAML 之前在项目中&#xff0c;我们也是用过YAML来做配置文件&#xff0c;他用于以人类可读的形式存储信息&#xff0c; 特点: 一种简易的可读语言&#xff0c;用于人和计算机交换数据 通常用来存储配置信息 跟python类似&…

算法训练营day46 647. 回文子串、516.最长回文子序列、动态规划总结篇

今天是动态规划的最后一篇内容了&#xff0c;本篇主要是针对回文字符串这种“与众不同”的递推规律来进行讲解 647. 回文子串 统计并返回这个字符串中 回文子串 的数目 暴力解法 两层for循环&#xff0c;遍历区间起始位置和终止位置&#xff0c;然后还需要一层遍历判断这个区…

Qt界面优化

1.QSS在网页前端开发领域中&#xff0c;CSS 是一个至关重要的部分&#xff0c;描述了一个网页的 “样式”&#xff0c;从而起到对网页美化的作用。所谓样式&#xff0c;包括不限于大小、位置、颜色、背景、间距、字体等等。网页开发作为 GUI 的典型代表&#xff0c;也对于其他客…

week1+2+3

408 计组 1.基本组成2.数据的表示和运算定点数&#xff1a;把数字分为定点整数和定点小数分开存储 浮点数&#xff1a;用科学计数法存储 原码 -全部取反-> 反码 反码 1->补码 补码 -符号位取反->移码带余除法&#xff1a;设x,m∈Z&#xff0c;m>0则存在唯一的整数q…

java8中javafx包缺少报错

今天拉取一个jdk1.8的项目里面有一个代码用到了javafx&#xff0c;这个我记得是jdk中的包&#xff0c;正常不应该报错的。然后发现jdk中还真没有&#xff0c;查了一下是因为版本问题。 Java 8 及之前&#xff1a;Oracle JDK 自带 JavaFX&#xff0c;OpenJDK 通常不包含Java 9 …

day072-代码检查工具-Sonar与maven私服-Nexus

文章目录0. 老男孩思想-选对池塘钓美人鱼1. 代码回滚方案2. SonarQube2.1 代码检查工具2.2 部署sonarqube2.2.1 软件要求2.2.2 安装软件2.2.3 启动sonar2.2.4 部署插件2.3 sonar检查java代码2.3.1 创建sona项目2.3.2 分析java代码2.3.3 Jenkins结合sonar检查代码2.4 sonar检查非…

【前端基础】15、列表元素、表格元素、表单元素(注:极其粗略的记载。)

一、列表元素 1、什么是列表元素2、有序列表&#xff08;ol、li&#xff09; ol有序列表 直接子元素只能是li。 li列表中的每一项。3、无序列表&#xff08;ul、li&#xff09; ol无序列表 直接子元素只能是li。 li列表中的每一项。4、定义列表&#xff08;dl、dt、dd&#xff…

IRFBG30PBF Vishay威世MOSFET场效应管

IRFBG30PBF Vishay威世&#xff1a;超快MOSFET 场效应管一、产品定位IRFBG30PBF 是Vishay威世推出的600V/30A N沟道功率MOSFET&#xff0c;采用第五代TrenchFET技术&#xff0c;专为开关电源、电机驱动、新能源逆变器等高功率场景设计。以85mΩ超低导通电阻和超快反向恢复&…

【07-AGI的讨论】

AI ANI&#xff1a;artificial narrow intelligence; 如 智能音箱&#xff1b;自动驾驶汽车&#xff0c;网络搜索&#xff0c;其他用于专业特定事项的工具&#xff1b; AGI&#xff1a;artificial general intelligence; building AI systems that could do anything a typical…

[激光原理与应用-225]:机械 - 3D图与2D图各自的作用

在机械设计与加工领域&#xff0c;3D图和2D图是两种核心的工程表达方式&#xff0c;它们在产品设计、制造、装配及维护等环节中扮演不同角色&#xff0c;具有互补性。以下是它们各自的作用及具体应用场景的详细解析&#xff1a;一、3D图的作用1. 直观展示产品全貌三维可视化&am…

【从零开始java学习|第一篇】java中的名词概念(JDK、JVM、JRE等等)

目录 一、核心运行环境三要素&#xff08;JVM/JRE/JDK&#xff09; 二、常用开发指令&#xff08;JDK 自带工具&#xff09; 三、一些其他概念 四、总结核心逻辑链 要入门 Java&#xff0c;理解核心概念之间的关系是基础。以下是 Java 中最核心的基础概念、工具及相关名词的…

UVa12345 Dynamic len(set(a[L:R]))

[TOC](UVa12345 Dynamic len(set(a[L:R]))) 题目链接 UVA - 12345 Dynamic len(set(a[L:R])) 题意 有编号从 0 到 n−1 的 n 个数&#xff0c;有两种操作&#xff1a; Q L R 询问编号 L 到编号 R−1 的数中有多少个不同的数字。M X Y 将编号为 X 的数字改为 Y。 你的任务就是…

[Ubuntu] VNC连接Linux云服务器 | 实现GNOME图形化

将桌面环境修改为 GNOME 并通过 VNC 远程访问的步骤 & TightVNC 的安装与配置说明&#xff1a;1. 安装 GNOME 桌面环境 sudo apt update sudo apt install ubuntu-gnome-desktop -y2. 安装 TightVNC 服务器 sudo apt install tightvncserver -y3. 初始化 VNC Server 并设置…

进程、网络通信方法

一、进程间通信(IPC)方法 适用于同一台主机上的进程间数据交换。 管道(Pipe) 匿名管道:单向通信,仅用于父子进程。 命名管道(FIFO):通过文件系统路径访问,支持无亲缘关系进程。 消息队列(Message Queue) 结构化消息(类型+数据),按类型读取,支持异步通信。…