VxWorks kernal programmers guide 下载本文

VxWorks Kernal Programmer’s Guide

第一部分:核心技术

1 概述

2 VxWorks配置

2.1 介绍

VxWorks是一个高可扩展性的操作系统,可以针对不同目的是要独立组件工具包轻松配置。VxWorks的定制版本也可以被创建,用于配置的备选项。本章讨论了基本的VxWorks配置,和小型化VxWorks配置,电源管理设施,和可以产生的VxWorks系统镜像类型。

2.2 关于VxWorks配置

VxWorks是一个灵活的可扩展的带多个设备的操作系统,这些设备可以包含,删除,不同配置,使用定制技术扩展,根据应用和系统需要,和开发周期不同阶段。默认的Vxworks镜像提供最初开发使用。

配置VxWorks的主要方式是VIPs和VSBs。风河Workbench开发套装和vxprj命令行配置工具可以用于和这些工程类型工作。关于风河Workbench和vxprj更多信息,参考Wind River Workbench by Example guide和VxWorks Command-Line Tools User’s Guide。

2.2.1 默认配置和镜像

VxWorks发布包括针对每个支持的BSP的默认系统镜像。每个系统镜像时一个可以引导好运行在目标系统上的二进制模块。一个系统镜像包括一组链接到一块的一组组件形成一个单一的不可移动的带已解决外部引用的对象模块。

默认的系统镜像设计用于开发环境。它们包含使用主机开发工具用于和系统交互的一组组件组成。大多数情况下,会为最初的系统开发提供足够的系统镜像(提供默认的驱动)。使用一个默认Vxworks镜像,你可以交互下载,且运行内核应用。

关于设置开发环境相关信息,参考Wind River Workbench by Example guide。关于VxWorks镜像类型信息,参考2.7 VxWorks Image Types。

2.2.2 VIP配置

VxWorks镜像工程用于配置不同组件组成的系统。VxWorks组件提供基于预编译库(归档文件)的操作系统工具单元,预编译库是通过特征引用的方式连接到系统中。关于VIPs更多信息,参考2.3 VxWorks Image Projects: VIPs。

2.2.3 VSB配置

对于有严格性能要求或资源限制的系统,VSB工程可以用于创建专门(频率较低的)的VxWorks库版本,和用于多样化优化。VSB选项和关联的条件化代码提供配置源代码基本的方法。一旦VxWorks库源码重新编译,一个VIP工程可以用于配置系统本身。关于VSB更多信息,参考2.4 VxWorks Source Build Projects: VSBs。

2.2.4 配置和定制

若由风河提供的VxWorks组件不提供你的系统要求的功能,你可以创建定制设备,如新的文件系统和网络协议,和组件包(参考20. Custom Components and CDFs),基于进程应用增加系统调用(参考21. Custom System Calls),创建你自己的调度(参考22. Custom Scheduler)。

2.2.5 配置工具:Workbench和vxprj

针对VIP和VSB提供的开发工具有风河Workbench开发套装和vxprj命令行配置工具。Workbench和vxprj可以用于配置和编译VxWorks系统,和其它管理VIP,VSB和其它类型的工程。

关于如何使用Workbench和vxprj,参考Wind River Workbench by Example和VxWorks Command-Line Tools User’s Guide。

2.3 VIPs

VxWorks镜像工程用于使用不同组件配置VxWorks系统。VxWorks组件提供基于预编译库的操作系统单元,预编译库通过特征引用的方法链接到一个系统中。VIP可能基于一个默认VxWorks配置或配置原型。配置原型提供设计用于具体目的的一组组件组成,如开发环境,DO-178B验证等。另外组件包用于使用一组组件配置系统,如那些提供POSIX支持的组件。

在开发周期过程中你可能想重新配置和重新编译VxWorks和专门选择用于支持应用开发需求的组件。如,你可能想保护内核shell和错误检测和报告工具,和删除包含在默认系统镜像中的其他组件。

对于生产系统,你将想重新配置VxWorks,仅需要操作过程中使用的组件,编译为合适的系统镜像类型(参考2.7 VxWorks Image Types)。你可能想删除针对主机开发需要的组件,如WDB

target agent和debugging components (INCLUDE_WDB and INCLUDE_DEBUG),和删除 支持其他操作系统的组件,应用不需要的组件,加速启动时间,和安全问题。

风河Workbench和vxprj命令行工具可以用于配置VxWorks,基于组件选择和删除。Workbench 配置工具展示了包含组件的图形化信息,和其他系统相关的信息。Vxprj工具也可以用于列出各种类型的组件信息。另外,VxWorks Component Reference提供所有组件和它们的参数,包,原型等的详细信息。(更多信息,参考2.3.1 VxWorks Components,2.3.3 Component Bundles and Configuration Profiles,和2.3.4 VxWorks Component Reference。)

2.3.1 VxWorks组件

一个VxWorks组件是VxWorks可以配置的基本功能单元。设计用于实时系统的组件,强调确定性和性能。每个组件提供一个或多个预编译静态库(归档文件),通常有一个或多个配置参数。组件通过特征引用的方法链接到系统中。

而一些组件是独立的,其它组件是有依赖性的,依赖的组件在运行时也必须包含在操作系统中。内核shell是一个依赖多个组件的组件。特征表是一个组件依赖其它组件的例子(内核shell和模块loader;更多信息,参考16. Kernel Shell and Loader)。组件依赖由VxWorks内核配置工具自动确定(风河Workbench和vxprj命令行工具)。

组件名

VxWorks组件使用CDFs文件packaged,用以INCLUDE_开始的宏名识别组件,和用户友好的描述。(关于配置文件的信息,参考20Custom Components and CDFs)

名,描述,配置VxWorks组件功能可以在Workbench中用GUI配置工具显示。Workbench为配置VxWorks选择组件提供了工具,设置组件参数,和配置和编译过程中组件间依赖关

系的自动确认机制。

命令行操作系统配置工具——vxprj——使用源于配置宏的命名传统来识别独立的操作系统组件。规范标识组件名称以INCLUDE开始。如,INCLUDE_MSG_Q是消息队列组件。除了配置工具vxprj工具提供了列举包含在工程中组件信息功能,配置参数等。

注:本书中,组件通过宏名识别。GUI配置工具提供了查找组件的功能。 基本的VxWorks组件

Table 2-1描述了VxWorks通用组件。名称以XXX结尾的表数组件家族,XXX由独立组件名的一个前缀代替。如INCLUDE_CPLUS_XXX,指包含INCLUDE_CPLUS_MIN和其它的组件家族。

Table 2-1注明的不包含VxWorks默认配置的所有组件。所有组件和参数的详细信息,参考VxWorks Component Reference。

2.3.2 设备驱动选择

设备驱动可以用Workbench和vxprj为系统增加或删除组件。一些驱动是VxBus兼容的,其它(传统驱动)不是。

注明仅VxBus兼容驱动可以用于SMP配置。关于VxWorks SMP和移植相关信息,参考24. VxWorks SMP和24.17 Migrating Code to VxWorks SMP。

注明VxBus驱动组件名不需要INCLUDE_元素(如DRV_SIO_NS16550),而非VxBus驱动使用INCLUDE_元素(如INCLUDE_ELT_3C509_END)。

关于VxBus设备驱动基础设施相关信息,参考VxWorks Device Driver Developer’s Guide。

2.3.3 组件包和组件原型

除了基本的VxWorks组件,组件包和用于配置VxWorks的配置原型。

组件包是管理组件组,可以做为一个单元增加到系统(如,BUNDLE_POSIX提供内核POSIX支持需要的所有组件)。

原型提供了一个操作系统功能基线,提供一个方便的方法,和VxWork生产安装默认配置不一样。原型如下:

PROFILE_SMALL_FOOTPRINT——VxWorks Small Footprint Profile,为必须的最小内核公共提供基本配置,和一个要求的小型化内存。

PROFILE_COMPATIBLE——VxWorks 5.5 Compatible Profile,提供兼容VxWorks 5.5 最小配置。

PROFILE_DEVELOPMENT——VxWorks内核开发原型,提供一个VxWorks内核,包含开发和调试组件,包含实时进程的支持。

PROFILE_ENHANCED_NET——VxWorks增强网络原型。为经典管理网络客户端工具设备增加合适组件到默认原型。增加的主要组件是CHCP客户端和DNS服务器,telnet服务器(不包含shell),和几个命令行式样配置工具。

PROFILE_CERT——VxWorks DO-178认证原型。提供VxWorks操作系统的一个DO-178验证API组。

PROFILE_BOOTAPP——VxWorks boot loader原型,为一个Vxworks boot loader提供工具。

更多信息,参考3.7 Configuring and Building Boot Loaders。

关于所有组件包和原型信息,参考VxWorks Component Reference。

2.3.4 VxWorks组件引用

关于所有VxWorks组件和太慢的参数,组件包,原型更详细的引用信息,参考VxWorks Component Reference。

2.4 VSBs

标准VxWorks组件设计用于实时系统,强调确定性和性能。它们提供了预编译库,通过特征引用的方法链接到系统中。这是对于提供快速构建比较方便,意味着可以构建更大,更具通用目的的库,不需要为了适应一个系统的需求针对大小和性格有非常严格的要求。

对于有严格性能需求和资源限制的系统,VSB工程可以用于创建专门的VxWorks库版本,并可以使用不同的优化。库基于用户不同的选择有条件的编译。一些情况下,一个VSB工程产生的库提供一个APIs子组,由标准的VxWorks组件提供,或其他有一些不同的行为。它们会提高性能,因为他们仅执行定义的一组代码。

为了在Vxworks系统中使用库版本和优化,一个VIP工程随后被创建,关联一个VSB工程。VIP工程之后用于配置VxWorks本身。

VSB选项和关联的条件代码针对源代码基本的配置提供方法。VSB工程工具提供的主要选项种类包括如下:

? 组件基本的非离散可配置功能选项(包括跨库和组件的功能)。如,系统可视化工具。

? 编译优化选项。如,浮点数支持。

? 多处理器系统相关选项。如为SMP VxWorks配置指定库。

? 用于构建功能库选项,通过源码传递(如不同的网络和中间件工具)。 VSB选择控制基于库代码条件编译的VxWorks功能增加和删除。基于VSB工程的系统可以像小型化系统一样提高系统性能,因为它们会删除系统不需要的代码,只留下执行的嗲吗。另外它们还充分使用专门优化。(如,cache模式)

Figure 2-1展示了VSB工程如何用于删除系统不需要的代码的抽象视图。这种情况下,一个VSB选项可以用于删除支持来自整个系统的XYZ功能。

一些情况下,具体APIs组或整个库可以用VSB选项删除。如,若创建一个无须支持RTPsd的VSB工程,则rtpLib不需要包含在系统中。

