1. 任务VxWorks
任务:在执行时每个程序都被称之为任务。VxWorks操作系统中,任务可以直接地或者以共享方式访问大多数系统资源,为了维护各自的线程,每个任务必须保持有足够的上下文环境。
(1) 任务状态:
就绪(READY):该状态时任务仅等待CPU的状态,不等待其他任何资源。
阻塞(PEND):任务由于一些资源不可用而被阻塞时的状态。
睡眠(DELAY):出于睡眠的任务状态。
挂起(SUSPEND):该状态时任务不执行,主要用于调试用。挂起仅仅约束任务的执行,并不约束状态的转换,因此pended-suspended状态时任务可以解锁,delayed-suspended状态时任务可以唤醒。
DELAY+S:既处于睡眠又处于挂起的任务状态。
PEND+S:既处于阻塞又处于挂起的任务状态。
PEND+T:带有超时值处于阻塞的任务状态。
PEND+S+T:带有超时值处于阻塞,同时又处于挂起的任务状态。
state+I:任务处于state且带有一个继承优先级。
----------------------------------------------------------------------------------------
| ready | ——> | pended | semTake () / msgQReceive () |
| ready | ——> | delayed | taskDelay () |
| ready | ——> | suspended | taskSuspend () |
| pended | ——> | ready | semGive () / msgQSend () |
| pended | ——> | suspended | taskSuspend () |
| delayed | ——> | ready | expired delay |
| delayed | ——> | suspended | taskSuspend () |
| suspended | ——> | ready | taskResume () / taskActivate () |
| suspended | ——> | pended | taskResume () |
| suspended | ——> | delayed | taskResume() |
---------------------------------------------------------------------------------------
(2) Wind任务调度
在Wind内核中,默认算法是基于优先级的抢占式调度算法,也可以使用轮转调度算法。
任务调度控制函数:
----------------------------------------------------------------------------------------
| 调用 | 描述 |
| kernelTimeSlice() | 控制轮转调度 |
| taskPrioritySet() | 改变任务优先级 |
| taskLock() | 禁止任务调度 |
| taskUnlock() | 允许任务调度 |
----------------------------------------------------------------------------------------
基于优先级的抢占式任务调度:
当一个新任务优先级高于系统当前执行任务的优先级时,它将抢占CPU执行。因此,系统内核将确保CPU分配给处于就绪状态的具有最高优先级的任务执行。
缺点:当多个相同优先级的任务需要共享一台处理器时,如果某个执行的任务永不阻塞,那么它将一直独占处理器,其他相同优先级的任务就没有机会执行。
轮转式调度:
当所有相同优先级的任务处于就绪状态时,轮转算法倾向于平均使用CPU,对于所有相同优先级的任务,通过时间片获得相同的CPU处理时间。
抢占上锁:
通过调用taskLock()和taskUnlock()函数,可以禁止使用Wind内核调度程序或启用Wind内核调度程序。当禁止使用调度程序时,若该任务正在执行,不会发生基于优先级的抢占。
抢占上锁只能阻止任务的上下文切换,并不禁止中断。
taskLock()和intLock()比较
任务优先级:所有应用任务的优先级应该在100-250之间;但是驱动程序支持的任务(与中断服务程序关联的任务)优先级能够位于51-99。
(3) 任务异常处理:
(4) 共享代码和重入
VxWorks操作系统中,大多数函数是可重入的。但若存在一个对应于命名为someName_r()的函数,someName() 因作为函数重入的版本将认为是不可重入的。例如,ldiv() 有一个对应函数ldiv_r(),则ldiv() 是不可重入的。
重入技术:
. 动态堆栈变量
. 被信号保护的全局和静态变量
. 任务变量:taskVarAdd(), taskVarDelete()和taskVarGet()
(5) 操作系统任务VxWorks
. tUserRoot:内核执行的首个任务,入口点是安装目录/target/config/all/usrConfig.c下函数usrRoot(),可初始化VxWorks操作系统的大部分程序,发起诸如日志任务、异常处理任务、网络任务和tRlogind后台程序。正常情况下根任务在所有初始化结束后,终止任务并且被删除。
. tLogTask:日志任务
. tExcTask:异常处理任务,必须拥有系统的最高优先级。
. tNetTask:网络任务,用于VxWorks网络任务级程序处理。通常配置INCLUDE_NET_LIB组件的VxWorks操作系统可以发起网络任务。
. tWdbTask:目标代理任务,用INCLUDE_WDB组件配置的VxWorks操作系统包括目标代理功能。
. 可选组建的任务
. tShell
. tRlogind
. tTelnetd
. tPortmapd
2. 任务间通信
(1) 共享内存,数据的简单共享
在VxWorks操作系统中所有任务存在于一个单独的线性地址空间中,所以任务间共享数据结构是很容易实现的。全局变量、线性缓冲、环形缓冲、连接链和指针都可以被运行在不同上下文中的代码直接引用。
(2) 信号量,基本的互斥和同步
. 实现资源互斥访问的方法包括:
中断上锁(中断上锁时不要调用VxWorks操作系统函数,强行使用会导致意外的中断):intLock() 和intUnlock()
抢占上锁:taskLock() 和taskUnlock()
信号量对资源的上锁
. VxWorks操作系统中的信号量类型
二进制,最快最通用的信号量,适用于同步和互斥。
互斥,为解决内在互斥问题、优先级继承、删除安全以及递归问题等而最优化的一种特殊二进制信号量。
计数,类似于二进制信号量,但其跟踪信号量被释放的次数,适用于单个资源多个实例需要保护的情况。
. 队列类型:
SEM_Q_PRIORITY:根据优先级顺序
SEM_Q_FIFO:根据先进先出顺序
. 二进制信号量
|
|
|
. 互斥信号量
基本行为与二进制信号量一致,不同之处如下:
仅用于互斥;
仅能由提取它(即调用semTake())的任务释放;
不能在中断服务程序中释放;
semFlush()函数操作非法;
.. 优先级倒置:互斥信号量选项SEM_INVERSION_SAF能够继承优先级算法,优先级继承协议确保在资源阻塞的所有任务中优先级最高的且拥有资源执行资格的任务将优先执行。一旦任务的优先级被提高,它以提高后的优先级执行;直到释放其占有的全部互斥信号量后,该任务将返回到正常或者标准的优先级。该选项要求与优先级队列(SEM_Q_PRIORITY)一起使用。
.. 删除安全:一个受信号量保护的临界区域内经常需要保护执行任务避免被意外地删除。删除一个在临界区执行的任务可能会导致意想不到的后果。原语semSafe()和semUnsafe()提供了一种任务安全的方法。但是在使用互斥信号量选项SEM_DELETE_SAFE时,每次使用semTake()将隐含调用taskSafe(),使用semGive()将隐含调用taskUnsafe()。使用这种方式,任务在占用信号量时不会被删除。
.. 递归资源访问:互斥信号量能够递归获得。在释放信号量前,递归获取的互斥信号量被释放和提取的次数应该相等,这通过一个计数器跟踪实现。
. 计数器信号量
是实现任务同步和互斥的另一种手段,适用于保护多份复制的资源。
(3) 消息队列
在VxWorks操作系统里,单个CPU里任务间的主要通信方式使用消息队列。
------------------------------------------------------------------------------------
| 调用 | 描述 |
| msgQCreate() | 分配并初始化一个消息队列 |
| msgQDelete() | 终止并释放一个消息队列 |
| msgQSend() | 向一个消息队列发送消息 |
| msgQReceive() | 从一个消息队列接收消息 |
------------------------------------------------------------------------------------
消息的优先级:MSG_PRI_NORMAL和MSG_PRI_URGENT
中断服务程序能够向消息管道中写入,但不能从消息管道中读取。
(4) 管道
管道使用VxWorks操作系统中的I/O系统,并提供替换消息队列的接口。管道是由驱动程序pipeDrv管理的虚拟I/O设备,任务能够使用标准I/O 对管道进行打开、读取或写入等操作,另外也可以调用函数ioctl。
与消息管道类似,中断服务程序能够向管道写入,但不能从管道读取。
(5) 任务间网络通信
套接字Sockets
远程程序调用RPC
(6) 信号
VxWorks支持软件信号功能。信号可以异步改变任务的控制流程。任何任务或中断服务程序可以向指定任务发送信号。接收到信号的任务立即挂起当前的执行线程,在下次调度执行时转而执行指定的信号处理程序。信号处理程序在接收任务的上下文中执行,并使用任务的堆栈。即使在任务被阻塞时,仍可调用信号处理程序。
通常信号处理程序可作为中断处理程序看待,任何导致调用程序阻塞的函数均不能在信号处理程序中调用。
Wind内核支持两种类型的信号接口:UNIX BSD风格的信号和POSIX兼容信号。为了简化设计,建议在一个应用程序中使用一种类型接口,不要混合使用不同接口。
基本信号函数:
----------------------------------------------------------------------------------------------------------
| POSIX 1003.1b兼容调用|UNIX BSD兼容调用| 描述 |
| signal() | signal() | 指定信号的处理程序 |
| kill() | kill() | 向任务发送信号 |
| raise() | N/A | 向自身发送信号 |
| sigaction() | sigvec() | 检查或设置信号的处理程序 |
| sigsuspend() | pause() | 挂起任务直至任务提交 |
| sigpending() | N/A | 恢复一组用于传递而被阻塞的信号|
| sigemptyset() ------- | ------------------------ | ---------------------------------------------|
| sigfillset() ------- | | |
| sigaddset() -------- | sigsetmask() | 设置信号屏蔽 |
| sigdelset() -------- | | |
| sigismember() ------- |--------------------- ---|---------------------------------------------|
| sigprocmask() | sigsetmask() | 设置阻塞信号的屏蔽 |
| sigprocmask() | sigblock() | 增加到一组阻塞的信号中 |
----------------------------------------------------------------------------------------------------------
信号发生通常与硬件中断相联系。例如总线出错、非法指令以及浮点数异常都可能产生某种信号。
3. 事件VxWorks
VxWorks事件是一种在任务和中断处理程序间,或任务和VxWorks结构体间的通信方式。在VxWorks事件上下文中,这些结构体被用作为资源,包括信号量和消息队列。只有任务能够接收事件;然而任务、中断处理程序或资源都可以发送事件。
(1) 事件pSOS
. 发送和接收事件
任务、中断服务程序以及资源都使用同一个应用编程接口ev_send()来发送事件。
对于从资源接收事件的任务来说,任务必须用资源寄存,而且请求资源在空闲时发送一系列指定的事件;这种资源可以使信号量,也可以是消息队列。
. 等待事件
任务能够从一个或多个资源等待多个事件。每个资源可以发送多个事件,同样任务也可以等待接收一个或多个事件。
. 事件的寄存
从资源接收事件时,资源只能寄存一个任务。如果另一个任务随后用同样的资源寄存,那么不会通知原先寄存的任务就自动解除原有的寄存。VxWorks事件寄存的处理与pPOS事件则不同。
. 空闲资源
当资源给任务发送事件表明空闲时,不意味着资源的空闲状态可以保留。因此,从资源等待事件的任务在资源空闲时被解除阻塞;但同时资源也可能被取走。
对于两个或两个以上的任务持续交换资源所有权的情况,资源虽然被释放,但并不处于空闲状态,所以资源将不会发送事件。
. 应用编程接口
------------------------------------------------------------------------------------------------
| 函数 | 描述 |
| ev_send() | 给任务发送事件 |
| ev_receive() | 等待事件 |
| sm_notify() | 寄存一个被信号量告知可用的任务 |
| q_notify() | 寄存一个被消息队列告知有消息到来的任务 |
| q_vnoify | 寄存一个被可变长度的消息队列告知有消息到来的任务 |
------------------------------------------------------------------------------------------------
(2) 事件VxWorks
VxWorks事件执行以pPOS事件为基石。
. 空闲资源定义
互斥信号量:当一个互斥信号量被释放并且在其上没有任务阻塞
二进制信号量:当没有任务占有或等待一个二进制信号量
计数器信号量:一个计数器信号量在其计数值非零且其上没有阻塞任务时
消息队列:队列中有消息存在,且没有等待该队列中消息而阻塞的任务
. VxWorks对pPOS事件的扩展
单任务资源寄存:在pPOS系统中一个任务用资源寄存发送pSOS事件时,它会无意地取消另一个已用该资源寄存的任务寄存,第一个用该资源寄存的任务将无限期地被阻止。VxWorks事件则提供了一个选项,在该选项中如果另一个任务已经用某个资源寄存了,则不允许第二个任务用该资源再寄存。如果第二个任务用该资源寄存,将返回一个错误。
立即发送选项:当一个pPOS任务用资源寄存时,即使寄存时资源处于空闲状态,也不会立即给任务发送时间。对于VxWorks事件,默认行为与之相同。然而,VxWorks事件提供了一个选项,即若该资源在寄存时处于空闲状态,该选项允许任务请求资源立即给其发送事件。
自动取消寄存选项:pPOS执行过程序要任务在从资源接收任务后明确地取消寄存。VxWorks执行提供一个选项,该选项可以通知资源仅发送一次事件,然后在发送后自动取消寄存。
自动解除资源堵塞:当删除资源(一个信号量或者消息队列时),调用函数semDelete()和msgQDelete()解除所有任务的挂起。在任务等待被删除资源发送事件时,该措施保护任务避免无限期地堵塞。然后任务继续执行,导致任务挂起的函数eventReceive()返回一个ERROR值。
事件25到32(VXEV25或0x01000000到VXEV32或0x80000000)用作系统保留用,VxWorks用户不可以使用这些事件。
(3) 比较API
------------------------------------------------------------------------------------------------------------------
| VxWorks函数 | pPOS函数 | 注释 |
| eventSend | ev_send | 直接端口 |
| eventReceive | ev_receive | 直接端口 |
| eventClear | | VxWorks中的新功能 |
| semEvStart | sm_notify | SemEvStart等价于用非零事件参数调用sm_notify |
| semEvStop | sm_notify | SemEvStop等价于用事件参数为0调用sm_notify |
| msgQEvStart | q_vnotify | msgQEvStart等价于用非零事件参数调用q_notify |
| msgQEvStop | q_vnotify | msgQEvStop等价于用事件参数为0调用q_notify |
| | q_notify | VxWorks没有一个固定长度的消息队列机制 |
------------------------------------------------------------------------------------------------------------------
4. 看门狗定时器
VxWorks包括一个看门狗定时器机制,允许任何C函数与一个特定的时间延时器联系。看门狗定时器作为系统时钟中断服务程序的一部分来维护。被看门狗定时器调用的函数通常作为系统时钟中断级的中断服务代码来执行。但如果内核由于某种原因不能立即执行能够函数(例如一个优先中断或者内核状态),函数将放在tExcTask工作队列中。tExcTask工作队列中的函数以tExcTask(通常是0)优先级来执行。
-------------------------------------------------------------------------------------------
| 调用 | 描述 |
| wdCreate() | 分配并初始化一个看门狗定时器 |
| wdDelete() | 终止并释放一个看门狗定时器 |
| wdStart() | 启动一个看门狗定时器 |
| wdCancel() | 取消当前的一个计数的看门狗定时器 |
-------------------------------------------------------------------------------------------
5. 中断服务代码
为了尽快地响应中断,VxWorks中断处理程序在所有任务上下文之外的一个特殊上下文内执行。因此,中断处理不涉及到任务上下文的切换。
--------------------------------------------------------------------------------------------
| 调用 | 描述 |
| intConnect() | 设置中断处理的C程序 |
| intContext() | 如果是从中断级调用,返回真 |
| intCount() | 获得当前中断嵌套深度 |
| intLevelSet() | 设置处理器的中断屏蔽级 |
| intLock() | 禁止中断 |
| intUnlock() | 重新允许中断 |
| intVecBaseSet() | 设置向量基地址 |
| intVecBaseGet() | 得到向量基地址 |
| intVecSet() | 设置异常向量 |
| intVecGet() | 获得异常向量 |
--------------------------------------------------------------------------------------------
调用中断服务程序函数存在着很多的限制。例如,在应用中断服务程序时不能使用printf(), malloc()和semTake()函数,但是可以使用semGive(), logMsg(), msgQSend()和bcopy()这样的函数。
产生这些限制的原因是由于中断服务程序不在一个固定的任务上下文中执行,而且没有任务控制块,因此所有中断服务程序必须共享一个单独的堆栈。
. 中断服务程序基本限制为禁止调用导致调用者堵塞的函数。
. malloc()和free()都要求获得信号量,中断服务程序不能调用任何用于创建或删除的函数。
. 中断服务程序不能通过VxWorks驱动程序来执行I/O操作,因为大多数的设备驱动器可能会堵塞等待设备的调用者,因此它们需要一个任务上下文。但VxWorks管道驱动器是个例外,它设计用于中断服务程序的写操作。
. VxWorks提供了一个记录功能,允许向系统任务平台打印文本信息。这个机制是专门为中断服务程序使用而设计的,同时它也是从中断服务程序打印信息的最常用方法。
. 中断服务程序同时禁止调用浮点协处理器函数。在VxWorks操作系统中,由intConnect()函数建立的中断驱动代码不能保存和恢复浮点寄存器。若中断服务程序需要使用浮点指令,则必须明确地保存和恢复fppArchLib中函数浮点协处理器的寄存器。
. 所有VxWorks函数库,像连接链和环形缓冲器,都可以被中断服务程序使用。