今天我们来看看VxWorks系统里如何创建任务。
与任务相关的API由系统库taskLib提供。
常用的函数是taskSpawn(),32位系统里(以下同)函数原型如下:
我们在《任务是啥?》里提到过,Task可以看作是Stack和TCB组成的。因此taskSpawn()的第一步就是为Stack和TCB分配内存,然后初始化它们,最后将这个任务放入Ready队列。
这个函数的参数比较多,我们挨个看一下
name:任务的名字,主要只是开发者在Shell中调试时才使用它。名字中的字符和长度没有什么限制,甚至可以指定为NULL,那么系统会自动给它分配为tN,N是从1开始递增的十进制数。甚至不同任务的名字可以是相同的,所以通过名字来管理任务的话,要注意一下。而操作系统或应用程序在管理任务时,通常使用的是它们的ID。名字和ID可以通过这两个函数互相转换
priority:
任务的优先级,VxWorks调度任务时就是基于它。优先级的取值范围是0-255,可以动态改变或查询
注意:多个任务间,优先级的高低是相对的。假如系统里只有两个任务A和B,优先级分别是1和10,或者分别是1和100,这两种情况下的调度是一模一样的。都是高优先级的会抢占,必须等它退出Ready队列,低优先级的才有可能占用CPU。不像有的操作系统采用分时调度,优先级的高低会影响时间片的长短。
那在我们自己的应用程序里,优先级设置为多少合适呢?很多人习惯于将内核任务设置为100,用户态任务的稍低一些,150或200,这个并没有什么限制,只要平衡好多个应用任务之间的关系即可。不过建议应用任务的优先级不要高于系统任务的。例如,在《Task之常见系统任务》里,我们提到过WDB任务的优先级默认为3,所以我们应用任务尽量不要高于4,否则可能会影响到调试了。
options
任务选项,以Bit为单位,每个版本的选项不尽相同,因此代码中尽量使用选项的宏定义,而不要使用对应的数值。例如6.9里可用的选项如下。
l VX_FP_TASK 使用浮点协处理器,否则含有浮点操作的任务在切换时不会保存浮点寄存器的值,导致浮点异常。特例是C 语句被编译后,会产生浮点指令,因此任务中调用C 语句时,就一定要使能这个选项。
l VX_NO_STACK_FILL不填充Stack
l VX_NO_STACK_PROTECT不提供Stack的上溢和下溢保护
l VX_ALTIVEC_TASK 使用Altivec指令集(仅PowerPC)
l VX_SPE_TASK 使用SPE引擎(仅PowerPC)
l VX_DSP_TASK使用DSP(仅SuperH )
l VX_PRIVATE_ENV支持私有环境变量(6.9中实际已取消,仅是为了兼容)
stackSize
任务的栈,单位是Byte,从系统内存池分配。一经分配,尺寸就固定不变了。如果分配的数值过大,就会增加一点点初始化时间,并浪费部分内存空间,影响倒是不大;而分配的过小,就有栈溢出的风险,这可是致命的了。因此,开发过程中,要评估一下具体的数值。那如何才能知道分配的是否合适呢?可以在Shell里使用checkStack()来检查,因为创建任务时,Stack的每个Byte默认被填充为0xee,checkStack()通过检查Stack中0xee的变化来判断Stack的使用边界。
不过,如果使用了选项VX_NO_STACK_FILL,或者设置Kernel的配置参数VX_GLOBAL_NO_STACK_FILL为TRUE,Stack的内容就不会被填充0xee了,checkStack()也就没办法正常工作了。但好处是,taskSpawn()创建任务时的速度会加快一些
entryPt
任务主函数的入口地址,可以包含10个int型参数,arg1- arg10。
如果参数不是int型的,可以考虑使用指针。如果参数多于10个,可以考虑使用结构体。如果参数的类型大于32Bit,可以考虑使用联合体,不过要注意大小端
返回值
taskSpawn()的返回值就是Kernel分配给任务的ID,它是一个32Bit的数值,是全局唯一的。不过任务退出后,这个ID值是可以再次分配给其它任务的,也就是可以重复使用的。任务可以通过taskIdSelf()查询自己的ID,通过taskIdVerify()验证某任务是否存在,通过taskIdListGet()获取当前的任务列表。taskLib中很多API使用task ID为参数,这个参数取值为0的话,一般就是表示当前任务自己了。
另外,创建任务还可以使用taskCreate(),它只是初始化任务,需要taskActivate()把它放到Ready队列。
这样分成两步做的好处就是可以在整个系统初始化时就把任务也初始化好,需要使用的时候,再进行激活,相当于减少了激活时间。其实taskSpawn()就是它俩的合成
taskSpawn()中初始化Stack和TCB的操作就是在taskCreate()中完成的,下图是很多年前的一段Benchmark数据
可以看到在当时的软硬件(Pentium3)配置下,Kernel操作的耗时一般都是微秒级的,但是taskSpawn()比其它函数要慢很多。因此,当项目的实时性需求非常高时,可以考虑使用taskCreate()和taskActivate()的组合。
还有一个POSIX风格的API可以用来创建任务或者获得任务句柄,taskOpen()。这个函数多数是在支持进程时使用,因为它可以把任务创建为公共对象,以便于多进程与Kernel间相互访问。我们在介绍RTP通信时,再详细介绍它
这正是:
任务功能强大,创建有些复杂。
追求实时性能,可以分段进行。