更具体一点,默认包含在几个VxWorks库(如semBLib)中的任务系统可视化工具,由不同的组件提供(如INCLUDE_SEM_BINARY)。 提供这个功能的代码因此有效的跨越几个库和组件。若系统视图没有打算使用(如大多数部署系统),一个VSB工程可以用于从系统中所有VxWorks中删除系统可视化工具。这提高了体积和性能。

2.4.1 基本的操作系统VSB选项

VSB工程设施(Workbench或vxprj)提供一组扩展选项用于选择配置一个VSB工程。这些包括具体功能选项,和设计用于性能和体积优化的选项。 针对基本的VxWorks操作系统功能VSB工程可选选项如下: ? 具体BSP优化(如浮点数支持) ? 不一致的缓存模式(PowerPC) ? System Viewer Instrumentation

? 实时进程 ? 对象管理

? 共享内存对象(VxMP) ? 任务钩子函数 ? CPU电源管理 ? 高级选项

? VxWorks BSP验证测试套装 ? SMP

? SMP确定性或性能 ? AMP

这些选项在如下详细描述。注明其它技术(如网络)提供更多的附加选项,在相关的编程指导和平台用户指南中描述。

针对一系列选项和描述,参考风河Wrokbench VSB功能和vxprj工具。

2.4.2 VSB原型

VSB原型提供提供了一个方便的定义一组VSB选项的方法用于创建设计用于具体目的的操作系统设备。一个VSB原型做如下事情:

? 展示一组供用户改变的VSB选项。

? 隐藏和原型目的无关的其它选项或用户不能改变的选项。 ? 为原型目的设置所有默认选项。

如,参考2.5.2 Configuring Small Footprint VxWorks。

2.4.3 使用VSB工程创建VxWorks系统基本步骤

使用VSB工程来配置和构建VxWorks,涉及如下基本步骤(使用Workbench或vxprj命令行工具):

1. 创建一个VSB工程,选择期望的VSB选项,之后编译工程创建具体VxWorks库版本和使用其它优化;

2. 基于VSB工程创建有一个VIP。

3. 使用VIP,用期望的组件配置VxWorks,之后基于专门的库版本编译工程,创建系统镜像。

注:工具配置防止增加用VSB工程创建的定制库不支持的组件。

2.4.4 针对VSB系统开发内核应用

因为用VSB工程创建的系统包含标准VxWorks库版本,趋向于运行在那些系统上的内核应用必须带编译时连接到版本库。相关信息,参考Wind River Workbench by Example guide和

VxWorks

Command-Line Tools User’s Guide。

2.5 小型化Vxworks配置

VxWorks可以使用一个小型化原型配置产生一个携带小型化内存的最小内核。

注:所有体系结构不支持VxWorks的小型化配置。关于支持的信息,参考VxWorks

Architecture

Supplement和VxWorks BSP Reference。

2.5.1

2.5.2

2.5.3

2.5.4

2.5.5

2.5.6

关于小型化VxWorks 配置小型化VxWorks

针对小型化VxWorks的配置和编译步骤 针对小型化VxWorks开发应用 应用例子

调试小型化VxWorks

2.6 电源管理

开始于VxWorks 6.2版本,增强电源管理设备由因特体系结构(IA)提供。早期版本针对其它体系结构提供的设备保持不变。针对其它体系结构提高的新设备在以后发布。参考2.6.1 Power Management for IA Architecture和2.6.2 Power Management for Other Architectures。

2.6.1 针对IA体系结构的电源管理

2.6.2 针对其他体系结构的电源管理

2.7 VxWorks镜像类型

不同的VxWorks镜像类型可以用于不同的引导方法,存储,加载和执行场景。如下镜像的默认版本在VxWorks安装中被提供。可以创建由不同组件组成的定制版本。

仅vxWorks镜像类型——有时值一个可下载镜像——需要一个boot loader。这种方法在开发环境下最通用(最实际),因为不需要每次修改的时候重新烧录到flash或拷贝到目标存储媒介。镜像通常存储在主机系统中。

其它镜像类型——有时指独立镜像——不需要一个独立的boot loader。通常用于生产系统,存在在flash中。独立镜像需要配置非默认启动参数(参考2.7.3 Boot Parameter Configuration for Standalone VxWorks Images)。

不同的VxWorks镜像类型,用法,行为描述如下: vxWorks

VxWorks镜像类型目的是开发过程中使用,通常指downloadable。在生产系统下也非常用于,boot loader 和系统镜像被存储在磁盘上。在开发环境中,镜像通常存储在主机系统(或网络服务器上),通过boot loader下载到目标系统,并加载到RAM。特征表在主机上维持(vxWorks.sym文件),用于主机开发工具。保存特征表在主机上减少了要加载到目标机上的镜像大小,减少了启动时间。

若VxWorks配置了INCLUDE_STANDALONE_SYM_TBL组件,特征表包含在VxWorks镜像中。

vxWorks_rom

一个存储在目标机上非易失性设备上的VxWorks镜像。它拷贝本身到RAM,之后切换处理器到RAM执行。因为镜像没有压缩,所有比基于ROM镜像大,因此有一个比较慢的启动时间;但是比vxWorks_romResident镜像执行时间快。关于启动参数配置信息,参考2.7.3 Boot Parameter Configuration for Standalone VxWorks Images。

vxWorks_romCompress

一个存储在目标机上非易失性设备上的VxWorks镜像。几乎整个压缩,有很小未压缩部分在设备上电或复位时处理器立即执行。这一部分代码负责解压缩ROM镜像中压缩部分到RAM,切换处理器到RAM执行。因为是压缩镜像和其它镜像比需要更小的存储空间,但是解压缩延长启动时间。和vxWorks_rom比启动时间更长,使用的ROM空间少。和vxWorks_rom有同样的运行速度。关于启动参数的配置,参考2.7.3 Boot Parameter Configuration for Standalone VxWorks Images。

vxWorks_romResident

一个存储在目标机上ROM中的VxWorks镜像。启动时仅拷贝dss段到RAM;text段

仍保持在ROM中。因此描述为ROM-resident。启动比较快,使用RAM很小,但是运行比较慢,因为ROM访问提取指令比RAM提取指令慢。对于有内存限制的系统比较有用。启动参数配置信息,参考2.7.3 Boot Parameter Configuration for Standalone VxWorks Images。

2.7.1 默认VxWorks镜像

默认VxWorks镜像文件在installDir/vxworks-6.x/target/proj/projName下。如: /home/moi/myInstallDir/vxworks-6.x/target/proj/wrSbc8260_diab/default_rom/vxWorks_rom

2.7.2 针对开发和生产系统的VxWorks镜像

对于很多生产系统通常必须在ROM中存储一个和VxWorks链接的内核应用模块。VxWorks要配置在启动时自动执行应用。系统镜像可以见到存储应用模块允许被其它函数调用,或被终端用户交互使用(如,诊断程序)。

为了生产一个基于ROM的系统,你必须链接模块到v xWorks,并编译为适合ROM的系统镜像类型。相关信息,参考4.12 Configuring VxWorks to Run Applications Automatically。若你想在启动时自动启动应用,你必须配置VxWorks如下(4.12 Configuring VxWorks to Run Applications Automatically)。或参考4.13 Image Size Considerations。

注明,开发过程中,VxWorks必须配置WDB目标代理通讯接口,用于连接主机和目标机系统(网络,串口等)。默认,配置为一个增强网络驱动(END)连接。更多信息,参考D. WDB Target Agent。注明在使用主机开发工具之前,如shell和debugger,必须启动一个目标服务器,配置为通讯的相同模式。

关于配置带不同操作系统工具的VxWorks的信息,参考2.3.2 Device Driver Selection。 若你想在flash在存储镜像,想使用用户TrueFFS,参考13.3.6 Reserving a Region in Flash for a Boot Image。

2.7.3 单独VxWorks镜像启动参数配置

依赖系统需求,你可能需要为一个独立的镜像静态的重新配置启动参数——也就是说,不需要一个boot loader——指所有的系统镜像,除了downloadable vxWorks镜像。如一个网络目标机大多数需要设置本身的IP地址为一些默认的信息。

使用INCLUDE_BSP_MACROS组件的DEFAULT_BOOT_LINE配置参数设置启动参数。针对vxWorks的配置过程和boot loader的配置过程一样。关于boot loader的信息,参考3.5.2 Description of Boot Parameters。关于静态配置的信息,参考3.7.3 Configuring Boot Parameters Statically。

3 Boot Loader

3.1 介绍

一个VxWorks boot loader是一个应用,目的是加载一个VxWorks image到目标机。有时

VxWorks

Bootrom,但是这个词不推荐使用(合并应用和媒介)。如VxWorks,boot loader可以用

不同的工具配置,如命令行工具动态设置启动参数,一个网络loader,和一个文件系统loader。 相同的boot loader VxWorks 配置用于单处理器(UP),对称多处理器(SMP),和非对称多处理器(AMP)。

在一个开发环境中,boot loader对于从一个主机系统加载一个VxWorks镜像时非常有用的,在主机系统上,VxWorks可以快速修改和编译。当boot loader和操作系统都存储在磁盘

上或其它媒介上时,也可以用于生产系统。

Self-booting (standalone) VxWorks images不要求一个boot loader。这些镜像通常用于生产系统(存储在non-volatile设备上)。更多信息,参考2.7 VxWorks Image Types。

常常,boot loader被烧录到一个non-volatile设备(flash内存或EEPROM)上具体地址处,这个地址是目标机上电或重新启动后,处理器执行的第一条代码处。获取烧录在non-volatile设备上的boot loader,或写入到一个磁盘的方法取决于目标机,在BSP参考文档中描述。

VxWorks产品安装包括针对每一个安装BSP的默认boot loader。若它们不满足要求,你可以创建定制boot loaders。如,你可能需要使用一个不同的网卡驱动通过网络来加载Vxworks镜像,或你为了部署系统删除boot loader shell。

本章涉及的信息,特别是关于安装一个cross-development环境的信息,参考Wind River Workbench by Example。

3.2 使用默认boot loader

默认boot loader设计用于一个网络目标,必须配置相关参数,如主机和目标网络地址,加载文件全目录和文件名,用户名等。为了使用默认boot loader,你必须使用boot loader shell交互式的改变默认参数,以至于loader可以找到主机上的VxWorks镜像,并加载到目标机。

进入引导参数后,目标机启动VxWorks镜像。大多数目标机,新的设置会保存(在non-volatile设备或磁盘上),所有你可以直接启动目标机,不需要重新设置默认参数。

在一个终端控制台上,可以和boot loader shell交互,这个终端控制台,通过串口连接主机和目标机,在主机上启动一个终端应用程序。建立通讯的具体信息,参考相关文档。

