在freertos基础系列《freertos系列第10篇—freertos任务创建和删除》中介绍了任务创建api函数xtaskcreate(),我们这里先回顾一下这个函数的声明:
basetype_t xtaskcreate( taskfunction_tp vtaskcode, const char * constpcname, unsigned short usstackdepth, void *pvparameters, ubasetype_t uxpriority, taskhandle_t *pvcreatedtask );
这个api函数的作用是创建新的任务并将它加入到任务就绪列表,函数参数含义为:
pvtaskcode:函数指针,指向任务函数的入口。任务永远不会返回(位于死循环内)。该参数类型taskfunction_t定义在文件projdefs.h中,定义为:typedef void(*taskfunction_t)( void * ),即参数为空指针类型并返回空类型。pcname:任务描述。主要用于调试。字符串的最大长度(包括字符串结束字符)由宏configmax_task_name_len指定,该宏位于freertosconfig.h文件中。usstackdepth:指定任务堆栈大小,能够支持的堆栈变量数量(堆栈深度),而不是字节数。比如,在16位宽度的堆栈下,usstackdepth定义为100,则实际使用200字节堆栈存储空间。堆栈的宽度乘以深度必须不超过size_t类型所能表示的最大值。比如,size_t为16位,则可以表示堆栈的最大值是65535字节。这是因为堆栈在申请时是以字节为单位的,申请的字节数就是堆栈宽度乘以深度,如果这个乘积超出size_t所表示的范围,就会溢出,分配的堆栈空间也不是我们想要的。pvparameters:指针,当任务创建时,作为一个参数传递给任务。uxpriority:任务的优先级。具有mpu支持的系统,可以通过置位优先级参数的portprivilege_bit位,随意的在特权(系统)模式下创建任务。比如,创建一个优先级为2的特权任务,参数uxpriority可以设置为 ( 2 | portprivilege_bit )。pvcreatedtask:用于回传一个句柄(id),创建任务后可以使用这个句柄引用任务。
虽然xtaskcreate()看上去很像函数,但其实是一个宏,真正被调用的函数是xtaskgenericcreate(),xtaskcreate()宏定义如下所示:
#define xtaskcreate( pvtaskcode, pcname, usstackdepth,pvparameters, uxpriority, pxcreatedtask ) \ xtaskgenericcreate( ( pvtaskcode ),( pcname ), ( usstackdepth ), ( pvparameters ), ( uxpriority ), ( pxcreatedtask), ( null ), ( null ), ( null ) )
可以看到,xtaskcreate比xtaskgenericcreate少了三个参数,在宏定义中,这三个参数被设置为null。这三个参数用于使用静态变量的方法分配堆栈、任务tcb空间以及设置mpu相关的参数。一般情况下,这三个参数是不使用的,所以任务创建宏xtaskcreate定义的时候,将这三个参数对用户隐藏了。接下来的章节中,为了方便,我们还是称xtaskcreate()为函数,虽然它是一个宏定义。
上面我们提到了任务tcb(任务控制块),这是一个需要重点介绍的关键点。它用于存储任务的状态信息,包括任务运行时的环境。每个任务都有自己的任务tcb。任务tcb是一个相对比较大的数据结构,这也是情理之中的,因为与任务相关的代码占到整个freertos代码量的一半左右,这些代码大都与任务tcb相关,我们先来介绍一下任务tcb数据结构的定义:
typedef struct tsktaskcontrolblock{ volatile stacktype_t *pxtopofstack; /*当前堆栈的栈顶,必须位于结构体的第一项*/ #if ( portusing_mpu_wrappers == 1 ) xmpu_settings xmpusettings; /*mpu设置,必须位于结构体的第二项*/ #endif listitem_t xstatelistitem; /*任务的状态列表项,以引用的方式表示任务的状态*/ listitem_t xeventlistitem; /*事件列表项,用于将任务以引用的方式挂接到事件列表*/ ubasetype_t uxpriority; /*保存任务优先级,0表示最低优先级*/ stacktype_t *pxstack; /*指向堆栈的起始位置*/ char pctaskname[ configmax_task_name_len ];/*任务名字*/ #if ( portstack_growth > 0 ) stacktype_t *pxendofstack; /*指向堆栈的尾部*/ #endif #if ( portcritical_nesting_in_tcb == 1 ) ubasetype_t uxcriticalnesting; /*保存临界区嵌套深度*/ #endif #if ( configuse_trace_facility == 1 ) ubasetype_t uxtcbnumber; /*保存一个数值,每个任务都有唯一的值*/ ubasetype_t uxtasknumber; /*存储一个特定数值*/ #endif #if ( configuse_mutexes == 1 ) ubasetype_t uxbasepriority; /*保存任务的基础优先级*/ ubasetype_t uxmutexesheld; #endif #if ( configuse_application_task_tag == 1 ) taskhookfunction_t pxtasktag; #endif #if( confignum_thread_local_storage_pointers > 0 ) void *pvthreadlocalstoragepointers[confignum_thread_local_storage_pointers ]; #endif #if( configgenerate_run_time_stats == 1 ) uint32_t ulruntimecounter; /*记录任务在运行状态下执行的总时间*/ #endif #if ( configuse_newlib_reentrant == 1 ) /* 为任务分配一个newlibreent结构体变量。newlib是一个c库函数,并非freertos维护,freertos也不对使用结果负责。如果用户使用newlib,必须熟知newlib的细节*/ struct _reent xnewlib_reent; #endif #if( configuse_task_notifications == 1 ) volatile uint32_t ulnotifiedvalue; /*与任务通知相关*/ volatile uint8_t ucnotifystate; #endif #if( configsupport_static_allocation == 1 ) uint8_t ucstaticallocationflags; /* 如果堆栈由静态数组分配,则设置为pdtrue,如果堆栈是动态分配的,则设置为pdfalse*/ #endif #if( include_xtaskabortdelay == 1 ) uint8_t ucdelayaborted; #endif } tsktcb; typedef tsktcb tcb_t;
下面我们详细的介绍这个数据结构的主要成员:
指针pxtopofstack必须位于结构体的第一项,指向当前堆栈的栈顶,对于向下增长的堆栈,pxtopofstack总是指向最后一个入栈的项目。
如果使用mpu,xmpusettings必须位于结构体的第二项,用于mpu设置。
接下来是状态列表项xstatelistitem和事件列表项xeventlistitem,我们在上一章介绍列表和列表项的文章中提到过:列表被freertos调度器使用,用于跟踪任务,处于就绪、挂起、延时的任务,都会被挂接到各自的列表中。调度器就是通过把任务tcb中的状态列表项xstatelistitem和事件列表项xeventlistitem挂接到不同的列表中来实现上述过程的。在task.c中,定义了一些静态列表变量,其中有就绪、阻塞、挂起列表,例如当某个任务处于就绪态时,调度器就将这个任务tcb的xstatelistitem列表项挂接到就绪列表。事件列表项也与之类似,当队列满的情况下,任务因入队操作而阻塞时,就会将事件列表项挂接到队列的等待入队列表上。
uxpriority用于保存任务的优先级,0为最低优先级。任务创建时,指定的任务优先级就被保存到该变量中。
指针pxstack指向堆栈的起始位置,任务创建时会分配指定数目的任务堆栈,申请堆栈内存函数返回的指针就被赋给该变量。很多刚接触freertos的人会分不清指针pxtopofstack和pxstack的区别,这里简单说一下:pxtopofstack指向当前堆栈栈顶,随着进栈出栈,pxtopofstack指向的位置是会变化的;pxstack指向当前堆栈的起始位置,一经分配后,堆栈起始位置就固定了,不会被改变了。那么为什么需要pxstack变量呢,这是因为随着任务的运行,堆栈可能会溢出,在堆栈向下增长的系统中,这个变量可用于检查堆栈是否溢出;如果在堆栈向上增长的系统中,要想确定堆栈是否溢出,还需要另外一个变量pxendofstack来辅助诊断是否堆栈溢出,后面会讲到这个变量。
字符数组pctaskname用于保存任务的描述或名字,在任务创建时,由参数指定。名字的长度由宏configmax_task_name_len(位于freertosconfig.h中)指定,包含字符串结束标志。
如果堆栈向上生长(portstack_growth > 0),指针pxendofstack指向堆栈尾部,用于检验堆栈是否溢出。
变量uxcriticalnesting用于保存临界区嵌套深度,初始值为0。
接下来两个变量用于可视化追踪,仅当宏configuse_trace_facility(位于freertosconfig.h中)为1时有效。变量uxtcbnumber存储一个数值,在创建任务时由内核自动分配数值(通常每创建一个任务,值增加1),每个任务的uxtcbnumber值都不同,主要用于调试。变量uxtasknumber用于存储一个特定值,与变量uxtcbnumber不同,uxtasknumber的数值不是由内核分配的,而是通过api函数vtasksettasknumber()来设置的,数值由函数参数指定。
如果使用互斥量(configuse_mutexes == 1),任务优先级被临时提高时,变量uxbasepriority用来保存任务原来的优先级。
变量ucstaticallocationflags也需要说明一下,我们前面说过任务创建api函数xtaskcreate()只能使用动态内存分配的方式创建任务堆栈和任务tcb,如果要使用静态变量实现任务堆栈和任务tcb就需要使用函数xtaskgenericcreate()来实现。如果任务堆栈或任务tcb由静态数组和静态变量实现,则将该变量设置为pdtrue(任务堆栈空间由静态数组变量实现时为0x01,任务tcb由静态变量实现时为0x02,任务堆栈和任务tcb都由静态变量实现时为0x03),如果堆栈是动态分配的,则将该变量设置为pdfalse。
到这里任务tcb的数据结构就讲完了,下面我们用一个例子来讲述任务创建的过程,为方便起见,假设被创建的任务叫“任务a”,任务函数为vtask_a():
taskhandle_t xhandle; xtaskcreate(vtask_a,”task a”,120,null,1,&xhandle);
这里创建了一个任务,任务优先级为1,由于硬件平台是32为架构,所以指定了120*4=480字节的任务堆栈,向任务函数vtask_a()传递的参数为空(null),任务句柄由变量xhandle保存。当这个语句执行后,任务a被创建并加入就绪任务列表,我们这章的主要目的,就是看看这个语句在执行过程中,发生了什么事情。
1.创建任务堆栈和任务tcb
调用函数prvallocatetcbandstack()创建任务堆栈和任务tcb。有两种方式创建任务堆栈和任务tcb,一种是使用动态内存分配方法,这样当任务删除时,任务堆栈和任务控制块空间会被释放,可用于其它任务;另一种是使用静态变量来实现,在创建任务前定义好全局或者静态堆栈数组和任务控制块变量,在调用创建任务api函数时,将这两个变量以参数的形式传递给任务创建函数xtaskgenericcreate()。如果使用默认的xtaskcreate()创建任务函数,则使用动态内存分配,因为与静态内存分配有关的参数不可见(在本文一开始我们说过xtaskcreate()其实是一个带参数的宏定义,真正被执行的函数是xtaskgenericcreate(),参考宏xtaskcreate()的定义可以知道,xtaskcreate()对外隐藏了使用静态内存分配的参数,在调用xtaskgenericcreate()时,这些参数被设置为null)。
任务堆栈成功分配后,经过对齐的堆栈起始地址被保存到任务tcb的pxstack字段。如果使能堆栈溢出检查或者使用可视化追踪功能,则使用固定值tskstack_fill_byte(0xa5)填充堆栈。
函数prvallocatetcbandstack()的源码去除断言和不常用的条件编译后如下所示:
static tcb_t *prvallocatetcbandstack( const uint16_t usstackdepth, stacktype_t * const puxstackbuffer, tcb_t * const pxtaskbuffer ){tcb_t *pxnewtcb;stacktype_t *pxstack; /* 分配堆栈空间*/ pxstack = ( stacktype_t * ) pvportmallocaligned( ( ( ( size_t ) usstackdepth ) * sizeof( stacktype_t ) ), puxstackbuffer ); if( pxstack != null ) { /* 分配tcb空间 */ pxnewtcb = ( tcb_t * ) pvportmallocaligned( sizeof( tcb_t ), pxtaskbuffer ); if( pxnewtcb != null ) { /* 将堆栈起始位置存入tcb*/ pxnewtcb->pxstack = pxstack; } else { /* 如果tcb分配失败,释放之前申请的堆栈空间 */ if( puxstackbuffer == null ) { vportfree( pxstack ); } } } else { pxnewtcb = null; } if( pxnewtcb != null ) { /* 如果需要,使用固定值填充堆栈 */ #if( ( configcheck_for_stack_overflow> 1 ) || ( configuse_trace_facility == 1 ) || ( include_uxtaskgetstackhighwatermark== 1 ) ) { /* 仅用于调试 */ ( void ) memset( pxnewtcb->pxstack, ( int ) tskstack_fill_byte, ( size_t ) usstackdepth * sizeof( stacktype_t ) ); } #endif } return pxnewtcb;}
2.初始化任务tcb必要的字段
调用函数prvinitialisetcbvariables()初始化任务tcb必要的字段。在调用创建任务api函数xtaskcreate()时,参数pcname(任务描述)、uxpriority(任务优先级)都会被写入任务tcb相应的字段,tcb字段中的xstatelistitem和xeventlistitem列表项也会被初始化,初始化后的列表项如图2-1所示。在图2-1中,列表项xeventlistitem的成员列表项值xitemvalue被初始为4,这是因为我在应用中设置的最大优先级数目(configmax_priorities)为5,而xeventlistitem. xitemvalue等于configmax_priorities减去任务a的优先级(为1),即5-1=4。这一点很重要,在这里xitemvalue不是直接保存任务优先级,而是保存优先级的补数,这意味着xitemvalue的值越大,对应的任务优先级越小。freertos内核使用vlistinsert函数(详细见高级篇第一章)将事件列表项插入到一个列表,这个函数根据xitemvalue的值的大小顺序来进行插入操作。使用宏listget_owner_of_head_entry获得列表中的第一个列表项的xitemvalue值总是最小,也就是优先级最高的任务!
图2-1:初始化状态和事件列表项
此外,tcb其它的一些字段也被初始化,比如临界区嵌套次数、运行时间计数器、任务通知值、任务通知状态等,函数prvinitialisetcbvariables()的源码如下所示:
static void prvinitialisetcbvariables( tcb_t * const pxtcb, const char * const pcname, ubasetype_t uxpriority, \ const memoryregion_t * const xregions, const uint16_t usstackdepth ){ubasetype_t x; /* 将任务描述存入tcb */ for( x = ( ubasetype_t ) 0; x < ( ubasetype_t ) configmax_task_name_len; x++ ) { pxtcb->pctaskname[ x ] = pcname[ x ]; if( pcname[ x ] == 0x00 ) { break; } } /* 确保字符串有结束 */ pxtcb->pctaskname[ configmax_task_name_len - 1 ] = 'static void prvinitialisetcbvariables( tcb_t * const pxtcb, const char * const pcname, ubasetype_t uxpriority, \ const memoryregion_t * const xregions, const uint16_t usstackdepth ){ubasetype_t x; /* 将任务描述存入tcb */ for( x = ( ubasetype_t ) 0; x < ( ubasetype_t ) configmax_task_name_len; x++ ) { pxtcb->pctaskname[ x ] = pcname[ x ]; if( pcname[ x ] == 0x00 ) { break; } } /* 确保字符串有结束 */ pxtcb->pctaskname[ configmax_task_name_len - 1 ] = '\0'; /* 调整优先级,宏configmax_priorities的值在freertosconfig.h中设置 */ if( uxpriority >= ( ubasetype_t ) configmax_priorities ) { uxpriority = ( ubasetype_t ) configmax_priorities - ( ubasetype_t ) 1u; } pxtcb->uxpriority = uxpriority; #if ( configuse_mutexes == 1 ) /*使用互斥量*/ { pxtcb->uxbasepriority = uxpriority; pxtcb->uxmutexesheld = 0; } #endif /* configuse_mutexes */ /*初始化列表项*/ vlistinitialiseitem( &( pxtcb->xstatelistitem ) ); vlistinitialiseitem( &( pxtcb->xeventlistitem ) ); /* 设置列表项xstatelistitem的成员pvowner指向当前任务控制块 */ listset_list_item_owner( &( pxtcb->xstatelistitem ), pxtcb ); /* 设置列表项xeventlistitem的成员xitemvalue*/ listset_list_item_value( &( pxtcb->xeventlistitem ), ( ticktype_t ) configmax_priorities - ( ticktype_t ) uxpriority ); /* 设置列表项xeventlistitem的成员pvowner指向当前任务控制块 */ listset_list_item_owner( &( pxtcb->xeventlistitem ), pxtcb ); #if ( portcritical_nesting_in_tcb ==1 ) /*使能临界区嵌套功能*/ { pxtcb->uxcriticalnesting = ( ubasetype_t ) 0u; } #endif /* portcritical_nesting_in_tcb */ #if ( configuse_application_task_tag == 1 ) /*使能任务标签功能*/ { pxtcb->pxtasktag = null; } #endif /* configuse_application_task_tag */ #if ( configgenerate_run_time_stats == 1 ) /*使能事件统计功能*/ { pxtcb->ulruntimecounter = 0ul; } #endif /* configgenerate_run_time_stats */ #if ( portusing_mpu_wrappers == 1 ) /*使用mpu功能*/ { vportstoretaskmpusettings( &( pxtcb->xmpusettings ), xregions, pxtcb->pxstack, usstackdepth ); } #else /* portusing_mpu_wrappers */ { ( void ) xregions; ( void ) usstackdepth; } #endif /* portusing_mpu_wrappers */ #if( confignum_thread_local_storage_pointers != 0 )/*使能线程本地存储指针*/ { for( x = 0; x < ( ubasetype_t )confignum_thread_local_storage_pointers; x++ ) { pxtcb->pvthreadlocalstoragepointers[ x ] = null; } } #endif #if ( configuse_task_notifications == 1 ) /*使能任务通知功能*/ { pxtcb->ulnotifiedvalue = 0; pxtcb->ucnotifystate = tasknot_waiting_notification; } #endif #if ( configuse_newlib_reentrant == 1 ) /*使用newlib*/ { _reent_init_ptr( ( &( pxtcb->xnewlib_reent ) ) ); } #endif #if( include_xtaskabortdelay == 1 ) { pxtcb->ucdelayaborted = pdfalse; } #endif}'; /* 调整优先级,宏configmax_priorities的值在freertosconfig.h中设置 */ if( uxpriority >= ( ubasetype_t ) configmax_priorities ) { uxpriority = ( ubasetype_t ) configmax_priorities - ( ubasetype_t ) 1u; } pxtcb->uxpriority = uxpriority; #if ( configuse_mutexes == 1 ) /*使用互斥量*/ { pxtcb->uxbasepriority = uxpriority; pxtcb->uxmutexesheld = 0; } #endif /* configuse_mutexes */ /*初始化列表项*/ vlistinitialiseitem( &( pxtcb->xstatelistitem ) ); vlistinitialiseitem( &( pxtcb->xeventlistitem ) ); /* 设置列表项xstatelistitem的成员pvowner指向当前任务控制块 */ listset_list_item_owner( &( pxtcb->xstatelistitem ), pxtcb ); /* 设置列表项xeventlistitem的成员xitemvalue*/ listset_list_item_value( &( pxtcb->xeventlistitem ), ( ticktype_t ) configmax_priorities - ( ticktype_t ) uxpriority ); /* 设置列表项xeventlistitem的成员pvowner指向当前任务控制块 */ listset_list_item_owner( &( pxtcb->xeventlistitem ), pxtcb ); #if ( portcritical_nesting_in_tcb ==1 ) /*使能临界区嵌套功能*/ { pxtcb->uxcriticalnesting = ( ubasetype_t ) 0u; } #endif /* portcritical_nesting_in_tcb */ #if ( configuse_application_task_tag == 1 ) /*使能任务标签功能*/ { pxtcb->pxtasktag = null; } #endif /* configuse_application_task_tag */ #if ( configgenerate_run_time_stats == 1 ) /*使能事件统计功能*/ { pxtcb->ulruntimecounter = 0ul; } #endif /* configgenerate_run_time_stats */ #if ( portusing_mpu_wrappers == 1 ) /*使用mpu功能*/ { vportstoretaskmpusettings( &( pxtcb->xmpusettings ), xregions, pxtcb->pxstack, usstackdepth ); } #else /* portusing_mpu_wrappers */ { ( void ) xregions; ( void ) usstackdepth; } #endif /* portusing_mpu_wrappers */ #if( confignum_thread_local_storage_pointers != 0 )/*使能线程本地存储指针*/ { for( x = 0; x < ( ubasetype_t )confignum_thread_local_storage_pointers; x++ ) { pxtcb->pvthreadlocalstoragepointers[ x ] = null; } } #endif #if ( configuse_task_notifications == 1 ) /*使能任务通知功能*/ { pxtcb->ulnotifiedvalue = 0; pxtcb->ucnotifystate = tasknot_waiting_notification; } #endif #if ( configuse_newlib_reentrant == 1 ) /*使用newlib*/ { _reent_init_ptr( ( &( pxtcb->xnewlib_reent ) ) ); } #endif #if( include_xtaskabortdelay == 1 ) { pxtcb->ucdelayaborted = pdfalse; } #endif}
3.初始化任务堆栈
调用函数pxportinitialisestack()初始化任务堆栈,并将最新的栈顶指针赋值给任务tcb的pxtopofstack字段。
调用函数pxportinitialisestack()后,相当于执行了一次系统节拍时钟中断:将一些重要寄存器入栈。虽然任务还没开始执行,也并没有中断发生,但看上去就像寄存器已经被入栈了,并且部分堆栈值被修改成了我们需要的已知值。对于不同的硬件架构,入栈的寄存器也不相同,所以我们看到这个函数是由移植层提供的。对于cortex-m3架构,需要依次入栈xpsr、pc、lr、r12、r3~r0、r11~r4,假设堆栈是向下生长的,初始化后的堆栈如图3-1所示。
在图3-1中我们看到寄存器xpsr被初始为0x01000000,其中bit24被置1,表示使用thumb指令;寄存器pc被初始化为任务函数指针vtask_a,这样当某次任务切换后,任务a获得cpu控制权,任务函数vtask_a被出栈到pc寄存器,之后会执行任务a的代码;lr寄存器初始化为函数指针prvtaskexiterror,这是由移植层提供的一个出错处理函数。当中断发生时,lr被设置成中断要返回的地址,但是每个任务都是一个死循环,正常情况下不应该退出任务函数,所以一旦从任务函数退出,说明那里出错了,这个时候会调用寄存器lr指向的函数来处理这个错误,即prvtaskexiterror;根据atpcs(arm-thumb过程调用标准),我们知道子函数调用通过寄存器r0~r3传递参数,在文章的最开始讲xtaskcreate()函数时,提到这个函数有一个空指针类型的参数pvparameters,当任务创建时,它作为一个参数传递给任务,所以这个参数被保存到r0中,用来向任务传递参数。
任务tcb结构体成员pxtopofstack表示当前堆栈的栈顶,它指向最后一个入栈的项目,所以在图中它指向r4,tcb结构体另外一个成员pxstack表示堆栈的起始位置,所以在图中它指向堆栈的最开始处。
图3-1:初始化任务堆栈
4.进入临界区
调用taskenter_critical()进入临界区,这是一个宏定义,最终进入临界区的代码由移植层提供。
5.当前任务数量增加1
在tasks.c中 ,定义了一些静态私有变量,用来跟踪任务的数量或者状态等等,其中变量uxcurrentnumberoftasks表示当前任务的总数量,每创建一个任务,这个变量都会增加1。
6.为第一次运行做必要的初始化
如果这是第一个任务(uxcurrentnumberoftasks等于1),则调用函数prvinitialisetasklists()初始化任务列表。freertos使用列表来跟踪任务,在tasks.c中,定义了静态类型的列表变量:
privileged_datastatic list_t pxreadytaskslists[ configmax_priorities ];/*按照优先级排序的就绪态任务*/privileged_datastatic list_t xdelayedtasklist1; /*延时的任务 */privileged_datastatic list_t xdelayedtasklist2; /*延时的任务 */privileged_datastatic list_t xpendingreadylist; /*任务已就绪,但调度器被挂起 */ #if (include_vtaskdelete == 1 ) privileged_data static list_t xtaskswaitingtermination; /*任务已经被删除,但内存尚未释放*/#endif #if (include_vtasksuspend == 1 ) privileged_data static list_t xsuspendedtasklist; /*当前挂起的任务*/#endif
现在这些列表都要进行初始化,会调用api函数vlistinitialise()初始化列表,这个函数在《freertos高级篇1—freertos列表和列表项》中讲过,每个列表的初始化方式都是相同的,以就绪态列表pxreadytaskslists[0]为例,初始化后如图6-1所示:
图6-1:初始化后的列表
函数prvinitialisetasklists()的源代码如下所示:
static void prvinitialisetasklists( void ){ubasetype_tuxpriority; for( uxpriority = ( ubasetype_t ) 0u; uxpriority < ( ubasetype_t ) configmax_priorities; uxpriority++ ) { vlistinitialise( &( pxreadytaskslists[ uxpriority ] ) ); } vlistinitialise( &xdelayedtasklist1 ); vlistinitialise( &xdelayedtasklist2 ); vlistinitialise( &xpendingreadylist ); #if ( include_vtaskdelete == 1 ) { vlistinitialise( &xtaskswaitingtermination ); } #endif /* include_vtaskdelete */ #if ( include_vtasksuspend == 1 ) { vlistinitialise( &xsuspendedtasklist ); } #endif /* include_vtasksuspend */ /* start with pxdelayedtasklist using list1 and the pxoverflowdelayedtasklistusing list2. */ pxdelayedtasklist = &xdelayedtasklist1; pxoverflowdelayedtasklist = &xdelayedtasklist2;}
7.更新当前正在运行的任务tcb指针
tasks.c中定义了一个任务tcb指针型变量:
privileged_data tcb_t * volatile pxcurrenttcb= null;
这是一个全局变量,在tasks.c中只定义了这一个全局变量。这个变量用来指向当前正在运行的任务tcb,我们需要多了解一下这个变量。freertos的核心是确保处于优先级最高的就绪任务获得cpu运行权。在下一章讲述任务切换时会知道,任务切换就是找到优先级最高的就绪任务,而找出的这个最高优先级任务的tcb,就被赋给变量pxcurrenttcb。
如果调度器还没有准备好(程序刚开始运行时,可能会先创建几个任务,之后才会启动调度器),并且新创建的任务优先级大于变量pxcurrenttcb指向的任务优先级,则设置pxcurrenttcb指向当前新创建的任务tcb(确保pxcurrenttcb指向优先级最高的就绪任务)。
if( xschedulerrunning == pdfalse ){ if( pxcurrenttcb->uxpriority <= uxpriority ) { pxcurrenttcb = pxnewtcb; } else { mtcoverage_test_marker(); }}
8.将新创建的任务加入就绪列表数组
调用prvaddtasktoreadylist(pxnewtcb)将创建的任务tcb加入到就绪列表数组中,任务的优先级确定了加入到就绪列表数组的哪个下标。比如我们新创建的任务优先级为1,则这个任务被加入到列表pxreadytaskslists[1]中。
prvaddtasktoreadylist()其实是一个宏,由一系列语句组成,去除其中的跟踪宏外,这个宏定义如下所示:
#defineprvaddtasktoreadylist( pxtcb ) \ taskrecord_ready_priority( ( pxtcb)->uxpriority ); \ vlistinsertend( &( pxreadytaskslists[ (pxtcb )->uxpriority ] ), &( ( pxtcb )->xstatelistitem ) );
宏taskrecord_ready_priority()用来更新变量uxtopreadypriority,这个变量在tasks.c中定义为静态变量,记录处于就绪态的最高任务优先级。这个变量参与了freertos的最核心代码:确保处于优先级最高的就绪任务获得cpu运行权。它在这里参与如何最快的找到优先级最高的就绪任务。为了最快,不同的架构会各显神通,一些架构还有特殊指令可用,所以这个宏由移植层提供。我们会在下一章介绍任务切换时,以cortex-m3架构为例,详细介绍如何最快的找到优先级最高的就绪任务。
函数vlistinsertend()将列表项插入到列表末端,在《freertos高级篇1—freertos列表和列表项》中已经提到过,这里会结合着例子再看一下这个函数。从前面我们直到,在调用函数vlistinsertend()之前,就绪列表pxreadytaskslists[1]和任务tcb的状态列表项xstatelistitem都已经初始化好了,见图6-1和图2-1,为了方便查看,我们将这两幅图合成一副,见图8-1。
图8-1:初始化后的列表和列表项
调用vlistinsertend(a,b)会将列表项b,插入到列表a的后面,函数执行完毕后,列表和列表项的关系如图8-2所示。
图8-2:插入一个列表项后的列表
在此基础上,假设又创建了任务b,任务a和任务b优先级相同,都为1。和任务a一样,任务b也有它自己的任务tcb,其中的状态列表项字段xstatelistitem也要插入到列表pxreadytaskslists[1]中,新的列表和列表项如图8-3所示。
图8-3:相同优先级就绪列表挂接两个列表项
9.退出临界区
调用taskexit_critical()退出临界区,这是一个宏定义,最终退出临界区的代码由移植层提供。
10.执行上下文切换
如果上面的步骤都正确执行,并且调度器也开始工作,则判断当前任务的优先级是否大于新创建的任务优先级。如果新创建的任务优先级更高,则调用taskyield_if_using_preemption()强制进行一次上下文切换,切换后,新创建的任务将获得cpu控制权,精简后的代码如下所示。
if( xreturn == pdpass ) { if( xschedulerrunning != pdfalse ) { /* 如果新创建的任务优先级大于当前任务优先级,则新创建的任务应该被立即执行。*/ if(pxcurrenttcb->uxpriority < uxpriority ) { taskyield_if_using_preemption(); } } }
以上就是freertos进阶之任务创建完全解析的详细内容,更多关于freertos进阶任务创建分析的资料请关注其它相关文章!