【FreeRTOS 教程 三】协程状态、优先级、实现及调度

目录

一、协程介绍:

(1)协程的特点:

(2)协程的优势:

二、协程状态:

(1)协程状态说明:

(2)协程状态图示:

三、协程实现:

(1)协程结构体:

(2)协程优先级:

(3)调度协程:

(4)混合任务和协程

四、协程的局限和限制:

(1)共享堆栈:

(2)switch 语句的使用:

五、协程API:

(1)xCoRoutineCreate:

(2)crDELAY:

(3)crQUEUE_SEND:

(4)crQUEUE_RECEIVE:

(5)crQUEUE_SEND_FROM_ISR:

(6)crQUEUE_RECEIVE_FROM_ISR:

(7)vCoRoutineSchedule:

六、协程使用示例:

(1)创建协程示例:

(2) 调度协程:

(3) 创建协程并启动 RTOS 调度器:

(4)使用索引参数:

七、空闲任务:

(1)空闲任务说明:

(2)空闲任务钩子:

八、线程本地存储指针:

(1)线程本地存储指针: 

(2)线程本地整数: 

(3)线程本地结构体:

九、协程常见问题报错:

(1)CoRoutineCreate函数未定义:

(2)协程最高优先级未定义:

(3)内存空间不足报错:

(4)空闲任务未定义:

十、协程程序编写:

(1)基础协程创建:

(2)协程中索引的使用:

十一、FreeRTOS教程示例代码下载:


一、协程介绍:

协程是一种计算机程序组件,它允许不同的执行线程进行协作式的多任务处理。与传统的线程或任务相比,协程具有一些独特的特点和优势,尤其是在资源受限的环境中,如小型嵌入式系统或物联网设备。

(1)协程的特点:

  • 堆栈使用:协程之间共享同一个堆栈,这大大减少了程序运行时所需的RAM资源。这种共享堆栈的方式使得协程在内存使用上比传统的多线程或多任务模型更为高效。
  • 调度和优先级:协程通常采用优先级协同调度,这意味着协程的执行顺序是由其优先级决定的,但它们也可以在支持抢占式调度的系统中使用。这种调度方式允许协程在需要时主动放弃CPU控制权,从而实现更高效的任务切换。
  • 宏实现:协程通常通过一组宏来实现,这意味着它们的创建和管理是通过预处理器指令来完成的,而不是通过函数调用。这种方式简化了协程的实现,但也可能限制了它们的灵活性。
  • 使用限制:由于协程间共享堆栈,它们在构造时需要遵守一些严格的限制,以避免堆栈溢出或数据损坏。此外,协程的使用也受到API调用位置的限制,因为不是所有的API调用都适合在协程环境中使用。

(2)协程的优势:

  • 减少RAM使用:由于协程间共享堆栈,它们在运行时所需的内存资源大大减少,这对于内存受限的设备来说是一个显著的优势。
  • 减少重入问题:协程的协作式操作减少了函数重入的问题,因为协程在执行过程中可以主动放弃CPU控制权,从而避免了多线程环境中常见的同步和互斥问题。
  • 跨平台移植:协程可以在不同的架构之间移植,这意味着开发者可以更容易地将协程程序从一个平台迁移到另一个平台。
  • 优先级完全:协程相对于其他协程具有完全的优先级,但如果与任务混用,协程总是会被任务抢占。
  • 协作式操作:协程之间只能进行协作式操作,这意味着它们需要主动放弃CPU控制权,以便其他协程可以执行。

二、协程状态:

协程仅用于 RAM 严重受限的极小处理器, 通常不会用于 32 位微控制器。

(1)协程状态说明:

协程可以存在于以下状态中:

  • 运行:当协程实际执行时,它被称为处于运行状态。协程当前正在使用处理器。
  • 就绪:就绪的协程是那些能够执行(未阻塞)但目前未执行的协程。
  • 协程处于就绪状态的可能情况包括:
    • 另一个具有相同或更高优先级的协程已处于运行状态
    • 任务处于运行状态——只有在应用程序同时使用任务和协程时才会出现这种情况。
  • 阻塞:如果协程当前正在等待时间事件或外部事件,则该协程被称为处于阻塞状态 。

例如,如果协程调用 crDELAY(),它将阻塞(被置于阻塞状态), 直到延迟期结束(即时间事件)。阻塞的协程不可用于 调度。

(2)协程状态图示:

当前没有等同于任务挂起状态的协程。

三、协程实现:

(1)协程结构体:

协程应具有以下结构体:

类型 crCOROUTINE_CODE 定义为返回 void 并以 CoRoutineHandle_t 和索引作为其参数的函数 。

void vACoRoutineFunction( CoRoutineHandle_t xHandle,
                          UBaseType_t uxIndex )
{
    crSTART( xHandle );

    for( ;; )
    {
       //应用程序代码.
    }

    crEND();
}

实现协程的函数都应属于这种类型(如上代码所示) 。

调用 xCoRoutineCreate() 即可创建协程。

注意事项:

  • 所有协程函数都必须以调用 crSTART() 开始。
  • 所有协程函数都必须以调用 crEND() 结束。
  • 协程函数不应返回任何值,因此通常实现为连续循环。
  • 可通过单个协程函数创建多个协程。提供的 uxIndex 参数 作为区分此类协程的方法。

(2)协程优先级:

每个协程均会被分配优先级,值从 0 到 (configMAX_CO_ROUTINE_PRIORITIES-1) 不等。 configMAX_CO_ROUTINE_PRIORITIES 在 FreeRTOSConfig.h 中定义, 可以根据应用程序进行设置。

  • 优先级数字较低,表示协程优先级也较低。
  • 协程优先级只与其他协程相关。如果在同一应用程序内混用任务和协程, 则任务的优先级将始终高于协程。

(3)调度协程:

通过重复调用 vCoRoutineSchedule()来调度协程。调用 vCoRoutineSchedule() 的最佳位置为空闲任务钩子。即使应用程序 仅使用协程也是如此,因为一旦启动调度器,将仍然会自动 创建空闲任务。

(4)混合任务和协程

从空闲任务中调度协程,可在同一应用程序中轻松混合任务和 协程。这种情况下,只有在没有优先级高于 空闲任务的任务可以执行时,协程才会执行。

四、协程的局限和限制:

与同等任务相比,协程的优势是使用的 RAM 较少,但代价是使用协程时存在一些限制条件。与任务相比,协程的限制性更强,使用起来也更复杂 。

(1)共享堆栈:

当协程阻塞时,协程的堆栈无法维持。这意味着在堆栈上分配的变量很可能丢失其值。要克服这一问题,需将在阻塞调用中维持其值的变量声明为静态。例如:

void vACoRoutineFunction( CoRoutineHandle_t xHandle,
                          UBaseType_t uxIndex )
{
    static char c = 'a'; // 定义一个静态字符变量c,并初始化为'a'。
                         // 由于是静态的,它的值在协程调用之间会保持不变。

    // 协程必须以调用crSTART()开始。
    crSTART( xHandle ); // 标记协程的开始。

    for( ;; ) // 无限循环,协程的主要执行部分。
    {
        // 如果我们在这里将c设置为'b'...
        c = 'b'; // 将变量c的值设置为'b'。

        // ...然后进行一个阻塞调用...
        crDELAY( xHandle, 10 ); // 协程延迟一段时间(以tick为单位)。这里设置为10个tick。

        // ...只有在c被声明为静态变量(如这里所示)的情况下,
        // 才能保证在这里c仍然等于'b'。
        // 因为如果c不是静态的,它的值可能会在协程被挂起和恢复时被其他协程改变。
    }

    // 协程必须以调用crEND()结束。
    crEND(); // 标记协程的结束。
}

共享堆栈的另一个结果是,能从协程函数本身调用可能导致协程阻塞的 API 函数, 而不能从协程调用的函数中调用 。例如:

void vACoRoutineFunction( CoRoutineHandle_t xHandle, UBaseType_t uxIndex )
{
    // 协程必须以调用crSTART()开始。
    crSTART( xHandle ); // 初始化协程,设置协程的状态为运行中。

    for( ;; ) // 无限循环,协程的主要执行逻辑在这里。
    {
        // 在这里进行阻塞调用是可以的,
        crDELAY( xHandle, 10 ); // 协程执行阻塞调用,延迟10个tick时间。

        // 但是不能在vACalledFunction()函数内进行阻塞调用。
        vACalledFunction(); // 调用另一个函数,该函数中不能有阻塞调用。
    }

    // 协程必须以调用crEND()结束。
    crEND(); // 标记协程结束,设置协程的状态为结束。
}

void vACalledFunction( void )
{
    // 在这里不能进行阻塞调用!
    // 因为vACalledFunction()可能在协程或任务中被调用,
    // 如果在这里进行阻塞调用,可能会导致死锁或其他不可预测的行为。
}

(2)switch 语句的使用:

FreeRTOS 下载进中的默认协程实现不允许在 switch 语句中进行阻塞调用。例如:

void vACoRoutineFunction( CoRoutineHandle_t xHandle, UBaseType_t uxIndex )
{
    // 协程必须以调用crSTART()开始。
    crSTART( xHandle ); // 初始化协程,设置协程的状态为运行中。

    for( ;; ) // 无限循环,协程的主要执行逻辑在这里。
    {
        // 在这里进行阻塞调用是可以的,
        crDELAY( xHandle, 10 ); // 协程执行阻塞调用,延迟10个tick时间。

        // 根据变量aVariable的值执行不同的操作
        switch( aVariable )
        {
            case 1 : // 当aVariable等于1时
                      // 在这里不能进行阻塞调用!
                      break; // 结束当前case分支
            default: // 当aVariable不等于1时
                      // 或者在这里也不能进行阻塞调用!
        }
        // 注意:在switch语句中的任何一个case分支中都不能进行阻塞调用。
    }

    // 协程必须以调用crEND()结束。
    crEND(); // 标记协程结束,设置协程的状态为结束。
}

五、协程API:

(1)xCoRoutineCreate:

函数原型:

BaseType_t xCoRoutineCreate
    (
      crCOROUTINE_CODE pxCoRoutineCode,
      UBaseType_t uxPriority,
      UBaseType_t uxIndex
    );
  • 作用:创建新协程,并将其添加到做好运行准备的协程的列表中。
参数/返回值名类型描述
pxCoRoutineCodeCrCOROUTINE_CODE指向协程函数的指针。协程函数需要特殊的语法,以适应FreeRTOS协程的要求。
uxPriorityUBaseType_t协程运行时相对于其他协程的优先级。
uxIndexUBaseType_t用于区分执行相同函数的不同协程,有助于在系统中标识特定的协程实例。
返回值(成功)BaseType_t如果协程成功创建并添加到做好运行准备的协程的列表中,则返回pdPASS
返回值(失败)BaseType_t否则返回ProjDefs.h定义的错误代码,指示创建协程过程中遇到的问题。

用法示例: 

// 要创建的协程函数。
void vFlashCoRoutine( CoRoutineHandle_t xHandle, UBaseType_t uxIndex )
{
    // 如果协程中的变量需要在阻塞调用之间保持值,则必须声明为静态变量。
    // 对于const变量,这可能不是必要的。
    static const char cLedToFlash[2] = { 5, 6 };
    static const TickType_t uxFlashRates[2] = { 200, 400 };

    // 每个协程都必须以调用crSTART()开始。
    crSTART( xHandle );

    for( ;; ) // 无限循环,协程的主要执行逻辑在这里。
    {
        // 这个协程只是延迟一个固定的时间,然后切换一个LED。
        // 使用这个函数创建了两个协程,因此使用uxIndex参数来告诉协程
        // 应该闪烁哪个LED以及延迟多长时间。这假设xQueue已经创建。
        vParTestToggleLED( cLedToFlash[uxIndex] );
        crDELAY( xHandle, uxFlashRates[uxIndex] );
    }

    // 每个协程都必须以调用crEND()结束。
    crEND();
}

// 创建两个协程的函数。
void vOtherFunction( void )
{
    unsigned char ucParameterToPass;
    TaskHandle_t xHandle;

    // 在优先级0创建两个协程。第一个协程被赋予索引0
    // (从上面的代码来看)每200个tick闪烁LED 5。第二个
    // 协程被赋予索引1,因此每400个tick闪烁LED 6。
    for( uxIndex = 0; uxIndex < 2; uxIndex++ )
    {
        xCoRoutineCreate( vFlashCoRoutine, 0, uxIndex );
    }
}
  • CoRoutineHandle_t:引用协程的类型。
  • 协程句柄会自动传递给所有协程函数。

(2)crDELAY:

函数原型:

void crDELAY( CoRoutineHandle_t xHandle,
              TickType_t xTicksToDelay )
  • crDELAY是一个宏。原型中的数据类型仅供参考。该函数可以将协程延迟一段固定时间。crDELAY只能从协程函数本身调用,不能从协程函数调用的函数调用。这是因为协程无法维持自己的堆栈。
参数名类型描述
xHandleCoRoutineHandle_t要推迟的协程的句柄。这是协程函数的参数之一。
xTicksToDelayTickType_t协程应推迟的滴答数。此滴答数可以转换为实际时间,实际时间由configTICK_RATE_HZ(在FreeRTOSConfig.h中设置)定义。