默认boot loade镜像位于installDir/vxworks-6.x/target/config/bspName。Boot loader命令和参数在3.4.1 Boot Loader Shell Commands描述和3.5 Boot Parameters。提供不同的Boot loader镜像类型,如3.3 Boot Loader Image Types描述。

3.3 Boot Loader Image类型

Boot loader镜像可以存储在ROM,flash,磁盘,或存储在网络上。Boot loader镜像是ELF个数。二进制版本(.bin)用于磁盘,16进制文件版本(.hex)用于烧录non-volatile设备。什么样的目标机选择什么样的boot loader,参考BSP相关文档。

下面描述了boot loader的不同版本。下面列出的每一组镜像的第一个镜像是通过PROFILE_BOOTAPP配置原型产生的,第二个通过传统的bspDir/config.h方法。关于编译方法的更多信息,参考3.7 Configuring and Building Boot Loaders。

Compressed Image

vxWorks_romCompress 和 bootrom 文件 这个镜像几乎是整个压缩的。有一小部分未压缩部分,在设备上电或复位之后处理器立即运行。这部分初始化内存和解压缩压缩部分(存储在非易失性设备上)到RAM,导致处理器切换到RAM执行。压缩镜像比其他boot loader镜像小,因此使用比较小的非易失性存储空间。然而,解压缩增加了启动时间。

Uncompressed Image

vxWorks_rom 和 bootrom_uncmp 文件 这个镜像没有压缩。拷贝本身到RAM,处理器切换到RAM执行。因为镜像没有压缩,所以比压缩镜像大。然而,启动时间比较快,因为不需要要求的解压缩操作。

Resident in Non-Volatile Storage

vxWorks_romResident 和 bootrom_res 文件

这个镜像在启动时仅拷贝数据段到RAM,文本段保留在非易失存储上。这意味着处理器总是在非易失性存储外执行指令。因此有时描述为ROM-驻留型。这种类型的boot loader要求足够大的RAM来加载VxWorks内核。因此对于带有较小的RAM板卡,RAM是为应用保存数据的情况是很有用的。

Boot loader镜像位于installDir/vxworks-6.x/target/config/bspName。注明大多数默认BSP的默认镜像都需要配置网络开发环境。关于创建一个定制boot loader的信息,参考3.7 Configuring and Building Boot Loaders。

3.4 Boot Loader Shell

Boot loader shell提供了如何命令:

? 改变启动参数(如主机和目标IP地址) ? 重新启动目标系统 ? 管理启动过程

配置INCLUDE_BOOT_SHELL组件到boot loader,来包含boot loader shell。

提醒:不要增加INCLUDE_WDB_BANNER,INCLUDE_SIMPLE_BANNER,INCLUDE_SHELL或组件到boot loader。这些组件和boot loader shell冲突。若你包含了这些组件中的的其中一个,你会遇到错误。

3.5 Boot Parameters

启动参数包括需要定位和加载一个VxWorks内核的所有信息,还有其它用于管理启动过程的信息。根据具体启动配置要求,可以包含主机和目标机IP地址和全路径和要引导的VxWorks镜像名,用户名等。启动参数可以在运行时交互改变,也可以在创建boot loader时静态配置。交互改变启动参数要通过重启生效(在一个非易失性设备或磁盘上)。启动参数用于独立VxWorks镜像——不需要一个boot loader——boot loader自身用(更多信息,参考2.7 VxWorks Image Types)。 3.5.1 显示目前的启动参数

3.5.2 启动参数描述

3.5.3 交互式改变启动参数

3.6 Rebooting VxWorks

3.7 配置和编译Boot Loaders

3.8 安装boot Loaders

3.9 从一个网卡启动

3.10

从一个目标文件系统启动 从使用TSFS的主机文件系统启动

3.11

4 内核应用程序

4.1 介绍

VxWorks内核应用执行在和内核本身一样的模式和内存空间。这方面和用于其它操作系统的应用不一样,如UNIX和Linux;也和VxWorks实时进程RTP应用不一样。

内核应用可以交换下载和运行在一个VxWorks目标机上,或链接到操作系统镜像中,(可选)在启动时自动执行。

这一章提供了关于编写内核应用代码,创建为用户使用的静态库,内核对象静态实例化,执行应用等相关的信息。关于多任务,I/O,文件系统,其它内核中有的vxWorks工具相关的信息,参考相关章节。

关于Workbench和命令行编译环境相关信息,参考Wind River Workbench by Example和VxWorks Command-Line Tools User’s Guide。

关于开发用户模式RTP应用相关信息(作为用户模式的实时进程一样执行),参考VxWorks Application Programmer’s Guide。

4.2 关于内核应用

执行在内核中的VxWorks应用创建为一个浮动对象模块。它们可以被称为最特别的kernel-based application modules,但是通常为了方便称它们为kernel application modules或kernel applications。不要和执行在用户模式(实时进程RTPs)的应用混淆。

当一个基于内核应用模块编译后,用户代码被链接到请求的VxWorks库,产生一个ELF二进制文件。内核应用包含定义操作系统接口和数据结构的头文件后使用VxWorks工具。

内核应用模块可能是:

通过对象模块loader加载或动态链接到操作系统中。 静态链接到操作系统,作为系统镜像的一部分。 下载内核模块对应快速开发和调试非常有用,因为操作系统不需要为应用的每个迭代重新编译。这个方法也可能用于生产系统的诊断工具。不同的开发工具,包括调试器和shell(主机或内核),可以用于下载和管理模块。模块可以从任何主机支持的文件系统中(NFS,ftp等)下载到目标机。

内核应用模块也可以存储在目标机flash或ROM中,在ROMFS文件系统中,或磁盘上。一旦它们已经加载到目标机,内核应用模块可以在shell或Wrokbench上立即启动这些应用。

静态链接到操作系统的应用模块可以在shell或Workbench中交互运行。VxWorks也可以配置它们为启动时自动启动。静态链接和自动启动在生产系统中比较稳定。

一个运行在内核空间中的应用不会像一个进程一样执行;简化为另外一组执行在内核空间的其它一组任务。内核不保护任何一个参与的内核应用导致的任何不良行为——内核应用和内核以管理员模式运行在同样的地址空间。

警告:若你想移植一个内核应用到用户模式应用,执行为一个RTP,你必须确保代码满足一个RTP应用的要求和编译为一个RTP。你必须确保VxWorks配置支持RTPs。更多信息,参考VxWorks Application Programmer's Guide。

4.3 C和C++库

风河固有C库和Dinkum C和C++库都为VxWorks应用开发提供了。如Table 4-1展示,VxWorks固有库用于C内核应用开发,和库用于所有案例。

VxWorks固有C库提供ANSI规格外函数。注明不对多字节字符提供支持。

关于这些库的更多信息,参考VxWorks and Dinkum API references。关于C++工具更多信息,参考5. C++ Development。

4.4 内核应用结构

内核应用代码和通常C或C++应用相似,不一样的地方是,内核应用不需要传统的main()函数(不像一个基于进程的VxWorks应用)。仅需要一个入口点函数,启动应用运行需要的所有任务。

注明:若你的内核应用包括一个main()函数,也不会自动启动。下载或存储在系统镜像中的内核应用模块必须被交互启动(或被另外一个已经运行的应用启动)。操作系统也可以配置为自动启动(参考a4.12 Configuring VxWorks to Run Applications Automatically)。 入口点函数执行所有需要的数据初始化,启动所有运行应用使用的任务。如一个内核应用可能有一个函数命名为myAppStartUp( ):

void myAppStartUp (void) {

runFoo();

tidThis = taskSpawn(\(FUNCPTR) thisRoutine,0,0,0,0,0,0,0,0,0,0); tidThat = taskSpawn(\(FUNCPTR) thatRoutine,0,0,0,0,0,0,0,0,0,0);

tidAnother = taskSpawn(\(FUNCPTR) anotherRoutine,0,0,0,0,0,0,0,0,0,0); return (OK); }

关于VxWorks任务和多任务信息,参考6. Tasks and Multitasking。关于和C++一同工作的信息,参考5. C++ Development。

4.5 VxWorks头文件

很多内核应用大量使用VxWorks操作系统工具或工具库。这个通常需要源模块引用Vxworks头文件。如下部分讨论了VxWorks头文件的使用方法。

VxWorks头文件支持针对所有全局VxWorks函数的ANSI C函数原型声明。VxWorks通过

ANSI

X3.159-1989标准规范所有的头文件。

VxWorks系统头文件在installDir/vxworks-6.x/target/h目录下和子目录下。 4.5.1 VxWorks头文件:vxWorks.h

头文件vxWorks.h必须在每个使用VxWorks工具的内核应用模块中首先包含。它包含用于其它

VxWorks模块扩展的基本定义和类型。很多其他VxWorks头文件要求这些定义。

#include

4.5.2 其它VxWorks头文件

内核应用可以包含其它VxWorks头文件,若需要访问VxWorks工具。如一个使用VxWorks链接表库的模块必须包含lstLib.h头文件:

#include

针对每个库的API引用入口列出了用于库的所有必须的头文件。

4.5.3 ANSI 头文件

所有的ANSI规范的头文件都包含在VxWorks中。那些编译器独立的或具体VxWorks的头文件在installDir/vxworks-6.x/target/h,而一个编译器独立的(如stddef.h和stdarg.h)在编译器安装时被提供。每个工具链知道如何查找自身的内部头文件;不需要特殊的编译标志。

4.5.4 ANSI C++头文件

每个编译器有自身的C++库和C++头文件(如iostream和new)。C++头文件位于编译器安装目录,不是installDir/vxworks-6.x/target/h。不需要特殊标志来使能编译器查找这些头文件。关于C++开发更多信息,参考5. C++ Development。

注:VxWorks 5.5先前版本,风河推荐使用-nostdinc标志。目前版本不要使用,会防止编译器查找头文件,如stddef.h。

4.5.5 –I编译器标志

默认,编译器首先在源模块中查找头文件,之后在源模块中子目录中查找。通常编译器总是在查找源模块其它子目录之前查找目录installDir/vxworks-6.x/target/h;为了确保这个顺序,总是在VxWorks下增加如下标志:

-I %WIND_BASE%/target/h %WIND_BASE%/target/h/wrn/coreip 一些头文件位于子目录中 。为了在这些目录下引用头文件,确保include对应子目录名,所有使用单元-I标识符指明,文件会被找到。如:

#include #include

4.5.6

VxWorks嵌套头文件

一些VxWorks工具利用其它低水平的VxWorks工具。如,tty管理工具使用环形缓存子程序库。Tty头文件tyLib.h使用环形缓存头文件rngLib.h.提供的定义。

将会不方便的是需要你意识到包含头文件的内部依赖和排序。取而代之的是,所有的VxWorks头文件显式包含了所有先决条件的头文件。因此,tyLib.h本身包含了头文件rngLib.h。(一个例外是基本VxWorks头文件vxWorks,所有其他的头文件已经包含)

