VxWorks笔记整理
一、开篇
1、实时操作系统:
硬实时系统:能够在指定期限内完成任务(即使在最坏的处理条件下也能如此)的操作系统;
软实时系统:统计意义上的实时。
2、优先级反转:
一个高优先级任务通过信号量机制访问共享资源时,该信号量已被一低优先级任务占有,而这个低优先级任务在访问共享资源时可能又被其它一些中等优先级任务抢先,因此造成高优先级任务被许多具有较低优先级任务阻塞,实时性难以得到保证。
任务优先级反转解决方法:
优先级天花板:当任务申请某资源时, 把该任务的优先级提升到可访问这个资源的所有任务中的最高优先级。
优先级继承:任务 t1 申请共享资源 S 时, 如果 S 正在被任务 t3 使用,通过比较任务 t3 与 t1的优先级,如发现任务 t3 的优先级小于 t1 的优先级, 则将任务 t3 的优先级提升到 t1 的优先级,等 t3 释放资源 S 后,再恢复任务 t3 的原优先级。
二、VxWorks实时任务调度
每个任务都有一个TCB(Task Control Block)任务控制块,内核用来管理任务调度功能。
任务状态:Pend、Delay、Suspended、Ready;
1、任务上下文:
任务上下文的切换发生在一个任务停止,另一个任务开始的时候。保存当前任务的内容到TCB中。恢复要执行任务的TCB。上下文切换一定要快。
2、任务调度策略:
- 基于优先级的抢占式调度:不同优先级的任务;创建任务时指定优先级 taskSpawn、任务创建后设置优先级 taskPrioritySet。
- 罗宾调度(时间轮转调度):相同优先级的任务;KernelTimeSlice设置时间片的长度,以tick为单位。
3、创建任务:
taskSpawn("sendTest", 100, 0, 5*1024, (FUNCPTR)sendTestTask, portNo,0,0,0,0,0,0,0,0,0);
// (任务名称,优先级,任务属性,栈大小, 入口函数指针, 参数一)
4、堆栈:
-
堆:是为动态分配预留的内存空间;由开发人员分配和释放。
-
栈:栈是为执行线程留出的内存空间;栈由操作系统自动分配释放 ,用于存放函数的参数值、局部变量等,其操作方式类似于数据结构中的栈。
三、VxWorks信号量
1、信号量的分类:
序号 | 名称 | 特点 |
---|---|---|
1 | 二进制信号量 | 满足任务间同步与互斥,开销最小 |
2 | 互斥信号量 | 优化后的二进制信号量,用来解决互斥固有问题(优先级翻转) |
3 | 计数信号量 | 记录有效的资源数目,监视同一资源上的多个实例 |
2、同步和互斥:
- 同步即任务按照一定顺序先后执行,如任务A执行完成之后才能执行任务B;
- 互斥是指多任务在访问临界资源时具有排他性。为使多个任务互斥访问临界资源,只需要为该资源设置一个信号量,相当于一个令牌,哪个任务拿到这个令牌即有权使用该资源。
3、信号量相关的函数:
序号 | 函数 | 功能 |
---|---|---|
1 | semBCreate | 创建并初始化一个二进制信号量 |
2 | semMCreate | 创建并初始化一个互斥信号量 |
3 | semCCreate | 创建并初始化一个计数信号量 |
4 | semTake | 等待信号量 |
5 | semGive | 释放信号量 |
6 | semDelete | 删除一个信号量 |
7 | semFlush | 唤醒阻塞在某信号量上的所有任务 |
4、二进制信号量实行同步和互斥:
1、同步:
- 初始状态设为不可用;
semIdMu = semBCreate(SEM_Q_FIFO, SEM_EMPTY);
- 在不同任务中分别单独调用
semTake
和semGive
。
典型用法:将中断服务程序从冗长的事件处理中解放出来以缩短中断响应时间。
SEM_ID semIdSyn = NULL;
void TaskA()
{
semGive(semIdSyn); // 释放信号量
}
void TaskB()
{
semTake(semIdSyn, WAIT_FOREVER);
}
void TaskSyn()
{
semIdSyn = semBCreate(SEM_Q_FIFO, SEM_EMPTY); // 创建二进制信号量,初始状态为 empty;
taskSpawn("TaskA",120,0,5*1024,(FUNCPTR)TaskA,0,0,0,0,0,0,0,0,0,0);
taskSpawn("TaskB",120,0,5*1024,(FUNCPTR)TaskB,0,0,0,0,0,0,0,0,0,0);
}
2、互斥:
- 初始状态设为可用;
semIdMu = semBCreate(SEM_Q_FIFO, SEM_FULL)
; - 在同一个任务中,成对调用
semTake
和semGive
。
典型用法:对多个任务共享内存缓冲区或同一个I/O设备之类的资源进行保护。
void TaskA()
{
semTake(semIdMu, WAIT_FOREVER); // 等待信号量被释放
semGive(semIdMu); // 释放信号量
}
void TaskB()
{
semTake(semIdMu, WAIT_FOREVER);
semGive(semIdMu); // 释放信号量
}
void TaskMu()
{
semIdMu = semBCreate(SEM_Q_FIFO, SEM_FULL); // 创建二进制信号量,初始状态为 FULL
//semIdMu = semMCreate(SEM_Q_PRIORITY);//采用互斥信号量实现
taskSpawn("TaskA",110,0,5*1024,(FUNCPTR)TaskA,0,0,0,0,0,0,0,0,0,0);
taskSpawn("TaskB",120,0,5*1024,(FUNCPTR)TaskB,0,0,0,0,0,0,0,0,0,0);
}
3、信号量属性:
信号量属性 | 作用 |
---|---|
SEM_DELETE_SAFE | 保护拥有该信号量的任务不被意外删除。此选项为每个semtake 启用隐式任务Safe ,并为每个semGive 启用隐式任务taskUnsafe |
SEM_INVERSION_SAFE | 使用此选项,拥有该信号量的任务将以挂起在该信号量上的所有任务的最高优先级执行。该选项必须伴随着SEM_Q_PRIORITY 队列模式 |
在由信号量保护的关键区域内,通常需要保护正在执行的任务被意外删除。删除在关键区域执行的任务可能是灾难性的
内在互斥问题: 优先级继承、删除安全、对资源的递归访问
优先级继承 | 拥有该信号量的任务将以挂起在该信号量上的所有任务的最高优先级执行。该选项必须伴随着SEM_Q_PRIORITY 队列模式 |
删除安全 | 保护拥有该信号量的任务不被意外删除。此选项为每个semtake 启用隐式任务Safe ,并为每个semGive 启用隐式任务taskUnsafe |
对资源的递归访问 | 一个互斥信号量可以被拥有它的任务反复进行semTake 而不引起该任务的阻塞。一个占有互斥信号量的任务,若要释放掉互斥信号量,则必须执行与semTake 同样次数的semGive 操作 |
四、VxWorks共享内存、消息队列和管道
1、任务间通信:
多任务系统的任务间有通信需求。
1、任务间通信由三个部分组成:
-
共享的信息或者数据;
-
数据可用时的任务通知机制;
-
防止任务间相互干扰机制;
2、两个通用的方法:
-
共享内存:共享内存通常与信号量一起使用。
tTaskA
通过调用fooSet()
设置全局结构fooBuffer
;
tTaskB
通过调用fooGet()
获得全局结构fooBuffer
的值。共享内存通常与信号量一起使用。
-
消息传递:vxWorks使用管道(pipe)与消息队列(message queue)进行任务间消息传递。
管道(pipe)与消息队列(message queue)都提供:消息的FIFO缓存、同步、互斥。
2、创建消息队列:
MSG_Q_ID msgQCreate (maxMsgs, maxMsgLength, options) //返回引用消息队列的ID或者NULL
//maxMsgs:最大消息数 maxMsgLength :单个消息的最大字节数。
//options : 对挂起任务的消息类型。MSG_Q_FIFO 或者MSG_Q_PRIORITY
3、发送消息:
STATUS msgQSend (msgQId, buffer, nBytes,timeout,priority)
msgQId | msgQCreate 返回的ID | buffer | 要放入队列的消息地址 |
nBytes | 消息大小 | timeout | 消息队列是满的时候最大等待时间(ticks),及WAIT_FOREVER,NO_WAIT |
priority | 消息优先级。 MSG_PRI_URGENT 将放队前, MSG_PRI_NORMAL 将放队尾 |
- 当队列满时,发送任务将挂起;
- 使用
NO_WAIT
超时从中断级别发送; - 成功返回 OK,无效
msgId
等错误返回 ERROR 。
4、接收消息:
msgQReceive (msgQId, buffer,maxNBytes, timeout) //WAIT_FOREVER,NO_WAIT
- 成功返回读取的字节数,失败返回 ERROR;
- 消息未被读取部分将被丢弃;
- 没有消息,读取任务将被挂起,直到超时;
- 挂起任务将按优先级或者 FIFO 等待;
msgQReceive
至少返回第一个消息的字节数;
5、删除消息队列:
STATUS msgQDelete (msgQId)
- 删除消息队列;
- 挂起在消息队列的任务将进入
READY
,msgQReveive()
与msgQsend()
将返回ERROR
;
使用消息队列获取数据:
- 用一个轮询任务或者 ISR 从设备取数据放入消息队列;
- 低优先级任务从队列取数据处理。
五、事件与信号
1、事件介绍:
- 事件提供了任务与任务,ISR与任务,信号量与任务,消息队列与任务之间同步的方法;
- 事件可以由ISR、任务、消息队列发送、释放信号量操作发送事件;
- VXWORKS提供32个事件,event25-event31为windriver使用;
- 事件提供了一种无需分配额外系统资源即可协调复杂活动矩阵的方法。
2、时间标志与事件寄存器:
- 每个任务控制块都有一个事件寄存器,用于存储来发到任务的事件;
- 事件寄存器不能访问,需要由eventReceive拷贝进去;
- 没有机制记录收到过事件的次数;
- 事件标志是一个32位编码字,前24bit可以用户自定义;
- 每个flag没有明确意思,由任务自行决定如何响应。
3、消息队列事件:
- 当消息队列为空且没任务阻塞在队列上时,可以发送事件;
- 消息队列事件没有机制计算队列中消息数量;
- 一个任务可以注册到多个消息队列上;
- 一个消息队列一次只能被一个任务注册。
六、VxWorks中断、异常与定时
中断可以抢占优先级最高的任务。
1、ISR限制:
- ISR中不能使用vxWorks部分功能,特别是部分不能阻塞的功能:
- 不能调用
semTake()
; - 不能调用
malloc()
,因为内部使用了信号量; - 不能调用系统IO,如
printf
;
- 不能调用
- 可以在ISR中调用的一个操作是向管道中
write()
。
2、典型ISR:
- 读写内存映射的寄存器;
- 与任务进行通信:
- 写内存;
- 以非阻塞的方式写消息到消息队列;
- 释放二进制信号量。
3、调试ISR:
-
将信息输出到控制台:
logMsg (“foo = %d\n”, foo, 0, 0, 0, 0, 0);
向任务
tLogTask
发送请求,由该任务执行printf
。 -
使用系统级调试。