zoukankan      html  css  js  c++  java
  • FreeRTOS-03-其它任务相关函数

    说明:
    本文仅作为学习FreeRTOS的记录文档,作为初学者肯定很多理解不对甚至错误的地方,望网友指正。
    FreeRTOS是一个RTOS(实时操作系统)系统,支持抢占式、合作式和时间片调度。适用于微处理器或小型微处理器的实时应用。
    本文档使用的FreeRTOS版本:FreeRTOS Kernel V10.4.1
    参考文档:《FreeRTOS_Reference_Manual_V10.0.0.pdf》《FreeRTOS_Real_Time_Kernel-A_Hands-On_Tutorial_Guide.pdf》《STM32F4 FreeRTOS开发手册_V1.1.pdf》
    参考视频:正点原子FreeRTOS手把手教学-基于STM32_哔哩哔哩_bilibili

    5 其它任务相关函数

    介绍一些任务辅助函数,方便查询任务的相关信息。

    5.1 设置任务优先级

    函数原型:

    #include “FreeRTOS.h”
    #include “task.h”
    void vTaskPrioritySet( TaskHandle_t pxTask, UBaseType_t uxNewPriority );
    

    函数描述:设置任务优先级。

    函数参数:pxTask设置任务优先级的任务句柄。如果任务设置自己的优先级,这个参数可以填为NULL。

    返回值:重新设置的任务优先级值。0表示最低优先级,configMAX_PRIORITIES – 1表示最高优先级。

    5.2 获取任务优先级

    函数原型:

    #include “FreeRTOS.h”
    #include “task.h”
    UBaseType_t uxTaskPriorityGet( TaskHandle_t pxTask );
    

    函数描述:获取任务优先级。

    函数参数:pxTask查询任务优先级的任务句柄。如果任务查询自己的优先级,这个参数可以填为NULL。

    返回值:查询任务的优先级值。

    测试代码:

    configSTACK_DEPTH_TYPE Task_STACK_SIZE = 5;
    UBaseType_t  Task_Priority = 12;
    
    void task_code(void *para)
    {
        static unsigned int cnt = 0;
    
        for (;;)
        {
            PRINT(" task cnt %u...", cnt);
            cnt++;
            vTaskDelay(1000);
        }
    }
    
    void task_func(void)
    {
        TaskHandle_t xhandle;
        UBaseType_t  uxCreatedPriorty, uxOurPriorty;
        
        if (xTaskCreate(task_code, "demo task", 
            Task_STACK_SIZE, NULL, Task_Priority,
            &xhandle) != pdPASS)
        {
            PRINT("creat task failed!
    ");
        } else
        {
            uxCreatedPriorty = uxTaskPriorityGet(xhandle);
            uxOurPriorty = uxTaskPriorityGet(NULL);
            PRINT("created task priority: %d", uxCreatedPriorty);
            PRINT("our task priority: %d", uxOurPriorty);
    
            vTaskPrioritySet(xhandle, 3);
            uxCreatedPriorty = uxTaskPriorityGet(xhandle);
            uxOurPriorty = uxTaskPriorityGet(NULL);
            PRINT("after changed, created task priority: %d", uxCreatedPriorty);
            PRINT("after changed, our task priority: %d", uxOurPriorty);
        }
    }
    

    默认创建任务优先级为12,然后更改任务优先级为3。

    编译,运行,结果如下:

    $ ./build/freertos-simulator 
    created task priority: 12
    our task priority: 12
    after changed, created task priority: 3
    after changed, our task priority: 3
     task cnt 0...
     task cnt 1...
     ... ...
    

    5.3 获取系统中所有任务的状态

    函数原型:

    #include “FreeRTOS.h”
    #include “task.h”
    UBaseType_t uxTaskGetSystemState( 
        TaskStatus_t * const pxTaskStatusArray,
    	const UBaseType_t uxArraySize,
    	unsigned long * const pulTotalRunTime );
    

    函数描述:获取系统中所有任务的任务状态。TaskStatus_t是一个保存任务状态信息的结构体,结构体中包括任务句柄、任务名称、堆栈、优先级等信息。要使用这个函数需要打开configUSE_TRACE_FACILITY宏。

    函数参数:pxTaskStatusArray:指向TaskStatus_t数据结构数组的首地址,每个任务至少包含一个TaskStatus_t结构体。任务的结构体数目可以使用uxTaskGetNumberOfTasks函数获得。

    uxArraySize:保存任务状态数组的数组的大小。

    pulTotalRunTime:如果configGENERATE_RUN_TIME_STATS配置为1,这个参数保存系统总的运行时间。

    返回值:统计到的任务状态的数目,也就是pxTaskStatusArray数组成员个数,如果uxArraySize参数太小,返回值可能为0。

    TaskStatus_t结构体定义如下:

    typedef struct xTASK_STATUS
    {
        TaskHandle_t xHandle;                       //任务句柄
        const char * pcTaskName;                    //任务名字
        UBaseType_t xTaskNumber;                    //任务编号
        eTaskState eCurrentState;                   //任务当前状态
        UBaseType_t uxCurrentPriority;            	//任务当前优先级  
        UBaseType_t uxBasePriority;                 //任务基础优先级
        uint32_t ulRunTimeCounter;                  //任务运行总时间
        StackType_t * pxStackBase;                  //堆栈基地址
        configSTACK_DEPTH_TYPE usStackHighWaterMark;//从任务创建以来任务堆栈剩余的最小大小,这个值太小说明堆栈有溢出的风险。
    } TaskStatus_t;
    

    5.4 获取系统中单个任务的状态

    函数原型:

    #include “FreeRTOS.h”
    #include “task.h”
    void vTaskGetTaskInfo( TaskHandle_t xTask,
    	TaskStatus_t *pxTaskStatus,
    	BaseType_t xGetFreeStackSpace,
    	eTaskState eState );
    

    函数描述:获取单个任务的任务状态。要使用这个函数需要打开configUSE_TRACE_FACILITY宏。

    函数参数:xTask:任务句柄;pxTaskStatus:存放获取的任务状态信息;

    xGetFreeStackSpace:TaskStatus_t结构中有个成员usStackHighWaterMark存放了任务创建以来任务堆栈剩余的最小大小,但是计算这个值需要一些时间,所以可以通过设置xGetFreeStackSpace值为pdFALSE来跳过这个步骤,当设置为pdTRUE才会检查堆栈剩余的最小大小。

    eState:TaskStatus_t结构中有个成员eCurrentState存放任务的当前运行状态,但是获取任务状态需要花费不少时间,可通过参数eState直接将任务状态赋值给eCurrentState。也可以将eStates设置为eInvalid,那么任务状态信息有函数vTaskGetInfo()函数获取。

    测试代码:

    configSTACK_DEPTH_TYPE Task_STACK_SIZE = 20;
    UBaseType_t  Task_Priority = 12;
    
    void task_code(void *para)
    {
        static unsigned int cnt = 0;
    
        for (;;)
        {
            PRINT(" task cnt %u...", cnt);
            cnt++;
            vTaskDelay(1000);
        }
    }
    
    void task_func(void)
    {
        TaskHandle_t xhandle;
        TaskStatus_t xTaskDetails;
        char *state_str[] = {"running", "ready", "blocked", "suspended", "deleted", "invalid"};
        
        if (xTaskCreate(task_code, "demo task", 
            Task_STACK_SIZE, NULL, Task_Priority,
            &xhandle) != pdPASS)
        {
            PRINT("creat task failed!
    ");
        } else
        {
            vTaskPrioritySet(xhandle, 3);
            vTaskGetTaskInfo(xhandle, &xTaskDetails, pdTRUE, eInvalid);
            PRINT("Task name           : %s", xTaskDetails.pcTaskName);
            PRINT("Task number         : %d", xTaskDetails.xTaskNumber);
            PRINT("Task CurrentState   : %s", state_str[xTaskDetails.eCurrentState]);
            PRINT("Task CurrentPriority: %d", xTaskDetails.uxCurrentPriority);
            PRINT("Task BasePriority   : %d", xTaskDetails.uxBasePriority);
            PRINT("Task RunTimeCounter : %d", xTaskDetails.ulRunTimeCounter);
            PRINT("Task StackBase      : %p", xTaskDetails.pxStackBase);
            PRINT("Task StackHighWaterMark: %u", xTaskDetails.usStackHighWaterMark);
        }
    }
    

    编译,运行,结果如下:

    $ ./build/freertos-simulator 
    Task name           : demo task
    Task number         : 1
    Task CurrentState   : running
    Task CurrentPriority: 3
    Task BasePriority   : 3
    Task RunTimeCounter : 0
    Task StackBase      : 0x2060010
    Task StackHighWaterMark: 15
     task cnt 0...
     task cnt 1...
    

    5.5 获取调度器运行状态

    函数原型:

    #include “FreeRTOS.h”
    #include “task.h”
    BaseType_t xTaskGetSchedulerState( void );
    

    函数描述:获取调度器当前的运行状态。使用这个函数需要将宏INCLUDE_xTaskGetSchedulerState置为1。

    函数参数:

    返回值:INCLUDE_xTaskGetSchedulerState:调度器未启动。调度器启动使用vTaskStartSchedule()函数完成,所以xTaskGetSchedulerState()函数在vTaskStartSchedule()函数之前调用会返回这个值。

    taskSCHEDULER_RUNNING:调度器正在运行。

    taskSCHEDULER_SUSPENDED:调度器被挂起,因为调用了vTaskSuspendAll()函数。

    5.6 获取任务运行状态

    函数原型:

    #include “FreeRTOS.h”
    #include “task.h”
    eTaskState eTaskGetState( TaskHandle_t pxTask );
    

    函数描述:获取任务的运行状态。使用此函数需要将INCLUDE_eTaskGetState宏置为1。

    函数参数:xTask:要获取的任务句柄

    返回值:任务的运行状态,eTaskState是一个枚举变量。

    /* Task states returned by eTaskGetState. */
    typedef enum
    {
        eRunning = 0,     /* A task is querying the state of itself, so must be running. */
        eReady,           /* The task being queried is in a read or pending ready list. */
        eBlocked,         /* The task being queried is in the Blocked state. */
        eSuspended,       /* The task being queried is in the Suspended state, 
                             or is in the Blocked state with an infinite time out. */
        eDeleted,         /* The task being queried has been deleted, but its TCB has not yet been freed. */
        eInvalid          /* Used as an 'invalid state' value. */
    } eTaskState;
    

    5.7 设置任务的tag(标签)值

    函数原型:

    #include “FreeRTOS.h”
    #include “task.h”
    void vTaskSetApplicationTaskTag( TaskHandle_t xTask, TaskHookFunction_t pxTagValue );
    

    函数描述:设置任务的标签值,标签值的具体函数和用法由用户决定。FreeRTOS内核不会使用这个标签值。如果要使用这个函数必须将configUSE_APPLICATION_TASK_TAG宏置为1。

    函数参数:xTask:任务句柄,如果设为NULL表示设置自身任务的标签值。

    pxTagValue:要设置的标签值,这是一个TaskHookFunction_t类型的函数指针,也可以设置为其它值。

    返回值:

    5.8 获取任务的tag(标签)值

    函数原型:

    #include “FreeRTOS.h”
    #include “task.h”
    TaskHookFunction_t xTaskGetApplicationTaskTag( TaskHandle_t xTask );
    

    函数描述:获取任务的tag(标签)值,任务控制块中有个成员变量pxTaskTag来保存任务的标签值。标签的功能由用户决定。内核一般不会使用这个标签值。使用这个函数需要将configUSE_APPLICATION_TASK_TAG 宏置为1。

    函数参数:xTask:任务句柄

    返回值:任务的标签值。

    5.9 获取当前任务句柄

    函数原型:

    #include “FreeRTOS.h”
    #include “task.h”
    TaskHandle_t xTaskGetCurrentTaskHandle( void );
    

    函数描述:获取当前任务(运行态)的任务的句柄。其实获取到的就是任务控制块。使用这个函数需要将INCLUDE_xTaskGetCurrentTaskHandle宏置为1。

    函数参数:

    返回值:当前任务的任务句柄。

    5.10 获取某个任务句柄

    函数原型:

    #include “FreeRTOS.h”
    #include “task.h”
    TaskHandle_t xTaskGetHandle( const char *pcNameToQuery );
    

    函数描述:根据任务名回去任务句柄。使用xTaskCreate()或者xTaskCreateStatic()函数创建任务时,有一个pcName参数,这个参数就是存放的任务名。xTaskGetHandle就是通过这个任务名来查询任务句柄的。使用这个函数必须将INCLUDE_xTaskGetHandle宏置为1。

    函数参数:任务名,C语言字符串。

    返回值:没有找到pcNameToQuery对应的任务返回NULL;找到了返回对应的任务句柄。

    5.11 获取空闲任务的句柄

    函数原型:

    #include “FreeRTOS.h”
    #include “task.h”
    TaskHandle_t xTaskGetIdleTaskHandle( void );
    

    函数描述:获取空闲任务的任务句柄。使用这个函数需要将INCLUDE_xTaskGetIdleTaskHandle宏置为1。

    函数参数:

    返回值:空闲函数的任务句柄。

    5.12 检查任务堆栈剩余大小

    函数原型:

    #include “FreeRTOS.h”
    #include “task.h”
    UBaseType_t uxTaskGetStackHighWaterMark( TaskHandle_t xTask );
    

    函数描述:每个任务都有自己的堆栈。当任务创建的时候指定了堆栈的大小。这个函数用于检查任务从创建到现在的历史剩余最小值。值越小说明堆栈溢出的可能性越大。FreeRTOS把这个历史最小值叫做”高水位线“。使用此函数需要将INCLUDE_uxTaskGetStackHighWaterMark宏设置为1。

    函数参数:xTask:要查询的任务句柄。参数为NULL表示查询任务自身。

    返回值:任务堆栈的”高水位线“值,也就是历史剩余最小值。

    5.13 获取任务名

    函数原型:

    #include “FreeRTOS.h”
    #include “task.h”
    char * pcTaskGetName( TaskHandle_t xTaskToQuery );
    

    函数描述:获取任务的任务名。

    函数参数:xTask:要查询的任务句柄,此参数为NULL表示查询自身任务。

    返回值:返回任务所对应的任务名。

    5.14 查询任务调度器计数器值

    函数原型:

    #include “FreeRTOS.h”
    #include “task.h”
    TickType_t xTaskGetTickCount( void );
    

    函数描述:查询任务调度器从启动到现在时间计数器xTickCount的值。

    函数参数:

    返回值:时间计数器xTickCount的值

    xTickCount:是系统的时钟节拍值,并不是真实的时间值。每个滴答定时器中断xTickCount就会加1,1秒滴答定时器中断多少次取决于宏configTICK_RATE_HZ。理论上xTickCount存在溢出的问题,但是这个溢出对内核没有影响,如果用户有使用的话就要考虑溢出。什么时候溢出取决于宏configUSE_16_BIT_TICKS,此宏为1的时候xTickCount为16位的变量,此宏为0的时候xTickCount为32位的变量。

    5.15 获取系统任务数

    函数原型:

    #include “FreeRTOS.h”
    #include “task.h”
    UBaseType_t uxTaskGetNumberOfTasks( void );
    

    函数描述:获取系统当前任务数

    函数参数:

    返回值:当前系统中的任务数量。包括挂起态、阻塞态、就绪态、空闲任务、运行态任务。

    5.16 获取任务列表

    函数原型:

    #include “FreeRTOS.h”
    #include “task.h”
    void vTaskList( char *pcWriteBuffer );
    

    函数描述:获取任务的详细信息。函数会创建一个表格来描述每个任务的详细信息。使用这个函数必须将configUSE_TRACE_FACILITY和configUSE_STATS_FORMATTING_FUNCTIONS宏置为1。

    函数参数:pcWriteBuffer保存任务状态信息的存储,这个存储要足够大。

    返回值:无。

    任务的详细信息如下:

    image-20210801205218591

    Name:任务名

    State:任务状态。X:任务正在执行;B:阻塞态;R:就绪态;S:挂起态;D:任务已经被删除。

    Priority:任务优先级

    Stack:任务堆栈”高水位线“,也就是堆栈历史最小剩余大小。

    Num:任务编号,这个编号是唯一的。当多个任务使用同一个任务名时,可以使用这个编号进行区分。

    5.17 统计任务的运行时间信息

    函数原型:

    #include “FreeRTOS.h”
    #include “task.h”
    void vTaskGetRunTimeStats( char *pcWriteBuffer );
    

    函数描述:获取任务的运行时间统计信息。使用这个函数必须将configGENERATE_RUN_TIME_STATS和configUSE_STATS_FORMATTING_FUNCTIONS宏置为1。

    如果宏configGENERATE_RUN_TIME_STATS设置为1,还需要定义下列的宏:

    portCONFIGURE_TIMER_FOR_RUN_TIME_STATS():此宏用来初始化一个外设来提供时间统计功能所需要的时基,一般是定时器/计数器。这个时基的分辨率一定要比FreeRTOS的系统时钟高,一般设置为比系统时钟高10~20倍。

    portGET_RUN_TIME_COUNTER_VALUE()或者portALT_GET_RUN_TIME_COUNTER_VALUE(Time):这两个宏实现其中一个即可。用于获取当前的时基的时间值。

    函数参数:pcWriteBuffer保存任务运行时间信息的存储,这个存储要足够大。

    返回值:

    任务的运行时间统计信息如下:

    image-20210801210902871

    任务的统计信息提供了每个任务获取到的CPU使用权总的时间。表里面提供了每个任务的运行时间和其所占总时间的百分比。

    5.18 设置线程本地存储指针的值

    函数原型:

    #include “FreeRTOS.h
    #include “task.h”
    void vTaskSetThreadLocalStoragePointer( TaskHandle_t xTaskToSet,
                                           BaseType_t xIndex, void *pvValue );
    

    函数描述:此函数用于设置线程本地存储指针的值,每个任务都有自己的指针数组来作为线程本地存储,使用这些线程本地存储可以用来在任务控制块中存储一些应用信息,这些信息只属于任务自己。线程本地存储指针数组的大小由configNUM_THREAD_LOCAL_STORAGE_POINTERS宏决定。如果要使用这个函数,这个宏就能设置为0。

    函数参数:xTaskToSet:任务句柄,如果设为NULL表示自身任务。xIndex:要设置的线程本地存储指针数组的索引。pvValue:要存储的值。

    返回值:

    5.19 获取线程本地存储指针的值

    函数原型:

    #include “FreeRTOS.h”
    #include “task.h”
    void *pvTaskGetThreadLocalStoragePointer( TaskHandle_t xTaskToQuery,
                                             BaseType_t xIndex );
    

    函数描述:此函数用于获取线程本地存储指针的值,如果要使用这个函数configNUM_THREAD_LOCAL_STORAGE_POINTERS宏不能设置为0。

    函数参数:xTaskToQuery:任务句柄,如果设为NULL表示自身任务。xIndex:要设置的线程本地存储指针数组的索引。

    返回值:获取到的线程本地存储指针的值。

    5.19 任务状态查询API函数实验

    目的:学习使用任务状态查询相关API函数,包括uxTaskGetSystemState()、vTaskGetInfo()、eTaskGetState()、vTaskList()。

    设计:创建query_task,用于任务状态和信息查询任务,此任务中使用任务状态和信息相关的API函数。创建print_task:间隔1s不停打印计数信息,提示系统正在运行。

    测试代码:

    configSTACK_DEPTH_TYPE Print_Task_STACK_SIZE = 5;
    UBaseType_t  Print_Task_Priority = 1;
    TaskHandle_t Print_xhandle;
    
    configSTACK_DEPTH_TYPE Query_Task_STACK_SIZE = 20;
    UBaseType_t  Query_Task_Priority = 2;
    TaskHandle_t Query_xhandle;
    
    char InfoBuffer[1000];
    void print_task_code(void *para)
    {
        static unsigned int cnt = 0;
    
        for (;;)
        {
            PRINT(" print task cnt %u...", cnt);
            cnt++;
            vTaskDelay(1000);
        }
    }
    
    void query_task_code(void *para)
    {
        unsigned int totalRunTime;
        UBaseType_t  arraySize, x;
        TaskStatus_t *statusArray;
    
        PRINT("----------- uxTaskGetSystemState() ---------------");
        arraySize = uxTaskGetNumberOfTasks();
        statusArray = pvPortMalloc(arraySize * sizeof(TaskStatus_t));
        if (statusArray != NULL)
        {
            arraySize = uxTaskGetSystemState(statusArray, arraySize, &totalRunTime);
            PRINT("TaskName      TaskPriority    TaskNumber");
            for (x = 0; x < arraySize; x++)
            {
                PRINT("%-16s%-16d%-2d",
                    statusArray[x].pcTaskName,
                    statusArray[x].uxCurrentPriority,
                    statusArray[x].xTaskNumber);
            }
        }
        vPortFree(statusArray);
        PRINT("----------- uxTaskGetSystemState() end -----------
    ");
        
        PRINT("----------- vTaskGetInfo() ----------------");
        TaskHandle_t taskHandle;
        TaskStatus_t taskStatus;
        taskHandle = xTaskGetHandle("print task");
        vTaskGetInfo(taskHandle, &taskStatus, pdTRUE, eInvalid);
        PRINT("              task name: %s", taskStatus.pcTaskName);
        PRINT("            task number: %d", taskStatus.xTaskNumber);
        PRINT("             task state: %d", taskStatus.eCurrentState);
        PRINT("  task current priority: %d", taskStatus.uxCurrentPriority);
        PRINT("     task base priority: %d", taskStatus.uxBasePriority);
        PRINT("task stack base address: 0x%x", taskStatus.pxStackBase);
        PRINT("   task high water mark: %d", taskStatus.usStackHighWaterMark);
        PRINT("  task run time counter: %d",taskStatus.ulRunTimeCounter);
        PRINT("----------- vTaskGetInfo() end ------------
    ");
    
        PRINT("----------- eTaskGetState() ----------------");
        eTaskState taskState;
        char *state_str[] = {"running", "ready", "blocked", "suspended", "deleted", "invalid"};
        taskHandle = xTaskGetHandle("query task");
        taskState = eTaskGetState(taskHandle);
        PRINT("task state:%s", state_str[taskState]);
        PRINT("----------- eTaskGetState() end ------------
    ");
    
        PRINT("----------- vTaskList() ----------------");
        PRINT("Name         State  Priority   Stack   Num");
        PRINT("******************************************");
        vTaskList(InfoBuffer);
        PRINT("%s", InfoBuffer);
        PRINT("----------- vTaskList() end ------------
    ");
        
        for (;;)
        {
            static unsigned int cnt = 0;
            PRINT(" query task cnt %u...", cnt);
            cnt++;
            vTaskDelay(1000);
        }
    }
    
    void creat_task(void)
    {
        if (xTaskCreate(print_task_code, "print task", 
            Print_Task_STACK_SIZE, NULL, Print_Task_Priority,
            &Print_xhandle) != pdPASS)
        {
            PRINT("creat task failed!
    ");
        }
        
        if (xTaskCreate(query_task_code, "query task", 
            Query_Task_STACK_SIZE, NULL, Query_Task_Priority,
            &Query_xhandle) != pdPASS)
        {
            PRINT("creat task failed!
    ");
        }
        vTaskStartScheduler();
    }
    

    编译、运行,结果如下:

    $ ./build/freertos-simulator 
    ----------- uxTaskGetSystemState() ---------------
    TaskName      TaskPriority    TaskNumber
    query task      2               2 
    print task      1               1 
    IDLE            0               3 
    Tmr Svc         30              4 
    ----------- uxTaskGetSystemState() end -----------
    
    ----------- vTaskGetInfo() ----------------
                  task name: print task
                task number: 1
                 task state: 1
      task current priority: 1
         task base priority: 1
    task stack base address: 0x868010
       task high water mark: 0
      task run time counter: 0
    ----------- vTaskGetInfo() end ------------
    
    ----------- eTaskGetState() ----------------
    task state:running
    ----------- eTaskGetState() end ------------
    
    ----------- vTaskList() ----------------
    Name         State  Priority   Stack   Num
    ******************************************
    query task      X       2       15      2
    print task      R       1       0       1
    IDLE            R       0       65      3
    Tmr Svc         B       30      135     4
    
    ----------- vTaskList() end ------------
    
     query task cnt 0...
     print task cnt 0...
     query task cnt 1...
     print task cnt 1...
    

    可以得到,“print task”任务处于就绪态,任务优先级为1,栈空间已经用完了。任务编号为1。“query task”处于运行态,任务优先级为2,栈空间剩余15,任务编号为2。

  • 相关阅读:
    Android 按键消息处理Android 按键消息处理
    objcopy
    SQLite多线程读写实践及常见问题总结
    android动画坐标定义
    Android动画效果translate、scale、alpha、rotate
    Android公共库(缓存 下拉ListView 下载管理Pro 静默安装 root运行 Java公共类)
    Flatten Binary Tree to Linked List
    Distinct Subsequences
    Populating Next Right Pointers in Each Node II
    Populating Next Right Pointers in Each Node
  • 原文地址:https://www.cnblogs.com/mrlayfolk/p/15088125.html
Copyright © 2011-2022 走看看