通常,先决头文件的显式包含可以抛出一个问题:一个头文件只能包含一次,否则会产生重要错误(因为C预处理器认为重复定义会造成潜在的代码冲突问题)。然而,所有的头文件包含条件编译语句和保证只编译一次的定义,和你include多长没关系。因此,一个内核应用模块仅需要包含直接的头文件,无需关心头文件中的内部依赖和顺序,不会产生冲突。

4.5.7 VxWorks私有头文件

一些VxWorks元素是内部可能变化的细节,所有不能再内核中引用。仅支持一个模块的工具用法通过在头文件中公共定义,和通过模块的子函数接口。你的坚持确保了你的应用代码不会VxWorks模块实现中内部变化的影响。

一些使用HIDDEN组件标志的内部细节:

/* HIDDEN */ ...

/* END HIDDEN */

内部细节也可以用private头文件隐藏:存储在installDir/vxworks-6.x/target/h/private目录中的文件。这些文件的命名规范使用后缀P.h在installDir/vxworks-6.x/target/h目录中。如semLib的私有头文件是installDir/vxworks-6.x/target/h/private/semLibP.h。

4.6 静态内核对象实例化

VxWorks内核对象——如任务和信号量——可以动态或静态实例化。静态实例化提供性能和确定性优势。专有VxWorks C 宏提供静态实例化。

4.6.1 关于内核对象的静态实例化

一个内核对象的静态实例化意味着对象被声明为一个编译时变量(使用一个专有Vxworks宏),通常用于全局范围。因此编译器为应用对象分配空间,不需要在运行时分配。因此,对象在启动时可以立即初始化。

与静态实例化对比,内核对象的动态实例化涉及运行时系统内存分配,之后在使用之前初始化。同样,对象删除涉及对象的验证,之后返回内存给系统。对象创建和删除同样依赖动态内存分配,通常使用malloc( )和free( )函数。

应用使用动态实例化必须考虑运行时内存不足,对象不能成功创建的情况(采取一些合适的错误恢复过程或退出)。另外,动态分配是一个相对比较慢的操作,会阻塞一个单一函数的调用(如,taskSpawn( ),semXCreate( ),等)。

如下实例说明了动态和静态代码实例化的区别(但是不要使用任何VxWorks对象实例化宏)。

动态实例化

struct my_object * pMyObj; ...

pMyObj = (struct my_object *) malloc (sizeof (struct my_object)); if (pMyObj != NULL) {

objectInit (pMyOjb); return (OK); } else {

/* failure path */ return (ERROR); }

静态实例化

struct my_object myObj; ...

objectInit (&myOjb);

/* myObj now ready for use */

可以静态实例化的内核对象 如下内核对象可以静态实例化: ? 任务 ? 信号量 ? 消息队列 ? 看门狗定时器

更详细的信息,参考4.6.4 Static Instantiation of Tasks,4.6.5 Static Instantiation Of Semaphores,4.6.6 Static Instantiation of Message Queues,和4.6.7 Static Instantiation of Watchdog Timers。

静态实例化和代码大小

对象的编译时声明不在可执行文件中,一个VxWorks镜像中或存储介质中(如 flash内存)占据任何空间。若一个对象在编译时声明,但是没有初始化,编译器设置它为未初始化数据段(bss)。未初始化数据要求被ANSI C标准设置为0。

当未初始化数据会导致运行时内存占用,所有需要动态分配。另外情况,内存占用一样。 静态初始化优势

内核对象的静态初始化提供了几个优势,和动态初始化比较: ? 对象的静态初始化是一个快速,比较确定性操作。

? 应用逻辑简单因为不用考虑动态初始化内存分配失败的问题。 ? 静态对象命名不会失败,除非程序本身太大,系统内存不能容纳。

? 小型化VxWorks配置可以用针对动态内存分配的删除工具创建(如,参考2.5 Small-Footprint VxWorks Configuration)。

应用和静态初始化

内核对象的静态初始化为实时应用提供了显著优势,大多数应用应该大量考虑静态初始化。大多数应用要求一些对象在整个应用运行生命周期内存在,不要求删除。因此这些对象可以静态初始化。使用静态初始化,使得软件更具备鲁棒性,确定性和更快速。

另外,小系统在设计时不要使用动态内存分配工具,一个VxWorks配置是专门用于这样的设计(更多信息,参考2.5 Small-Footprint VxWorks Configuration)。

注:静态初始化应该在内核应用中使用。不设计用于RTP应用(用户模式)。

4.6.2 static声明范围

内核对象通常声明为全局变量,因为对象IDs通常用于任务间通讯和同步。然而,也不需要是全局的。在函数范围内提供一个对象声明,生命周期仅在函数运行范围内有效。

4.6.3 关于宏使用的警告

为了保证合适的宏表达式,任务初始化宏必须使用一个反斜杠字符,若声明或调用换行时。如,如下确保了使用VX_TASK_INSTANTIATE宏进行一个任务的静态初始化会被编译器合适处理:

myTaskId = VX_TASK_INSTANTIATE(myTask, 100, 0, 4096, pEntry, \\ 0,1,2,3,4,5,6,7,8,9);

宏使用详细描述在4.6.4 Static Instantiation of Tasks,4.6.5 Static Instantiation Of Semaphores,4.6.6 Static Instantiation of Message Queues,和4.6.7 Static Instantiation of Watchdog Timers。

4.6.4 任务的静态实例化

VX_TASK宏在编译时声明了一个任务对象。宏使用两个参数:任务名和栈大小。和使用taskSpawn( )不一样,名称可能为NULL指针,名称强制带有VX_TASK宏。栈大小必须为一个非零整数值,必须为一个编译时常量。

VX_TASK_INSTANTIATE宏可以用于VX_TASK来静态初始化和调度一个任务取代动态初始化taskSpawn( )。同样,VX_TASK_INITIALIZE宏可以用于VX_TASK来初始化一个任务,但是任务一直处于挂起状态,指定使用taskActivate( )函数激活。

VX_TASK_INSTANTIATE返回发起的任务ID,或任务发起异常,返回ERROR。相同的任务名必须用于VX_TASK_INSTANTIATE和VX_TASK宏。如:

#include #include VX_TASK(myTask,4096); int myTaskId;

STATUS initializeFunction (void) {

myTaskId = VX_TASK_INSTANTIATE(myTask, 100, 0, 4096, pEntry, \\ 0,1,2,3,4,5,6,7,8,9); if (myTaskId != ERROR)

return (OK); /* instantiation succeeded */ else

return (ERROR); }

为了初始化一个任务,但是一直保持挂起状态,直到调用VX_TASK_INITIALIZE宏之后。taskActivate( )函数用于之后运行任务。相同的参数必须用于VX_TASK_INSTANTIATE和taskActivate( ).。如:

#include #include VX_TASK(myTask,4096); int myTaskId;

STATUS initializeFunction (void) {

myTaskId = VX_TASK_INITIALIZE(myTask, 100, 0, 4096, pEntry, \\

0,1,2,3,4,5,6,7,8,9); if (myTaskId != NULL) {

taskActivate (myTaskId); return (OK); } else

return (ERROR); }

更多信息,参考the taskLib API reference entry。

关于任务和任务管理函数相关通用信息,参考6.2 About Tasks and Multitasking,6.4 Task Scheduling,和6.5 Task Creation and Management。

4.6.5 信号量的静态实例化

VX_BINARY_SEMAPHORE, VX_COUNTING_SEMAPHORE,VX_MUTEX_SEMAPHORE, 和 VX_READ_WRITE_SEMAPHORE宏分别用于在编译时声明一个二进制类型信号量,counting,

互斥。

通过这些宏声明的信号量通过分别调用semBInitialize( ), semCInitialize( ) semMInitialize( ), 和 semRWInitialize( )函数初始化。三个semXInitialize( )函数和他们相关的semXCreate( )函数功能一样。

相同的信号量命名必须成对使用VX_XXX_SEMAPHORE宏和semXInitialize( )函数。函数的返回值是一个信号量ID,之后用于执行信号量操作。如:

#include #include

VX_BINARY_SEMAPHORE(mySemB); /* declare the semaphore */ SEM_ID mySemBId; /* semaphore ID for further operations */ STATUS initializeFunction (void) {

if ((mySemBId = semBInitialize (mysemB, options, 0)) == NULL) return (ERROR); /* initialization failed */ else

return (OK); }

更多信息,参考API references for semBLib, semCLib, and semMLib。关于信号量的通用信息,参考7.4 Interrupt Locks。

4.6.6 消息队列的静态实例化

VX_MSG_Q宏在编译时声明一个消息队列。使用三个参数:名称,消息队列中最多消息数,每个消息的最大字节数。msgQInitialize( )函数用于初始化消息队列,使得为使用准备好。相同的消息队列名——还有针对消息队列大小和最大消息数的相同值——必须使用宏和函数。如:

#include #include

VX_MSG_Q(myMsgQ,100,16); /* declare the msgQ */

MSG_Q_ID myMsgQId; /* MsgQ ID to send/receive messages */ STATUS initializeFunction (void) {

if ((myMsgQId = msgQInitialize (myMsgQ, 100, 16, options)) == NULL) return (ERROR); /* initialization failed */ else

return (OK); }

更多信息,参考API reference for msgQLib。关于消息队列通用信息,参考7.7 Message Queues。

4.6.7 看门狗定时器的静态实例化

VX_WDOG宏在编译时声明一个看门狗定时器。使用一个参数,看门狗定时器的名字。wdInitialize( )函数用于初始化看门狗定时器和使能使用。相同的看门狗名必须用于宏和函数。如:

#include #include

VX_WDOG(myWdog); /* declare the watchdog */

WDOG_ID myWdogId; /* watchdog ID for further operations */ STATUS initializeFunction (void) {

if ((myWdogId = wdInitialize (myWdog)) == NULL) return (ERROR); /* initialization failed */ else

return (OK); }

更多信息,参考API reference for wdLib。关于消息队列的通用信息,参考8.4 Watchdog Timers。

4.7 内核应用和内核组件需求

VxWorks是一个高配置操作系统。当内核应用模块独立于操作系统编译(参考4.9 Building Kernel Application Modules),编译过程不能确定是否应用上的VxWorks实例最终运行,因为,要保证VxWorks内核包含应用所需的所有组件(如网卡和文件系统等)。因此,应用代码检查内核设备不存在的错误提醒(也就是说,检查API调用的返回值)和正确的回复比较有用。

当内核应用模块链接到操作系统时,编译系统产生和丢失组件相关的错误。Workbench和vxprj命令行工具也可以提供为重新配置VxWorks进行依赖检查的机制。

4.8 内核应用和内核组件需求

4.9 编译内核应用模块

