浅析FreeRTOS源码:prvInitialiseNewTask函数

该函数所在位置FreeRTOS/task.c

1、函数名及参数分析

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 )

不得不说FreeRTOS源码的函数命名很耿直,读函数名就能知道这个函数是要干什么的。前缀prv表示private,表示这个函数有static修饰。Initialise New Task翻译过来就是初始化一个新的任务,也就是这个函数主要的功能了。
pxTaskCode是任务函数,前缀p表示pointer,前缀x表示stdint.h这个头文件中没有定义的数据类型;
pcName是字符串,可以给新建的任务取个名,c表示char;
ulStackDepth是栈深度,也就是栈大小,ul表示unsigned long;
pvParameters是任务函数的参数,v表示void;
uxPriority是任务优先级,其底层是unsigned long类型,数值不应超过FreeRTOSConfig.h中配置的configMAX_PRIORITIES这个宏,如果超过了,在这个函数中会自动把这个任务的优先级设置为configMAX_PRIORITIES-1
pxNewTCB是Task Control Block;
xRegions是内存区域的指针。

2、函数主体分析

	StackType_t * pxTopOfStack;
    UBaseType_t x;

上来先定义两个局部变量,StackType_t本质是u32类型,该变量是指向栈顶端的指针;UBaseType_t本质是unsigned long类型,用来遍历pcName中的每一个字符(在后面的分析中会看到)。

#if ( portUSING_MPU_WRAPPERS == 1 )
    BaseType_t xRunPrivileged;
    if( ( uxPriority & portPRIVILEGE_BIT ) != 0U )
    {
        xRunPrivileged = pdTRUE;
    }
    else
    {
        xRunPrivileged = pdFALSE;
    }
    uxPriority &= ~portPRIVILEGE_BIT;
#endif /* portUSING_MPU_WRAPPERS == 1 */

#if宏判断,若portable.h中,portUSING_MPU_WRAPPERS这个宏为1,即打开了内存保护单元(Memory Protection Unit),才会编译这段代码。
xRunPrivileged是指示该任务是否应该运行在特权模式的flag。在FreeRTOS.h中定义了portPRIVILEGE_BIT为0,所以不管uxPriority是多少,相与的结果都是0,所以上面的代码进入else的部分,这部分将xRunPrivileged设置为false。portPRIVILEGE_BIT取反,二进制表现为全1,与uxPriority相与,结果还是uxPriority本身。

/* 如用户未要求,不依赖memset函数。 */
#if ( tskSET_NEW_STACKS_TO_KNOWN_VALUE == 1 )
    (void) memset( pxNewTCB->pxStack, ( int ) tskSTACK_FILL_BYTE, ( size_t ) ulStackDepth * sizeof( StackType_t ) );
#endif /* tskSET_NEW_STACKS_TO_KNOWN_VALUE */

#if宏判断,如果tskSET_NEW_STACKS_TO_KNOWN_VALUE这个宏(新分配的栈写入特定值)置为1,则编译下面的代码。
memset函数,向pxStack指向的地址处写入tskSTACK_FILL_BYTE(其值为0xa5)。官方说这个操作是用于辅助debug的,个人猜测,可以通过测试内存中有多少控件被写入了0xa5这个数值,来判断任务创建时,栈有没有被正确初始化。

