本来计划是消息队列、信号量、任务通知、事件集、邮件的文章
但是因为自己调试的时候遇到了一个问题,还是把堆栈溢出问题放到前面来说
..增加临界区的使用说明                                                2021/11/7
..临界区的使用部分增加任务挂起与临界区说明                              2021/11/26

目录

  • 任务堆栈问题的出现
  • FreeRTOS任务栈溢出检测
    • vApplicationStackOverflowHook
  • FreeRTOS任务运行情况查询
    • vTaskList
  • 临界区的使用
    • 临界区API介绍
      • taskENTER_CRITICAL
      • taskEXIT_CRITICAL
      • taskENTER_CRITICAL_FROM_ISR
      • taskEXIT_CRITICAL_FROM_ISR( x )
    • 任务挂起与临界区区别

说明:FreeRTOS 专栏与我的 RT-Thread 专栏不同,我的 RT-Thread 专栏是从理论学习一步一步循序渐进,从 0 起步的 完整教学,而 FreeRTOS 更偏向于 我直接拿来使用,需要用到什么,然后引出知识点,在使用中发现问题,解然后再解决问题。

本 FreeRTOS 专栏记录的开发环境:

  1. FreeRTOS记录(一、熟悉开发环境以及CubeMX下FreeRTOS配置)
  2. FreeRTOS记录(二、FreeRTOS任务API认识和源码简析)
  3. FreeRTOS记录(三、FreeRTOS任务调度原理解析_Systick、PendSV、SVC)

任务堆栈问题的出现

为了写记录,自己先建立好了几个任务,其中就有 I2C读取温湿度传感器数据的任务,最初每个任务是使用的系统默认128字(512字节)作为默认大小,但是我感觉有些任务512字节有些浪费,比如提示系统运行的跑马灯,于是我把一些任务改成了64字大小,当然设置成为64,需要先把系统的最小任务大小设置为64。

刚开始我把这些简单的任务都设置成为64了,然后发现运行的时候,就会死掉,死掉的原因当然是堆栈溢出了,当然,很容易想到是我的 I2C 读取温湿度的任务导致的溢出,其他任务实在没干什么事情,
I2C任务和其他任务的大小如下:

osThreadDef(Led_toggle, Start_Led_toggle, osPriorityLow, 0,64);Led_toggleHandle = osThreadCreate(osThread(Led_toggle), NULL);/* definition and creation of printfTask */osThreadDef(printfTask, StartprintfTask, osPriorityLow, 0, 128);printfTaskHandle = osThreadCreate(osThread(printfTask), NULL);/* definition and creation of KeyTask */osThreadDef(KeyTask, StartKeyTask, osPriorityIdle, 0, 64);KeyTaskHandle = osThreadCreate(osThread(KeyTask), NULL);/* definition and creation of THread */osThreadDef(THread, StartTHread, osPriorityNormal, 0, 64);THreadHandle = osThreadCreate(osThread(THread), NULL);/* definition and creation of spiflash */osThreadDef(spiflash, Startspiflash, osPriorityIdle, 0, 64);spiflashHandle = osThreadCreate(osThread(spiflash), NULL);...
/* USER CODE END Header_StartTHread */
void StartTHread(void const * argument)
{/* USER CODE BEGIN StartTHread */float T=0,H=0;/*64会溢出字的内存空间不够SHT21 协议读取*//* Infinite loop */for(;;){SHT2X_THMeasure();T=(getTemperature()/100.0);H=(getHumidity()/100.0); osThreadSuspendAll();printf("\r\n%4.2f C\r\n%4.2f%%\r\n",T,H);osThreadResumeAll();osDelay(3000);}/* USER CODE END StartTHread */
}

后面还是把THread任务大小改成128,看上去每隔一定时间采集一定的次数,一切正常
(实际上有个bug就存在了,只是其他任务比较简单,看不出任何问题)。

本来准备把任务通知、消息队列的使用说明一下,然后开启了一个硬件定时器准备周期采集温湿度数据,从中断时发送任务通知,和从任务中发送任务通知。

开启定时器以后,定时器中断优先级改为6,定时器时间为1S一次中断,受FreeRTOS管理的中断优先级我是按照默认的设置为5:

通过我们前面的知识就知道,FreeRTOS进入临界区以后,硬件定时器的中断是会被屏蔽的。所以在检查问题的时候这个问题不用考虑。