VxWorks内核应用可以用Workbench或用命令行编译(命令行环境包括一组有用的默认makefile规则)。关于Workbench和命令行编译环境的用法,参考Wind River Workbench by Example guide和VxWorks Command-Line Tools User’s Guide。

提醒:VxWorks内核应用必须针对运行的系统类型编译。针对UP VxWorks系统编译,

针对SMP VxWorks编译,针对基于版本库创建的VxWorks系统,版本库基于VSB工程(UP或SMP),二进制不兼容。注明loader拒绝一个内核模块若和加载的系统不兼容,控制台会输出一个错误信息,并设置errno为S_loadLib_INCOMPATIBLE_MODULE。

4.10 下载内核应用对象模块到目标机

内核应用对象模块可以从Workbench或内核shell中下载。一旦一个模块加载到目标内存,模块中的任何子函数会被触发,任务发起,模块使用的调试工具等。通常使用启动函数来运行应用比较有用。(参考4.4 Kernel Application Structure)。

关于使用内核shell和内核loader信息,参考16.2 Kernel Shell和16.3 Kernel Object-Module Loader。关于使用Workbench更多信息,参考Wind River Workbench by Example guide。

4.11 链接内核应用对象模块到VxWorks

VxWorks内核应用可以使用Workbench或命令行开发工具链接到VxWorks。关于使用Workbench和命令行编译环境信息,参考Wind River Workbench by Example guide和VxWorks Command-Line Tools User’s Guide。

4.12 配置VxWorks自动运行应用程序

VxWorks可以配置为在启动时自动启动内核应用。这样做,执行如下步骤: 1.配置INCLUDE_USER_APPL组件到VxWorks中;

2.在应用的入口点usrAppInit( )函数中增加调用,在installDir/vxworks-6.x/target/proj/projDir/usrAppInit.c文件中。

假如,应用入口点函数myAppStartUp( )启动所有应用需要的任务,你需要在usrAppInit( )函数中增加调用如下:

void usrAppInit (void) {

#ifdef USER_APPL_INIT

USER_APPL_INIT; /* for backwards compatibility */ #endif

myAppStartUp(); }

3.链接基于内核应用对象模块到内核镜像(参考4.11 Linking Kernel Application Object Modules with VxWorks)。

4.13 镜像大小考虑

系统镜像大小通常要重点考虑,尤其是当内核应用模块链接到操作系统后。这是真实的是否镜像被一个boot loader加载或自启动(参考2.7 VxWorks Image Types)。

提醒:对于基于ROM镜像,确保ROM_SIZE配置参数影响ROMs使用的容量。

4.13.1 boot loader和可下载镜像

通常,VxWorks boot loader代码被拷贝到RAM的一个起始地址上面的常量地址RAM_HIGH_ADRS,boot loader轮流拷贝可下载系统镜像到RAM_LOW_ADRS位置。这些常量的值是体系结构独立的,但是在任何情况下系统镜像不能超过两个常量间的空间。否则系统会覆盖boot loader代码,当下载时,潜在杀死启动进程。

为了帮助避免这个除夕,上一个命令执行当编译一个新的Vxworks镜像为vxsize,表示

新可执行镜像大小和ROM中预留了多少空间:

vxsize 386 -v 00100000 00020000 vxWorks

vxWorks: 612328(t) + 69456(d) + 34736(b) = 716520 (235720 bytes left)

(In this output, t stands for text segment, d for data segment, and b for bss.)

确信RAM_HIGH_ADRS小于LOCAL_MEM_SIZE。若新镜像太大,vxsize产生一个警告。这种情况下,你应该重新配置boot loader来拷贝启动ROM代码到足够的内存地址位置,通过增加config.h文件中RAM_HIGH_ADRS值,和BSP的makefile(两个值也一致)。之后重新编译boot loader。更多信息,参考Reconfiguring Memory Layout for a Persistent Memory Region。

4.13.2 自启动镜像

对于自启动镜像,驻留ROM VxWorks 系统的数据段加载到RAM_LOW_ADRS(在makefile中定义)以最小化内存碎片。

对于有限内存的CPU板(小于1M RAM),确信RAM_HIGH_ADRS小于LOCAL_MEM_SIZE,有足够的空间来容纳数据段。注明RAM_HIGH_ADRS定义在BSP makefile和中(要一致)。

5 C++开发

5.1 介绍

这一章提供了VxWorks系统下使用风河和GNU工具链进行C++开发相关信息。 警告:风河编译器C++和GNU C++库文件不兼容。

注明:本章提供了VxWorks内核有的相关设施信息。关于实时进程相关信息,参考VxWorks Application Programmer’s Guide相关章节。

5.2 配置C++到VxWorks

默认,VxWorks仅包括mini C++支持。你可以通过增加如下组件支持C++功能: INCLUDE_CTORS_DTORS

默认包含在内核中,确保编译器产生的初始化函数,包括C++静态对象的初始化,在内核启动时调用。

INCLUDE_CPLUS

包括基本的C++应用支持。通常和INCLUDE_CPLUS_LANG混合使用。 INCLUDE_CPLUS_LANG

包含C++语言功能支持,如new, delete和异常处理。 INCLUDE_CPLUS_IOSTREAMS 包括所有的库功能。

INCLUDE_CPLUS_DEMANGLER

包括C++命令,在使用内核shell loader时非常用于,因为它提供用于内核shell特征表查询返回命令特征名。若同时包含INCLUDE_CPLUS和INCLUDE_SYM_TBL组件,这个组件被默认增加。

5.3 C++代码需求

任何使用C++的VxWorks任务必须用VX_FP_TASK选项发起。默认,从主机工具发起任务(风河shell)默认使能VX_FP_TASK。

警告:当使用VX_FP_TASK选项发起使用C++的任务失败时,可能很难调试,在运行时产生不可预见的浮点数寄存器破坏。

若你从你的C代码中引用一个(非重载,全局)C++特征,你必须使用extern \通过原型C链接:

#ifdef __cplusplus

extern \#else

void myEntryPoint (); #endif

你也可以使用这个语法来在C++代码中访问C特征。VxWorksC特征自动存在在C++代码中,因为VxWorks头文件使用这种声明机制。

每个编译器有自身的C++库和C++头(如iostream和new)。C++头位于编译器安装目录,不是installDir/vxworks-6.x/target/h中。不需要做什么就可以使能编译器找到这些头。

注: VxWorks 5.5之前发布版本,风河推荐使用-nostdinc标志。目前的发布版本不需要使用这个标志,防止编译器找不到头文件,如stddef.h。

5.4 在信号处理和ISRs中使用C++

小心在信号处理和ISRs中使用C++代码。相关信息,参考8.2.5 Signal Handlers和8.3.3 Caveats With Regard to Writing ISRs。

5.5 在DKM工程中使用C++

下载到VxWorks内核中的C++代码应该链接到一个单一下载对象模块中。也必须被一直使用,任何COMDAT或已经连接的部分会销毁。VxWorks为在可下载模块中调用静态构造和析构函数提供了几种措施。

警告:风河编译器C++和GNU C++库文件是不兼容的。用C++写的DKM必须用VxWorks使用的编译来编译。 5.5.1 使用一个单一C++模块

VxWorks loader 仅支持self-contained的C++模块。一个自包含的C++模块是一个不能使用来之其它C++模块的类,它的类也不能被其它C++模块使用。尤其,一个模块必须包含自身标准库拷贝,或不使用C++标准库。

为了生产自包含模块,所有下载的C++对象文件应于被链接到一个单一可下载对象模块。

卸载一个不是自包含的C++模块可能会导致来之其他模块创建对象的空引用到卸载模块中的数据结构。尤其,若标准库流部分来之后面卸载的一个模块的初始化是会出现的。这种情况下,任何更进一步的iostreams的使用会伴随一个内核异常失败(访问无效的地址)。

警告:C++对象文件必须链接到一个DKM模块。

关于内核loader的信息,参考16.3 Kernel Object-Module Loader。

5.5.2 Munching 一个C++应用模块

在一个C++模块加载到VxWorks内核前,必须经历一个附件主机处理步骤,由于历史原因,称为munching。Munching执行如下任务: 初始化静态对象支持

确保为所有的静态对象在C++运行时以正确的顺序调用构造和析构函数。

对于风河编译器,自动销毁COMDAT部分;对于GUN编译器,自动销毁linkonce。

Munching 必须在编译之后,下载之前执行。 Munching例子

对于每个工具链,如下例子编译一个C++应用源文件,hello.cpp,依赖.o文件运行munch,编译产生ctdt.c文件,用ctdt.o文件链接应用产生一个可下载模块,hello.out。 使用风河工具链 1 编译源文件:

$ dcc -tPPC604FH:vxworks61 -Xlocal-data-area-static-only -XO \\

-IinstallDir/vxworks-6.x/target/h -DCPU=PPC32 -DTOOL_FAMILY=diab -DTOOL=diab \\ -D_WRS_KERNEL -c hello.cpp

2.munch对象文件:

$ ddump -Ng hello.o | tclsh \\

installDir/vxworks-6.x/host/resource/hutils/tcl/munch.tcl -c ppc > ctdt.c

3.编译munch输出:

$ dcc -tPPC604FH:vxworks61 -Xlocal-data-area-static-only -XO \\

-IinstallDir/vxworks-6.x/target/h -DCPU=PPC32 -DTOOL_FAMILY=diab -DTOOL=diab \\ -D_WRS_KERNEL -c ctdt.c

4.用munched对象文件链接最初对象文件创建一个可下载模块: $ dld -tPPC604FH:vxworks61 -X -r4 -o hello.out hello.o ctdt.o 注明:-r4选项销毁任何保护在输入文件中的COMDAT部分。 使用GUN工具链 1. 编译源代码:

ccppc -mcpu=604 -mstrict-align -O2 -fno-builtin \\ -IinstallDir/vxworks-6.x/target/h \\

-DCPU=PPC604 -DTOOL_FAMILY=gnu -DTOOL=gnu -c hello.cpp

2.munch一个对象文件:

nmppc hello.o | wtxtcl installDir/vxworks-6.x/host/src/hutils/munch.tcl \\ -c ppc > ctdt.c

3.编译munch输出:

ccppc -mcpu=604 -mstrict-align -fdollars-in-identifiers -O2 \\ -fno-builtin -IinstallDir/vxworks-6.x/target/h \\ -DCPU=PPC604 -DTOOL_FAMILY=gnu -DTOOL=gnu -c ctdt.c

4使用munched对象文件链接最初的对象文件创建一个可下载模块:

ccppc -r -nostdlib -Wl,-X \\

-T installDir/vxworks-6.x/target/h/tool/gnu/ldscripts/link.OUT \\ -o hello.out hello.o ctdt.o

注明:VxWorks内核对象模块loader不直接支持linkonce部分。取代的是,在加载之前linkonce部分必须被混合或销毁到标准的text和data部分。GNU -T选项销毁任何包含在输入文件中的linkonce部分。