用法示例:

// 要创建的协程函数。
void vACoRoutine( CoRoutineHandle_t xHandle, UBaseType_t uxIndex )
{
    // 如果协程中的变量需要在阻塞调用之间保持值,则必须声明为静态变量。
    // 对于const变量,这可能不是必要的。我们在这里要延迟200毫秒。
    static const xTickType xDelayTime = 200 / portTICK_PERIOD_MS;

    // 每个协程都必须以调用crSTART()开始。
    crSTART( xHandle );

    for( ;; ) // 无限循环,协程的主要执行逻辑在这里。
    {
        // 延迟200毫秒。
        crDELAY( xHandle, xDelayTime );
        // 在这里执行一些操作。
        // 例如,可以切换LED状态、发送消息、处理数据等。
    }
    // 每个协程都必须以调用crEND()结束。
    crEND();
}

(3)crQUEUE_SEND:

函数原型:

crQUEUE_SEND(    CoRoutineHandle_t xHandle,
                  QueueHandle_t xQueue,
                  void *pvItemToQueue,
                  TickType_t xTicksToWait,
                  BaseType_t *pxResult     )
  • crQUEUE_SEND是一个宏。原型中的数据类型仅供参考。
  • crQUEUE_SEND()和 crQUEUE_RECEIVE() 宏是协程版的函数, 对应于任务中使用的xQueueSend()和 xQueueReceive()函数。
  • crQUEUE_SEND和 crQUEUE_RECEIVE只能在协程中使用,而 xQueueSend()和xQueueReceive()只能在任务中使用。
  • 请注意,协程只能向其他协程发送数据。协程不能通过队列向任务发送数据, 任务也不能通过队列向协程发送数据。
  • crQUEUE_SEND只能通过协程函数本身调用, 不能通过协程函数调用的其他函数调用。这是因为 协程无法维持自己的堆栈。
参数名类型描述
xHandleCoRoutineHandle_t调用协程的句柄。这是协程函数的参数之一。
xQueueQueueHandle_t队列的句柄,数据将发布到此队列。使用xQueueCreate() API函数创建队列时,该句柄作为返回值获得。
pvItemToQueuevoid *指向发布到队列中的数据的指针。每个队列项的字节数会在创建队列时指定。此字节数从pvItemToQueue复制到队列本身。
xTicksToDelayTickType_t如果队列中无立即可用空间,协程会阻塞以等待队列可用空间的滴答数。此滴答数可以转换为实际时间,实际时间由configTICK_RATE_Hz(在FreeRTOSConfig.h中设置)定义。常量portTICK_PERIOD_MS可用于将滴答数转换为毫秒。
pxResultBaseType_t *如果从队列中成功检索到数据,则pxResult指向的变量将设置为pdPASS,否则设置为ProjDefs.h中定义的错误代码。

用法示例:

// 协程函数,它阻塞一段固定时间,然后将一个数字发布到队列上。
static void prvCoRoutineFlashTask( CoRoutineHandle_t xHandle, UBaseType_t uxIndex )
{
    // 如果协程中的变量需要在阻塞调用之间保持值,则必须声明为静态变量。
    static BaseType_t xNumberToPost = 0; // 要发布到队列的数字。
    static BaseType_t xResult; // 用于存储队列操作的结果。

    // 协程必须以调用crSTART()开始。
    crSTART( xHandle );

    for( ;; ) // 无限循环,协程的主要执行逻辑在这里。
    {
        // 假设队列已经创建好了。
        crQUEUE_SEND( xHandle, // 协程句柄。
                      xCoRoutineQueue, // 队列句柄。
                      &xNumberToPost, // 指向要发送数据的指针。
                      NO_DELAY, // 不等待,如果队列满则立即返回。
                      &xResult ); // 存储操作结果。

        if( xResult != pdPASS )
        {
            // 消息未能发布到队列!
            // 可以在这里添加错误处理代码。
        }
        // 增加要发布到队列的数字。
        xNumberToPost++;

        // 延迟100个tick。
        crDELAY( xHandle, 100 );
    }
    // 协程必须以调用crEND()结束。
    crEND();
}

(4)crQUEUE_RECEIVE:

函数原型:

void crQUEUE_RECEIVE(
                      CoRoutineHandle_t xHandle,
                      QueueHandle_t xQueue,
                      void *pvBuffer,
                      TickType_t xTicksToWait,
                      BaseType_t *pxResult
                    )
  • crQUEUE_RECEIVE是一个宏。原型中的数据类型仅供参考。
  • crQUEUE_SEND()和 crQUEUE_RECEIVE()宏是协程版的函数, 对应于任务中使用的 xQueueSend()和 xQueueReceive()函数。crQUEUE_SEND和 crQUEUE_RECEIVE只能在协程中使用,而 xQueueSend()和 xQueueReceive() 只能在任务中使用。
  • 请注意,协程只能向其他协程发送数据。协程不能通过队列向任务发送数据, 任务也不能通过队列向协程发送数据。
  • crQUEUE_RECEIVE只能通过协程函数本身调用, 不能通过协程函数调用的其他函数调用。这是因为 协程无法维持自己的堆栈。
参数名描述
xHandle调用协程的句柄。这是协程函数的参数之一。
xQueue要接收数据的队列的句柄。使用xQueueCreate() API函数创建队列时,该句柄作为返回值获得。
pvBuffer将接收到的项目复制到其中的缓冲区。每个排队项的字节数是在创建队列时指定的。此字节数已复制到pvBuffer
xTickToDelay在无立即可用数据的情况下,协程处于阻塞状态以等待队列中可用数据时的滴答数。此滴答数可以转换为实际时间,实际时间由configTICK_RATE_HZ(在FreeRTOSConfig.h中设置)定义。常量portTICK_PERIOD_MS可用于将滴答数转换为毫秒。
pxResult用于存储队列操作结果的指针。如果从队列中成功检索到数据,则pxResult指向的变量将设置为pdPASS,否则设置为ProjDefs.h中定义的错误代码。

 用法示例:

// 协程函数,从队列中接收要闪烁的LED编号,并控制LED闪烁。
static void prvCoRoutineFlashWorkTask( CoRoutineHandle_t xHandle, UBaseType_t uxIndex )
{
    // 如果协程中的变量需要在阻塞调用之间保持值,则必须声明为静态变量。
    static BaseType_t xResult; // 用于存储从队列接收操作的结果。
    static UBaseType_t uxLEDToFlash; // 存储从队列中接收到的LED编号。

    // 所有协程都必须以调用crSTART()开始。
    crSTART( xHandle );

    for( ;; ) // 无限循环,协程的主要执行逻辑在这里。
    {
        // 等待队列中有数据可用。
        crQUEUE_RECEIVE( xHandle, // 协程句柄。
                         xCoRoutineQueue, // 队列句柄。
                         &uxLEDToFlash, // 指向存储接收数据的变量的指针。
                         portMAX_DELAY, // 无限期等待,直到队列中有数据。
                         &xResult ); // 存储操作结果。

        if( xResult == pdPASS )
        {
            // 成功接收到LED编号 - 控制对应的LED闪烁。
            vParTestToggleLED( uxLEDToFlash );
        }
    }

    // 所有协程都必须以调用crEND()结束。
    crEND();
}