在定时器中断中做了个简单的计数:

/*** @brief This function handles TIM3 global interrupt.*/
void TIM3_IRQHandler(void)
{/* USER CODE BEGIN TIM3_IRQn 0 */time3_count++;if(time3_count >= 10){time3_count = 0;}/* USER CODE END TIM3_IRQn 0 */HAL_TIM_IRQHandler(&htim3);/* USER CODE BEGIN TIM3_IRQn 1 *//* USER CODE END TIM3_IRQn 1 */
}

通过按键任务查看一下计数,这么做只是为了测试一切正常:

/* USER CODE END Header_StartKeyTask */
void StartKeyTask(void const * argument)
{/* USER CODE BEGIN StartKeyTask *//* Infinite loop */for(;;){if(HAL_GPIO_ReadPin(K2_GPIO_Port,K2_Pin) == 0){osDelay(10);if(HAL_GPIO_ReadPin(K2_GPIO_Port,K2_Pin) == 0){osThreadSuspendAll();printf("K2 pushed!!,time3_count is :%d\r\n",time3_count);osThreadResumeAll();while(HAL_GPIO_ReadPin(K2_GPIO_Port,K2_Pin) == 0){osDelay(10);}}}osDelay(1);   }/* USER CODE END StartKeyTask */
}void Start_Led_toggle(void const * argument)
{/* USER CODE BEGIN Start_Led_toggle *//* Infinite loop */for(;;){  osDelay(500);HAL_GPIO_TogglePin(LED1_GPIO_Port,LED1_Pin);  osDelay(500);HAL_GPIO_TogglePin(LED2_GPIO_Port,LED2_Pin);osDelay(500);}/* USER CODE END Start_Led_toggle */
}

于是问题就出来了。

现象是:我只要按了一次按键,I2C读取任务 和 LED灯任务就永远不会运行了,但是按键任务,包括硬件定时器数据都是正常的,就是程序没有死机,只是有2个任务死掉了。

能够想到应该是堆栈的问题,因为确实没有什么复杂的程序运行,优先级的问题我也考虑过了,每个任务是否会释放CPU控制权也都检测过。

还将FreeRTOS能够用的总堆大小改成了10K:

依然不行,最后还是将所有的任务大小还是改回了128字,任务看上去解决了,按键按下,能够获取数值,而且所有任务能够周期运行。

FreeRTOS任务栈溢出检测

然后想着FreeRTOS是有检测任务堆栈溢出功能的,于是找到相关的内容,参考了一些网上的资料。

在CubeMX中,选择使用堆栈溢出钩子函数,启动栈溢出检测方案为方案二,如下图:

vApplicationStackOverflowHook

选中以后生成的代码,多了一个vApplicationStackOverflowHook函数,直接在里面打印溢出的任务名,如下图:

/* Hook prototypes */
void vApplicationStackOverflowHook(xTaskHandle xTask, signed char *pcTaskName);/* USER CODE BEGIN 4 */
__weak void vApplicationStackOverflowHook(xTaskHandle xTask, signed char *pcTaskName)
{/* Run time stack overflow checking is performed ifconfigCHECK_FOR_STACK_OVERFLOW is defined to 1 or 2. This hook function iscalled if a stack overflow is detected. */printf("任务:%s 溢出\r\n",pcTaskName);
}

终于,问题的根本原因终于找到了:


顿时心中压抑一阵的疑问终于打开,温湿度读取使用128字的大小还是不够,那么解决办法当然还是增加这个任务的空间大小,改成192字,终于所有任务都正常了。

FreeRTOS任务运行情况查询

问题虽然已经解决,但是我们以后要怎样才能确定自己的任务大小呢,这时候,我们可以使用到可视化追踪功能,查看所有任务的运行情况和堆栈使用大小,在CubeMX中使能相应的功能,如下图:

然后还需要在Include definetions部分使能eTaskGetState:

vTaskList

然后在程序中,我们使用的是osThreadList 函数,其实也就是调用了vTaskList函数:

/**
* @brief   Lists all the current threads, along with their current state
*          and stack usage high water mark.
* @param   buffer   A buffer into which the above mentioned details
*          will be written
* @retval  status code that indicates the execution status of the function.
*/
osStatus osThreadList (uint8_t *buffer)
{#if ( ( configUSE_TRACE_FACILITY == 1 ) && ( configUSE_STATS_FORMATTING_FUNCTIONS == 1 ) )vTaskList((char *)buffer);
#endifreturn osOK;
}

