首页 > 其他分享 >FreeRTOS中任务创建函数xTaskCreate()的解析

FreeRTOS中任务创建函数xTaskCreate()的解析

时间:2024-08-07 19:39:48浏览次数:14  
标签:初始化 函数 FreeRTOS 任务 pxNewTCB 堆栈 解析 xTaskCreate pxTopOfStack

目录

函数 xTaskCreate()

此函数用于使用动态的方式创建任务,任务的任务控制块以及任务的栈空间所需的内存,均由 FreeRTOS 从 FreeRTOS 管理的堆中分配,若使用此函数,需要在 FreeRTOSConfig.h 文件中将宏configSUPPORT_DYNAMIC_ALLOCATION 配置为 1。此函数创建的任务会立刻进入就绪态,由任务调度器调度运行。函数原型如下所示:

BaseType_t xTaskCreate(
        TaskFunction_t pxTaskCode,  /* 指向任务函数的指针,类型为 void (*TaskFunction_t)( void * ) */
        const char *const pcName,   /* 任务名,最大长度为 configMAX_TASK_NAME_LEN */
        const uint16_t usStackDepth, /* 任务堆栈大小,单位:字(注意,单位不是字节) */
        void *const pvParameters,   /* 传递给任务函数的参数,若无则填 NULL */
        UBaseType_t uxPriority,     /* 任务优先级,最大值为(configMAX_PRIORITIES-1) */
        TaskHandle_t *const pxCreatedTask); /* 任务句柄,任务成功创建后,pxCreatedTask会保存新任务的任务控制块 */

函数 xTaskCreate()的返回值为pdPASS则表示创建任务成功,返回errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY则表示任务创建失败(这个错误码实际上是-1)。

要使用该函数的前提是 configSUPPORT_DYNAMIC_ALLOCATION 支持动态内存分配的宏为1,默认情况下是为1的。

以下是该函数的解析:

BaseType_t xTaskCreate(TaskFunction_t pxTaskCode,
                       const char *const pcName,
                       const uint16_t usStackDepth,
                       void *const pvParameters,
                       UBaseType_t uxPriority,
                       TaskHandle_t *const pxCreatedTask)
{
    TCB_t *pxNewTCB;
    BaseType_t xReturn;

/* 判断任务堆栈是向上还是向下的增长方式,如果栈是向上增长的就先分配
任务控制块内存再分配任务堆栈的内存,反之如果是向下增长的就先分配任务
栈空间再分配任务控制块内存,宏 portSTACK_GROWTH 用于定义栈的生长方向,
STM32 的栈是向下生长的,因此宏 portSTACK_GROWTH 定义为-1 */
#if (portSTACK_GROWTH > 0)
    {
        /* 堆栈是向上增长方式就先分配任务控制块的内存再分配堆栈内存,这样TCB就会在堆栈底部指针之下,
        堆栈向上增长时就不会覆盖的任务控制块的内存区域 */
        pxNewTCB = (TCB_t *)pvPortMalloc(sizeof(TCB_t));

        if (pxNewTCB != NULL)
        {
            /* 如果任务控制块的内存分配好了那就分配任务堆栈内存 */
            pxNewTCB->pxStack = (StackType_t *)pvPortMalloc((((size_t)usStackDepth) * sizeof(StackType_t)));
            // 可能由于申请的任务堆栈内存太大了,分配失败了就释放控制块的内存
            if (pxNewTCB->pxStack == NULL)
            {
                /* Could not allocate the stack.  Delete the allocated TCB. */
                vPortFree(pxNewTCB);
                pxNewTCB = NULL;
            }
        }
    }
/* 否则堆栈是向下增长的,就先分配堆栈内存再分配任务控制块内存,
这样就能确保任务控制块在任务堆栈内存的上方,防止被被任务执行时的堆栈操作所破坏 */
#else  /* portSTACK_GROWTH */
    {
        StackType_t *pxStack;

        /* 分配任务堆栈空间 */
        pxStack = (StackType_t *)pvPortMalloc((((size_t)usStackDepth) * sizeof(StackType_t)));

        if (pxStack != NULL)
        {
            /* 分配任务控制块内存 */
            pxNewTCB = (TCB_t *)pvPortMalloc(sizeof(TCB_t));

            if (pxNewTCB != NULL)
            {
                /* Store the stack location in the TCB. */
                pxNewTCB->pxStack = pxStack;
            }
            else
            {
                /* The stack cannot be used as the TCB was not created.  Free
                it again. */
                vPortFree(pxStack);
            }
        }
        else
        {
            pxNewTCB = NULL;
        }
    }
#endif /* portSTACK_GROWTH */

    if (pxNewTCB != NULL)
    {
/* 若支持静态分配内存以及支持动态分配内存的宏都为 1
或者MPU(内存保护单元)宏为1(是否启用 MPU 来提供任务保护和隔离)*/
#if (tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0)
        {
            /* 在同时满足静态、动态创建任务的情况下,标记该任务是动态创建的,
            以便之后删除任务时内存释放的操作 */
            pxNewTCB->ucStaticallyAllocated = tskDYNAMICALLY_ALLOCATED_STACK_AND_TCB;
        }
#endif /* configSUPPORT_STATIC_ALLOCATION */
        /* 初始化新任务 */
        prvInitialiseNewTask(pxTaskCode, pcName, (uint32_t)usStackDepth, pvParameters, uxPriority, pxCreatedTask, pxNewTCB, NULL);
        /* 添加新任务到就绪列表当中 */
        prvAddNewTaskToReadyList(pxNewTCB);
        /* 返回pdPASS表示创建任务成功 */
        xReturn = pdPASS;
    }
    else
    {
        xReturn = errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY;
    }

    return xReturn;
}

