【RTOS学习】软件定时器 | 中断处理

news2024/12/22 21:47:01

🐱作者:一只大喵咪1201
🐱专栏:《RTOS学习》
🔥格言:你只管努力,剩下的交给时间!
图

软件定时器 | 中断处理

  • 🏀软件定时器
    • ⚽守护任务
      • 守护任务的调度
    • ⚽使用软件定时器的函数
    • ⚽基本使用
  • 🏀中断处理
    • ⚽两套API函数
    • ⚽对比两套API函数
  • 🏀总结

🏀软件定时器

图
如上图,我们在手机上添加闹钟时,需要指定时间、指定类型(一次性的,还是周期性的)、指定做什么事;还有一些过时的、不再使用的闹钟。

软件定时器和手机闹钟是类似的:

  • 指定时间:启动定时器和运行回调函数,两者的间隔被称为定时器的周期(period)。

  • 指定类型,定时器有两种类型:

  • 一次性(One-shot timers):这类定时器启动后,它的回调函数只会被调用一次; 可以手工再次启动它,但是不会自动启动它。

  • 自动加载定时器(Auto-reload timers ):这类定时器启动后,时间到之后它会自动启动它; 这使得回调函数被周期性地调用。

  • 定要做什么事,就是指定回调函数。

实际的闹钟分为:有效、无效两类。软件定时器也是类似的,它由两种状态:

  • 运行(Running、Active):运行态的定时器,当指定时间到达之后,它的回调函数会被调用 。
  • 冬眠(Dormant):冬眠态的定时器还可以通过句柄来访问它(它仍然存在),但是它不再运行,它的回调函数不会被调用。

图
如上图所示定时器定时器的运行情况:

  • Timer1:它是一次性的定时器,在 t1 启动,周期是 6 个 Tick。经过 6 个tick 后,在 t7 执行回调函数。它的回调函数只会被执行一次,然后该定时器进入冬眠状态。
  • Timer2:它是自动加载的定时器,在 t1 启动,周期是 5 个 Tick。每经过 5个 tick 它的回调函数都被执行,在 t6、t11、t16 都会执行。

⚽守护任务

执行回调函数:

FreeRTOS 中有一个 Tick 中断,软件定时器基于 Tick 来运行。在哪里执行定时器回调函数?第一印象就是在 Tick 中断里执行:

  • 在 Tick 中断中判断定时器是否超时
  • 如果超时了,调用它的回调函数

但是,FreeRTOS 是实时操作系统,它不允许在内核、在中断中执行不确定的代码:如果定时器函数很耗时,就会导致Tick中断迟迟无法结束,影响任务调度,进而会影响整个系统。

  • 所以,FreeRTOS 中,不在 Tick 中断中执行软件定时器的回调函数。

在哪里执行?有资格执行函数的必然是一个任务,这个任务就是:RTOS 守护任务。以前被称为"Timer server",但是这个任务要做并不仅仅是定时器相关,所以改名为:RTOS Damemon Task

  • 除此之外,对软件定时器的具体操作也是由守护任务完成的。

我们用户只是在使用软件定时器,如启动,停止,删除,复位,改变定时周期等等操作。但是只是在用户任务中调用相关的API函数,具体的细节操作并不是由用户完成的,而是由守护任务完成的。

定时器的回调函数的原型如下:

void ATimerCallback(TimerHandle_t xTimer);

定时器的回调函数是在守护任务中被调用的,守护任务不是专为某个定时器服务的,它还要处理其他定时器。

所以,定时器的回调函数不要影响其他人:

  • 回调函数要尽快实行,不能进入阻塞状态。
  • 不要调用会导致阻塞的 API 函数,比如vTaskDelay()
  • 可以调用xQueueReceive()之类的函数,但是超时时间要设为 0:即刻返回,不可阻塞 。

执行用户命令:

图
如上图所示,当用户调用操作软件定时器的API时,其实是给守护任务发生了一些指令,守护任务根据指令做出相应的操作,如启动定,停止定时器等。

用户是在用户任务中调用的API,操作是在守护任务中根据不同用户指令执行的,所以就涉及到了两个任务之间的通信。

  • 这里任务之间的通信使用的是队列。