使用一个通用Makefile规则

若你使用VxWorks makefiel定义,你可以写一个兼容GUN和风河编译器工具链的makefile

CPU = PPC604 TOOL = gnu

TGT_DIR = $(WIND_BASE)/target include $(TGT_DIR)/h/make/defs.bsp

default : hello.out %.o : %.cpp

$(CXX) $(C++FLAGS) -c $< %.out : %.o

$(NM) $*.o | $(MUNCH) > ctdt.c

$(CC) $(CFLAGS) $(OPTION_DOLLAR_SYMBOLS) -c ctdt.c $(LD_PARTIAL) $(LD_PARTIAL_LAST_FLAGS) -o $@ $*.o ctdt.o

Munching,下载,链接之后,静态构造和析构被调用。这个步骤描述如下。

5.5.3 交互调用构造和析构函数

5.6 C++编译器区别

5.6.1

5.6.2

模板实例化 运行时类型信息

5.7 名称空间 5.8 C++ Demo例子

6 任务和多任务

6.1 介绍

现代实时系统基于多任务和任务间通讯的补充概念。一个多任务环境允许一个实时应用用一组独立的任务构建,每个任务由自己的执行线程和系统资源组。任务是VxWorks中调度的基本单元。内核或进程中所有的任务都隶属于同一个调度(不能调度VxWorks进程)。

关于VxWorks对POSIX线程支持的更多信息,参考9. POSIX Facilities。

注明:本章提供了VxWorks内核中存在的设备信息。关于实时进程相关的设备信息,参考VxWorks Application Programmer’s Guide相关章节。

6.2 关于任务和多任务

VxWorks任务是操作系统自身代码执行的基本单元,和作为一个进程执行的应用程序中一样。在其它操心系统下,通常使用线程。(更多信息,参考VxWorks支持的POSIX线程,参考9.10 POSIX Threads)

多任务是为应用控制和操作多个,分散的实时事件而提供的基本机制。VxWorks实时内核提供基本的多任务环节。对于一个单处理器系统,表象是多个任务在并发执行,其实内

核是基于调度策略来执行的。

每个任务由自身的上下文,指内核用于每次任务查看的调度运行需要的CPU环境和系统资源。对于一个上下文切换,一个任务的上下文保存在任务控制块中(TCB)。

一个任务的上下文包括:

? 一个执行的线程;也就是说任务的程序计数器 ? 一个任务的虚拟内存上下文(若支持进程) ? CPU寄存器和协处理器寄存器(可选) ? 动态变量和函数调用栈

? 标准输入、标准输出,标准错误的I/O指派 ? 一个延迟定时器 ? 一个时间片定时器 ? 内核控制结构 ? 信号处理

? 任务私有环境(环境变量)错误状态(errno) ? 调试和性能监控值

若VxWorks没有配置支持进程(INCLUDE_RTP),一个任务的上下文不包括虚拟内存上下文。所有的任务只能在一个但一个通用地址空间运行(内核)。

然而,若VxWorks配置了进程支持——不管进程是否激活——一个内核任务的上下文必须包含自身的虚拟内存上下文,因为系统有潜在操作除内核之外其它虚拟内存上下文的可能性。也就是说,系统中的任务可能运行在不同的虚拟内存上下文中(内核和一个或多个进程)。

关于虚拟内存上下文更多信息,参考15. Memory Management。

注:POSIX标准包括一个线程的概念,和任务相似,但是有其它功能,详细,参考9.10 POSIX Threads。

6.2.1 任务状态和转换

内核为系统中的每一个任务维持目前的状态。一个任务从一个状态改变为另外一个状态做为应用特定函数调用的行为结果(如,尝试使用目前不存在的信号量)和使用开发工具如调试器。

最高优先级的任务是目前执行的处于ready状态的任务。当用创建任务,立即进入ready状态。关于ready状态更多信息,参考Scheduling and the Ready Queue。

当使用VX_TASK_NOACTIVATE选项参数,用taskCreate( )创建任务,或taskOpen( ),实例化后的任务处于suspended状态。要通过taskActivate( )激活,导致任务进入ready状态。激活阶段是非常快的,及时使能应用创建并激活任务。

Tasks States and State Symbols 描述了任务状态和你使用开发工具看到的state symbols。 注明任务状态是附加的;一个任务可能在一个时刻处于多个状态。转换可能会发生在其中一个状态。如一个任务从挂起态到挂起和停止态转换。且之后改变为非挂起态,之后变化为停止态。

STOP状态用于调试工具,程序运行到一个断点位置时。也用于错误检测和报告工具()。展

示了shell 命令i(),任务状态信息。

基本任务状态转换说明

Figure 6-1提供了一个任务状态转换的简要说明。对于澄清目的,不显示附加的在Tasks States and State Symbols,讨论的信息,不显示用于调试工具显示的STOP状态。

列表中列出的函数是导致相关转换的例子。如,一个任务调用taskDelay( )从ready状态转换到delayed状态。

注明taskSpawn( )导致创建任务后,任务进入ready状态,而taskCreate( )函数创建任务后,任务进入suspend状态(使用VX_TASK_NOACTIVATE参数,调用taskOpen( )函数完成后续的目的)。

6.3 VxWorks系统任务

依据自身配置,VxWorks在启动时启动各种不同的任务,有的任务一直运行。一个基本的VxWorks配置关联的任务组,通常使用的可选组件关联一些任务,描述如下:

提醒:不要suspend,delete,change这些任务中的任何一个。这样做会导致不可预知的系统行为。

Basic VxWorks Tasks

可选组件的任务

如下任务是通用VxWorks配置的附加任务例子。

6.4 任务调度

多任务需要一个任务调度为准备好的任务分配CPU。VxWorks提供如下调度选项: ? 传统的VxWorks调度,提供基于优先级的强占调度,是一个轮询扩展。参考6.4.2 VxWorks Traditional Scheduler。

? VxWorksPOSIX线程调度,用于为RTPs中运行线程设计。参考9.12 POSIX and VxWorks Scheduling。

? 一个定制调度框架,允许你开发自己的调度机制,参考22. Custom Scheduler。 6.4.1 任务优先级

任务调度依赖任务的优先级,在任务创建时指派。VxWorks提供256个优先级级别,从0到255。 0最高,255最低。

一个任务在创建时指派优先级,之后可以编程改变。关于优先级指派信息,参考6.5.1 Task Creation and Activation和6.5.9 Task Scheduling Control。

Application Task Priorities

所有应用任务的优先级范围是100到255。 Driver Task Priorities

对比于应用任务的优先级,100到255,驱动支持的优先级范围是51到99。 这些任务是关键的;如若当从一个芯片上拷贝数据时支持的任务调用失败,则设备会丢失数据。驱动支持的任务例子包含tNet0,HDLC任务等。

系统任务tNet0的优先级是50,所以用户任务不能指派低于50的任务优先级;否则,网络连接会挂死,和阻碍主机工具的调试功能。

6.4.2 VxWorks传统调度

VxWorks传统调度提供基于优先级的抢占调度策略,和编程初始化轮询调度选项一样。传统的调度也可以参考original或native调度。

传统的调度默认由INCLUDE_VX_TRADITIONAL_SCHEDULER组件提供。

关于POSIX线程调度和定制调度信息,参考9.12 POSIX and VxWorks Scheduling和22. Custom Schedule。

Priority-Based Preemptive Scheduling 一个基于优先级抢占调度,当一个任务的优先级高于目前运行的任务时,CPU被抢占。因此,内核确保CPU总是会分配给已经准备好的最高优先级任务。这意味着若一个任务——优先级比目前运行的任务优先级高——已经准备好运行,内核立即保存任务的上下文,切换到高优先级任务上下文。如,Figure 6-2,t1任务被较高优先级的任务t2抢占,之后被任务t3抢占。当t3完成,继续t2运行。当t2完成运行,t1继续运行。

这种调度策略的缺点是,当多个具有相等优先级任务共享一个处理器时,若一个单个任务不能阻塞,会抢占处理器。因此,其它相同优先级的任务永久得不到机会运行。

轮询调度解决了这个问题(Round-Robin Scheduling)。

Scheduling and the Ready Queue

VxWorks调度维持一个FIFO ready队列机制,这个队列包含所有处于ready状态的每个优先级级别的任务。当当下优先级级别可以访问CPU时,队列前面对应优先级的任务开始执行。

一个任务在ready队列中的位置可能会发生变化,取决于执行的操作,如下: 若一个任务被抢占,调度运行高优先级任务,但是被抢占的任务依然在优先级列表的前面位置。

若一个任务pended,delayed,suspended,或stoped,被从ready队列中移除。之后再次准备好运行,在位于ready队列优先级列表的最后(关于任务状态和导致状态转移的操作,参考6.2.1 Task States and Transitions)。

若一个任务的优先级通过taskPrioritySet( )改变,将会位于新的优先级列表最后。 若一个任务临时基于互斥信号量优先级继承策略启动(SEM_INVERSION_SAFE),在相关优先级上执行完返回到原来的优先级时,处于原来优先级列表后面(关于互斥信号量和优先级继承,参考Priority Inheritance Policy)。

移动任务到优先级列表最后

taskRotate( )函数可以用于从就绪队列中对应优先级队列前面移到最后。如,如下调用移动任务到优先级级别100列表最后:

taskRotate(100); 移动目前正在执行的任务到对应优先级列表最后,使用TASK_PRIORITY_SELF参数。 taskRotate( )函数可以用于切换到轮询模式。允许编程控制就绪队列中相同优先级任务共享CPU,而不是让系统在既定时间间隔内这样做。关于轮询机制更多信息,参考Round-Robin Scheduling。

Round-Robin Scheduling

6.5 任务创建和管理

如下部分概述了基本VxWorks任务函数,可以在taskLib VxWorks库中找到,这些函数为任务创建和控制提供方法,还有获取任务信息。参考相关API。

为了交互使用,你可以用主机工具或内核shell控制VxWorks任务;参考Wind River Workbench by Example指导,Wind River Workbench Host Shell User’s Guide,和VxWorks Kernel Programmer’s Guide: Target Tools。

6.5.1 任务创建和激活

Table 6-2列出的函数用于创建任务。 函数的参数是新任务名(一个ASCII字符串),任务的优先级,一个options字,栈大小,主函数地址,传递个主函数的10个启动参数。

id = taskSpawn ( name, priority, options, stacksize, main, arg1, …arg10 ); 注明一个任务的优先级可以在创建之后改变;参考6.5.9 Task Scheduling Control。

taskSpawn( )函数创建新的任务上下文,包含分配的栈,使用具体参数安装调用主函数的任务环境。新任务从指定函数入口执行。

taskOpen( )函数提供一个类似POSIX 的API,来创建一个任务(使用是否激活参数)