1.该函数首先定义一个任务控制块的指针变量 pxNewTCB 用于保存该新创建的任务的信息和一个返回值 xReturn 用以指示任务创建成功与否。

2.随后进行判断栈的生长方向,根据不同的生长方向先后使用 pvPortMalloc 内存分配函数(这是FreeRTOS内部自己实现的内存分配函数)分配任务控制块的内存与任务堆栈的内存。

3.如果内存分配成功就对新任务进行初始化将新任务添加到就绪列表当中。

4.最后返回 xReturn 指示任务创建成功与否。

其中需要注意初始化新任务函数将新任务添加到就绪列表中的函数。

函数 prvInitialiseNewTask()

该函数是用来创建任务时初始化任务控制块中的成员变量的。

static void prvInitialiseNewTask(TaskFunction_t pxTaskCode,            /* 任务函数 */
                                 const char *const pcName,             /* 任务名 */
                                 const uint32_t ulStackDepth,          /* 任务栈大小 */
                                 void *const pvParameters,             /* 任务函数参数 */
                                 UBaseType_t uxPriority,               /* 任务优先级 */
                                 TaskHandle_t *const pxCreatedTask,    /* 返回的任务句柄 */
                                 TCB_t *pxNewTCB,                      /* 任务控制块 */
                                 const MemoryRegion_t *const xRegions) /* MPU 相关 */
{
    StackType_t *pxTopOfStack;
    UBaseType_t x;

/* 是否启用MPU(内存管理单元) */
#if (portUSING_MPU_WRAPPERS == 1)
    /* Should the task be created in privileged mode?
    任务应该在特权模式下创建吗? */
    BaseType_t xRunPrivileged;
    /* 在特权模式下创建任务 */
    if ((uxPriority & portPRIVILEGE_BIT) != 0U)
    {
        xRunPrivileged = pdTRUE;
    }
    else
    {
        xRunPrivileged = pdFALSE;
    }
    /* 将特权位清0 */
    uxPriority &= ~portPRIVILEGE_BIT;
#endif /* portUSING_MPU_WRAPPERS == 1 */

/* Avoid dependency on memset() if it is not required. */
/* 是否启用相关功能 */
#if ((configCHECK_FOR_STACK_OVERFLOW > 1) || (configUSE_TRACE_FACILITY == 1) || (INCLUDE_uxTaskGetStackHighWaterMark == 1))
    {
        /* Fill the stack with a known value to assist debugging. */
        /* 启用功能则进行内存块修改,将堆栈指针 pxNewTCB->pxStack
        所指向的堆栈区域初始化为指定的值,tskSTACK_FILL_BYTE(0xA5)。
        这可以用于在任务创建时清理堆栈,用已知值填充堆栈以帮助调试
        memset 是对已分配的内存块进行修改的函数,不负责内存的分配和释放 */
        (void)memset(pxNewTCB->pxStack, (int)tskSTACK_FILL_BYTE, (size_t)ulStackDepth * sizeof(StackType_t));
    }
#endif

/* 如果堆栈是向下增长的模式 */
#if (portSTACK_GROWTH < 0)
    {
        /* 获取任务栈顶指针 */
        pxTopOfStack = (StackType_t *)(((portPOINTER_SIZE_TYPE)pxTopOfStack) & (~((portPOINTER_SIZE_TYPE)portBYTE_ALIGNMENT_MASK)));

        /* 检查计算得到的堆栈顶部的对齐是否正确 */
        configASSERT((((portPOINTER_SIZE_TYPE)pxTopOfStack & (portPOINTER_SIZE_TYPE)portBYTE_ALIGNMENT_MASK) == 0UL));
    }
/*else堆栈是向上增长的*/
#else
    {
        /* 获取任务栈栈顶地址 */
        pxTopOfStack = pxNewTCB->pxStack;

        /* 检查计算得到的堆栈顶部的对齐是否正确 */
        configASSERT((((portPOINTER_SIZE_TYPE)pxNewTCB->pxStack & (portPOINTER_SIZE_TYPE)portBYTE_ALIGNMENT_MASK) == 0UL));

        // 计算栈顶地址最后位置
        pxNewTCB->pxEndOfStack = pxNewTCB->pxStack + (ulStackDepth - (uint32_t)1);
    }
#endif /* portSTACK_GROWTH */

    /* 将任务名字存储到任务控制块TCB中 */
    for (x = (UBaseType_t)0; x < (UBaseType_t)configMAX_TASK_NAME_LEN; x++)
    {
        pxNewTCB->pcTaskName[x] = pcName[x];

        if (pcName[x] == 0x00) /* 如果遇到字符串结束符号\0就跳出循环,因为已经复制任务名字结束了 */
        {
            break;
        }
        else
        {
            mtCOVERAGE_TEST_MARKER(); /* 用于测试代码覆盖率,并不影响实际功能 */
        }
    }

    /* 在任务名成员变量末尾加上'\0' */
    pxNewTCB->pcTaskName[configMAX_TASK_NAME_LEN - 1] = '\0';

    /* uxPriority 被用作数组索引,因此必须确保它不会太大
    检测任务优先级是否超过了最大的允许范围,超过了就对其进行限制 */
    if (uxPriority >= (UBaseType_t)configMAX_PRIORITIES)
    {
        uxPriority = (UBaseType_t)configMAX_PRIORITIES - (UBaseType_t)1U;
    }
    else
    {
        mtCOVERAGE_TEST_MARKER();
    }
    // 将实际或被限制的优先级进行赋值
    pxNewTCB->uxPriority = uxPriority;
/* 互斥量 */
#if (configUSE_MUTEXES == 1)
    {
        /* 用于解决优先级翻转问题 */
        pxNewTCB->uxBasePriority = uxPriority;
        /* 用于互斥信号量的递归功能 */
        pxNewTCB->uxMutexesHeld = 0;
    }
#endif /* configUSE_MUTEXES */
    /* 初始化状态、事件列表项 */
    vListInitialiseItem(&(pxNewTCB->xStateListItem));
    vListInitialiseItem(&(pxNewTCB->xEventListItem));

    /* 初始化任务状态列表项的拥有者为新的任务控制块 */
    listSET_LIST_ITEM_OWNER(&(pxNewTCB->xStateListItem), pxNewTCB);

    /* 设置列表项的值,因为事件列表总是按照优先级排序
    初始化事件列表项的值与任务优先级成反比(列表中的列表项按照列表项的值,以升序排序) */
    listSET_LIST_ITEM_VALUE(&(pxNewTCB->xEventListItem), (TickType_t)configMAX_PRIORITIES - (TickType_t)uxPriority);
    /* 初始化任务事件列表项的拥有者为任务控制块 */
    listSET_LIST_ITEM_OWNER(&(pxNewTCB->xEventListItem), pxNewTCB);

/* 判断是否允许任务临界区的嵌套深度的层数保存在任务控制块的字段当中,允许则清零层数 uxCriticalNesting */
#if (portCRITICAL_NESTING_IN_TCB == 1)
    {
        /* 任务单独临界区嵌套计数器初始化为 0 */
        pxNewTCB->uxCriticalNesting = (UBaseType_t)0U;
    }
#endif /* portCRITICAL_NESTING_IN_TCB */

/* 判断是否使用应用任务标签,允许则先使标签为NULL */
#if (configUSE_APPLICATION_TASK_TAG == 1)
    {
        pxNewTCB->pxTaskTag = NULL;
    }
#endif /* configUSE_APPLICATION_TASK_TAG */

/* 运行时间统计功能 */
#if (configGENERATE_RUN_TIME_STATS == 1)
    {
        /* 计数清零 */
        pxNewTCB->ulRunTimeCounter = 0UL;
    }
#endif /* configGENERATE_RUN_TIME_STATS */

/* 判断是否使用 MPU。MPU的目的是提供一种方便且安全的方式来管理任务的内存访问权限
从而增强系统的可靠性和安全性 */
#if (portUSING_MPU_WRAPPERS == 1)
    {
        vPortStoreTaskMPUSettings(&(pxNewTCB->xMPUSettings), xRegions, pxNewTCB->pxStack, ulStackDepth);
    }
#else
    {
        /* Avoid compiler warning about unreferenced parameter. */
        (void)xRegions;
    }
#endif
/* 判断是否进行初始化线程本地存储指针的数量和相应的操作 */
#if (configNUM_THREAD_LOCAL_STORAGE_POINTERS != 0)
    {
        for (x = 0; x < (UBaseType_t)configNUM_THREAD_LOCAL_STORAGE_POINTERS; x++)
        {
            pxNewTCB->pvThreadLocalStoragePointers[x] = NULL;
        }
    }
#endif
/* 任务通知功能 */
#if (configUSE_TASK_NOTIFICATIONS == 1)
    {
        pxNewTCB->ulNotifiedValue = 0;                          // 设置为0是为了在新任务开始时没有之前的通知状态,为任务的通知机制提供一个干净的起点
        pxNewTCB->ucNotifyState = taskNOT_WAITING_NOTIFICATION; // 表明此时任务不处于等待任务通知的状态
    }
#endif
/* 是否启用Newlib,使用Newlib可以确保每个任务拥有自己的独立状态,能够正确使用 Newlib 提供的库函数 */
#if (configUSE_NEWLIB_REENTRANT == 1)
    {
        /* Initialise this task's Newlib reent structure. */
        _REENT_INIT_PTR((&(pxNewTCB->xNewLib_reent)));
    }
#endif
/* 是否启用中断 延时等待功能,启用中断延时等待功能即可以在任务在进行delay延时还没结束的时候,此时可
能有一个紧急事件的到来,需要立即执行,所以中断延时等待功能就会立即打断此刻延时,任务不再是阻塞态
立即恢复到可运行状态 */
/* 注意:中断延时等待功能通常只能被任务本身使用来打断自己的延时函数的执行
而无法被中断本身使用来打断正在运行的中断 */
#if (INCLUDE_xTaskAbortDelay == 1)
    {
        pxNewTCB->ucDelayAborted = pdFALSE; // 表示任务目前没有中断延时等待
    }
#endif

#if (portUSING_MPU_WRAPPERS == 1)
    {
        pxNewTCB->pxTopOfStack = pxPortInitialiseStack(pxTopOfStack, pxTaskCode, pvParameters, xRunPrivileged);
    }
#else  /* portUSING_MPU_WRAPPERS */
    {
        /* 配置栈顶指针 */
        pxNewTCB->pxTopOfStack = pxPortInitialiseStack(pxTopOfStack, pxTaskCode, pvParameters);
    }
#endif /* portUSING_MPU_WRAPPERS */

    if ((void *)pxCreatedTask != NULL)
    {
        /* 将新任务的任务句柄赋值给 pxCreatedTask 这个参数,调用者就能通过任务句柄修改优先级,删除任务等 */
        *pxCreatedTask = (TaskHandle_t)pxNewTCB;
    }
    else
    {
        mtCOVERAGE_TEST_MARKER();
    }
}