#if ( portSTACK_GROWTH < 0 )// 栈是向下增长的
{
    pxTopOfStack = &( pxNewTCB->pxStack[ ulStackDepth - ( uint32_t ) 1 ] );// 获取栈顶地址
    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 ) );

    #if ( configRECORD_STACK_HIGH_ADDRESS == 1 )// 记录栈的高地址flag打开
    {
        /* 记录栈的高地址,官方说这一步可用于辅助 debug */
        pxNewTCB->pxEndOfStack = pxTopOfStack;
    }
    #endif /* configRECORD_STACK_HIGH_ADDRESS */
}
#else /* portSTACK_GROWTH 栈是向上增长的 */
{
	/* 获取栈顶地址 */
    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 */

上面这部分代码计算栈顶地址。
最外层的宏判断根据portSTACK_GROWTH确定栈是向上增长还是向下增长,分别编译不同的代码段。
portBYTE_ALIGNMENT_MASK定义为0x0007。值得注意的是第4行代码,在将栈顶地址和对齐掩码做对位相与之前,对两个变量分别做了强制类型转换,体现了代码的严谨。

/* 把任务名存到TCB里 */
if( pcName != NULL )
{
    for( x = ( UBaseType_t ) 0; x < ( UBaseType_t ) configMAX_TASK_NAME_LEN; x++ )
    {
        pxNewTCB->pcTaskName[ x ] = pcName[ x ];// 逐个字符保存任务名字
        if( pcName[ x ] == ( char ) 0x00 )		// 如果遇到0x00说明到了字符串结尾
        {
            break;								// 此时跳出循环
        }
        else									// 没遇到0x00,说明没到结尾
        {
            mtCOVERAGE_TEST_MARKER();			// 测试代码
        }
    }
    /* 确保这个表示任务名字的字符串以0x00结尾,看,很严谨吧。 */
    pxNewTCB->pcTaskName[ configMAX_TASK_NAME_LEN - 1 ] = '\0';
}
else											// pcName是空指针,说明没有传入任务命名
{
    mtCOVERAGE_TEST_MARKER();					// 测试代码
}

还记得prvInitialiseNewTask函数接收的参数pcName,和函数最开始定义的变量x吗?
if判断pcName是否为空,非空,则遍历这个变量里的每一个字符,将其写入pxNewTCB结构体的pcTaskName成员变量。

值得一提的是mtCOVERAGE_TEST_MARKER();这个代码段,在FreeRTOS的源代码中出现了好多次。它被定义在FreeRTOS.h中:

#ifndef mtCOVERAGE_TEST_MARKER
    #define mtCOVERAGE_TEST_MARKER()
#endif

这里仅仅是定义了mtCOVERAGE_TEST_MARKER()这么一个宏。因此代码段中每次出现mtCOVERAGE_TEST_MARKER(),都会被替换为空。那如果,我在这个宏定义的后面添加上我想要测试的代码呢?例如:

#define mtCOVERAGE_TEST_MARKER() do{						\
										led_toggle();		\
										printf("flag\n");	\
								 } while(0U)

那么每次在代码中出现mtCOVERAGE_TEST_MARKER()宏,都会被替换成我想要测试的代码,这是一种常用的debug的方式。你学会了吗~
下面继续看源码。

/* 官方:用作数组索引,所以该值不能太大 */
configASSERT( uxPriority < configMAX_PRIORITIES );		// 检查任务优先级不大于配置的最大优先级
if( uxPriority >= ( UBaseType_t ) configMAX_PRIORITIES )// 如果任务优先级大于等于配置的最大优先级
{
	// 把任务优先级设置为配置的最大优先级-1
    uxPriority = ( UBaseType_t ) configMAX_PRIORITIES - ( UBaseType_t ) 1U;
}
else													// 任务优先级没有大于配置的最大优先级
{
    mtCOVERAGE_TEST_MARKER();							// 测试代码
}

上面这部分源码就是分析函数参数时提到的,如果传入的任务优先级大于config文件里配置的最大可用优先级数,则把优先级设置为最大可用优先级数-1。

pxNewTCB->uxPriority = uxPriority;	// 把作为参数传入的优先级保存到TCB结构体中
#if ( configUSE_MUTEXES == 1 )		// 配置了使用互斥锁
{
    pxNewTCB->uxBasePriority = uxPriority;// 作为参数传入的优先级保存到TCB中的“基本优先级”成员变量。至于这个成员变量在使用互斥锁时如何产生作用,还有待我探索,欢迎大佬们指教。
}
#endif /* configUSE_MUTEXES */

vListInitialiseItem( &( pxNewTCB->xStateListItem ) );// 把状态列表的container设置为NULL
vListInitialiseItem( &( pxNewTCB->xEventListItem ) );// 把事件列表的container设置为NULL

listSET_LIST_ITEM_OWNER( &( pxNewTCB->xStateListItem ), pxNewTCB );// 把状态列表的owner设置为NewTCB,也就是让状态列表知道自己的爹是谁。