或得到一个已有任务的句柄。也为创建一个任务提供一个公共对象,所有进程和内核可见(参考6.5.3 Inter-Process Communication With Public Tasks)。taskOpen( )函数是最通用目的的任务创建函数。

taskSpawn( )函数体现了分配,初始化,激活的每一步步骤。初始化和激活函数由函数taskCreate( )和taskActivate( )提供。然而,风河推荐当你需要控制分配和激活时使用这些函数。

taskInit( )和taskInitExcStk( )区别是taskInit( )函数允许指明执行栈地址,而taskInitExcStk( )函数允许指明执行和异常栈地址。

任务静态实例化

Table 6-2列出的创建函数执行一个动态,两步操作,在运行时位任务对象分配内存,之后初始化对象。任务(和其它VxWorks对象)也可以静态实例化——这意味着在编译时为对象分配内存——之后在运行时实例化对象。

关于静态实例化更多信息,参考4.6 Static Instantiation of Kernel Objects。关于任务静态实例化更多信息,参考4.6.4 Static Instantiation of Tasks。 6.5.2 任务名称和ID

若使用taskOpen( )函数创建,一个任务可以显式命名(使用一个任意长度的ASCII字符串)。使用taskSpawn( )函数,不必须命名,这种情况下,使用一个空指针代替name参数,导致自动产生一个唯一任务名。名字格式为,tN,N是一个十进制数,没创建一个,N递增。不管任务如何创建,当任务发起时,都会返回一个任务ID。

为了避免命名冲突,VxWorks使用命名前缀约定,所有的从目标启动的内核任务都必须使用字母t前缀,所有从主机启动的任务使用u前缀。另外,一个实时进程初始化任务名是一个可执行文件名(更少的扩展),使用前缀字母i。

一个必须用一个反斜杠命名的任务是系统范围内都可以访问的公共对象(不仅仅是在创建的内存上下文中——进程或内核)。更多信息,参考6.5.3 Inter-Process Communication With Public Tasks。

大多数VxWorks任务函数使用一个任务ID作为指定任务的参数。VxWorks使用一个规范:一个为0的任务ID总表示调用任务,任务ID是一个4字节的任务数据结构。

Task Naming Rules

任务命名时要遵循如下任务命名规则:

? 公共任务名必须唯一,且必须以反斜杠开始(如,/tMyTask)。注明公共任务是整

个系统可见的——所有的进程和内核。更多关于公共任务的信息,参考6.5.3 Inter-Process Communication With Public Tasks。

? 私有任务名应该唯一。VxWorks不要求私有任务名唯一,但是最好使用唯一命名,避免用户混淆。(私有任务仅创建的实体内可见——内核或进程)

为了利用主机开发工具的优势,任务名不应该和全局函数或变量名冲突。 Task Name and ID Routines

Table 6-3列出了taskLib函数,管理任务IDs和名称。

6.5.3 进程间使用公共任务通讯

VxWorks任务可以创建为私有对象,仅可以在创建任务的内存空间中访问(内核或进程);或公共对象,在整个系统中都可以访问。任务创建为私有或公共对象,取决于任务命名方式。公共对象任务名必须以一个反斜杠开始。更多信息,参考6.5.2 Task Names and IDs。

创建一个任务为公共对象,运行其他任务从本进程外部发送信号或事件来和本任务交互。(使用taskKill( )和eventSend( )函数)

更详细的信息,参考7.10 Inter-Process Communication With Public Objects和the taskOpen entry in the VxWorks Kernel API Reference。

6.5.4 7.10 Inter-Process Communication With Public Objects, p.144Task Creation Options

当启动一个任务时,你可以传递一个或多个选项参数,如Table 6-4。结果取决于在指定选项上执行一个逻辑或操作。

浮点数操作

你必须在创建任务时,使用VX_FP_TASK选项: ? 执行浮点数操作。

? 调用任何返回一个浮点数值的函数。

? 调用任何使用一个浮点数作为一个参数的函数。

如:tid = taskSpawn (\

0, 0, 0, 0);

一些函数内部执行浮点数操作。VxWorks文档为每一个明确说明需要使用VX_FP_TASK选项的函数。

Filling Task Stacks 注明附加为独立任务使用VX_NO_STACK_FILL创建选项,你可以使用配置参数(当配置VxWorks时)为系统中所有的任务和中断禁用栈填充。

默认,任务和中断栈被用0xEE填充。使用checkStack( )函数调试开发环境中填充栈是非常有用的。通常不在部署系统中使用,因为在任务创建过程中,没有填充栈会提供更好的性能(静态初始化任务的启动时间)。

6.5.5 任务栈

每个任务的任务栈的大小在创建任务时指定(6.5.1 Task Creation and Activation)。 很难了解要分配多少任务栈空间。为了帮助避免栈溢出和破坏,你应该初始化一个较大的栈空间。之后在shell中使用checkStack( )或ti( )周期性监控栈。当你确定了实际的用法,根据测试和部署系统调整栈大小。

另外任务栈大小的实验,你也可以使用任务栈保护区来配置和测试系统(参考Task Stack Protection)。

Task Stack Protection

任务栈可以使用保护区和使能栈非执行来保护。 Task Stack Guard Zones

系统可以配置INCLUDE_PROTECT_TASK_STACK组件用于对任务栈进行保护区保护。若内存使用成为一个问题,组件可以在最终测试和部署系统中删除。

一个过运行保护区防止一个任务超越预定义栈大小和破坏数据或其它栈。一个下溢运行保护区通常防止缓存溢出栈底,破坏数据。当一个任务尝试访问任何保护区时,CPU产生异常。当插入一个保护区或使能栈非执行时,栈大小可以调整到多个MMU页大小。

注明,保护区不能捕捉超过页大小一个缓存(很少见)。如,保护区是一个页大小,4096个字节,栈接近它的最后,则若在栈上分配一个8000个字节的缓存,溢出不能检测到。

默认,内核模式任务不需要任务内存保护。配置INCLUDE_PROTECT_TASK_STACK组件,对执行栈提供上溢或下溢的保护区,不针对异常栈。内核中的栈保护区被映射到物理内存。

注明INCLUDE_RTP组件提供对用户模式任务保护区保护,不能应用到内核任务(关于用户模式任务栈保护,参考VxWorks Application Programmer’s Guide: Multitasking)。

注明INCLUDE_PROTECT_TASK_STACK组件不为使用VX_NO_STACK_PROTECT参数创建的任务提供栈保护。(参考6.5.4 7.10 Inter-Process Communication With Public Objects, p.144Task Creation Options)若一个任务使用这个选项,则这个任务没有栈保护。

保护区的大小通过如下配置参数定义:

内核任务执行栈溢出大小:TASK_KERNEL_EXEC_STACK_OVERFLOW_SIZE。 内核任务执行栈下溢大小:TASK_KERNEL_EXEC_STACK_UNDERFLOW_SIZE。

这些参数值可以修改系统范围上的保护区大小。保护区大小可以达到多个CPU MMU页大小。通过设置参数为0来防止一个保护区插入。

内核中栈保护区消耗RAM,保护区对应有效的映射内存。 Non-Executable Task Stacks 仅当系统配置组件,且若CPU支持在基于一个MMU页上分配内存时,VxWorks使用一个非执行属性创建内核任务。当栈使能非执行时,栈大小通常可以达到一个MMU页大小(这种情况下,保护区被插入)。

6.5.6

任务信息

Table 6-6列出的函数获取任务信息。因为任务状态是动态的,所以信息不回并发,除了任务处于休眠状态(也就是说挂起)。

关于具体任务变量和它们使用的信息,参考6.8.3 Task-Specific Variables。

6.5.7 任务删除和安全删除

任务可以在系统中动态删除。Table 6-7列出的函数用于产生任务和防止任务异常删除。

exit( )任务隐式调用,若任务创建时指定的入口函数返回时。

当删除一个任务时,无须通知其它任务这个删除操作。函数taskSafe( )和taskUnsafe( )解决了任务的异常删除问题。taskSafe( )函数保护重其它任务删除受保护的任务。当一个任务在一个关键区域执行或正在使用关键资源时,通常需要保护。

如,一个任务可能用一个信号量来互斥访问以下数据结构。当执行在临界代码区域,任务可能被另外一个任务删除。因为任务不能使能完成关键区域,数据结构可能处于不一致或破坏状态。更进一步说,使用的信号量永远得不到释放,其它任务也不能使用临界资源,处于冻结状态。

使用taskSafe( )函数防止持有信号量的任务立即被删除。任何尝试删除使用taskSafe( )函数保护的任务会被阻塞。当完成临界资源后,受保护任务可以使用taskUnsafe( )自己释放自己,为任何需要删除的任务准备。为了支持网状安全删除区域,a count保存taskSafe()和taskUnsafe( )的调用次数。当count为0时,允许删除,也就是说有多少unsafes,就有多少safes。仅被调用的任务受保护。一个任务不能使能另外一个任务安全或非安全删除。

如下代码段展示了如何使用taskSafe( )和taskUnsafe( )来保护一段临界代码:

taskSafe ();

semTake (semId, WAIT_FOREVER); /* Block until semaphore available */ .

. /* critical region code */ .

semGive (semId); /* Release semaphore */ taskUnsafe ();

安全删除通常和互斥耦合,如这个例子。为了方便和效率,一个特别类型的信号量,互斥信号量,为安全删除提供选项。更多信息,参考7.6.4 Mutual-Exclusion Semaphores。

6.5.8

任务执行控制

Table 6-8列出的函数提供直接控制一个任务执行。

任务执行过程中,若出现一些重要错误,则要求重启任务。重启机制,taskRestart( ),用原来的创建参数重新创建一个任务。

延迟操作为任务提供固定休眠的一个简化机制。任务延迟通常用于轮询应用。如延迟一个任务半秒,无须考虑时钟频率。调用taskDelay( )如下:

taskDelay (sysClkRateGet ( ) / 2);

sysClkRateGet( )函数返回系统速率:ticks/s。取代taskDelay( ),你可以使用POSIX nanosleep( )函数指明具体的延迟。仅单位不同;延迟函数是一样的,取决于系统时钟。详细,

参考9.6 POSIX Clocks and Timers。

注明调用taskDelay( )从ready队列中删除调用任务。当任务再次准备好运行,位于对应优先级就绪队列最后。这个行为用于通过使用0 ticks延迟函数放弃CPU给其它对等优先级的任务。

taskDelay (NO_WAIT); /* allow other tasks of same priority to run */ 只有taskDelay( )函数可以使用0参数。nanosleep( )函数不可以。关于就绪队列更多信息,参考Scheduling and the Ready Queue。

注:ANSI和POSIX APIs相似。

系统时钟分辨率通常是60Hz(一秒钟60下)。这是一个时钟tick比较长的时间,可以是100Hz或120Hz。因此,因为周期延迟是有效轮询,你会考虑使用使用事件驱动的技术。