用户任务将操作软件定时器的命令写入到命令队列中,守护任务从命令队列中读取命令并做出相应的操作。


  • 当 FreeRTOS 的配置项 configUSE_TIMERS 被设置为 1 时,在启动调度器时,会自动创建 RTOS Damemon Task。

  • 因为要创建命令队列,所以要配置configTIMER_QUEUE_LENGTH来指定命令队列长度。

  • 既然守护任务也是一个任务,所以要配置它的优先级configTIMER_TASK_PRIORITY以及栈大小configTIMER_TASK_STACK_DEPTH

守护任务的调度

守护任务的调度,跟普通的任务并无差别。当守护任务是当前优先级最高的就绪态任务时,它就可以运行。它的工作有两类:

  • 处理命令:从命令队列里取出命令、处理。
  • 执行定时器的回调函数。

能否及时处理定时器的命令、能否及时执行定时器的回调函数,严重依赖于守护任务的优先级。

守护任务的优先性级较低:

图
如上图:

  • t1:Task1 处于运行态,守护任务处于阻塞态。
  • t2:Task1 调用 xTimerStart()

要注意的是,xTimerStart()只是把"start timer"的命令发给"定时器命令队列",使得守护任务退出阻塞态。但是此时,Task1 的优先级高于守护任务,所以守护任务无法抢占 Task1。

  • t3:Task1 执行完 xTimerStart()

但是定时器真正的启动工作由守护任务来实现,所以xTimerStart()返回并不表示定时器已经被启动了。

  • t4:Task1 由于某些原因进入阻塞态,现在轮到守护任务运行。

守护任务从队列中取出"start timer"命令,启动定时器。

  • t5:守护任务处理完队列中所有的命令,再次进入阻塞态。Idel 任务时守护任务的优先级最高,它执行。
  • 注意:假设定时器在后续某个时刻 tX 超时了,超时时间是"tX-t2",而非"tX-t4"。
  • 超时时间是从xTimerStart()函数被调用时算起。

守护任务的优先性级较高:

图
如上图:

  • t1:Task1 处于运行态,守护任务处于阻塞态。
  • t2:Task1 调用xTimerStart()

此时守护任务的优先级高于 Task1,所以守护任务抢占 Task1,守护任务开始处理命令队列。

Task1 在执行xTimerStart()的过程中被抢占,这时它无法完成自己的函数。

  • t3:守护任务处理完命令队列中所有的命令,再次进入阻塞态。 此时 Task1 是优先级最高的就绪态任务,它开始执行。
  • t4:Task1之前被守护任务抢占,对xTimerStart()的调用尚未返回。现在开始继续运行此函数、返回。
  • t5:Task1 由于某些原因进入阻塞态,进入阻塞态。Idel 任务时优先级最高的就绪态任务,它执行。
  • 注意,定时器的超时时间是基于调用xTimerStart()的时刻 tX,而不是基于守护任务处理命令的时刻 tY。
  • 假设超时时间是 10 个 Tick,超时时间是"tX+10",而非"tY+10"。

⚽使用软件定时器的函数

创建:

/* 动态创建 */
TimerHandle_t xTimerCreate( 
				const char * const pcTimerName, 
                const TickType_t xTimerPeriodInTicks,
                const UBaseType_t uxAutoReload,
                void * const pvTimerID,
                TimerCallbackFunction_t pxCallbackFunction);
/* 静态创建 */
TimerHandle_t xTimerCreateStatic( 
				const char * const pcTimerName
                const TickType_t xTimerPeriodInTicks,
                const UBaseType_t uxAutoReload,
                void * const pvTimerID,
                TimerCallbackFunction_t pxCallbackFunction,
                StaticTimer_t * pxTimerBuffer);
  • pcTimerName:定时器名字, 用处不大, 尽在调试时用到 。
  • xTimerPeriodInTicks:定时周期, 以 Tick 为单位 。
  • uxAutoReload:定时器类型,pdTRUE表示自动加载, pdFALSE表示一次性 。
  • pvTimerID:回调函数可以使用此参数, 比如分辨是哪个定时器 。
  • pxCallbackFunction:回调函数。
  • 返回值:成功则返回定时器句柄,否则返回 NULL。
  • pxTimerBuffer:静态创建时,需要传入一个StaticTimer_t结构体,在上面构造定时器。

回调函数的类型是:

void ATimerCallback( TimerHandle_t xTimer ); 