1.先获取了任务栈顶指针,为方便后续任务栈的初始化进行操作。

2.将任务名字存储到任务控制块当中,这通常用于调试使用。

3.检测任务优先级是否超过设定范围,若超过则对其进行边界控制。

4.初始化任务的状态、事件列表项,初始化任务状态、事件列表项的所属者为当前任务控制块。

5.初始化任务栈栈顶指针。

6.最后将新的任务控制块赋值给形参pxCreatedTask。

该函数内部还通过调用 pxPortInitialiseStack() 函数初始化任务栈,就是往任务的栈中写入一些重要的信息,这些信息会在任务切换的时候被弹出到 CPU 寄存器中,以恢复任务的上下文信息,这些信息就包括 xPSR 寄存器的初始值、任务的函数地址(PC 寄存器)、任务错误退出函数地址(LR 寄存器)、任务函数的传入参数(R0 寄存器)以及为 R1~R12 寄存器预留空间,若使用了浮点单元,那么还会有EXC_RETURN 的值。同时该函数会返回更新后的栈顶指针。针对 ARM Cortex-M3 和针对 ARM Cortex-M4 和 ARM Cortex-M7 内核的函数 pxPortInitialiseStack() 稍有不同,原因在于 ARM Cortex-M4 和 ARM Cortex-M7 内核具有浮点单元,因此在任务栈中还需保存浮点寄存器的值。