(5)crQUEUE_SEND_FROM_ISR:

函数原型:

BaseType_t crQUEUE_SEND_FROM_ISR
            (
              QueueHandle_t xQueue,
              void *pvItemToQueue,
              BaseType_t xCoRoutinePreviouslyWoken
            )
  • crQUEUE_SEND_FROM_ISR()是一个宏。原型中的数据类型仅供参考。
  • crQUEUE_SEND_FROM_ISR()和 crQUEUE_RECEIVE_FROM_ISR()宏是协程版的函数,对应于任务中使用的 xQueueSendFromISR()和 xQueueReceiveFromISR()函数。
  • crQUEUE_SEND_FROM_ISR()和 crQUEUE_RECEIVE_FROM_ISR()只能用于 在协程和 ISR 之间传递数据,而 xQueueSendFromISR()和 xQueueReceiveFromISR()只能用于在任务和 ISR 之间 传递数据。crQUEUE_SEND_FROM_ISR只能从 ISR 调用,以将数据发送到 协程内正在使用的队列。
参数/返回值名类型描述
xQueueQueueHandle_t队列的句柄,数据项将发布到此队列。
pvItemToQueuevoid *指向待入队列的数据项的指针。队列能够存储的项目的大小在创建队列时即已定义,因此pvItemToQueue中的这些字节将复制到队列存储区。
xCoRoutinePreviouslyWokenBaseType_t包含此参数,ISR就可以从单个中断多次发送到同一个队列。第一个调用应始终传入pdFALSE。后续调用应传入从上一个调用返回的值。
返回值(pdTRUE)BaseType_t如果发布到队列将协程唤醒,则返回pdTRUE。ISR使用此信息来确定ISR之后是否需要上下文切换。
返回值(pdFALSE)BaseType_t否则返回pdFALSE

用法示例:

// 一个协程,它在队列上阻塞等待接收字符。
static void vReceivingCoRoutine( CoRoutineHandle_t xHandle, UBaseType_t uxIndex )
{
    char cRxedChar; // 用于存储接收到的字符。
    BaseType_t xResult; // 用于存储队列操作的结果。

    // 所有协程都必须以调用crSTART()开始。
    crSTART( xHandle );

    for( ;; ) // 无限循环,协程的主要执行逻辑在这里。
    {
        // 等待队列中有数据可用。这假设队列xCommsRxQueue已经创建好了!
        crQUEUE_RECEIVE( xHandle,
                         xCommsRxQueue,
                         &cRxedChar,
                         portMAX_DELAY,
                         &xResult );

        // 是否接收到了字符?
        if( xResult == pdPASS )
        {
            // 在这里处理接收到的字符。
        }
    }

    // 所有协程都必须以调用crEND()结束。
    crEND();
}

// 一个ISR,它使用队列将从串口接收的字符发送到协程。
void vUART_ISR( void )
{
    char cRxedChar; // 用于存储从UART接收到的字符。
    BaseType_t xCRWokenByPost = pdFALSE; // 用于记录是否有协程被唤醒。

    // 我们循环读取字符,直到UART中没有剩余字符。
    while( UART_RX_REG_NOT_EMPTY() )
    {
        // 从UART获取字符。
        cRxedChar = UART_RX_REG;

        // 将字符发布到队列。xCRWokenByPost将在循环的第一次迭代中被设置为pdFALSE。
        // 如果发布导致协程被唤醒(取消阻塞),那么xCRWokenByPost将被设置为pdTRUE。
        // 通过这种方式,我们可以确保如果多个协程被阻塞在队列上,无论有多少字符被发布到队列,只有一个协程会被这个ISR唤醒。
        xCRWokenByPost = crQUEUE_SEND_FROM_ISR( xCommsRxQueue,
                                                 &cRxedChar,
                                                 xCRWokenByPost );
    }
}

(6)crQUEUE_RECEIVE_FROM_ISR:

函数原型:

BaseType_t crQUEUE_SEND_FROM_ISR
           (
             QueueHandle_t xQueue,
             void *pvBuffer,
             BaseType_t * pxCoRoutineWoken
           )
  • crQUEUE_SEND_FROM_ISR() 和 crQUEUE_RECEIVE_FROM_ISR()宏是 协程版的函数,对应于任务中使用的 xQueueSendFromISR()和 xQueueReceiveFromISR()函数。
  • crQUEUE_SEND_FROM_ISR()和 crQUEUE_RECEIVE_FROM_ISR()只能用于在协程和 ISR 之间传递数据,而 xQueueSendFromISR()和 xQueueReceiveFromISR()只能用于在任务和 ISR 之间 传递数据。
  • crQUEUE_RECEIVE_FROM_ISR()只能从 ISR 中调用,以从协程中(发布到队列的协程)正在使用的队列接收数据 。
参数/返回值名类型描述
xQueueQueueHandle_t队列的句柄,数据项将发布到此队列。
pvBuffervoid *指向缓冲区的指针,接收到的项目将被放入此缓冲区中。队列能够存储的项目的大小在创建队列时即已定义,因此队列中的这些字节将复制到pvBuffer
pxCoRoutineWokenBaseType_t *协程在等待队列空间可用时可能会被阻塞。如果函数导致协程解除阻塞,*pxCoRoutineWoken将被设置为pdTRUE,否则*pxCoRoutineWoken将保持不变。
返回值(pdTRUE)BaseType_t如果从队列中成功接收项目,则返回pdTRUE
返回值(pdFALSE)BaseType_t否则返回pdFALSE

用法示例:

// 协程函数,向队列发送一个字符,然后阻塞一段固定时间。每次发送的字符都会递增。
static void vSendingCoRoutine( CoRoutineHandle_t xHandle, UBaseType_t uxIndex )
{
    // cCharToTx在协程阻塞时需要保持其值,因此必须声明为静态变量。
    static char cCharToTx = 'a';
    BaseType_t xResult;

    // 所有协程都必须以调用crSTART()开始。
    crSTART( xHandle );

    for( ;; ) // 无限循环,协程的主要执行逻辑在这里。
    {
        // 将下一个字符发送到队列。
        crQUEUE_SEND( xHandle,
                      xCoRoutineQueue,
                      &cCharToTx,
                      NO_DELAY,
                      &xResult );

        if( xResult == pdPASS )
        {
            // 字符成功发布到队列。
        }
        else
        {
            // 无法将字符发布到队列。
        }

        // 启用UART发送中断,以在这个假设的UART中引发中断。中断将从队列中获取字符并发送它。
        ENABLE_RX_INTERRUPT();

        // 递增到下一个字符,然后阻塞一段固定时间。
        // cCharToTx将在延迟期间保持其值,因为它被声明为静态变量。
        cCharToTx++;
        if( cCharToTx > 'x' )
        {
            cCharToTx = 'a';
        }
        crDELAY( 100 ); // 延迟100个tick。
    }

    // 所有协程都必须以调用crEND()结束。
    crEND();
}


// 使用队列从UART接收要发送的字符的ISR。
void vUART_ISR( void )
{
    char cCharToTx;
    BaseType_t xCRWokenByPost = pdFALSE;

    while( UART_TX_REG_EMPTY() ) // 检查UART发送寄存器是否为空。
    {
        // 队列中有等待发送的字符吗?
        // 如果发布操作唤醒了协程,xCRWokenByPost将自动设置为pdTRUE - 确保无论我们循环多少次,只有一个协程被唤醒。
        if( crQUEUE_RECEIVE_FROM_ISR( xQueue, &cCharToTx, &xCRWokenByPost ) )
        {
            SEND_CHARACTER( cCharToTx ); // 发送字符。
        }
    }
}

(7)vCoRoutineSchedule:

函数原型:

void vCoRoutineSchedule( void );
  • 该函数用于运行协程。
  • vCoRoutineSchedule()执行优先级最高的可运行协程 。协程保持执行,直到它阻塞、挂起或 被任务抢占。协同间互相协作执行,因此一个协程无法被另一个协程抢占,但可以被一个任务抢占。
  • 如果应用程序同时包含任务和协程,那么应从空闲任务(在空闲任务钩子中)调用 vCoRoutineSchedule 。

用法示例:

    void vApplicationIdleHook( void )
    {
        vCoRoutineSchedule( void );
    }

如果空闲任务没有执行任何其他函数,那按以下方式在循环中调用 vCoRoutineSchedule():

    void vApplicationIdleHook( void )
    {
        for( ;; )
        {
            vCoRoutineSchedule( void );
        }
    }

六、协程使用示例:

(1)创建协程示例:

创建一个简单的协程来闪烁 LED。

// 协程函数,用于周期性地闪烁一个LED。
void vFlashCoRoutine( CoRoutineHandle_t xHandle, UBaseType_t uxIndex )
{
    // 协程必须以调用crSTART()开始。
    // crSTART()用于初始化协程的状态。
    crSTART( xHandle );

    for( ;; ) // 无限循环,协程的主要执行逻辑在这里。
    {
        // 延迟一段固定时间。这里设置为10个tick。
        // crDELAY()是一个宏,用于挂起协程的执行,直到指定的tick数过去。
        crDELAY( xHandle, 10 );

        // 闪烁一个LED。这里假设vParTestToggleLED()是一个函数,
        // 用于切换指定编号的LED的状态。
        // 参数0表示第一个LED(编号从0开始)。
        vParTestToggleLED( 0 );
    }

    // 协程必须以调用crEND()结束。
    // crEND()用于标记协程的结束,设置协程的状态为结束。
    crEND();
}

(2) 调度协程:

通过重复调用 vCoRoutineSchedule() 来调度协程。执行这一操作的最佳位置是 空闲任务内部,通过编写空闲任务钩子函数来完成。首先,请确保 configUSE_IDLE_HOOK 在 FreeRTOSConfig.h中设置为 1。然后编写空闲任务钩子函数,如下所示:

void vApplicationIdleHook( void )
{
    vCoRoutineSchedule( void );
}

如果空闲任务没有执行任何其他函数,那按以下方式在循环中调用 vCoRoutineSchedule() 效率会更高: 

void vApplicationIdleHook( void )
{
    for( ;; )
    {
        vCoRoutineSchedule( void );
    }
}

(3) 创建协程并启动 RTOS 调度器:

协程可在 main() 中创建。

#include "task.h" // 包含任务管理相关的头文件,提供任务创建和管理的函数。
#include "croutine.h" // 包含协程管理相关的头文件,提供协程创建和管理的函数。

#define PRIORITY_0 0 // 定义优先级为0,这是最低的优先级。

void main( void ) // 主函数入口
{
    // 创建一个协程,调用vFlashCoRoutine函数作为协程的任务。
    // 这里使用的优先级是PRIORITY_0,即0,表示最低优先级。
    // 第三个参数0在这里没有被使用,仅作为参数传递。
    xCoRoutineCreate( vFlashCoRoutine, PRIORITY_0, 0 );

    // 注意:这里也可以创建任务(Tasks)!

    // 启动RTOS调度器。
    // 一旦调用vTaskStartScheduler(),调度器将开始运行,并且之前创建的协程和任务将开始执行。
    vTaskStartScheduler();
}

(4)使用索引参数:

现在假设我们要从同一函数中创建 8 个这样的协程。每个协程将 以不同速度闪烁不同的 LED。索引参数可用于在协程函数中 区分协程。

这一次,我们将创建 8 个协程,并向每个协程传递不同的索引。

#include "task.h"     // 包含FreeRTOS任务管理相关的头文件。
#include "croutine.h" // 包含FreeRTOS协程管理相关的头文件。

#define PRIORITY_0        0        // 定义优先级0,表示最低优先级。
#define NUM_COROUTINES    8        // 定义要创建的协程数量为8。

void main( void )
{
    int i; // 用于循环计数的变量。

    for( i = 0; i < NUM_COROUTINES; i++ ) // 循环创建NUM_COROUTINES个协程。
    {
        // 创建一个协程,调用vFlashCoRoutine函数作为协程的任务。
        // 使用PRIORITY_0作为协程的优先级,即0,表示最低优先级。
        // 将循环变量i作为索引传递给协程,这样每个协程可以有不同的索引值。
        xCoRoutineCreate( vFlashCoRoutine, PRIORITY_0, i );
    }

    // 注意:这里也可以创建任务(Tasks)!

    // 启动RTOS调度器。
    // 一旦调用vTaskStartScheduler(),调度器将开始运行,并且之前创建的协程和任务将开始执行。
    vTaskStartScheduler();
}

 协程函数也被扩展,因此每个协程使用的 LED 和闪烁速度都不同。