tu
如上图定时器结构体,创建好定时器以后,调用xTimerCreate传入的参数都记录到了该结构体中,后面通过定时器句柄就可以访问到这些成员。

启动:

BaseType_t xTimerStart( TimerHandle_t xTimer, 
						TickType_t xTicksToWait);
  • xTimer:哪个定时器。
  • xTicksToWait:超时时间。
  • 返回值:pdFAIL表示"启动命令"在 xTicksToWait 个 Tick 内无法写入队列,pdPASS表示成功 。

这里的超时时间和前面创建定时器的定时周期不是一个东西,这里的超时时间是指用户任务向命令队列中写命令时的超时时间。

停止:

BaseType_t xTimerStop( TimerHandle_t xTimer, 
						TickType_t xTicksToWait );
  • xTimer:哪个定时器。
  • xTicksToWait:超时时间。
  • 返回值:pdFAIL表示"停止命令"在 xTicksToWait 个 Tick 内无法写入队列,pdPASS表示成功 。

修改定时周期:

BaseType_t xTimerChangePeriod( TimerHandle_t xTimer, 
 							TickType_t xNewPeriod, 
 T							ickType_t xTicksToWait );
  • xTimer:哪个定时器。
  • xNewPeriod:新周期。
  • xTicksToWait: 超时时间, 命令写入队列的超时时间。

复位:

BaseType_t xTimerReset( TimerHandle_t xTimer, 
						TickType_t xTicksToWait );
  • xTimer:哪个定时器。
  • xTicksToWait: 超时时间, 命令写入队列的超时时间。

使用 xTimerReset()函数可以让定时器的状态从冬眠态转换为运行态,相当于使用 xTimerReset()函数。

如果定时器已经处于运行态,使用xTimerReset()函数就相当于重新确定超时时间。假设调用xTimerReset()的时刻是 tX,定时器的周期是 n,那么 tX+n 就是重新确定的超时时间。

删除:

BaseType_t xTimerDelete( TimerHandle_t xTimer, 
						TickType_t xTicksToWait );
  • xTimer:哪个定时器。
  • xTicksToWait: 超时时间, 命令写入队列的超时时间。

定时器ID:

tu
如上图,定时器的结构体如下,里面有一项 pvTimerID,它就是定时器 ID。

怎么使用定时器 ID,完全由程序员来决定:

  • 可以用来标记定时器,表示自己是什么定时器。
  • 可以用来保存参数,给回调函数使用。

它的初始值在创建定时器时由xTimerCreate()这类函数传入,后续可以使用这些函数来操作:

  • 更新 ID:使用vTimerSetTimerID()函数。
  • 查询 ID:查询pvTimerGetTimerID()函数。
  • 这两个函数不涉及命令队列,它们是直接操作定时器结构体。

⚽基本使用

图
首先定义这4个宏,然后才能使用软件定时器。

  • 守护任务的优先级要尽可能高,让定时器的命令或者回调函数及时被执行。

一般使用:

图
如上图所示,创建两个定时器,一个是一次性定时器,定时周期是10个Tick,另一个是周期定时器,定时周期是20个Tick。然后再创建一个任务,优先级是1。

图
如上图,在任务1中启动两个定时器,然后在while(1)中持续打印任务运行信息。

在一次性定时器的回调函数中,将一次性定时器运行标志位反转,并将运行次数加加,然后打印。

在周期性定时器回调函数中,将周期性定时器运行标志位反转,并将运行次数增加,然后打印。

  • 两个回调函数中都没有while(1)死循环。

图
如上图运行结果,先看右边串口,任务1在启动两个定时器以后,就不断打印自己的运行信息。其中一次性定时器运行信息打印了一次,周期性定时器运行信息打印了多次。

再看左边逻辑分析仪中标志位的变化,可以看到,一次性定时器的运行标志只发生了一次变化,周期性定时器的运行标志每隔20隔Tick就变化一次。

  • 定时器的回调函数并不是由任务1执行的,任务1中没有操作运行标志,更说明定时器的回调函数是由守护任务执行的。
  • 一次性定时器只起一次作用,而周期性定时器按定时周期起作用,因为回调函数中并没有死循环,定时器每起一次作用就调用一次回调函数。

消除抖动:

在嵌入式开发中,我们使用机械开关时经常碰到抖动问题:引脚电平在短时间内反复变化。

怎么读到确定的按键状态?

  • 连续读很多次,直到数值稳定:浪费 CPU 资源。
  • 使用定时器:要结合中断来使用。

对于第 2 种方法,处理方法如下图所示:
图

  • 在 t1 产生中断,这时不马上确定按键,而是复位定时器,假设周期时20ms,超时时间为"t1+20ms" 。
  • 由于抖动,在 t2 再次产生中断,再次复位定时器,超时时间变为"t2+20ms" 。
  • 由于抖动,在 t3 再次产生中断,再次复位定时器,超时时间变为"t3+20ms" 。
  • 在"t3+20ms"处,按键已经稳定,读取按键值 。

图
如上图代码所示,配置PA0作为按键,且开启按键外部中断,采样双边沿触发模式。

图
如上图,在中断函数中,使用定时器进行消抖,每发生一次按键中断,就打印一次中断发生的信息,并且计数发生次数,然后使用xTimerReset推迟超时时间,实现消抖。

图
如上图代码,将按键及中断初始化,然后创建一个一次性定时器,这里的超时时间设置为2秒,后面讲解原因。然后再创建一个任务。

图
如上图,在任务1中,只有一个死循环,是为了保证程序在一直执行,一次性定时器的回调函数中,打印回调函数的执行信息和执行次数。

  • 如果按键持续抖动,则会持续发生外部中断,定时器也会被不停复位,超时时间就在不断推后,回调函数始终得不到执行。
  • 当按键稳定后,定时器超时,回调函数执行,此时消抖完成,成功读取一次按键。

图
如上图,将程序使用软件仿真,打开模拟器中的GPIOA,如上图所示,其中第4步红色框中的勾用来就用来操作PA0引脚,来模拟按键中断产生。

  • 打上勾时,PA0的电平为高。
  • 没有勾时,PA0的电平为低。

所以我们只需要用鼠标不停电机第4步中的红色框位置,就可以模拟处按键过程中的抖动,当不再点击时,抖动消失,按键状态稳定。

  • 由于手动模拟,无法在20ms内完成,为了看到实验现象,将定时器的超时时间设置成2秒。

2秒钟内,每点击一次,模拟发生一次抖动,超时时间推后2秒。当不再点击时,按键稳定,等待定时器超时,一次按键读取完成。

图
如上图,本喵快速点击多次,产生了多次按键中断,定时器超时时间推迟了多次,最后消去了这几次抖动,读取一次按键状态。

🏀中断处理