函数 pxPortInitialiseStack()

StackType_t *pxPortInitialiseStack(StackType_t *pxTopOfStack, TaskFunction_t pxCode, void *pvParameters)
{
    /* 将栈顶指针递减一个单位,以便为接下来存储的数据腾出空间,这一步是为了在向下生长的堆栈分配正确的空间 */
    pxTopOfStack--;
    *pxTopOfStack = portINITIAL_XPSR; /* xPSR 该值表示 程序状态寄存器xPSR 的初始值 */

    pxTopOfStack--;
    /* 将任务函数的地址存储到目前栈顶指针位置 */
    *pxTopOfStack = ((StackType_t)pxCode) & portSTART_ADDRESS_MASK; /* PC */

    pxTopOfStack--;
    /* 将prvTaskExitError的值存储到栈顶指针所指向的位置,表示任务退出时跳转的地址LR */
    *pxTopOfStack = (StackType_t)prvTaskExitError; /* LR */

    /* 递减5个单位,为接下来的寄存器值腾出空间 */
    pxTopOfStack -= 5; /* R12, R3, R2 and R1. */

    /* 将任务参数 pvParameters 存储到栈顶指针所指向的位置,表示第一个通用寄存器 R0 的初始值 */
    *pxTopOfStack = (StackType_t)pvParameters; /* R0 */
    /* 递减8个单位,为后续寄存器值腾出空间 */

    pxTopOfStack -= 8; /* R11, R10, R9, R8, R7, R6, R5 and R4. */

    /* 返回更新好的栈顶指针,这样任务的堆栈在初始化过程中被正确的设置为了各个寄存器的初始值和其他必要的消息
    以便在上下文发生时能够正确的保存和恢复任务的状态(出栈入栈)*/
    return pxTopOfStack;
}