// 定义一个常量数组,包含每个协程控制的LED闪烁频率(以tick为单位)。
const int iFlashRates[ NUM_COROUTINES ] = { 10, 20, 30, 40, 50, 60, 70, 80 };
// 定义一个常量数组,包含每个协程控制的LED编号。
const int iLEDToFlash[ NUM_COROUTINES ] = { 0, 1, 2, 3, 4, 5, 6, 7 };

// 协程函数,用于控制LED以不同的频率闪烁。
void vFlashCoRoutine( CoRoutineHandle_t xHandle, UBaseType_t uxIndex )
{
    // 协程必须以调用crSTART()开始。
    crSTART( xHandle );

    for( ;; ) // 无限循环,协程的主要执行逻辑在这里。
    {
        // 延迟一段固定时间。这里使用uxIndex作为数组索引,
        // 从iFlashRates数组中获取对应的闪烁频率。由于每个协程都使用不同的索引值创建,
        // 因此每个协程将延迟不同的时间。
        crDELAY( xHandle, iFlashRates[ uxIndex ] );

        // 闪烁一个LED。再次使用uxIndex作为数组索引,
        // 从iLEDToFlash数组中获取要切换的LED编号。
        vParTestToggleLED( iLEDToFlash[ uxIndex ] );
    }

    // 协程必须以调用crEND()结束。
    crEND();
}

七、空闲任务:

(1)空闲任务说明:

  • RTOS 调度器启动时,自动创建空闲任务,以确保始终存在一个能够运行的任务。它以最低优先级创建, 以确保如果有更高的优先级应用程序任务处于准备就绪状态,则不使用任何 CPU 时间。
  • 空闲任务负责释放 RTOS 分配给 已删除任务的内存。因此,在使用 vTaskDelete() 函数来确保闲置任务不会匮乏处理时间的应用程序中, 这很重要。 空闲任务没有其他激活函数,因此可以在所有其他条件下合理地缺乏微控制器时间 。
  • 应用程序任务可以共享空闲任务优先级 (tskIDLE_PRIORITY)。 由configIDLE_SHOULD_YIELD 配置参数。

(2)空闲任务钩子:

  • 空闲任务钩子是在空闲任务的每个周期中调用的函数。
  • 如果希望应用程序函数以空闲优先级运行,则有两个选择:
  • 在空闲任务钩子中实现此函数:
    • 必须始终有至少一个任务已准备好运行。因此,必须确保钩子 函数不调用任何可能导致空闲任务阻塞的 API 函数(例如,vTaskDelay(),或 具有阻塞时间的队列或信号量函数)。协程 可以在钩子函数内阻塞。
  • 创建空闲优先级任务以实现该函数:
    • 这是一种更灵活的解决方案,但具有更高的 RAM 使用开销。
  • 要创建一个空闲钩子:
    • 在 FreeRTOSConfig.h中将 configUSE_IDLE_HOOK 设置为 1。
    • 定义具有以下名称和原型的函数:
    • void vApplicationIdleHook(void);
    • 通常使用空闲钩子函数将微控制器 CPU 设为节能模式。

八、线程本地存储指针:

  • 线程本地存储 (TLS) 允许应用程序编写者将值存储在任务的控制块中, 控制块内,使值特定于(本地)任务本身, 并允许每个任务具有自己的唯一值。
  • 线程本地存储最常用于 存储单线程程序会存储在全局变量中的值。例如,许多库包含一个 名为 errno 的全局变量。如果库函数向调用函数返回错误条件, 则调用函数可以检查 errno 值 以确定是什么错误。在单线程应用程序中,只需 将 errno 声明为全局变量,但在多线程应用程序中, 每个线程(任务)必须有其唯一的 errno 值, 否则一个任务可能会读取另一个任务的 errno 值。

(1)线程本地存储指针: 

  • 通过使用线程本地存储指针,FreeRTOS 为应用程序编写者提供了灵活的线程本地存储机制。
  • configNUM_THREAD_LOCAL_STORAGE_POINTERS 编译时配置常量为每个任务的 void 指针数组 (void*) 确定维度。 vTaskSetThreadLocalStoragePointer() API 函数用于 在 void 指针数组中设置值, pvTaskGetThreadLocalStoragePointer() API 函数用于从 void 指针数组读取值。

(2)线程本地整数: 

  • 小于或等于 void 指针大小的值 可以直接存储在线程本地存储指针数组中。例如, 如果 sizeof( void * ) 为 4,则可以使用简单的强制转换将 32 位值存储在 void 指针变量中, 以避免编译器发出警告。但是, 如果 sizeof( void * ) 为 2,则只能直接存储 16 位值。

示例: 直接从线程本地存储数组中的索引存储和检索 32 位值

uint32_t ulVariable; // 定义一个32位无符号整数变量。

/* 将32位的0x12345678值直接写入线程本地存储数组的索引1。
   传递NULL作为任务句柄的效果是写入调用任务的线程本地存储数组。 */
vTaskSetThreadLocalStoragePointer( NULL,  /* 任务句柄。 */
                                   1,     /* 数组索引。 */
                                   ( void * ) 0x12345678 );  /* 要写入的值。 */

/* 将32位变量ulVariable的值存储到调用任务的线程本地存储数组的索引0。 */
ulVariable = ERROR_CODE;  // 假设ERROR_CODE是一个已定义的错误代码。

vTaskSetThreadLocalStoragePointer( NULL,  /* 任务句柄。 */
                                   0,     /* 数组索引。 */
                                   ( void * ) ulVariable );  /* 要写入的值。 */

/* 从调用任务的线程本地存储数组的索引5读取值到ulVariable。 */
ulVariable = ( uint32_t ) pvTaskGetThreadLocalStoragePointer( NULL, 5 ); // 读取的值转换为uint32_t类型。

(3)线程本地结构体:

使用数组中的值作为指向内存中其他位置的结构体的指针。

typedef struct  // 定义一个结构体类型。
{  
    uint32_t ulValue1;  // 结构体的第一个成员,32位无符号整数。
    uint32_t ulValue2;  // 结构体的第二个成员,32位无符号整数。
} xExampleStruct;  // 结构体类型的名称。

xExampleStruct *pxStruct;  // 定义一个指向该结构体类型的指针。

/* 创建一个结构体实例供此任务使用。 */
pxStruct = pvPortMalloc( sizeof( xExampleStruct ) );  // 使用pvPortMalloc分配内存。

/* 设置结构体成员的值。 */
pxStruct->ulValue1 = 0;  // 设置结构体的第一个成员为0。
pxStruct->ulValue2 = 1;  // 设置结构体的第二个成员为1。

/* 将结构体指针存储在调用任务的线程本地存储数组的索引0。 */
vTaskSetThreadLocalStoragePointer( NULL,  /* 任务句柄,NULL表示当前任务。 */
                                   0,     /* 数组索引。 */
                                   ( void * ) pxStruct );  /* 要存储的结构体指针。 */