在 RTOS 中,需要应对各类事件。这些事件很多时候是通过硬件中断产生,怎么处理中断呢?假设当前系统正在运行 Task1 时,用户按下了按键,触发了按键中断。这个中断的处理流程如下:

  • CPU 跳到固定地址去执行代码,这个固定地址通常被称为中断向量,这个跳转时硬件实现的 。
  • 执行代码做什么?
  • 保存现场:Task1 被打断,需要先保存 Task1 的运行环境,比如各类寄存器的值 。
  • 分辨中断、调用处理函数(这个函数就被称为 ISR(interrupt service routine)
  • 恢复现场:继续运行 Task1,或者运行其他优先级更高的任务 。

ISR 是在内核中被调用的,ISR 执行过程中,用户的任务无法执行。ISR要尽量快,否则:

  • 其他低优先级的中断无法被处理:实时性无法保证。
  • 用户任务无法被执行:系统显得很卡顿 。

如果这个硬件中断的处理,就是非常耗费时间呢?对于这类中断的处理就要分为 2 部分:

  • ISR:尽快做些清理、记录工作,然后触发某个任务。
  • 任务:更复杂的事情放在任务中处理。
  • 所以:需要 ISR 和任务之间进行通信。

要在 FreeRTOS 中熟练使用中断,有几个原则要先说明:

  • FreeRTOS 把任务认为是硬件无关的,任务的优先级由程序员决定,任务何时运行由调度器决定。
  • ISR 虽然也是使用软件实现的,但是它被认为是硬件特性的一部分,因为它跟硬件密切相关 。
  • 何时执行?由硬件决定。
  • 哪个 ISR 被执行?由硬件决定。
  • ISR 的优先级高于任务:即使是优先级最低的中断,它的优先级也高于任务。
  • 任务只有在没有中断的情况下,才能执行。

⚽两套API函数

图
如上图按键中断代码所示,在ISR中调用了xTimerReset函数写命令到命令队列,这是ISR和守护任务在进行通信,该函数的第二个参数是超时时间,如果设置为porMAX_DELAY,ISR就有阻塞的可能,这对于ISR是绝对不允许的,所以需要另外的API函数来和守护任务通信。

图
如上图,应该将用于通信的API换成xTimerResetFromISR,可以看到,该函数相比于之前多了一个FromISR后缀,这是专门用来进行ISR和任务之间的通信的。

  • 每一类任务间通信函数都有两套,一套是用来实现用户任务间通信的,没有FromISR后缀,另一套是带有后缀的。

两套API函数列表:

类型在任务中在ISR中
队列(queue)xQueueSendToBack
xQueueSendToFront
xQueueReceive
xQueueOverwrite
xQueuePeek
xQueueSendToBackFromISR
xQueueSendToFrontFromISR
xQueueReceiveFromISR
xQueueOverwriteFromISR
xQueuePeekFromISR
信号量(semaphore)xSemaphoreGive
xSemaphoreTake
xSemaphoreGiveFromISR
xSemaphoreTakeFromISR
事件组(event group)xEventGroupSetBitsxEventGroupSetBitsFromISR

xHigherPriorityTaskWoken 参数:

所有带FromISR后缀的函数中,都有一个参数xHigherPriorityTaskWoken,该参数的含义是:是否有更高优先级的任务被唤醒了。如果为pdTRUE,则意味着后面要进行任务切换。

图
如上图,在xTimerResetFromISR中会改变xHigherPriorityTaskWoken的值,该函数调用结束以后,需要调用portYIELD_FROM_ISR来判断是否要发起调度。

⚽对比两套API函数

普通任务写队列:
图
如上图代码,是普通任务向队列中写数据xQueueSend的底层部分函数。

  1. 传参时,需要传入超时时间。
  2. 所有关于列表的判断放在一个死循环中,如果没有返回则会不断循环判断。
  3. 如果队列没有满可以写入数据,写入以后会立刻发起调度,好让更高优先级的任务抢占执行。

图
如上图是另外部分的函数。

  1. 队列满了时,无法写入数据。
  • 如果超时时间为0,则该任务不等待直接错误返回。
  • 如果超时时间没有达到,则将该任务放入阻塞链表。
  • 再次发起调度,好让其他任务执行。
  • 普通任务向队列中写数据时,有超时时间,死循环,还会在函数中直接发起任务调度。

ISR写队列:

图

如上图是ISR向队列中写数据xQueueSendFromISR的底层函数。

  1. 传入的参数没有超时时间,而是是否发起调度标志。
  2. 没有死循环,只判断一遍后就返回。
  3. 队列没有满,将数据写入队列后,再将处于等待状态的任务放入到就绪链表,但是并不立刻发起调度,而是将发起调度标志位置一。
  4. 队列满了,直接错误返回,没有其他操作链表的多余动作。
  • ISR向链表中写数据时,没有超时时间,没有死循环式的判断,数据写入以后仅记录需要发起调度,写入失败后直接返回。

差别:

xQueueSendxQueueSendFromISR
参数不同xTicksToWait: 队列满的话阻塞多久没有xTicksToWait
唤醒等待的任务写队列后,会唤醒等待数据的任务写队列后,记录要唤醒等待数据任务的需求
调度如果被唤醒的任务优先级更高,即刻调度如果被唤醒的任务优先级更高,不会调度
只是记录下来表示:需要调度
阻塞如果队列满,可以阻塞如果队列满,不能阻塞

对比两套API,发现ISR的API比普通的API高效很多,除了没有超时时间外,就是不会直接发起调度,而是将需要调度的需求记录下来。等ISR退出后,执行portYIELD_FROM_ISR(xHigherPriorityTaskWoken)函数来发起调度。

  • 发起调度不能以内核的身份发起,只能以普通任务的身份去发起。

其实在ISR中发起调度也没有意义,因为中断的优先级比所有任务都高,在中断中发起调度,调度器任务无法处理这个调度请求,因为是此时代码仍然在执行中断函数,就会直接导致程序阻塞。


这种记录调度需求的处理方式叫"中断的延迟处理"(Deferring interrupt processing)。

图
如上面流程图所示:

  • t1:任务 1 运行,任务 2 阻塞。
  • t2:发生中断,该中断的 ISR 函数被执行,任务 1 被打断。
  • ISR 函数要尽快能快速地运行,它做一些必要的操作(比如清除中断),然后唤醒任务 2 。
  • t3:在创建任务时设置任务 2 的优先级比任务 1 高(这取决于设计者),所以 ISR 返回后,运行的是任务 2,它要完成中断的处理。
  • t4:任务 2 处理完中断后,进入阻塞态以等待下一个中断,任务 1 重新运行 。

这样做有多种好处:

  • 效率高:避免不必要的任务切换。
  • 让 ISR 更可控:中断随机产生,在 API 中进行任务切换的话,可能导致问题更复杂 。
  • 可移植性好。

🏀总结

软件定时器是经常使用的一种定时方式,要清楚的意识到守护任务的存在,以及守护任务所做的工作,用户操作软件定时器的本质就是:用户任务和守护任务通过命令队列进行通信。

要知道中断的优先级是高于所有用户任务的,而且在中断服务函数中不会立刻发起任务调度,而是记录下需要调度的需求,等ISR退出以后,由调度器发起任务调度。

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

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

相关文章

如何配置微信小程序id

使用uni-app开发微信小程序项目,配置好微信小程序id是必不可少的。 一、如何找微信小程序id 二、如何配置微信小程序id

Unity之ShaderGraph如何实现冰冻效果

前言 今天我们来实现一个冰冻的效果,非常的炫酷哦。 如下图所示: 主要节点 Voronoi:根据输入UV生成 Voronoi 或Worley噪声。Voronoi 噪声是通过计算像素和点阵之间的距离生成的。通过由输入角度偏移控制的伪随机数偏移这些点,可以生成细胞簇。这些单元的规模以及产生的…

Seata入门系列【15】@GlobalLock注解使用场景及源码分析

1 前言 在Seata 中提供了一个全局锁注解GlobalLock,字面意思是全局锁,搜索相关文档,发现资料很少,所以分析下它的应用场景和基本原理,首先看下源码中对该注解的说明: // 声明事务仅在单个本地RM中执行 //…

Map和Set【OJ练习题】

文章目录 常用的Map和Set的使用方法1.数据去重2.统计出现的次数 数组中出现次数超过一半的数字缺失的第一个正整数只出现一次的数字随机链表的复制石头和宝石 常用的Map和Set的使用方法 1.数据去重 假设有10万个数据,如何去重重复的数据,重复的数据只保…

C++基础算法⑥——信奥一本通递归算法(全排列、分解因数、菲波那契数列、Pell数列、爬楼梯、汉诺塔问题)

递归算法 1199:全排列1200:分解因数1201:菲波那契数列1202:Pell数列1204:爬楼梯1205:汉诺塔问题 1199:全排列 由题目可知,输入一个字符串,我们要对字符串进行所有可能的排…

基于Jsp+Servlet+MySql的汉服网站的设计与实现-源码+毕业论文

源码和文档下载地址: https://juzhendongli.store/commodity/details/16 百度云盘中存储有。

Python 编写 Flink 应用程序经验记录(Flink1.17.1)

目录 官方API文档 提交作业到集群运行 官方示例 环境 编写一个 Flink Python Table API 程序 执行一个 Flink Python Table API 程序 实例处理Kafka后入库到Mysql 下载依赖 flink-kafka jar 读取kafka数据 写入mysql数据 flink-mysql jar 官方API文档 https://nigh…

计网小题题库整理第一轮(面向期末基础)(2)

该系列第二期,第一期链接在这~ 计网小题题库整理第一轮(面向期末基础)(1)https://blog.csdn.net/jsl123x/article/details/134030486?spm1001.2014.3001.5501 一.选择题 1、Internet的前身是 (C &#x…

Visual Studio远程连接Linux编译代码时,头文件在/usr/include中找不到,文件存在于/usr/include的子目录中

文章目录 1 问题的提出2 问题分析3 问题的解决 1 问题的提出 VS2022在编译数据安全传输平台时,远程连接到Centos上进行编译,但是提示找不到json头文件。 2 问题分析 在Linux系统下编译代码时,系统会主动到/usr/include目录主动搜索头文件。…

解释器模式——化繁为简的翻译机

● 解释器模式介绍 解释器模式(Interpreter Pattern)是一种用的比较少的行为型模式,其提供了一种解释语言的语法或表达的方式,该模式定义了一个表达式接口,通过该接口解释一个特定的上下文。在这么多的设计模式中&…

【神印王座】改编遇瓶颈,伊莱克斯无建模,皓晨加戏被绞杀,喜提五挂

【侵权联系删除】【文/郑尔巴金】 神印王座动画第78集已经更新了,官方实锤不会断更了,这可真的太爽了。龙皓晨在永恒之塔开始接受伊莱克斯的传承,不过剧情方面有点小瑕疵。伊莱克斯如此重要角色,竟然没有建模,龙皓晨更…

FreeRTOS 事件标志组 详解

目录 什么是事件标志组? 事件标志位 事件标志组 事件标志组相关 API 函数 1. 创建事件标志组 2. 设置事件标志位 3. 清除事件标志位 4. 等待事件标志位 事件标志组实操 什么是事件标志组? 事件标志位 表明某个事件是否发生,联想&am…

【JAVA学习笔记】47 - 异常,try-catch处理,throw处理

项目代码 https://github.com/yinhai1114/Java_Learning_Code/tree/main/IDEA_Chapter12/scr/com/yinhai/exception_ 〇、异常处理的引入 程序出现一个小问题如int num1 10;int num2 0;num1 / num2 > 10 / 0 会抛出错误,但这样不算致命的小问题就…

android studio启动Task配置

Android studio 高版本默认不开启Task配置,需要自己手动开启 1.低版本配置路径:(复制他人图片) 2.高版本路径:添加下图勾选配置即可 3.gradle task 3.1 初识task gradle中所有的构建工作都是由task完成的,它帮我们处…

案例精选|聚铭网络多产品联合部署为北京迎祥酒店建立信息安全屏障

北京迎祥酒店位于龙脉之上的北京后花园昌平区,总面积约18666平米,主营餐饮、住宿、汤泉、婚礼四大业务,酒店每一个细节都散发着国潮气息,充满艺术气质,祥瑞的照壁、精工的雕花、厚重的石刻、颇具京韵京味,是…

Python 自定义模块和包实现GUI(图形界面)登录界面

上一篇:Python 自定义模块和包设计英语生词本(文件版)-CSDN博客 紧接上一篇博文,当我们熟练掌握自定义模块和包、掌握文件的的读取与写入、掌握正则表达式内置模块"re"、掌握GUI(图形界面)的部分…

HarmonyOS原生分析能力,即开即用助力精细化运营

数据分析产品对开发者的价值呈现在两个层面,第一个是产品的层面,可以通过数据去洞察用户的行为,从而找到产品的优化点。另外一个就是运营层面,可以基于数据去驱动,来实现私域和公域的精细化运营。 在鸿蒙生态上&#…

Mac用NTFS文件夹读写NTFS硬盘 NTFS能复制多大的文件

Mac作为一款备受欢迎的计算机操作系统,具备了许多令人惊叹的功能和特性。然而,对于一些Mac用户来说,使用NTFS格式的硬盘可能存在一些疑问。他们可能想知道Mac是否能够读写NTFS格式的硬盘,以及NTFS格式的硬盘是否有文件大小的限制。…

067:mapboxGL上传CSV文件,显示图形,导出为Geojson文件

第067个 点击查看专栏目录 本示例的目的是演示如何在vue+mapbox中上传CSV文件,显示图形,导出为Geojson文件。 直接复制下面的 vue+mapbox源代码,操作2分钟即可运行实现效果 文章目录 示例效果使用的csv文件配置方式示例源代码(共140行)安装依赖相关API参考:专栏目标示例…

CentOS 搭建本地 yum 源方式 安装 httpd 服务

CentOS 搭建本地 yum 源方式 安装 httpd 服务 修改 yum 源 挂载光驱 mkdir -p /mnt/cdrom mount /dev/cdrom /mnt/cdromvi /etc/fstab追加以下内容: /dev/cdrom /mnt/cdrom iso9660 defaults 0 0手动修改CentOS-Base.repo 备份 yum 源配置文件 mv /etc/yum.re…