可以看到 pxPortInitialiseStack()函数主要是对栈指针进行一些必要的初始化与更新,最后返回更新好的栈顶指针并保存在新创建的任务控制块中,便于在上下文切换发生时能够正确的保存与恢复状态。

/* 配置栈顶指针 */
pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters );

函数 pxPortInitialiseStack()初始化后的任务栈如下图所示:

函数 prvAddNewTaskToReadyList()

函数 prvAddNewTaskToReadList()用于将新建的任务添加到就绪态任务列表中,具体的代码如下所示:

static void prvAddNewTaskToReadyList(TCB_t *pxNewTCB)
{
    /* 进入临界区,确保在操作就绪态任务列表时,中断不会访问列表 */
    taskENTER_CRITICAL();
    {
        /* 就绪列表中当前任务数量加1 */
        uxCurrentNumberOfTasks++;
        /* 此全局变量用于指示当前系统中处于就绪态任务中优先级最高的任务
        如果为空,即表示当前创建的任务为系统中的唯一的就绪任务 */
        if (pxCurrentTCB == NULL)
        {
            /* 没有其他就绪任务,或者所有其他任务都在挂起状态,那么优先级最高的就绪态任务就为当前任务 */
            pxCurrentTCB = pxNewTCB;
            /* 判断是否是第一个被创建的任务或者在没有其他活动任务的情况下创建的新任务,那么就执行
            初始化任务列表,仅在第一个任务被创建时执行一次 */
            if (uxCurrentNumberOfTasks == (UBaseType_t)1)
            {
                /* 第一次创建任务就初始化一些任务列表,比如延时任务列表、挂起任务列表、等待删除任务列表等等 */
                prvInitialiseTaskLists();
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }
        }
        /* 不是第一次创建的任务 */
        else
        {
            /* 如果调度器没有在运行,就判断当前任务优先级是否低于新创建的任务的任务优先级
            如果低于, 当任务调度器为运行时将 pxCurrentTCB 更新为优先级最高的就绪态任务 */
            if (xSchedulerRunning == pdFALSE)
            {
                if (pxCurrentTCB->uxPriority <= pxNewTCB->uxPriority)
                {
                    pxCurrentTCB = pxNewTCB;
                }
                else
                {
                    mtCOVERAGE_TEST_MARKER();
                }
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }
        }
        /* 这个是为新创建的任务分配一个唯一的任务号,每新创建一个任务都会++以确保每个任务都有一个唯一的标识号
        与uxCurrentNumberOfTasks不同,它是该列表中实际的任务数量 */
        uxTaskNumber++;
/* 可视化跟踪 */
#if (configUSE_TRACE_FACILITY == 1)
        {
            /* Add a counter into the TCB for tracing only. */
            /* 将该标识号保存的任务控制块中以便可视化跟踪使用,其实就是任务编号 */
            pxNewTCB->uxTCBNumber = uxTaskNumber;
        }
#endif /* configUSE_TRACE_FACILITY */
        /* 触发任务创建跟踪事件 */
        traceTASK_CREATE(pxNewTCB);
        /* 添加这个任务到就绪列表当中去 */
        prvAddTaskToReadyList(pxNewTCB);
        /* 宏定义的作用通常是为了提供编译器兼容性和可移植性 */
        portSETUP_TCB(pxNewTCB);
    }
    /* 退出临界区 */
    taskEXIT_CRITICAL();
    /* 任务调度器在运行,那么就需要判断,当前新建的任务优先级是否最高
       如果是,则需要切换任务 */
    if (xSchedulerRunning != pdFALSE)
    {
        /* 如果当前任务优先级小于新任务优先级那么应该切换到新任务运行了 */
        if (pxCurrentTCB->uxPriority < pxNewTCB->uxPriority)
        {
            /* 触发任务调度器进行任务切换,实际就是触发PensSV中断进行任务切换 */
            taskYIELD_IF_USING_PREEMPTION();
        }
        else
        {
            /* 测试代码覆盖率的工具 */
            mtCOVERAGE_TEST_MARKER();
        }
    }
    else
    {
        mtCOVERAGE_TEST_MARKER();
    }
}