/* 从调用任务的线程本地存储数组的索引0读取结构体指针。 */
pxStruct = ( xExampleStruct * ) pvTaskGetThreadLocalStoragePointer( NULL, 0 );  // 读取并转换为结构体指针类型。

存储指向调用任务线程本地存储数组中结构体的指针。

九、协程常见问题报错:

(1)CoRoutineCreate函数未定义:

显示xCoRoutineCreate函数未定义:

xCoRoutineCreate()的定义在#include "croutine.h"文件中。我们添加该头文件并打开,在其中查找xCoRoutineCreate的定义位置。

我们可以看到该函数在croutine.h中定义了,我们再到croutine.c文件中查看可以看到:该函数并没有启用。

向上翻动可以看到有:

即,由于configUSE_CO_ROUTINES没有定义默认为0,此时协程并不会启用。

我们可以在FreeRTOSconfig.h头文件中添加启用协程的宏定义。

(2)协程最高优先级未定义:

在添加完启用协程宏定义之后,还需要添加优先级宏定义,定义协程的最高优先级。不然会报错如下:

(3)内存空间不足报错:

 注意:使用协程时需要将静态创建任务宏定义注释或置0:

//#define configSUPPORT_STATIC_ALLOCATION  1

否则,会报错如下:

(4)空闲任务未定义:

 同上我们在,Tasks.c和Tasks.h文件中查找vApplicationIdleHook()函数的定义。发现vApplicationIdleHook的定义只存在与Tasks.c文件中:

向上滑动到该函数开头,添加空闲任务函数即可。

十、协程程序编写:

(1)基础协程创建:

创建协程实现串口打印,实现每隔1s打印字符串。

/***********************************
* @method  		创建协程实现串口打印,
*       			实现每隔1s打印字符串

* @Platform  	CSDN	 
* @author  		The_xzs
* @date    		2025.1.24
************************************/

// 协程函数
void Croutine( CoRoutineHandle_t xHandle,
							UBaseType_t uxIndex )
{
    crSTART( xHandle );

    for( ;; )
    {
		printf("Croutine1\r\n");
		crDELAY( xHandle, 1000);        // 再次延迟1000个时钟节拍。
    }

    crEND();
}

// 创建协程
void Croutine_Init( void )
{
/***********************************************
* @pxCoRoutineCode       指向协程函数的指针。
* @uxPriority     		 协程运行时相对于其他协程的优先级。
* @uxIndex               用于区分执行相同函数的不同协程(索引)。
* BaseType_t xCoRoutineCreate
* (
*	crCOROUTINE_CODE pxCoRoutineCode,
*	UBaseType_t uxPriority,
*	UBaseType_t uxIndex
* );
*************************************************/
		//协程函数为Croutine,优先级为0,索引为0
        xCoRoutineCreate(Croutine, 1, 0);
	
}

// 主函数
int main(void)
{  
    Uart_Init(115200);
    DMA1_Init();
    
    Croutine_Init();
	
    //启动RTOS调度器
	vTaskStartScheduler(); 
	
    return 0;
}

运行效果:

(2)协程中索引的使用:

根据索引值打印不同的字符串

#include "stm32f10x.h"                 // 包含STM32F10x系列微控制器的头文件
#include "FreeRTOS.h"
#include "task.h"                      // 包含任务相关函数的头文件,用于任务创建和管理。
#include "croutine.h"                  // 包含协程相关函数的头文件,用于协程创建和管理。
#include "stdio.h"
#include "uart.h"

/***********************************
* @method      根据索引值打印不同的字符串
*      
* @Platform  	CSDN	 
* @author  		The_xzs
* @date    		2025.1.24
************************************/

// 协程函数
void Croutine( CoRoutineHandle_t xHandle,
              UBaseType_t uxIndex )
{
    crSTART( xHandle );

    for( ;; )
    {
        // 根据索引值打印不同的字符串
        if( uxIndex == 0 )
        {
            printf("Croutine1\r\n");
        }
        else if( uxIndex == 1 )
        {
            printf("Croutine2\r\n");
        }
        else if( uxIndex == 2 )
        {
            printf("Croutine3\r\n");
        }
        else
        {
            printf("CroutineX\r\n");
        }
        crDELAY( xHandle, 1000);        // 再次延迟1000个时钟节拍。
    }

    crEND();
}

// 创建协程
void Croutine_Init( void )
{
    // 协程函数为Croutine,优先级为0,索引分别为0、1、2
    xCoRoutineCreate(Croutine, 1, 0);
    xCoRoutineCreate(Croutine, 1, 1);
    xCoRoutineCreate(Croutine, 1, 2);
}

// 主函数
int main(void)
{  
    Uart_Init(115200);
    DMA1_Init();
    
    Croutine_Init();
    
    //启动RTOS调度器
    vTaskStartScheduler(); 
    
    return 0;
}

运行效果:

十一、FreeRTOS教程示例代码下载:

FreeRTOS教程示例代码将会持续更新...

通过网盘分享的文件:FreeRTOS教程示例代码
链接: https://pan.baidu.com/s/1363h7hHmf8u2pjauwKyhtw?pwd=mi98 提取码: mi98

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

堆的存储(了解)

由于堆是⼀个完全⼆叉树&#xff0c;因此可以⽤⼀个数组来存储。&#xff08;如果不清楚大家可以回顾⼆叉树的存储&#xff08;上&#xff09;c文章里的顺序存储&#xff09; 结点下标为 i &#xff1a; 如果⽗存在&#xff0c;⽗下标为 i/2 &#xff1b; 如果左孩⼦存在&…

谭浩强C语言程序设计(3) 7章

1、递归实现N的阶乘 c复制 #include <cstdio> // 包含标准输入输出库// 计算n的阶乘 int total 0; // 定义全局变量total用于存储阶乘结果// 递归函数计算阶乘 int fac(int a){// 如果输入的数小于0&#xff0c;输出错误信息if (a < 0){printf("%d < 0,err…

WPF基础 | WPF 常用控件实战:Button、TextBox 等的基础应用

WPF基础 | WPF 常用控件实战&#xff1a;Button、TextBox 等的基础应用 一、前言二、Button 控件基础2.1 Button 的基本定义与显示2.2 按钮样式设置2.3 按钮大小与布局 三、Button 的交互功能3.1 点击事件处理3.2 鼠标悬停与离开效果3.3 按钮禁用与启用 四、TextBox 控件基础4.…

MATLAB的数据类型和各类数据类型转化示例

一、MATLAB的数据类型 在MATLAB中 &#xff0c;数据类型是非常重要的概念&#xff0c;因为它们决定了如何存储和操作数据。MATLAB支持数值型、字符型、字符串型、逻辑型、结构体、单元数组、数组和矩阵等多种数据类型。MATLAB 是一种动态类型语言&#xff0c;这意味着变量的数…