打印任务查看任务状态:

我们为了说明问题,下面的代码中THread任务的大小还是128字,因为还没有讲任务通知,信号量之类的知识,我这里使用自己定义的一个标志位printfstate_on 来判断是否需要打印任务状态:

  /* definition and creation of Led_toggle */osThreadDef(Led_toggle, Start_Led_toggle, osPriorityLow, 0, 128);Led_toggleHandle = osThreadCreate(osThread(Led_toggle), NULL);/* definition and creation of printfTask */osThreadDef(printfTask, StartprintfTask, osPriorityLow, 0, 256);printfTaskHandle = osThreadCreate(osThread(printfTask), NULL);/* definition and creation of KeyTask */osThreadDef(KeyTask, StartKeyTask, osPriorityIdle, 0, 128);KeyTaskHandle = osThreadCreate(osThread(KeyTask), NULL);/* definition and creation of THread */osThreadDef(THread, StartTHread, osPriorityNormal, 0, 128);THreadHandle = osThreadCreate(osThread(THread), NULL);/* definition and creation of spiflash */osThreadDef(spiflash, Startspiflash, osPriorityIdle, 0, 128);spiflashHandle = osThreadCreate(osThread(spiflash), NULL);/* USER CODE END Header_StartprintfTask */
void StartprintfTask(void const * argument)
{/* USER CODE BEGIN StartprintfTask *//* Infinite loop */for(;;){if(printfstate_on){printfstate_on =0;uint8_t mytaskstatebuffer[500];printf("==================================\r\n");printf("任务名          任务状态    优先级     剩余栈   任务序号\r\n");osThreadList((uint8_t *)&mytaskstatebuffer);printf("%s\r\n",mytaskstatebuffer);}osDelay(10);//释放CPU占用权不要忘了延时}/* USER CODE END StartprintfTask */
}/* USER CODE END Header_StartKeyTask */
void StartKeyTask(void const * argument)
{/* USER CODE BEGIN StartKeyTask *//* Infinite loop */for(;;){if(HAL_GPIO_ReadPin(K2_GPIO_Port,K2_Pin) == 0){osDelay(10);if(HAL_GPIO_ReadPin(K2_GPIO_Port,K2_Pin) == 0){taskENTER_CRITICAL();printf("K2 pushed!!,time3_count is :%d\r\n",time3_count);taskEXIT_CRITICAL();printfstate_on = 1;while(HAL_GPIO_ReadPin(K2_GPIO_Port,K2_Pin) == 0){osDelay(10);}}}osDelay(1);}/* USER CODE END StartKeyTask */
}

结果如下图:


图中有2个地方有问题,第一个是温湿度中的剩余栈为0 ,溢出了,第二个是KeyTask的任务序号异常。

这个基本上可以确定是,THread任务和 KeyTask任务 在内存空间上是使用的连续的内存空间,一前一后,THread任务溢出导致改写了属于 KeyTask任务所在内存的数据,导致他的任务序号异常。这是内存空间相关的知识。

然后还需要说一下任务状态的意思:


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

我们把THread任务大小改成 192 字,运行结果如下:

为了更加说明任务堆栈大小的问题,根据推荐视频里面的教程我也做了测试,我们可以看到 printfTask 的剩余栈只剩下12个字,所以我们在 printfTask 中把剩余的空间占用测试一下:

测试结果如下,printfTask 任务栈大小多用了2个字:

这个剩余栈的大小在视频中定义了一个为0的数组,直接会占用任务使用的栈,我测试并没有出现这种小现象,可能是因为gcc编译器的优化处理。

任务状态的查看是需要占用一定的的内存空间的,尤其是当任务多了以后,我们这里的使用只是方便调试阶段查找确定问题。

临界区的使用

在我们上面的例子中,在调试中使用到printf的时候都加了任务挂起和任务恢复osThreadSuspendAll()osThreadResumeAll(),除了这种操作,更加建议的操作是使用临界区。合理使用了临界区也会使得程序减少很多不必要的bug。

临界区在前面文章我们已经提到过很多FreeRTOS的临界区屏蔽中断使用的是basepri寄存器,那么什么情况下使用临界区呢:

  • 用户不想被打断的代码
  • 调用公共函数的代码(不可重入函数)
  • 读取或者修改变量(全局变量)
  • 对时序有精准要求的操作
  • 使用硬件资源(比如I2C通讯,但是得注意在通讯中不能使用利用了systick的延时函数)

临界区API介绍

临界区的相关API如下:

API名称 API说明
taskENTER_CRITICAL 进入临界段,内部调用taskDISABLE_INTERRUPTS
taskEXIT_CRITICAL 退出临界段,内部调用taskENABLE_INTERRUPTS
taskENTER_CRITICAL_FROM_ISR 进入临界段(在中断中使用)
taskEXIT_CRITICAL_FROM_ISR(x) 退出临界段(在中断中使用)
taskENABLE_INTERRUPTS 开启中断
taskDISABLE_INTERRUPTS 关闭受FreeRTOS管理的中断

x:上次中断屏蔽寄存器操作值

#define taskENTER_CRITICAL()     portENTER_CRITICAL()
#define taskENTER_CRITICAL_FROM_ISR() portSET_INTERRUPT_MASK_FROM_ISR()/*** task. h** Macro to mark the end of a critical code region.  Preemptive context* switches cannot occur when in a critical region.** NOTE: This may alter the stack (depending on the portable implementation)* so must be used with care!** \defgroup taskEXIT_CRITICAL taskEXIT_CRITICAL* \ingroup SchedulerControl*/
#define taskEXIT_CRITICAL()         portEXIT_CRITICAL()
#define taskEXIT_CRITICAL_FROM_ISR( x ) portCLEAR_INTERRUPT_MASK_FROM_ISR( x )
#define portSET_INTERRUPT_MASK_FROM_ISR()        ulPortRaiseBASEPRI()
#define portCLEAR_INTERRUPT_MASK_FROM_ISR(x)    vPortSetBASEPRI(x)
#define portDISABLE_INTERRUPTS()                vPortRaiseBASEPRI()
#define portENABLE_INTERRUPTS()                 vPortSetBASEPRI(0)
#define portENTER_CRITICAL()                    vPortEnterCritical()
#define portEXIT_CRITICAL()                     vPortExitCritical()

taskENTER_CRITICAL

taskENTER_CRITICAL 最终还是调用了vPortRaiseBASEPRI函数实现屏蔽中断的操作:

/*----------------------------------------------*/
...
#define portENTER_CRITICAL()                    vPortEnterCritical()
/*----------------------------------------------*/
...
/*----------------------------------------------*/
void vPortEnterCritical( void )
{portDISABLE_INTERRUPTS();uxCriticalNesting++;/* This is not the interrupt safe version of the enter critical function soassert() if it is being called from an interrupt context.  Only APIfunctions that end in "FromISR" can be used in an interrupt.  Only assert ifthe critical nesting count is 1 to protect against recursive calls if theassert function also uses a critical section. */if( uxCriticalNesting == 1 ){configASSERT( ( portNVIC_INT_CTRL_REG & portVECTACTIVE_MASK ) == 0 );}
}
/*----------------------------------------------*/
...
/*----------------------------------------------*/
#define portDISABLE_INTERRUPTS()                vPortRaiseBASEPRI()
/*----------------------------------------------*/
...
/*----------------------------------------------*/portFORCE_INLINE static void vPortRaiseBASEPRI( void )
{uint32_t ulNewBASEPRI;__asm volatile(" mov %0, %1                                              \n" \"    msr basepri, %0                                         \n" \"    isb                                                     \n" \"    dsb                                                     \n" \:"=r" (ulNewBASEPRI) : "i" ( configMAX_SYSCALL_INTERRUPT_PRIORITY ) : "memory");
}

taskEXIT_CRITICAL

taskEXIT_CRITICAL最终还是调用了vPortSetBASEPRI函数实现使能中断的操作:

/*----------------------------------------------*/
...
#define portEXIT_CRITICAL()                     vPortExitCritical()
/*----------------------------------------------*/
...
/*----------------------------------------------*/
void vPortExitCritical( void )
{configASSERT( uxCriticalNesting );uxCriticalNesting--;if( uxCriticalNesting == 0 ){portENABLE_INTERRUPTS();}
}
/*----------------------------------------------*/
...
/*----------------------------------------------*/
#define portENABLE_INTERRUPTS()                 vPortSetBASEPRI(0)
/*----------------------------------------------*/
...
/*----------------------------------------------*/portFORCE_INLINE static void vPortSetBASEPRI( uint32_t ulNewMaskValue )
{__asm volatile("  msr basepri, %0 " :: "r" ( ulNewMaskValue ) : "memory");
}

taskENTER_CRITICAL_FROM_ISR

taskENTER_CRITICAL_FROM_ISR最终调用了ulPortRaiseBASEPRI函数实现屏蔽中断的操作:

/*----------------------------------------------*/
...
#define portSET_INTERRUPT_MASK_FROM_ISR()       ulPortRaiseBASEPRI()
/*----------------------------------------------*/
...
/*----------------------------------------------*/
portFORCE_INLINE static uint32_t ulPortRaiseBASEPRI( void )
{uint32_t ulOriginalBASEPRI, ulNewBASEPRI;__asm volatile("  mrs %0, basepri                                         \n" \"    mov %1, %2                                              \n" \"    msr basepri, %1                                         \n" \"    isb                                                     \n" \"    dsb                                                     \n" \:"=r" (ulOriginalBASEPRI), "=r" (ulNewBASEPRI) : "i" ( configMAX_SYSCALL_INTERRUPT_PRIORITY ) : "memory");/* This return will not be reached but is necessary to prevent compilerwarnings. */return ulOriginalBASEPRI;
}
/*-----------------------------------------------------------*/

taskEXIT_CRITICAL_FROM_ISR( x )

taskENTER_CRITICAL_FROM_ISRtaskEXIT_CRITICAL一样,调用了vPortSetBASEPRI函数实现使能中断的操作,只不过他多了一个参数,参数为 上次中断屏蔽寄存器操作值:

/*----------------------------------------------*/
...
#define portCLEAR_INTERRUPT_MASK_FROM_ISR(x)    vPortSetBASEPRI(x)
/*----------------------------------------------*/
...
/*----------------------------------------------*/
portFORCE_INLINE static void vPortSetBASEPRI( uint32_t ulNewMaskValue )
{__asm volatile("  msr basepri, %0 " :: "r" ( ulNewMaskValue ) : "memory");
}
/*-----------------------------------------------------------*/

知道这些以后,所以在任务使用printf的前后改成(中断中是不建议使用printf的):

 taskENTER_CRITICAL();printf("\r\n%4.2f C\r\n%4.2f%%\r\n",T,H);taskEXIT_CRITICAL();

任务挂起与临界区区别

在我们前面使用过vTaskSuspendAll()xTaskResumeAll()函数。

taskENTER_CRITICALvTaskSuspendAll()的区别在于:

taskENTER_CRITICALtaskENABLE_INTERRUPTS不仅不能切换任务,还不能响应中断(不能响应可屏蔽中断),而
vTaskSuspendAll() 他们只是不能进行任务切换,但是没有屏蔽中断。

taskENTER_CRITICALtaskENABLE_INTERRUPTS的区别在于:

使用taskDISABLE_INTERRUPTS()taskENABLE_INTERRUPTS()不支持嵌套(比如程序中前面调用了2个taskDISABLE_INTERRUPTS() ,后面使用一个taskENABLE也可以打开中断)。
使用taskENTER_CRITICAL()taskEXIT_CRITICAL()支持嵌套,调用了几个taskENTER_CRITICAL() 就得调用几个 taskEXIT_CRITICAL()才能使能中断。

在使用中发现问题,解决问题,思考问题!

FreeRTOS记录(四、FreeRTOS任务堆栈溢出问题和临界区)相关推荐

  1. FreeRTOS记录(六、FreeRTOS消息队列—Enocean模块串口通讯、RAM空间不足问题分析)

    本篇文章记录FreeRTOS消息队列的使用,我不从理论开始介绍,直接用起来,然后从发现的问题分析记录解决. ..补充RAM空间不足问题内容,增加FreeRTOS任务占用的RAM空间说明 2021/11 ...

  2. FreeRTOS记录(九、一个裸机工程转FreeRTOS的实例)

    记录一下一个实际项目由裸机程序改成FreeRTOS,以前产品的平台还是C8051单片机上面的程序, 硬件平台改成了STM32L051,同时使用STM32CubeMX生成的工程,使用FreeRTOS系统 ...

  3. FreeRTOS记录(七、FreeRTOS信号量、事件标志组、邮箱和消息队列、任务通知的关系)

    我们在前面单独介绍过FreeRTOS的任务通知和消息队列, 但是在FreeRTOS中任务间的通讯还有信号量,邮箱,事件组标志等可以使用 这篇文章就这些成员与消息队列和任务通知的关系进行说明分析 ..增 ...

  4. 程序编译保护堆栈溢出保护机制

    目录 一,前言 二,堆栈溢出原理 三,操作系统内置的安全机制 四,参考 一,前言 缓冲区溢出(buffer-overflow)是一种非常普遍.同时非常危险的漏洞,在各种操作系统.应用软件中广泛存在.缓 ...

  5. FreeRTOS中的任务堆栈溢出检测机制

    关注+星标公众号,不错过精彩内容 转自 | 麦克泰技术 在FreeRTOS中,每个任务都拥有自己的堆栈,该堆栈的大小由创建任务时xTaskCreate函数的函数参数所决定. 但当任务所使用的堆栈空间超 ...

  6. FreeRTOS检测堆栈溢出方法

    方法一:查询当前任务剩余堆栈 #define INCLUDE_uxTaskGetStackHighWaterMark 1getRemainStack[0] = uxTaskGetStackHighWa ...

  7. FreeRTOS 任务栈大小确定及其溢出检测

    以下转载自https://www.cnblogs.com/yangguang-it/p/7123727.html FreeRTOS 的任务栈设置 不管是裸机编程还是 RTOS 编程,栈的分配大小都非常 ...

  8. FreeRTOS记录(八、用软件定时器?还是硬件定时器?)

    FreeRTOS软件定时器,相对前面的内容来说,软件定时器还是比较简单的,我们简单测试一下 因为是简单介绍,所以原理和源码的分析不会那么详细,具体可以根据文中API查看源码 使用起来记住创建,启动,回 ...

  9. 系统在此应用程序堆栈溢出_Web应用程序:在开始之前选择正确的技术堆栈

    系统在此应用程序堆栈溢出 You have a great online business idea along with investors and a team ready to get behi ...

最新文章

  1. 转:SAP 零售业POS心得分享
  2. linux禁止客户端上传文件_图片/文件上传如此简单|macOS 图床客户端 uPic
  3. 多线程导出大规模excel文件
  4. Ajax技术应用方面
  5. pytorch下载mnist超时解决方案
  6. wpf 锁定计算机vb,wpf 窗体自动关闭
  7. 大数据和python哪个好_大数据语言之争:Java和python哪个好?
  8. Windbg/KD驱动调试点滴–将平时调试的一些小方法共享给大家 --------- 转
  9. MySQL实战45讲学习笔记:MySQL架构(第一讲)
  10. IOS用CGContextRef画各种图形(文字、圆、直线、弧线、矩形、扇形、椭圆、三角形、圆角矩形、贝塞尔曲线、图片)
  11. (76)FPGA面试题-Verilog实现下降沿检测
  12. 多品类适合电商美工收藏|品质页面PSD模板
  13. 2021年数智化高峰论坛圆满召开
  14. envi安装成功教程 附下载地址
  15. 如何不用密钥破解tableau
  16. R数据分析:如何绘制回归分析结果的森林图
  17. Java读中文乱码解决方案
  18. 【计蒜客 - 蓝桥训练】蒜厂年会(循环数列的最大子段和)
  19. Trie——51nod1526 分配笔名
  20. 二维码的前世今生 与 六大测试点梳理

热门文章

  1. 格兰杰因果关系检验(Granger Causality Test)
  2. dedecms(3)
  3. 制作elasticsearch 镜像_推荐的简单易用的PE系统有哪些?如何制作PE启动U盘?
  4. Install OpenERP in TurnkeyLinux Core
  5. XT800的文字数字不显示错误
  6. 【Verilog基础】【总线协议】AHB BURST传输可以提前终止吗?
  7. 解决Request method 'POST' not supported问题
  8. 在WinPE中用Windows通用安装器安装WinServer2008R2系统
  9. 50-50的定投策略回测
  10. 【毕业设计】自动泊车系统(APS)记录