总结

总的来说 xTaskCreate() 函数的流程就是:

1.根据堆栈生长方式为任务控制块(实际上就是一个结构体,里面保存着当前任务的各个信息)、任务运行时的栈大小进行分配空间。其中STM32的栈是向下增长的所以就先分配任务栈内存然后再分配任务控制块内存。

2.接着就调用 prvInitialiseNewTask() 函数对新创建的任务进行初始化,主要是对任务控制块中的成员进行初始化,以及初始化任务的状态列表项、事件列表项,内部再调用 pxPortInitialiseStack() 函数初始化任务栈指针,主要是更新栈指针为任务进行上下文切换时任务状态的保存与恢复做准备。

3.随后就是调用 prvAddNewTaskToReadyList() 函数将任务添加到就绪列表项中,当前任务就处于了就绪状态,若当前没有比它更高优先级的任务它将会被执行。

标签:初始化,函数,FreeRTOS,任务,pxNewTCB,堆栈,解析,xTaskCreate,pxTopOfStack
From: https://www.cnblogs.com/RAM-YAO/p/18347760

相关文章

  • 解析rasterfileio.dll:图像处理核心组件与修复指南
    rasterfileio.dll是一个动态链接库(DynamicLinkLibrary,简称DLL)文件,通常与图像处理软件、GIS(地理信息系统)软件或CAD(计算机辅助设计)软件有关。特别是,它可能与ESRI公司的ArcGIS软件相关联,ArcGIS是一种广泛使用的GIS平台,用于地图创建、地理数据分析、地理信息系统管理和空间可视化......
  • 将iap的接收升级数据部分移植到freertos系统中
    目录前言二、移植过程1.在任务中添加代码三、遇到的问题1.boot跳转卡死在TIM6的中断使能2.代码进入app后却卡死在boot的.s文件的B.处总结前言        在完成基于TCP服务器的iap裸机程序后得到一个新的任务,该任务让我把iap中通过TCP接收数据的代码移植......
  • Python 循环引用与内存泄漏:深度解析
    Python循环引用与内存泄漏:深度解析在Python编程中,循环引用和内存泄漏是两个需要特别注意的问题。本文将深入探讨Python中的循环引用现象、其导致的内存泄漏问题,并提供详细的解决思路与方法。同时,我们还将分析一些常见场景,并分享扩展与高级技巧,帮助读者全面理解和应对这一......
  • uni-app步骤条steps源码解析(十八)
    【背景】在显示中许多任务都不是一步执行完成的,需要分好多步进行;例如:网上购买一个商品需要先在网上下单-->当地物流人员取件-->中间物流转送--->目的地物流接收--->配送到买家手中;因此监控每个步骤的状态显的尤为重要。本期将为大家介绍步骤条控件steps。先看效果图:   ......
  • express如何解析multipart/form-data格式的数据
    最近在学习express,遇到了multipart/form-data请求参数接收不到的问题,控制台打印为{}空对象 问了下AI说是用express内置的方法app.use(express.urlencoded({extended:true}));或者下载body-parser使用app.use(bodyParser.urlencoded({extended:true}));结果都不行,控制台还是......
  • [MRCTF2020]套娃 php字符串解析绕过,jsfuck编码
    进来看到代码<!--//1st$query=$_SERVER['QUERY_STRING'];if(substr_count($query,'_')!==0||substr_count($query,'%5f')!=0){die('Y0uareSocutE!');}if($_GET['b_u_p_t']!=='23333'......
  • Leetcode 141. 环形链表(超详图解,解析过程)
    141.环形链表点击跳转leetcode原题给你一个链表的头节点head,判断链表中是否有环。如果链表中有某个节点,可以通过连续跟踪next指针再次到达,则链表中存在环。为了表示给定链表中的环,评测系统内部使用整数pos来表示链表尾连接到链表中的位置(索引从0开始)。注意:pos......
  • 基于vscode搭建freertos环境
    前言目前网上windows仿真freertos的资料都是比较久远的,不太适合现有的开发,因此重新整理了一下资料.目标:使用Vscode进行FreeRTOS开发和仿真.关键词:freertos,vscode,llvm,cmake,windows环境配置编译器目前使用的是llvm-MinGW-msvcrt:Releases·mstorsjo/llvm-mingw(g......
  • 深入解析:人工智能视觉利器OpenCV的技术奥秘
    人工智能视觉利器OpenCV的技术奥秘1.图像处理基础1.1数字图像基础知识1.1.1像素1.1.2色彩空间1.2图像处理中的常见任务1.2.1图像分割1.2.2图像识别1.2.3图像检测1.3颜色检测与图像处理的结合2.OpenCV简介2.1OpenCV的历史和发展早期发展持续演进开源社区的......
  • QT解析读取XML文件并显示在列表视图里
      背景:本地用数据库管理用户数据不方便,需要手动增删查改账户,存在安全风险,两个方案可供替代:1.调用接口来获取用户信息json,通过软件解析json字符串提取用户账号信息。2.直接跳过调用接口那一步,选择xml文件路径并解析。(由于第一种方案行不通,故使用第二种)步骤一:界面设计添加一......