6.5.9 任务调度控制

任务在创建时被指派一个优先级(参考6.5.1 Task Creation and Activation)。在运行过程中,可以通过调用taskPrioritySet( )改变一个任务的优先级。动态改变优先级的功能允许应用跟踪实际操作流程的变化。小心使用taskPrioritySet( )函数改变一个任务的优先级,改变后任务位于对应优先级就绪队列最后。关于就绪对象相关信息,参考Scheduling and the Ready Queue。

Table 6-9列出了用于控制任务块的一组函数。

6.5.10 任务扩展:钩子函数

为了允许额外任务相关的工具增加到系统,VxWorks提供了钩子函数,允许当任务创建时,一个任务上下文切换时,或一个任务被删除时,增加的函数被触发。TCB中有空闲域来为一个任务的上下文应用扩展。这些钩子函数在Table 6-10列出;更多信息,参考taskHookLib相关API。

任务创建的钩子函数在创建任务的上下文中执行。任务创建钩子必须考虑钩子函数中创建的任何内核对象的所有权关系(如看门狗定时器,信号量等)。因为创建钩子函数在创建任务的上下文中执行,新任务的进程拥有新的内核对象。可能必须要指派这些对象的所有权给新的任务进程。这会防止当进程创建的任务中止时,不期望的对象被回收。

当使用任务切换钩子函数时,要意识到如下限制:

? 不要假设任何虚拟内存上下文为当前内存上下文,处理内核上下文(用ISRs)。 ? 不要依赖目前任务的识别或依赖这个信息触发任何函数,如taskIdSelf( )。

? 不要依赖taskIdVerify (pOldTcb)来确定是否为自我析构任务情况下删除执行的钩子。取而代之的是,删除钩子中其它状态信息当切换钩子时要被检测到(通过设置NULL指针)。

内核上下文中用户安装的切换钩子被调用,因此不能访问所有VxWorks设施。Table 6-11总结了一个任务切换钩子中可以调用的函数;通常任何不涉及内核调用的函数。

注:关于POSIX扩展信息,参考9. POSIX Facilities。

6.6 任务错误状态:errno

按照惯例,C库函数设置一个单一全局整数变量errno为一个正确的错误号,当函数遇到错误时。这个惯例指定为ANSI C 标准的一部分。

注:这一部分描述了VxWorks单处理器配置中errno的实现和用法。关于SMP的errno和其他全局变量的信息,参考24.17.8 SMP CPU-Specific Variables and Uniprocessor Global Variables,关于移植的信息,参考24.17 Migrating Code to VxWorks SMP。 6.6.1 errno的分层定义

VxWorks中,errno同时以两种方式定义。ANSI C标准,一个潜在称为errno的全局变量,可以通过主机开发工具使用名字展示。

然而,在errno.h文件中errno被定义为一个宏。这个定义整个VxWorks可见除了一个函数。这个宏被定义为一个返回全局变量地址的函数__errno( ),errno(你可能猜的到,这是一个单一函数,本身不使用errno宏定义)。这个策略产生一个有用的工具:因为__errno( )是一个函数,在调试时你可以设置断点,来确定产生错误的地方。

尽管如此,因为宏errno的结果是全局变量errno的地址,C程序可以用标准的方式设置errno值:

errno = someErrorNumber;

对于任何其它的errno实现,不要关注相同名称的局部变量值。

6.6.2 针对每个任务的一个独立errno值

VxWorks中,潜在全局errno是一个单一的预定义全局变量,可以被链接到VxWorks中的应用代码直接引用(或静态在主机上引用或在加载时动态使用)。

然而,在VxWorks多任务环境中errno很有用,每个任务必须查找自身版本的errno。因此,被保存,通过内核回复,作为每次一个上下文切换产生时每个任务上下文一部分。

同样,interrupt service routines (ISRs)查看自身的errno版本号。通过保存和恢复中断栈上的errno,作为内核自动提供的进入和退出中断代码的一部分(8.3.1 Connecting Routines to Interrupts)。

因此,不管VxWorks上下文,不用直接操作全局变量errno的情况下一个错误码可以保持和提交。

6.6.3 错误返回惯例

几乎所有的VxWorks函数都遵循一个规范,通过实际函数返回值来表示函数操作正确与否。很多函数仅返回OK (0)或ERROR (-1)。一些正常返回一个非负数的函数(如,open( )函数返回一个文件描述符)也返回ERROR表示一个错误。返回一个指针的函数,通常返回NULL(0)表示一个错误。大多数情况下,一个函数返回如一个错误表示也设置errno为特别的错误码。

全局变量errno从来不会被VxWorks函数清除。因此,它的值总表示上一次设置的错误状态。当一个VxWorks子函数在调用其他函数时得到一个错误提示,通常无须修改errno,返回自身的错误提示。因此,errno值在低层次函数中设置保持为错误类型的提示。

6.6.4 错误状态值指派

VxWorks errno值编码产生错误的模块,使用最重要的两个字节为独立错误计数。所有VxWorks模块数范围是1-500;一个0模块数errno值用于源码兼容性。

其它errno值(也就是说,所有的正值)提供给应用使用。

参考相关VxWorks API,获取定义和编码errno值规范的更多信息。

6.7 任务异常处理

程序代码或数据中的错误会导致硬件异常条件如非法指令,总线或地址错误,除数为0,等。VxWorks异常处理报关注这些异常(参考19. Error Detection and Reporting)。

任务可以通过信号工具关联它们的句柄来处理特定的硬件异常。若一个任务针对一个异常提供了一个信号回调,上面描述的默认异常回调是不会执行的。一个用户定义的信号回调在从重要错误中恢复时非常重要。通常,调用setjmp( )函数定义函数中的恢复点,在回调中调用longjmp( )函数来恢复上下文。注明longjmp( )恢复任务的信号屏蔽的状态。

信号也用于通知软件异常,还有硬件异常。在8.2 Signals描述,和sigLib中相关API。

6.8 共享代码和可重入性

7 任务间和进程间通讯

7.1 介绍

任务间通讯工具允许任务同步为了协调任务的行为。VxWorks中任务间通讯工具包括中断锁,任务锁,各种类型信号量,消息队列,管道, V xWorks事件和消息通道。

对于进程间和内核进程通信,VxWorks信号量和消息队列,管道和事件(还有POSIX信号量和事件)可以创建为公共对象来提供跨越内存边界的任务间通讯(内核和进程之间,不同进程之间)。另外,消息通道提供基于socket的进程间通讯和进程间通讯机制。

注:这一章提供了VxWorks内核中存在的设备信息。关于实时进程中存在的相关信息,参考VxWorks Application Programmer’s Guide相关章节。

注:这一章提供了通用于UP和SMP VxWorks配置的多任务设施。也提供了具体与UP的相关设施信息。后面,注明了SMP系统相关信息。

正常情况下,VxWorks的SMP和UP配置共享相同的API——仅有一小部分函数不同。注明一些编程实践——隐式同步技术依赖任务优先级取代显式锁——SMP系统不合适。

关于SMP编程更多信息,参考24. VxWorks SMP。关于具体移植相关信息,参考24.17 Migrating Code to VxWorks SMP。

7.2 关于任务间和进程间通讯

VxWorks任务间和进程间设备为不同任务行为和通讯提供了同步机制。最简单的情况,执行在相同内存空间中(内核或进程)的任务可以通过访问共享数据结构轻易实现通讯和数据交换。(相关信息,参考7.3 Shared Data Structures。)然而,它们的访问应该使用设计用于互斥访问一个共享资源的工具,如通用目的二进制信号量。很多专业的同步和互斥机制被提供用于处理更复杂和需要更高性能的场景,还有直接进行数据交换的机制和事件通知机制。

VxWorkst提供的进程间和任务间通讯工具如下: 中断锁

提供禁用中断的方法,防止被ISRs抢占。中断锁不上一个通用目的机制,应该谨慎使用,参考7.4 Interrupt Locks。

任务锁

提供禁用被其它任务抢占的方法。任务锁不上一个通用目的机制,应谨慎使用。参考7.5 Task Locks。

信号量

提供任务同步和互斥的主要方法,如7.6 Semaphores描述。信号量可以用公共对象创建,用于进程间通讯;相关信息,参考7.10 Inter-Process Communication With Public Objects。关于POSIX信号量信息,参考9.13 POSIX Semaphores。

消息队列

为任务间直接消息通讯提供高水平的机制。参考7.7 Message Queues。消息队列创建为公共对象,允许进程间通讯使用;相关信息,参考7.10 Inter-Process Communication With Public

Objects。 管道

提供给消息队列工具的一个可选消息接口。管道操作通过I/O系统操作,考虑用于标准I/O函数和select()。参考7.8 Pipes。

VxWorks事件

提供任务和其它任务间的通讯和同步方法,中断服务程序(ISRs)和任务,信号量和任务,消息队列和任务,参考7.9 VxWorks Events。

关于信号的信息,用于通用目的任务间和进程间通讯,参考8.2 Signals。

关于设计专用于多CPU系统的同步和通讯相关信息,参考see24. VxWorks SMP, 25.

Overview of

VxWorks AMP, 31. Shared-Memory Objects: VxMP和32. Distributed Shared Memory: DSHM和33. Message Channels。

注明:通常情况下,SMP和UP的VxWorks配置为任务间和进程间通讯共享相同的设备——仅很小一部分程序有差别。

这一部分提供了通用两种配置的API信息,和具体与AP配置相关信息。后者也注明了SMP有的信息。关于Vxworks SMP系统配置相关信息,参考24. VxWorks SMP;和移植相关信息,参考24.17 Migrating Code to VxWorks SMP。

7.3 共享数据结构

相同内存空间(一个单一进程或内核中)中不同任务通讯的最基本的方法是通过访问共享数据结构。因为单一进程中或内核中存在的所有任务存在在一个单一线性地址空间,任务间通过共享数据结构通讯;参考Figure 7-1。

全局变量,线性缓存,环形缓存,链表,指针可以通过代码在不同的任务上下文运行。然而,访问共享数据结构可以通过一个互斥体控制,如信号量。(7.6 Semaphores)

关于用于进程间通讯的共享数据域相关信息,参考VxWorks Application Programmer’s Guide。

7.4 中断锁

intLock( )函数禁用中断,因此防止被ISRs抢占。可以在一个任务或ISR上下文中调用。intUnLock( )函数重新使能中断。如下代码用于包含一个临界区域:

foo () {

int lockKey = intLock(); .

. /* critical region of code that cannot be interrupted */ .

intUnlock(lockKey); }

当一个任务和中断共同访问一个变量或数据结构时,使用intLock( )函数防止抢占。保护的代码量要最小化,意味着代码行数少,没有函数调用。若调用太长,会直接影响中断延迟和导致系统变得不确定性。