模型I/O

文章目录 什么是模型I/O模型I/O功能之输出解析器输出解析器的功能输出解析器的使用Pydantic JSON输出解析器结构化输出解析器 什么是模型I/O 模型I/O在所有LLM应用中&#xff0c;核心元素无疑都是模型本身。与模型进行有效的交互是实现高效、灵活和可扩展应用的关键。LangChain…

docker安装Redis:docker离线安装Redis、docker在线安装Redis、Redis镜像下载、Redis配置、Redis命令

一、镜像下载 1、在线下载 在一台能连外网的linux上执行docker镜像拉取命令 docker pull redis:7.4.0 2、离线包下载 两种方式&#xff1a; 方式一&#xff1a; -&#xff09;在一台能连外网的linux上安装docker执行第一步的命令下载镜像 -&#xff09;导出 # 导出镜像…

QT串口通信,实现单个温湿度传感器数据的采集

1、硬件设备 RS485中继器(一进二出),usb转485模块、电源等等 => 累计115元左右。 2、核心代码 #include "MainWindow.h" #include "ui_MainWindow.h"MainWindow::

android主题设置为..DarkActionBar.Bridge时自定义DatePicker选中日期颜色

安卓自定义DatePicker选中日期颜色 背景&#xff1a;解决方案&#xff1a;方案一&#xff1a;方案二&#xff1a;实践效果&#xff1a; 背景&#xff1a; 最近在尝试用原生安卓实现仿element-ui表单校验功能&#xff0c;其中的的选择日期涉及到安卓DatePicker组件的使用&#…

6.工厂模式(Factory Method)

定义 通过“对象创建” 模式绕开new&#xff0c;来避免对象创建&#xff08;new&#xff09;过程中所导致的紧耦合&#xff08;依赖具体类&#xff09;&#xff0c;从而支持对象创建的稳定。它是接口抽象之后的第一步工作。 动机 在软件系统中&#xff0c;经常面临着创建对象…

Java CAS操作

通过前面的学习认识到了CPU缓存&#xff0c;Java内存模型&#xff0c;以及线程安全的原子、可见、顺序三大特性。本文则重点认识CAS操作&#xff0c;这是Java并发编程常见的一个操作&#xff0c;AbstractQueuedSynchronizer基于此操作提供了丰富的同步器和各种锁。 CAS&#x…

【Numpy核心编程攻略:Python数据处理、分析详解与科学计算】1.25 视觉风暴:NumPy驱动数据可视化

1.25 视觉风暴&#xff1a;NumPy驱动数据可视化 目录 #mermaid-svg-i3nKPm64ZuQ9UcNI {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-i3nKPm64ZuQ9UcNI .error-icon{fill:#552222;}#mermaid-svg-i3nKPm64ZuQ9UcNI …

鸟瞰欧洲(意境欧洲) 第一季

目录 《鸟瞰欧洲 第一季》纪录片笔记一、基本信息二、详细内容&#xff08;一&#xff09;剧集设置&#xff08;二&#xff09;各国亮点1. **荷兰**2. **意大利**3. **德国**4. **英国**5. **西班牙**6. **波兰** &#xff08;三&#xff09;拍摄特色 三、特色与评价四、总结五…

【MQ】探索 Kafka

高性能 消息的顺序性、顺序写磁盘 零拷贝 RocketMQ内部主要是使用基于mmap实现的零拷贝&#xff0c;用来读写文件 减少cpu的拷贝次数和上下文切换次数&#xff0c;实现文件的高效读写操作 Kafka 零拷贝 Kafka 使用到了 mmap 和 sendfile 的方式来实现零拷贝。分别对应 Jav…

供应链系统设计-供应链中台系统设计(十一)- 清结算中心概念片篇

概述 上篇供应链系统设计-供应链中台系统设计&#xff08;十&#xff09;- 清结算中心概念片篇文中提到了什么是金融客户、资金账号、资金账户、以及资金账号和资金账户的关系&#xff0c;如下图所示&#xff1a; 这些对于清算和结算来说都是前置的概念&#xff0c;本篇文章我…

allegro修改封闭图形线宽

说在前面 我们先把最优解说在前面,然后后面再说如果当时不熟悉软件的时候为了挖孔是用了shapes该怎么修改回来。 挖空最方便的方式是在cutout层画一个圆弧,下面开始图解,先add一个圆弧 z 最好是在画的时候就选择好层,如果忘记了后续再换回去也行,但好像软件有bug,此处并…

使用scikit-learn中的KNN包实现对鸢尾花数据集的预测

引言 K最近邻&#xff08;KNN&#xff09;算法是一种简单且直观的分类算法。它通过计算数据点之间的距离来对新样本进行分类。鸢尾花数据集是一个经典的机器学习数据集&#xff0c;包含了三种不同类型的鸢尾花&#xff0c;每种类型由四个特征&#xff08;花萼长度、花萼宽度、…

Hive:静态分区(分区语法,多级分区,分区的查看修改增加删除)

hive在建表时引入了partition概念。即在建表时&#xff0c;将整个表存储在不同的子目录中&#xff0c;每一个子目录对应一个分区。在查询时&#xff0c;我们就可以指定分区查询&#xff0c;避免了hive做全表扫描&#xff0c;从而提高查询率。 oracle和Hive分区的区别 orcale在…

基于FPGA的BT656解码

概述 BT656全称为“ITU-R BT.656-4”或简称“BT656”,是一种用于数字视频传输的接口标准。它规定了数字视频信号的编码方式、传输格式以及接口电气特性。在物理层面上,BT656接口通常包含10根线(在某些应用中可能略有不同,但标准配置为10根)。这些线分别用于传输视频数据、…

随机矩阵投影长度保持引理及其证明

原论文中的引理 2 \textbf{2} 2 引理 2 \textbf{2} 2的内容​​ &#x1f449;前提 1 1 1&#xff1a;设一个随机矩阵 S ( s i j ) ∈ R t d S\text{}(s_{ij})\text{∈}\mathbb{R}^{t\text{}d} S(sij​)∈Rtd&#xff0c;每个元素 s i j s_{ij} sij​独立同分布于 N ( 0 , …

详细解释java当中的所有知识点(前言及数据类型及变量)(第一部分)

会将java当中的所有的知识点以及相关的题目进行分享&#xff0c;这是其中的第一部分&#xff0c;用红色字体标注出重点&#xff0c;以及加粗的方式进行提醒 目录 一、Java语言概述 1.Java语言简介 2.语言优势 二、main方法 1.Java程序结构组成 2.运行Java程序 3.注释 4.…