/* 官方注释:事件列表按照优先级排序 */
listSET_LIST_ITEM_VALUE( &( pxNewTCB->xEventListItem ), ( TickType_t ) configMAX_PRIORITIES - ( TickType_t ) uxPriority ); // 把事件列表的ItemValue值设置为configMAX_PRIORITIES - uxPriority
/* 为什么要设置成最大值-当前值的形式呢?因为事件列表是按照优先级排序的,优先级高的任务应该排在前面,所以当前值越大,也就是优先级越高,最大值-当前值得到的索引就越小,而越小的索引在数组结构中是越靠前的。 */
listSET_LIST_ITEM_OWNER( &( pxNewTCB->xEventListItem ), pxNewTCB );// 让事件列表知道自己的爹是NewTCB。

#if ( portUSING_MPU_WRAPPERS == 1 )// 使用了内存保护单元
{
    vPortStoreTaskMPUSettings( &( pxNewTCB->xMPUSettings ), xRegions, pxNewTCB->pxStack, ulStackDepth );// 没找到函数的定义,但是看名字能知道函数作用是保存任务的MPU设定
}
#else
{
    /* 官方注释:避免编译器提示变量定义了但未使用 */
    ( void ) xRegions;
}
#endif

#if ( ( configUSE_NEWLIB_REENTRANT == 1 ) || ( configUSE_C_RUNTIME_TLS_SUPPORT == 1 ) )
{
    /* Allocate and initialize memory for the task's TLS Block. */
    configINIT_TLS_BLOCK( pxNewTCB->xTLSBlock );
}
#endif

/* 官方注释:初始化TCB任务,让任务看起来像是正在运行,但被调度器打断了。
 * 返回地址设置为任务函数的起始地址。一旦任务被初始化,栈顶端的变量将被更新。 */
#if ( portUSING_MPU_WRAPPERS == 1 )// 用MPU
{
    /* 官方注释:要是接口可以检测到栈溢出,那就把栈结束的地址也传给栈初始函数好了。 */
    #if ( portHAS_STACK_OVERFLOW_CHECKING == 1 )// 检测栈溢出的宏置1
    {
        #if ( portSTACK_GROWTH < 0 )// 栈向下增长
        {
            pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxNewTCB->pxStack, pxTaskCode, pvParameters, xRunPrivileged );// 初始化栈,保存栈顶地址
        }
        #else /* portSTACK_GROWTH 栈向上增长 */
        {
            pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxNewTCB->pxEndOfStack, pxTaskCode, pvParameters, xRunPrivileged );// 初始化栈,保存栈顶地址,注意观察跟上面传参的不同
        }
        #endif /* portSTACK_GROWTH */
    }
    #else /* portHAS_STACK_OVERFLOW_CHECKING 检测栈溢出的宏置0 */
    {
        pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters, xRunPrivileged );// 跟上面传参的区别在于少了传入栈结束的地址。
    }
    #endif /* portHAS_STACK_OVERFLOW_CHECKING */
}
#else /* portUSING_MPU_WRAPPERS 没用MPU */
{
    /* 这一部分跟上面使用了MPU的代码大同小异,就不重复注释了 */
    #if ( portHAS_STACK_OVERFLOW_CHECKING == 1 )
    {
        #if ( portSTACK_GROWTH < 0 )// 栈向下增长
        {
            pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxNewTCB->pxStack, pxTaskCode, pvParameters );
        }
        #else /* portSTACK_GROWTH 栈向上增长 */
        {
            pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxNewTCB->pxEndOfStack, pxTaskCode, pvParameters );
        }
        #endif /* portSTACK_GROWTH */
    }
    #else /* portHAS_STACK_OVERFLOW_CHECKING 没开启栈溢出检测 */
    {
        pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters );
    }
    #endif /* portHAS_STACK_OVERFLOW_CHECKING */
}
#endif /* portUSING_MPU_WRAPPERS */

if( pxCreatedTask != NULL )// 传入的句柄参数非空
{
    /* 官方注释:用匿名方式传出TCB句柄。通过TCB可以修改任务的优先级、删除和创建任务。 */
    *pxCreatedTask = ( TaskHandle_t ) pxNewTCB;
}
else
{
    mtCOVERAGE_TEST_MARKER();				// 测试代码
}

总结一下

prvInitialiseNewTask这个函数的主要作用是初始化并根据传入参数配置了pxNewTCB这个结构体,然后把这个结构体与传入的句柄相连接,让用户可以通过句柄来动态配置pxNewTCB结构体,进而配置这个任务。

  • 10
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值