为了正常的体验网站,请在浏览器设置里面开启Javascript功能!

VxWorks-kernal-programmer's-guide

2020-02-16 28页 doc 7MB 26阅读

用户头像 个人认证

真诚文档交流

本人从事临床麻醉五年有余,工作兢兢业业,拥有丰富的临床麻醉经验及临床医学资料,并取得了助理医师资格。

举报
VxWorks-kernal-programmer's-guideVxWorksKernalProgrammer’sGuide第一部分:核心技术1概述2VxWorks配置2.1介绍VxWorks是一个高可扩展性的操作系统,可以针对不同目的是要独立组件工具包轻松配置。VxWorks的定制版本也可以被创建,用于配置的备选项。本章讨论了基本的VxWorks配置,和小型化VxWorks配置,电源管理设施,和可以产生的VxWorks系统镜像类型。2.2关于VxWorks配置VxWorks是一个灵活的可扩展的带多个设备的操作系统,这些设备可以包含,删除,不同配置,使用定制技术扩展,根据应用和...
VxWorks-kernal-programmer's-guide
VxWorksKernalProgrammer’sGuide第一部分:核心技术1概述2VxWorks配置2.1介绍VxWorks是一个高可扩展性的操作系统,可以针对不同目的是要独立组件工具包轻松配置。VxWorks的定制版本也可以被创建,用于配置的备选项。本章讨论了基本的VxWorks配置,和小型化VxWorks配置,电源管理设施,和可以产生的VxWorks系统镜像类型。2.2关于VxWorks配置VxWorks是一个灵活的可扩展的带多个设备的操作系统,这些设备可以包含,删除,不同配置,使用定制技术扩展,根据应用和系统需要,和开发周期不同阶段。默认的Vxworks镜像提供最初开发使用。配置VxWorks的主要方式是VIPs和VSBs。风河Workbench开发套装和vxprj命令行配置工具可以用于和这些工程类型工作。关于风河Workbench和vxprj更多信息,参考WindRiverWorkbenchbyExampleguide和VxWorksCommand-LineToolsUser’sGuide。2.2.1默认配置和镜像VxWorks发布包括针对每个支持的BSP的默认系统镜像。每个系统镜像时一个可以引导好运行在目标系统上的二进制模块。一个系统镜像包括一组链接到一块的一组组件形成一个单一的不可移动的带已解决外部引用的对象模块。默认的系统镜像设计用于开发环境。它们包含使用主机开发工具用于和系统交互的一组组件组成。大多数情况下,会为最初的系统开发提供足够的系统镜像(提供默认的驱动)。使用一个默认Vxworks镜像,你可以交互,且运行内核应用。关于设置开发环境相关信息,参考WindRiverWorkbenchbyExampleguide。关于VxWorks镜像类型信息,参考2.7VxWorksImageTypes。2.2.2VIP配置VxWorks镜像工程用于配置不同组件组成的系统。VxWorks组件提供基于预编译库(归档文件)的操作系统工具单元,预编译库是通过特征引用的方式连接到系统中。关于VIPs更多信息,参考2.3VxWorksImageProjects:VIPs。2.2.3VSB配置对于有严格性能要求或资源限制的系统,VSB工程可以用于创建专门(频率较低的)的VxWorks库版本,和用于多样化优化。VSB选项和关联的条件化代码提供配置源代码基本的方法。一旦VxWorks库源码重新编译,一个VIP工程可以用于配置系统本身。关于VSB更多信息,参考2.4VxWorksSourceBuildProjects:VSBs。2.2.4配置和定制若由风河提供的VxWorks组件不提供你的系统要求的功能,你可以创建定制设备,如新的文件系统和网络协议,和组件包(参考20.CustomComponentsandCDFs),基于进程应用增加系统调用(参考21.CustomSystemCalls),创建你自己的调度(参考22.CustomScheduler)。2.2.5配置工具:Workbench和vxprj针对VIP和VSB提供的开发工具有风河Workbench开发套装和vxprj命令行配置工具。Workbench和vxprj可以用于配置和编译VxWorks系统,和其它管理VIP,VSB和其它类型的工程。关于如何使用Workbench和vxprj,参考WindRiverWorkbenchbyExample和VxWorksCommand-LineToolsUser’sGuide。2.3VIPsVxWorks镜像工程用于使用不同组件配置VxWorks系统。VxWorks组件提供基于预编译库的操作系统单元,预编译库通过特征引用的方法链接到一个系统中。VIP可能基于一个默认VxWorks配置或配置原型。配置原型提供设计用于具体目的的一组组件组成,如开发环境,DO-178B验证等。另外组件包用于使用一组组件配置系统,如那些提供POSIX支持的组件。在开发周期过程中你可能想重新配置和重新编译VxWorks和专门选择用于支持应用开发需求的组件。如,你可能想保护内核shell和错误检测和报告工具,和删除包含在默认系统镜像中的其他组件。对于生产系统,你将想重新配置VxWorks,仅需要操作过程中使用的组件,编译为合适的系统镜像类型(参考2.7VxWorksImageTypes)。你可能想删除针对主机开发需要的组件,如WDBtargetagent和debuggingcomponents(INCLUDE_WDBandINCLUDE_DEBUG),和删除支持其他操作系统的组件,应用不需要的组件,加速启动时间,和安全问题。风河Workbench和vxprj命令行工具可以用于配置VxWorks,基于组件选择和删除。Workbench配置工具展示了包含组件的图形化信息,和其他系统相关的信息。Vxprj工具也可以用于列出各种类型的组件信息。另外,VxWorksComponentReference提供所有组件和它们的参数,包,原型等的详细信息。(更多信息,参考2.3.1VxWorksComponents,2.3.3ComponentBundlesandConfigurationProfiles,和2.3.4VxWorksComponentReference。)2.3.1VxWorks组件一个VxWorks组件是VxWorks可以配置的基本功能单元。设计用于实时系统的组件,强调确定性和性能。每个组件提供一个或多个预编译静态库(归档文件),通常有一个或多个配置参数。组件通过特征引用的方法链接到系统中。而一些组件是独立的,其它组件是有依赖性的,依赖的组件在运行时也必须包含在操作系统中。内核shell是一个依赖多个组件的组件。特征是一个组件依赖其它组件的例子(内核shell和模块loader;更多信息,参考16.KernelShellandLoader)。组件依赖由VxWorks内核配置工具自动确定(风河Workbench和vxprj命令行工具)。组件名VxWorks组件使用CDFs文件packaged,用以INCLUDE_开始的宏名识别组件,和用户友好的描述。(关于配置文件的信息,参考20CustomComponentsandCDFs)名,描述,配置VxWorks组件功能可以在Workbench中用GUI配置工具显示。Workbench为配置VxWorks选择组件提供了工具,设置组件参数,和配置和编译过程中组件间依赖关系的自动确认机制。命令行操作系统配置工具——vxprj——使用源于配置宏的命名传统来识别独立的操作系统组件。规范标识组件名称以INCLUDE开始。如,INCLUDE_MSG_Q是消息队列组件。除了配置工具vxprj工具提供了列举包含在工程中组件信息功能,配置参数等。注:本书中,组件通过宏名识别。GUI配置工具提供了查找组件的功能。基本的VxWorks组件Table2-1描述了VxWorks通用组件。名称以XXX结尾的表数组件家族,XXX由独立组件名的一个前缀代替。如INCLUDE_CPLUS_XXX,指包含INCLUDE_CPLUS_MIN和其它的组件家族。Table2-1注明的不包含VxWorks默认配置的所有组件。所有组件和参数的详细信息,参考VxWorksComponentReference。2.3.2设备驱动选择设备驱动可以用Workbench和vxprj为系统增加或删除组件。一些驱动是VxBus兼容的,其它(传统驱动)不是。注明仅VxBus兼容驱动可以用于SMP配置。关于VxWorksSMP和移植相关信息,参考24.VxWorksSMP和24.17MigratingCodetoVxWorksSMP。注明VxBus驱动组件名不需要INCLUDE_元素(如DRV_SIO_NS16550),而非VxBus驱动使用INCLUDE_元素(如INCLUDE_ELT_3C509_END)。关于VxBus设备驱动基础设施相关信息,参考VxWorksDeviceDriverDeveloper’sGuide。2.3.3组件包和组件原型除了基本的VxWorks组件,组件包和用于配置VxWorks的配置原型。组件包是管理组件组,可以做为一个单元增加到系统(如,BUNDLE_POSIX提供内核POSIX支持需要的所有组件)。原型提供了一个操作系统功能基线,提供一个方便的方法,和VxWork生产安装默认配置不一样。原型如下:PROFILE_SMALL_FOOTPRINT——VxWorksSmallFootprintProfile,为必须的最小内核公共提供基本配置,和一个要求的小型化内存。PROFILE_COMPATIBLE——VxWorks5.5CompatibleProfile,提供兼容VxWorks5.5最小配置。PROFILE_DEVELOPMENT——VxWorks内核开发原型,提供一个VxWorks内核,包含开发和调试组件,包含实时进程的支持。PROFILE_ENHANCED_NET——VxWorks增强网络原型。为经典管理网络客户端工具设备增加合适组件到默认原型。增加的主要组件是CHCP客户端和DNS服务器,telnet服务器(不包含shell),和几个命令行式样配置工具。PROFILE_CERT——VxWorksDO-178认证原型。提供VxWorks操作系统的一个DO-178验证API组。PROFILE_BOOTAPP——VxWorksbootloader原型,为一个Vxworksbootloader提供工具。更多信息,参考3.7ConfiguringandBuildingBootLoaders。关于所有组件包和原型信息,参考VxWorksComponentReference。2.3.4VxWorks组件引用关于所有VxWorks组件和太慢的参数,组件包,原型更详细的引用信息,参考VxWorksComponentReference。2.4VSBs标准VxWorks组件设计用于实时系统,强调确定性和性能。它们提供了预编译库,通过特征引用的方法链接到系统中。这是对于提供快速构建比较方便,意味着可以构建更大,更具通用目的的库,不需要为了适应一个系统的需求针对大小和性格有非常严格的要求。对于有严格性能需求和资源限制的系统,VSB工程可以用于创建专门的VxWorks库版本,并可以使用不同的优化。库基于用户不同的选择有条件的编译。一些情况下,一个VSB工程产生的库提供一个APIs子组,由标准的VxWorks组件提供,或其他有一些不同的行为。它们会提高性能,因为他们仅执行定义的一组代码。为了在Vxworks系统中使用库版本和优化,一个VIP工程随后被创建,关联一个VSB工程。VIP工程之后用于配置VxWorks本身。VSB选项和关联的条件代码针对源代码基本的配置提供方法。VSB工程工具提供的主要选项种类包括如下:·组件基本的非离散可配置功能选项(包括跨库和组件的功能)。如,系统可视化工具。·编译优化选项。如,浮点数支持。·多处理器系统相关选项。如为SMPVxWorks配置指定库。·用于构建功能库选项,通过源码传递(如不同的网络和中间件工具)。VSB选择控制基于库代码条件编译的VxWorks功能增加和删除。基于VSB工程的系统可以像小型化系统一样提高系统性能,因为它们会删除系统不需要的代码,只留下执行的嗲吗。另外它们还充分使用专门优化。(如,cache模式)Figure2-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)·SystemViewerInstrumentation·实时进程·对象管理·共享内存对象(VxMP)·任务钩子函数·CPU电源管理·高级选项·VxWorksBSP验证测试套装·SMP·SMP确定性或性能·AMP这些选项在如下详细描述。注明其它技术(如网络)提供更多的附加选项,在相关的编程指导和平台用户指南中描述。针对一系列选项和描述,参考风河WrokbenchVSB功能和vxprj工具。2.4.2VSB原型VSB原型提供提供了一个方便的定义一组VSB选项的方法用于创建设计用于具体目的的操作系统设备。一个VSB原型做如下事情:·展示一组供用户改变的VSB选项。·隐藏和原型目的无关的其它选项或用户不能改变的选项。·为原型目的设置所有默认选项。如,参考2.5.2ConfiguringSmallFootprintVxWorks。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库版本,趋向于运行在那些系统上的内核应用必须带编译时连接到版本库。相关信息,参考WindRiverWorkbenchbyExampleguide和VxWorksCommand-LineToolsUser’sGuide。2.5小型化Vxworks配置VxWorks可以使用一个小型化原型配置产生一个携带小型化内存的最小内核。注:所有体系结构不支持VxWorks的小型化配置。关于支持的信息,参考VxWorksArchitectureSupplement和VxWorksBSPReference。2.5.1关于小型化VxWorks2.5.2配置小型化VxWorks2.5.3针对小型化VxWorks的配置和编译步骤2.5.4针对小型化VxWorks开发应用2.5.5应用例子2.5.6调试小型化VxWorks2.6电源管理开始于VxWorks6.2版本,增强电源管理设备由因特体系结构(IA)提供。早期版本针对其它体系结构提供的设备保持不变。针对其它体系结构提高的新设备在以后发布。参考2.6.1PowerManagementforIAArchitecture和2.6.2PowerManagementforOtherArchitectures。2.6.1针对IA体系结构的电源管理2.6.2针对其他体系结构的电源管理2.7VxWorks镜像类型不同的VxWorks镜像类型可以用于不同的引导方法,存储,加载和执行场景。如下镜像的默认版本在VxWorks安装中被提供。可以创建由不同组件组成的定制版本。仅vxWorks镜像类型——有时值一个可下载镜像——需要一个bootloader。这种方法在开发环境下最通用(最实际),因为不需要每次修改的时候重新烧录到flash或拷贝到目标存储媒介。镜像通常存储在主机系统中。其它镜像类型——有时指独立镜像——不需要一个独立的bootloader。通常用于生产系统,存在在flash中。独立镜像需要配置非默认启动参数(参考2.7.3BootParameterConfigurationforStandaloneVxWorksImages)。不同的VxWorks镜像类型,用法,行为描述如下:vxWorksVxWorks镜像类型目的是开发过程中使用,通常指downloadable。在生产系统下也非常用于,bootloader和系统镜像被存储在磁盘上。在开发环境中,镜像通常存储在主机系统(或网络服务器上),通过bootloader下载到目标系统,并加载到RAM。特征表在主机上维持(vxWorks.sym文件),用于主机开发工具。保存特征表在主机上减少了要加载到目标机上的镜像大小,减少了启动时间。若VxWorks配置了INCLUDE_STANDALONE_SYM_TBL组件,特征表包含在VxWorks镜像中。vxWorks_rom一个存储在目标机上非易失性设备上的VxWorks镜像。它拷贝本身到RAM,之后切换处理器到RAM执行。因为镜像没有压缩,所有比基于ROM镜像大,因此有一个比较慢的启动时间;但是比vxWorks_romResident镜像执行时间快。关于启动参数配置信息,参考2.7.3BootParameterConfigurationforStandaloneVxWorksImages。vxWorks_romCompress一个存储在目标机上非易失性设备上的VxWorks镜像。几乎整个压缩,有很小未压缩部分在设备上电或复位时处理器立即执行。这一部分代码负责解压缩ROM镜像中压缩部分到RAM,切换处理器到RAM执行。因为是压缩镜像和其它镜像比需要更小的存储空间,但是解压缩延长启动时间。和vxWorks_rom比启动时间更长,使用的ROM空间少。和vxWorks_rom有同样的运行速度。关于启动参数的配置,参考2.7.3BootParameterConfigurationforStandaloneVxWorksImages。vxWorks_romResident一个存储在目标机上ROM中的VxWorks镜像。启动时仅拷贝dss段到RAM;text段仍保持在ROM中。因此描述为ROM-resident。启动比较快,使用RAM很小,但是运行比较慢,因为ROM访问提取指令比RAM提取指令慢。对于有内存限制的系统比较有用。启动参数配置信息,参考2.7.3BootParameterConfigurationforStandaloneVxWorksImages。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_rom2.7.2针对开发和生产系统的VxWorks镜像对于很多生产系统通常必须在ROM中存储一个和VxWorks链接的内核应用模块。VxWorks要配置在启动时自动执行应用。系统镜像可以见到存储应用模块允许被其它函数调用,或被终端用户交互使用(如,诊断程序)。为了生产一个基于ROM的系统,你必须链接模块到vxWorks,并编译为适合ROM的系统镜像类型。相关信息,参考4.12ConfiguringVxWorkstoRunApplicationsAutomatically。若你想在启动时自动启动应用,你必须配置VxWorks如下(4.12ConfiguringVxWorkstoRunApplicationsAutomatically)。或参考4.13ImageSizeConsiderations。注明,开发过程中,VxWorks必须配置WDB目标代理通讯接口,用于连接主机和目标机系统(网络,串口等)。默认,配置为一个增强网络驱动(END)连接。更多信息,参考D.WDBTargetAgent。注明在使用主机开发工具之前,如shell和debugger,必须启动一个目标服务器,配置为通讯的相同模式。关于配置带不同操作系统工具的VxWorks的信息,参考2.3.2DeviceDriverSelection。若你想在flash在存储镜像,想使用用户TrueFFS,参考13.3.6ReservingaRegioninFlashforaBootImage。2.7.3单独VxWorks镜像启动参数配置依赖系统需求,你可能需要为一个独立的镜像静态的重新配置启动参数——也就是说,不需要一个bootloader——指所有的系统镜像,除了downloadablevxWorks镜像。如一个网络目标机大多数需要设置本身的IP地址为一些默认的信息。使用INCLUDE_BSP_MACROS组件的DEFAULT_BOOT_LINE配置参数设置启动参数。针对vxWorks的配置过程和bootloader的配置过程一样。关于bootloader的信息,参考3.5.2DescriptionofBootParameters。关于静态配置的信息,参考3.7.3ConfiguringBootParametersStatically。3BootLoader3.1介绍一个VxWorksbootloader是一个应用,目的是加载一个VxWorksimage到目标机。有时VxWorksBootrom,但是这个词不推荐使用(合并应用和媒介)。如VxWorks,bootloader可以用不同的工具配置,如命令行工具动态设置启动参数,一个网络loader,和一个文件系统loader。相同的bootloaderVxWorks配置用于单处理器(UP),对称多处理器(SMP),和非对称多处理器(AMP)。在一个开发环境中,bootloader对于从一个主机系统加载一个VxWorks镜像时非常有用的,在主机系统上,VxWorks可以快速修改和编译。当bootloader和操作系统都存储在磁盘上或其它媒介上时,也可以用于生产系统。Self-booting(standalone)VxWorksimages不要求一个bootloader。这些镜像通常用于生产系统(存储在non-volatile设备上)。更多信息,参考2.7VxWorksImageTypes。常常,bootloader被烧录到一个non-volatile设备(flash内存或EEPROM)上具体地址处,这个地址是目标机上电或重新启动后,处理器执行的第一条代码处。获取烧录在non-volatile设备上的bootloader,或写入到一个磁盘的方法取决于目标机,在BSP参考文档中描述。VxWorks产品安装包括针对每一个安装BSP的默认bootloader。若它们不满足要求,你可以创建定制bootloaders。如,你可能需要使用一个不同的网卡驱动通过网络来加载Vxworks镜像,或你为了部署系统删除bootloadershell。本章涉及的信息,特别是关于安装一个cross-development环境的信息,参考WindRiverWorkbenchbyExample。3.2使用默认bootloader默认bootloader设计用于一个网络目标,必须配置相关参数,如主机和目标网络地址,加载文件全目录和文件名,用户名等。为了使用默认bootloader,你必须使用bootloadershell交互式的改变默认参数,以至于loader可以找到主机上的VxWorks镜像,并加载到目标机。进入引导参数后,目标机启动VxWorks镜像。大多数目标机,新的设置会保存(在non-volatile设备或磁盘上),所有你可以直接启动目标机,不需要重新设置默认参数。在一个终端控制台上,可以和bootloadershell交互,这个终端控制台,通过串口连接主机和目标机,在主机上启动一个终端应用程序。建立通讯的具体信息,参考相关文档。默认bootloade镜像位于installDir/vxworks-6.x/target/config/bspName。Bootloader命令和参数在3.4.1BootLoaderShellCommands描述和3.5BootParameters。提供不同的Bootloader镜像类型,如3.3BootLoaderImageTypes描述。3.3BootLoaderImage类型Bootloader镜像可以存储在ROM,flash,磁盘,或存储在网络上。Bootloader镜像是ELF个数。二进制版本(.bin)用于磁盘,16进制文件版本(.hex)用于烧录non-volatile设备。什么样的目标机选择什么样的bootloader,参考BSP相关文档。下面描述了bootloader的不同版本。下面列出的每一组镜像的第一个镜像是通过PROFILE_BOOTAPP配置原型产生的,第二个通过传统的bspDir/config.h方法。关于编译方法的更多信息,参考3.7ConfiguringandBuildingBootLoaders。CompressedImagevxWorks_romCompress和bootrom文件这个镜像几乎是整个压缩的。有一小部分未压缩部分,在设备上电或复位之后处理器立即运行。这部分初始化内存和解压缩压缩部分(存储在非易失性设备上)到RAM,导致处理器切换到RAM执行。压缩镜像比其他bootloader镜像小,因此使用比较小的非易失性存储空间。然而,解压缩增加了启动时间。UncompressedImagevxWorks_rom和bootrom_uncmp文件这个镜像没有压缩。拷贝本身到RAM,处理器切换到RAM执行。因为镜像没有压缩,所以比压缩镜像大。然而,启动时间比较快,因为不需要要求的解压缩操作。ResidentinNon-VolatileStoragevxWorks_romResident和bootrom_res文件这个镜像在启动时仅拷贝数据段到RAM,文本段保留在非易失存储上。这意味着处理器总是在非易失性存储外执行指令。因此有时描述为ROM-驻留型。这种类型的bootloader要求足够大的RAM来加载VxWorks内核。因此对于带有较小的RAM板卡,RAM是为应用保存数据的情况是很有用的。Bootloader镜像位于installDir/vxworks-6.x/target/config/bspName。注明大多数默认BSP的默认镜像都需要配置网络开发环境。关于创建一个定制bootloader的信息,参考3.7ConfiguringandBuildingBootLoaders。3.4BootLoaderShellBootloadershell提供了如何命令:·改变启动参数(如主机和目标IP地址)·重新启动目标系统·管理启动过程配置INCLUDE_BOOT_SHELL组件到bootloader,来包含bootloadershell。提醒:不要增加INCLUDE_WDB_BANNER,INCLUDE_SIMPLE_BANNER,INCLUDE_SHELL或组件到bootloader。这些组件和bootloadershell冲突。若你包含了这些组件中的的其中一个,你会遇到错误。3.5BootParameters启动参数包括需要定位和加载一个VxWorks内核的所有信息,还有其它用于管理启动过程的信息。根据具体启动配置要求,可以包含主机和目标机IP地址和全路径和要引导的VxWorks镜像名,用户名等。启动参数可以在运行时交互改变,也可以在创建bootloader时静态配置。交互改变启动参数要通过重启生效(在一个非易失性设备或磁盘上)。启动参数用于独立VxWorks镜像——不需要一个bootloader——bootloader自身用(更多信息,参考2.7VxWorksImageTypes)。3.5.1显示目前的启动参数3.5.2启动参数描述3.5.3交互式改变启动参数3.6RebootingVxWorks3.7配置和编译BootLoaders3.8安装bootLoaders3.9从一个网卡启动3.10从一个目标文件系统启动3.11从使用TSFS的主机文件系统启动4内核应用程序4.1介绍VxWorks内核应用执行在和内核本身一样的模式和内存空间。这方面和用于其它操作系统的应用不一样,如UNIX和Linux;也和VxWorks实时进程RTP应用不一样。内核应用可以交换下载和运行在一个VxWorks目标机上,或链接到操作系统镜像中,(可选)在启动时自动执行。这一章提供了关于编写内核应用代码,创建为用户使用的静态库,内核对象静态实例化,执行应用等相关的信息。关于多任务,I/O,文件系统,其它内核中有的vxWorks工具相关的信息,参考相关章节。关于Workbench和命令行编译环境相关信息,参考WindRiverWorkbenchbyExample和VxWorksCommand-LineToolsUser’sGuide。关于开发用户模式RTP应用相关信息(作为用户模式的实时进程一样执行),参考VxWorksApplicationProgrammer’sGuide。4.2关于内核应用执行在内核中的VxWorks应用创建为一个浮动对象模块。它们可以被称为最特别的kernel-basedapplicationmodules,但是通常为了方便称它们为kernelapplicationmodules或kernelapplications。不要和执行在用户模式(实时进程RTPs)的应用混淆。当一个基于内核应用模块编译后,用户代码被链接到请求的VxWorks库,产生一个ELF二进制文件。内核应用包含定义操作系统接口和数据结构的头文件后使用VxWorks工具。内核应用模块可能是:通过对象模块loader加载或动态链接到操作系统中。静态链接到操作系统,作为系统镜像的一部分。下载内核模块对应快速开发和调试非常有用,因为操作系统不需要为应用的每个迭代重新编译。这个方法也可能用于生产系统的诊断工具。不同的开发工具,包括调试器和shell(主机或内核),可以用于下载和管理模块。模块可以从任何主机支持的文件系统中(NFS,ftp等)下载到目标机。内核应用模块也可以存储在目标机flash或ROM中,在ROMFS文件系统中,或磁盘上。一旦它们已经加载到目标机,内核应用模块可以在shell或Wrokbench上立即启动这些应用。静态链接到操作系统的应用模块可以在shell或Workbench中交互运行。VxWorks也可以配置它们为启动时自动启动。静态链接和自动启动在生产系统中比较稳定。一个运行在内核空间中的应用不会像一个进程一样执行;简化为另外一组执行在内核空间的其它一组任务。内核不保护任何一个参与的内核应用导致的任何不良行为——内核应用和内核以管理员模式运行在同样的地址空间。警告:若你想移植一个内核应用到用户模式应用,执行为一个RTP,你必须确保代码满足一个RTP应用的要求和编译为一个RTP。你必须确保VxWorks配置支持RTPs。更多信息,参考VxWorksApplicationProgrammer'sGuide。4.3C和C++库风河固有C库和DinkumC和C++库都为VxWorks应用开发提供了。如Table4-1展示,VxWorks固有库用于C内核应用开发,和库用于所有案例。VxWorks固有C库提供ANSI规格外函数。注明不对多字节字符提供支持。关于这些库的更多信息,参考VxWorksandDinkumAPIreferences。关于C++工具更多信息,参考5.C++Development。4.4内核应用结构内核应用代码和通常C或C++应用相似,不一样的地方是,内核应用不需要传统的main()函数(不像一个基于进程的VxWorks应用)。仅需要一个入口点函数,启动应用运行需要的所有任务。注明:若你的内核应用包括一个main()函数,也不会自动启动。下载或存储在系统镜像中的内核应用模块必须被交互启动(或被另外一个已经运行的应用启动)。操作系统也可以配置为自动启动(参考a4.12ConfiguringVxWorkstoRunApplicationsAutomatically)。入口点函数执行所有需要的数据初始化,启动所有运行应用使用的任务。如一个内核应用可能有一个函数命名为myAppStartUp():voidmyAppStartUp(void){runFoo();tidThis=taskSpawn("tThis",200,0,STACK_SIZE,(FUNCPTR)thisRoutine,0,0,0,0,0,0,0,0,0,0);tidThat=taskSpawn("tThat",220,0,STACK_SIZE,(FUNCPTR)thatRoutine,0,0,0,0,0,0,0,0,0,0);tidAnother=taskSpawn("tAnother",230,0,STACK_SIZE,(FUNCPTR)anotherRoutine,0,0,0,0,0,0,0,0,0,0);return(OK);}关于VxWorks任务和多任务信息,参考6.TasksandMultitasking。关于和C++一同工作的信息,参考5.C++Development。4.5VxWorks头文件很多内核应用大量使用VxWorks操作系统工具或工具库。这个通常需要源模块引用Vxworks头文件。如下部分讨论了VxWorks头文件的使用方法。VxWorks头文件支持针对所有全局VxWorks函数的ANSIC函数原型声明。VxWorks通过ANSIX3.159-1989标准规范所有的头文件。VxWorks系统头文件在installDir/vxworks-6.x/target/h目录下和子目录下。4.5.1VxWorks头文件:vxWorks.h头文件vxWorks.h必须在每个使用VxWorks工具的内核应用模块中首先包含。它包含用于其它VxWorks模块扩展的基本定义和类型。很多其他VxWorks头文件要求这些定义。#include<vxWorks.h>4.5.2其它VxWorks头文件内核应用可以包含其它VxWorks头文件,若需要访问VxWorks工具。如一个使用VxWorks链接表库的模块必须包含lstLib.h头文件:#include<lstLib.h>针对每个库的API引用入口列出了用于库的所有必须的头文件。4.5.3ANSI头文件所有的ANSI规范的头文件都包含在VxWorks中。那些编译器独立的或具体VxWorks的头文件在installDir/vxworks-6.x/target/h,而一个编译器独立的(如stddef.h和stdarg.h)在编译器安装时被提供。每个工具链知道如何查找自身的内部头文件;不需要特殊的编译标志。4.5.4ANSIC++头文件每个编译器有自身的C++库和C++头文件(如iostream和new)。C++头文件位于编译器安装目录,不是installDir/vxworks-6.x/target/h。不需要特殊标志来使能编译器查找这些头文件。关于C++开发更多信息,参考5.C++Development。注:VxWorks5.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<xWorks.h>#include<sys/stat.h>4.5.6VxWorks嵌套头文件一些VxWorks工具利用其它低水平的VxWorks工具。如,tty管理工具使用环形缓存子程序库。Tty头文件tyLib.h使用环形缓存头文件rngLib.h.提供的定义。将会不方便的是需要你意识到包含头文件的内部依赖和排序。取而代之的是,所有的VxWorks头文件显式包含了所有先决条件的头文件。因此,tyLib.h本身包含了头文件rngLib.h。(一个例外是基本VxWorks头文件vxWorks,所有其他的头文件已经包含)通常,先决头文件的显式包含可以抛出一个问题:一个头文件只能包含一次,否则会产生重要错误(因为C预处理器认为重复定义会造成潜在的代码冲突问题)。然而,所有的头文件包含条件编译语句和保证只编译一次的定义,和你include多长没关系。因此,一个内核应用模块仅需要包含直接的头文件,无需关心头文件中的内部依赖和顺序,不会产生冲突。4.5.7VxWorks私有头文件一些VxWorks元素是内部可能变化的细节,所有不能再内核中引用。仅支持一个模块的工具用法通过在头文件中公共定义,和通过模块的子函数接口。你的坚持确保了你的应用代码不会VxWorks模块实现中内部变化的影响。一些使用HIDDEN组件标志的内部细节:/*HIDDEN*/.../*ENDHIDDEN*/内部细节也可以用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内核对象——如任务和信号量——可以动态或静态实例化。静态实例化提供性能和确定性优势。专有VxWorksC宏提供静态实例化。4.6.1关于内核对象的静态实例化一个内核对象的静态实例化意味着对象被声明为一个编译时变量(使用一个专有Vxworks宏),通常用于全局范围。因此编译器为应用对象分配空间,不需要在运行时分配。因此,对象在启动时可以立即初始化。与静态实例化对比,内核对象的动态实例化涉及运行时系统内存分配,之后在使用之前初始化。同样,对象删除涉及对象的验证,之后返回内存给系统。对象创建和删除同样依赖动态内存分配,通常使用malloc()和free()函数。应用使用动态实例化必须考虑运行时内存不足,对象不能成功创建的情况(采取一些合适的错误恢复过程或退出)。另外,动态分配是一个相对比较慢的操作,会阻塞一个单一函数的调用(如,taskSpawn(),semXCreate(),等)。如下实例说明了动态和静态代码实例化的区别(但是不要使用任何VxWorks对象实例化宏)。动态实例化structmy_object*pMyObj;...pMyObj=(structmy_object*)malloc(sizeof(structmy_object));if(pMyObj!=NULL){objectInit(pMyOjb);return(OK);}else{/*failurepath*/return(ERROR);}静态实例化structmy_objectmyObj;...objectInit(&myOjb);/*myObjnowreadyforuse*/可以静态实例化的内核对象如下内核对象可以静态实例化:·任务·信号量·消息队列·看门狗定时器更详细的信息,参考4.6.4StaticInstantiationofTasks,4.6.5StaticInstantiationOfSemaphores,4.6.6StaticInstantiationofMessageQueues,和4.6.7StaticInstantiationofWatchdogTimers。静态实例化和代码大小对象的编译时声明不在可执行文件中,一个VxWorks镜像中或存储介质中(如flash内存)占据任何空间。若一个对象在编译时声明,但是没有初始化,编译器设置它为未初始化数据段(bss)。未初始化数据要求被ANSIC标准设置为0。当未初始化数据会导致运行时内存占用,所有需要动态分配。另外情况,内存占用一样。静态初始化优势内核对象的静态初始化提供了几个优势,和动态初始化比较:·对象的静态初始化是一个快速,比较确定性操作。·应用逻辑简单因为不用考虑动态初始化内存分配失败的问题。·静态对象命名不会失败,除非程序本身太大,系统内存不能容纳。·小型化VxWorks配置可以用针对动态内存分配的删除工具创建(如,参考2.5Small-FootprintVxWorksConfiguration)。应用和静态初始化内核对象的静态初始化为实时应用提供了显著优势,大多数应用应该大量考虑静态初始化。大多数应用要求一些对象在整个应用运行生命周期内存在,不要求删除。因此这些对象可以静态初始化。使用静态初始化,使得软件更具备鲁棒性,确定性和更快速。另外,小系统在设计时不要使用动态内存分配工具,一个VxWorks配置是专门用于这样的设计(更多信息,参考2.5Small-FootprintVxWorksConfiguration)。注:静态初始化应该在内核应用中使用。不设计用于RTP应用(用户模式)。4.6.2static声明范围内核对象通常声明为全局变量,因为对象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.4StaticInstantiationofTasks,4.6.5StaticInstantiationOfSemaphores,4.6.6StaticInstantiationofMessageQueues,和4.6.7StaticInstantiationofWatchdogTimers。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<vxWorks.h>#include<taskLib.h>VX_TASK(myTask,4096);intmyTaskId;STATUSinitializeFunction(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);/*instantiationsucceeded*/elsereturn(ERROR);}为了初始化一个任务,但是一直保持挂起状态,直到调用VX_TASK_INITIALIZE宏之后。taskActivate()函数用于之后运行任务。相同的参数必须用于VX_TASK_INSTANTIATE和taskActivate().。如:#include<vxWorks.h>#include<taskLib.h>VX_TASK(myTask,4096);intmyTaskId;STATUSinitializeFunction(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);}elsereturn(ERROR);}更多信息,参考thetaskLibAPIreferenceentry。关于任务和任务管理函数相关通用信息,参考6.2AboutTasksandMultitasking,6.4TaskScheduling,和6.5TaskCreationandManagement。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<vxWorks.h>#include<semLib.h>VX_BINARY_SEMAPHORE(mySemB);/*declarethesemaphore*/SEM_IDmySemBId;/*semaphoreIDforfurtheroperations*/STATUSinitializeFunction(void){if((mySemBId=semBInitialize(mysemB,options,0))==NULL)return(ERROR);/*initializationfailed*/elsereturn(OK);}更多信息,参考APIreferencesforsemBLib,semCLib,andsemMLib。关于信号量的通用信息,参考7.4InterruptLocks。4.6.6消息队列的静态实例化VX_MSG_Q宏在编译时声明一个消息队列。使用三个参数:名称,消息队列中最多消息数,每个消息的最大字节数。msgQInitialize()函数用于初始化消息队列,使得为使用准备好。相同的消息队列名——还有针对消息队列大小和最大消息数的相同值——必须使用宏和函数。如:#include<vxWorks.h>#include<msgQLib.h>VX_MSG_Q(myMsgQ,100,16);/*declarethemsgQ*/MSG_Q_IDmyMsgQId;/*MsgQIDtosend/receivemessages*/STATUSinitializeFunction(void){if((myMsgQId=msgQInitialize(myMsgQ,100,16,options))==NULL)return(ERROR);/*initializationfailed*/elsereturn(OK);}更多信息,参考APIreferenceformsgQLib。关于消息队列通用信息,参考7.7MessageQueues。4.6.7看门狗定时器的静态实例化VX_WDOG宏在编译时声明一个看门狗定时器。使用一个参数,看门狗定时器的名字。wdInitialize()函数用于初始化看门狗定时器和使能使用。相同的看门狗名必须用于宏和函数。如:#include<vxWorks.h>#include<wdLib.h>VX_WDOG(myWdog);/*declarethewatchdog*/WDOG_IDmyWdogId;/*watchdogIDforfurtheroperations*/STATUSinitializeFunction(void){if((myWdogId=wdInitialize(myWdog))==NULL)return(ERROR);/*initializationfailed*/elsereturn(OK);}更多信息,参考APIreferenceforwdLib。关于消息队列的通用信息,参考8.4WatchdogTimers。4.7内核应用和内核组件需求VxWorks是一个高配置操作系统。当内核应用模块独立于操作系统编译(参考4.9BuildingKernelApplicationModules),编译过程不能确定是否应用上的VxWorks实例最终运行,因为,要保证VxWorks内核包含应用所需的所有组件(如网卡和文件系统等)。因此,应用代码检查内核设备不存在的错误提醒(也就是说,检查API调用的返回值)和正确的回复比较有用。当内核应用模块链接到操作系统时,编译系统产生和丢失组件相关的错误。Workbench和vxprj命令行工具也可以提供为重新配置VxWorks进行依赖检查的机制。4.8内核应用和内核组件需求4.9编译内核应用模块VxWorks内核应用可以用Workbench或用命令行编译(命令行环境包括一组有用的默认makefile规则)。关于Workbench和命令行编译环境的用法,参考WindRiverWorkbenchbyExampleguide和VxWorksCommand-LineToolsUser’sGuide。提醒:VxWorks内核应用必须针对运行的系统类型编译。针对UPVxWorks系统编译,针对SMPVxWorks编译,针对基于版本库创建的VxWorks系统,版本库基于VSB工程(UP或SMP),二进制不兼容。注明loader拒绝一个内核模块若和加载的系统不兼容,控制台会输出一个错误信息,并设置errno为S_loadLib_INCOMPATIBLE_MODULE。4.10下载内核应用对象模块到目标机内核应用对象模块可以从Workbench或内核shell中下载。一旦一个模块加载到目标内存,模块中的任何子函数会被触发,任务发起,模块使用的调试工具等。通常使用启动函数来运行应用比较有用。(参考4.4KernelApplicationStructure)。关于使用内核shell和内核loader信息,参考16.2KernelShell和16.3KernelObject-ModuleLoader。关于使用Workbench更多信息,参考WindRiverWorkbenchbyExampleguide。4.11链接内核应用对象模块到VxWorksVxWorks内核应用可以使用Workbench或命令行开发工具链接到VxWorks。关于使用Workbench和命令行编译环境信息,参考WindRiverWorkbenchbyExampleguide和VxWorksCommand-LineToolsUser’sGuide。4.12配置VxWorks自动运行应用程序VxWorks可以配置为在启动时自动启动内核应用。这样做,执行如下步骤:1.配置INCLUDE_USER_APPL组件到VxWorks中;2.在应用的入口点usrAppInit()函数中增加调用,在installDir/vxworks-6.x/target/proj/projDir/usrAppInit.c文件中。假如,应用入口点函数myAppStartUp()启动所有应用需要的任务,你需要在usrAppInit()函数中增加调用如下:voidusrAppInit(void){#ifdefUSER_APPL_INITUSER_APPL_INIT;/*forbackwardscompatibility*/#endifmyAppStartUp();}3.链接基于内核应用对象模块到内核镜像(参考4.11LinkingKernelApplicationObjectModuleswithVxWorks)。4.13镜像大小考虑系统镜像大小通常要重点考虑,尤其是当内核应用模块链接到操作系统后。这是真实的是否镜像被一个bootloader加载或自启动(参考2.7VxWorksImageTypes)。提醒:对于基于ROM镜像,确保ROM_SIZE配置参数影响ROMs使用的容量。4.13.1bootloader和可下载镜像通常,VxWorksbootloader代码被拷贝到RAM的一个起始地址上面的常量地址RAM_HIGH_ADRS,bootloader轮流拷贝可下载系统镜像到RAM_LOW_ADRS位置。这些常量的值是体系结构独立的,但是在任何情况下系统镜像不能超过两个常量间的空间。否则系统会覆盖bootloader代码,当下载时,潜在杀死启动进程。为了帮助避免这个除夕,上一个命令执行当编译一个新的Vxworks镜像为vxsize,表示新可执行镜像大小和ROM中预留了多少空间:vxsize386-v0010000000020000vxWorksvxWorks:612328(t)+69456(d)+34736(b)=716520(235720bytesleft)(Inthisoutput,tstandsfortextsegment,dfordatasegment,andbforbss.)确信RAM_HIGH_ADRS小于LOCAL_MEM_SIZE。若新镜像太大,vxsize产生一个警告。这种情况下,你应该重新配置bootloader来拷贝启动ROM代码到足够的内存地址位置,通过增加config.h文件中RAM_HIGH_ADRS值,和BSP的makefile(两个值也一致)。之后重新编译bootloader。更多信息,参考ReconfiguringMemoryLayoutforaPersistentMemoryRegion。4.13.2自启动镜像对于自启动镜像,驻留ROMVxWorks系统的数据段加载到RAM_LOW_ADRS(在makefile中定义)以最小化内存碎片。对于有限内存的CPU板(小于1MRAM),确信RAM_HIGH_ADRS小于LOCAL_MEM_SIZE,有足够的空间来容纳数据段。注明RAM_HIGH_ADRS定义在BSPmakefile和中(要一致)。5C++开发5.1介绍这一章提供了VxWorks系统下使用风河和GNU工具链进行C++开发相关信息。警告:风河编译器C++和GNUC++库文件不兼容。注明:本章提供了VxWorks内核有的相关设施信息。关于实时进程相关信息,参考VxWorksApplicationProgrammer’sGuide相关章节。5.2配置C++到VxWorks默认,VxWorks仅包括miniC++支持。你可以通过增加如下组件支持C++功能:INCLUDE_CTORS_DTORS默认包含在内核中,确保编译器产生的初始化函数,包括C++静态对象的初始化,在内核启动时调用。INCLUDE_CPLUS包括基本的C++应用支持。通常和INCLUDE_CPLUS_LANG混合使用。INCLUDE_CPLUS_LANG包含C++语言功能支持,如new,delete和异常处理。INCLUDE_CPLUS_IOSTREAMS包括所有的库功能。INCLUDE_CPLUS_DEMANGLER包括C++命令,在使用内核shellloader时非常用于,因为它提供用于内核shell特征表查询返回命令特征名。若同时包含INCLUDE_CPLUS和INCLUDE_SYM_TBL组件,这个组件被默认增加。5.3C++代码需求任何使用C++的VxWorks任务必须用VX_FP_TASK选项发起。默认,从主机工具发起任务(风河shell)默认使能VX_FP_TASK。警告:当使用VX_FP_TASK选项发起使用C++的任务失败时,可能很难调试,在运行时产生不可预见的浮点数寄存器破坏。若你从你的C代码中引用一个(非重载,全局)C++特征,你必须使用extern"C"通过原型C链接:#ifdef__cplusplusextern"C"voidmyEntryPoint();#elsevoidmyEntryPoint();#endif你也可以使用这个语法来在C++代码中访问C特征。VxWorksC特征自动存在在C++代码中,因为VxWorks头文件使用这种声明机制。每个编译器有自身的C++库和C++头(如iostream和new)。C++头位于编译器安装目录,不是installDir/vxworks-6.x/target/h中。不需要做什么就可以使能编译器找到这些头。注:VxWorks5.5之前发布版本,风河推荐使用-nostdinc标志。目前的发布版本不需要使用这个标志,防止编译器找不到头文件,如stddef.h。5.4在信号处理和ISRs中使用C++小心在信号处理和ISRs中使用C++代码。相关信息,参考8.2.5SignalHandlers和8.3.3CaveatsWithRegardtoWritingISRs。5.5在DKM工程中使用C++下载到VxWorks内核中的C++代码应该链接到一个单一下载对象模块中。也必须被一直使用,任何COMDAT或已经连接的部分会销毁。VxWorks为在可下载模块中调用静态构造和析构函数提供了几种措施。警告:风河编译器C++和GNUC++库文件是不兼容的。用C++写的DKM必须用VxWorks使用的编译来编译。5.5.1使用一个单一C++模块VxWorksloader仅支持self-contained的C++模块。一个自包含的C++模块是一个不能使用来之其它C++模块的类,它的类也不能被其它C++模块使用。尤其,一个模块必须包含自身标准库拷贝,或不使用C++标准库。为了生产自包含模块,所有下载的C++对象文件应于被链接到一个单一可下载对象模块。卸载一个不是自包含的C++模块可能会导致来之其他模块创建对象的空引用到卸载模块中的数据结构。尤其,若标准库流部分来之后面卸载的一个模块的初始化是会出现的。这种情况下,任何更进一步的iostreams的使用会伴随一个内核异常失败(访问无效的地址)。警告:C++对象文件必须链接到一个DKM模块。关于内核loader的信息,参考16.3KernelObject-ModuleLoader。5.5.2Munching一个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-chello.cpp2.munch对象文件:$ddump-Nghello.o|tclsh\installDir/vxworks-6.x/host/resource/hutils/tcl/munch.tcl-cppc>ctdt.c3.编译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-cctdt.c4.用munched对象文件链接最初对象文件创建一个可下载模块:$dld-tPPC604FH:vxworks61-X-r4-ohello.outhello.octdt.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-chello.cpp2.munch一个对象文件:nmppchello.o|wtxtclinstallDir/vxworks-6.x/host/src/hutils/munch.tcl\-cppc>ctdt.c3.编译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-cctdt.c4使用munched对象文件链接最初的对象文件创建一个可下载模块:ccppc-r-nostdlib-Wl,-X\-TinstallDir/vxworks-6.x/target/h/tool/gnu/ldscripts/link.OUT\-ohello.outhello.octdt.o注明:VxWorks内核对象模块loader不直接支持linkonce部分。取代的是,在加载之前linkonce部分必须被混合或销毁到标准的text和data部分。GNU-T选项销毁任何包含在输入文件中的linkonce部分。使用一个通用Makefile规则若你使用VxWorksmakefiel定义,你可以写一个兼容GUN和风河编译器工具链的makefileCPU=PPC604TOOL=gnuTGT_DIR=$(WIND_BASE)/targetinclude$(TGT_DIR)/h/make/defs.bspdefault:hello.out%.o:%.cpp$(CXX)$(C++FLAGS)-c$<%.out:%.o$(NM)$*.o|$(MUNCH)>ctdt.c$(CC)$(CFLAGS)$(OPTION_DOLLAR_SYMBOLS)-cctdt.c$(LD_PARTIAL)$(LD_PARTIAL_LAST_FLAGS)-o$@$*.octdt.oMunching,下载,链接之后,静态构造和析构被调用。这个步骤描述如下。5.5.3交互调用构造和析构函数5.6C++编译器区别5.6.1模板实例化5.6.2运行时类型信息5.7名称空间5.8C++Demo例子6任务和多任务6.1介绍现代实时系统基于多任务和任务间通讯的补充概念。一个多任务环境允许一个实时应用用一组独立的任务构建,每个任务由自己的执行线程和系统资源组。任务是VxWorks中调度的基本单元。内核或进程中所有的任务都隶属于同一个调度(不能调度VxWorks进程)。关于VxWorks对POSIX线程支持的更多信息,参考9.POSIXFacilities。注明:本章提供了VxWorks内核中存在的设备信息。关于实时进程相关的设备信息,参考VxWorksApplicationProgrammer’sGuide相关章节。6.2关于任务和多任务VxWorks任务是操作系统自身代码执行的基本单元,和作为一个进程执行的应用程序中一样。在其它操心系统下,通常使用线程。(更多信息,参考VxWorks支持的POSIX线程,参考9.10POSIXThreads)多任务是为应用控制和操作多个,分散的实时事件而提供的基本机制。VxWorks实时内核提供基本的多任务环节。对于一个单处理器系统,表象是多个任务在并发执行,其实内核是基于调度策略来执行的。每个任务由自身的上下文,指内核用于每次任务查看的调度运行需要的CPU环境和系统资源。对于一个上下文切换,一个任务的上下文保存在任务控制块中(TCB)。一个任务的上下文包括:·一个执行的线程;也就是说任务的程序计数器·一个任务的虚拟内存上下文(若支持进程)·CPU寄存器和协处理器寄存器(可选)·动态变量和函数调用栈·标准输入、标准输出,标准错误的I/O指派·一个延迟定时器·一个时间片定时器·内核控制结构·信号处理·任务私有环境(环境变量)错误状态(errno)·调试和性能监控值若VxWorks没有配置支持进程(INCLUDE_RTP),一个任务的上下文不包括虚拟内存上下文。所有的任务只能在一个但一个通用地址空间运行(内核)。然而,若VxWorks配置了进程支持——不管进程是否激活——一个内核任务的上下文必须包含自身的虚拟内存上下文,因为系统有潜在操作除内核之外其它虚拟内存上下文的可能性。也就是说,系统中的任务可能运行在不同的虚拟内存上下文中(内核和一个或多个进程)。关于虚拟内存上下文更多信息,参考15.MemoryManagement。注:POSIX标准包括一个线程的概念,和任务相似,但是有其它功能,详细,参考9.10POSIXThreads。6.2.1任务状态和转换内核为系统中的每一个任务维持目前的状态。一个任务从一个状态改变为另外一个状态做为应用特定函数调用的行为结果(如,尝试使用目前不存在的信号量)和使用开发工具如调试器。最高优先级的任务是目前执行的处于ready状态的任务。当用创建任务,立即进入ready状态。关于ready状态更多信息,参考SchedulingandtheReadyQueue。当使用VX_TASK_NOACTIVATE选项参数,用taskCreate()创建任务,或taskOpen(),实例化后的任务处于suspended状态。要通过taskActivate()激活,导致任务进入ready状态。激活阶段是非常快的,及时使能应用创建并激活任务。TasksStatesandStateSymbols描述了任务状态和你使用开发工具看到的statesymbols。注明任务状态是附加的;一个任务可能在一个时刻处于多个状态。转换可能会发生在其中一个状态。如一个任务从挂起态到挂起和停止态转换。且之后改变为非挂起态,之后变化为停止态。STOP状态用于调试工具,程序运行到一个断点位置时。也用于错误检测和报告工具()。展示了shell命令i(),任务状态信息。基本任务状态转换说明Figure6-1提供了一个任务状态转换的简要说明。对于澄清目的,不显示附加的在TasksStatesandStateSymbols,讨论的信息,不显示用于调试工具显示的STOP状态。列表中列出的函数是导致相关转换的例子。如,一个任务调用taskDelay()从ready状态转换到delayed状态。注明taskSpawn()导致创建任务后,任务进入ready状态,而taskCreate()函数创建任务后,任务进入suspend状态(使用VX_TASK_NOACTIVATE参数,调用taskOpen()函数完成后续的目的)。6.3VxWorks系统任务依据自身配置,VxWorks在启动时启动各种不同的任务,有的任务一直运行。一个基本的VxWorks配置关联的任务组,通常使用的可选组件关联一些任务,描述如下:提醒:不要suspend,delete,change这些任务中的任何一个。这样做会导致不可预知的系统行为。BasicVxWorksTasks可选组件的任务如下任务是通用VxWorks配置的附加任务例子。6.4任务调度多任务需要一个任务调度为准备好的任务分配CPU。VxWorks提供如下调度选项:·传统的VxWorks调度,提供基于优先级的强占调度,是一个轮询扩展。参考6.4.2VxWorksTraditionalScheduler。·VxWorksPOSIX线程调度,用于为RTPs中运行线程设计。参考9.12POSIXandVxWorksScheduling。·一个定制调度框架,允许你开发自己的调度机制,参考22.CustomScheduler。6.4.1任务优先级任务调度依赖任务的优先级,在任务创建时指派。VxWorks提供256个优先级级别,从0到255。0最高,255最低。一个任务在创建时指派优先级,之后可以编程改变。关于优先级指派信息,参考6.5.1TaskCreationandActivation和6.5.9TaskSchedulingControl。ApplicationTaskPriorities所有应用任务的优先级范围是100到255。DriverTaskPriorities对比于应用任务的优先级,100到255,驱动支持的优先级范围是51到99。这些任务是关键的;如若当从一个芯片上拷贝数据时支持的任务调用失败,则设备会丢失数据。驱动支持的任务例子包含tNet0,HDLC任务等。系统任务tNet0的优先级是50,所以用户任务不能指派低于50的任务优先级;否则,网络连接会挂死,和阻碍主机工具的调试功能。6.4.2VxWorks传统调度VxWorks传统调度提供基于优先级的抢占调度策略,和编程初始化轮询调度选项一样。传统的调度也可以参考original或native调度。传统的调度默认由INCLUDE_VX_TRADITIONAL_SCHEDULER组件提供。关于POSIX线程调度和定制调度信息,参考9.12POSIXandVxWorksScheduling和22.CustomSchedule。Priority-BasedPreemptiveScheduling一个基于优先级抢占调度,当一个任务的优先级高于目前运行的任务时,CPU被抢占。因此,内核确保CPU总是会分配给已经准备好的最高优先级任务。这意味着若一个任务——优先级比目前运行的任务优先级高——已经准备好运行,内核立即保存任务的上下文,切换到高优先级任务上下文。如,Figure6-2,t1任务被较高优先级的任务t2抢占,之后被任务t3抢占。当t3完成,继续t2运行。当t2完成运行,t1继续运行。这种调度策略的缺点是,当多个具有相等优先级任务共享一个处理器时,若一个单个任务不能阻塞,会抢占处理器。因此,其它相同优先级的任务永久得不到机会运行。轮询调度解决了这个问题(Round-RobinScheduling)。SchedulingandtheReadyQueueVxWorks调度维持一个FIFOready队列机制,这个队列包含所有处于ready状态的每个优先级级别的任务。当当下优先级级别可以访问CPU时,队列前面对应优先级的任务开始执行。一个任务在ready队列中的位置可能会发生变化,取决于执行的操作,如下:若一个任务被抢占,调度运行高优先级任务,但是被抢占的任务依然在优先级列表的前面位置。若一个任务pended,delayed,suspended,或stoped,被从ready队列中移除。之后再次准备好运行,在位于ready队列优先级列表的最后(关于任务状态和导致状态转移的操作,参考6.2.1TaskStatesandTransitions)。若一个任务的优先级通过taskPrioritySet()改变,将会位于新的优先级列表最后。若一个任务临时基于互斥信号量优先级继承策略启动(SEM_INVERSION_SAFE),在相关优先级上执行完返回到原来的优先级时,处于原来优先级列表后面(关于互斥信号量和优先级继承,参考PriorityInheritancePolicy)。移动任务到优先级列表最后taskRotate()函数可以用于从就绪队列中对应优先级队列前面移到最后。如,如下调用移动任务到优先级级别100列表最后:taskRotate(100);移动目前正在执行的任务到对应优先级列表最后,使用TASK_PRIORITY_SELF参数。taskRotate()函数可以用于切换到轮询模式。允许编程控制就绪队列中相同优先级任务共享CPU,而不是让系统在既定时间间隔内这样做。关于轮询机制更多信息,参考Round-RobinScheduling。Round-RobinScheduling6.5任务创建和管理如下部分概述了基本VxWorks任务函数,可以在taskLibVxWorks库中找到,这些函数为任务创建和控制提供方法,还有获取任务信息。参考相关API。为了交互使用,你可以用主机工具或内核shell控制VxWorks任务;参考WindRiverWorkbenchbyExample指导,WindRiverWorkbenchHostShellUser’sGuide,和VxWorksKernelProgrammer’sGuide:TargetTools。6.5.1任务创建和激活Table6-2列出的函数用于创建任务。函数的参数是新任务名(一个ASCII字符串),任务的优先级,一个options字,栈大小,主函数地址,传递个主函数的10个启动参数。id=taskSpawn(name,priority,options,stacksize,main,arg1,…arg10);注明一个任务的优先级可以在创建之后改变;参考6.5.9TaskSchedulingControl。taskSpawn()函数创建新的任务上下文,包含分配的栈,使用具体参数安装调用主函数的任务环境。新任务从指定函数入口执行。taskOpen()函数提供一个类似POSIX的API,来创建一个任务(使用是否激活参数)或得到一个已有任务的句柄。也为创建一个任务提供一个公共对象,所有进程和内核可见(参考6.5.3Inter-ProcessCommunicationWithPublicTasks)。taskOpen()函数是最通用目的的任务创建函数。taskSpawn()函数体现了分配,初始化,激活的每一步步骤。初始化和激活函数由函数taskCreate()和taskActivate()提供。然而,风河推荐当你需要控制分配和激活时使用这些函数。taskInit()和taskInitExcStk()区别是taskInit()函数允许指明执行栈地址,而taskInitExcStk()函数允许指明执行和异常栈地址。任务静态实例化Table6-2列出的创建函数执行一个动态,两步操作,在运行时位任务对象分配内存,之后初始化对象。任务(和其它VxWorks对象)也可以静态实例化——这意味着在编译时为对象分配内存——之后在运行时实例化对象。关于静态实例化更多信息,参考4.6StaticInstantiationofKernelObjects。关于任务静态实例化更多信息,参考4.6.4StaticInstantiationofTasks。6.5.2任务名称和ID若使用taskOpen()函数创建,一个任务可以显式命名(使用一个任意长度的ASCII字符串)。使用taskSpawn()函数,不必须命名,这种情况下,使用一个空指针代替name参数,导致自动产生一个唯一任务名。名字格式为,tN,N是一个十进制数,没创建一个,N递增。不管任务如何创建,当任务发起时,都会返回一个任务ID。为了避免命名冲突,VxWorks使用命名前缀约定,所有的从目标启动的内核任务都必须使用字母t前缀,所有从主机启动的任务使用u前缀。另外,一个实时进程初始化任务名是一个可执行文件名(更少的扩展),使用前缀字母i。一个必须用一个反斜杠命名的任务是系统范围内都可以访问的公共对象(不仅仅是在创建的内存上下文中——进程或内核)。更多信息,参考6.5.3Inter-ProcessCommunicationWithPublicTasks。大多数VxWorks任务函数使用一个任务ID作为指定任务的参数。VxWorks使用一个规范:一个为0的任务ID总表示调用任务,任务ID是一个4字节的任务数据结构。TaskNamingRules任务命名时要遵循如下任务命名规则:·公共任务名必须唯一,且必须以反斜杠开始(如,/tMyTask)。注明公共任务是整个系统可见的——所有的进程和内核。更多关于公共任务的信息,参考6.5.3Inter-ProcessCommunicationWithPublicTasks。·私有任务名应该唯一。VxWorks不要求私有任务名唯一,但是最好使用唯一命名,避免用户混淆。(私有任务仅创建的实体内可见——内核或进程)为了利用主机开发工具的优势,任务名不应该和全局函数或变量名冲突。TaskNameandIDRoutinesTable6-3列出了taskLib函数,管理任务IDs和名称。6.5.3进程间使用公共任务通讯VxWorks任务可以创建为私有对象,仅可以在创建任务的内存空间中访问(内核或进程);或公共对象,在整个系统中都可以访问。任务创建为私有或公共对象,取决于任务命名方式。公共对象任务名必须以一个反斜杠开始。更多信息,参考6.5.2TaskNamesandIDs。创建一个任务为公共对象,运行其他任务从本进程外部发送信号或事件来和本任务交互。(使用taskKill()和eventSend()函数)更详细的信息,参考7.10Inter-ProcessCommunicationWithPublicObjects和thetaskOpenentryintheVxWorksKernelAPIReference。6.5.47.10Inter-ProcessCommunicationWithPublicObjects,p.144TaskCreationOptions当启动一个任务时,你可以传递一个或多个选项参数,如Table6-4。结果取决于在指定选项上执行一个逻辑或操作。浮点数操作你必须在创建任务时,使用VX_FP_TASK选项:·执行浮点数操作。·调用任何返回一个浮点数值的函数。·调用任何使用一个浮点数作为一个参数的函数。如:tid=taskSpawn("tMyTask",90,VX_FP_TASK,20000,myFunc,2387,0,0,0,0,0,0,0,0,0);一些函数内部执行浮点数操作。VxWorks文档为每一个明确说明需要使用VX_FP_TASK选项的函数。FillingTaskStacks注明附加为独立任务使用VX_NO_STACK_FILL创建选项,你可以使用配置参数(当配置VxWorks时)为系统中所有的任务和中断禁用栈填充。默认,任务和中断栈被用0xEE填充。使用checkStack()函数调试开发环境中填充栈是非常有用的。通常不在部署系统中使用,因为在任务创建过程中,没有填充栈会提供更好的性能(静态初始化任务的启动时间)。6.5.5任务栈每个任务的任务栈的大小在创建任务时指定(6.5.1TaskCreationandActivation)。很难了解要分配多少任务栈空间。为了帮助避免栈溢出和破坏,你应该初始化一个较大的栈空间。之后在shell中使用checkStack()或ti()周期性监控栈。当你确定了实际的用法,根据测试和部署系统调整栈大小。另外任务栈大小的实验,你也可以使用任务栈保护区来配置和测试系统(参考TaskStackProtection)。TaskStackProtection任务栈可以使用保护区和使能栈非执行来保护。TaskStackGuardZones系统可以配置INCLUDE_PROTECT_TASK_STACK组件用于对任务栈进行保护区保护。若内存使用成为一个问题,组件可以在最终测试和部署系统中删除。一个过运行保护区防止一个任务超越预定义栈大小和破坏数据或其它栈。一个下溢运行保护区通常防止缓存溢出栈底,破坏数据。当一个任务尝试访问任何保护区时,CPU产生异常。当插入一个保护区或使能栈非执行时,栈大小可以调整到多个MMU页大小。注明,保护区不能捕捉超过页大小一个缓存(很少见)。如,保护区是一个页大小,4096个字节,栈接近它的最后,则若在栈上分配一个8000个字节的缓存,溢出不能检测到。默认,内核模式任务不需要任务内存保护。配置INCLUDE_PROTECT_TASK_STACK组件,对执行栈提供上溢或下溢的保护区,不针对异常栈。内核中的栈保护区被映射到物理内存。注明INCLUDE_RTP组件提供对用户模式任务保护区保护,不能应用到内核任务(关于用户模式任务栈保护,参考VxWorksApplicationProgrammer’sGuide:Multitasking)。注明INCLUDE_PROTECT_TASK_STACK组件不为使用VX_NO_STACK_PROTECT参数创建的任务提供栈保护。(参考6.5.47.10Inter-ProcessCommunicationWithPublicObjects,p.144TaskCreationOptions)若一个任务使用这个选项,则这个任务没有栈保护。保护区的大小通过如下配置参数定义:内核任务执行栈溢出大小:TASK_KERNEL_EXEC_STACK_OVERFLOW_SIZE。内核任务执行栈下溢大小:TASK_KERNEL_EXEC_STACK_UNDERFLOW_SIZE。这些参数值可以修改系统范围上的保护区大小。保护区大小可以达到多个CPUMMU页大小。通过设置参数为0来防止一个保护区插入。内核中栈保护区消耗RAM,保护区对应有效的映射内存。Non-ExecutableTaskStacks仅当系统配置组件,且若CPU支持在基于一个MMU页上分配内存时,VxWorks使用一个非执行属性创建内核任务。当栈使能非执行时,栈大小通常可以达到一个MMU页大小(这种情况下,保护区被插入)。6.5.6任务信息Table6-6列出的函数获取任务信息。因为任务状态是动态的,所以信息不回并发,除了任务处于休眠状态(也就是说挂起)。关于具体任务变量和它们使用的信息,参考6.8.3Task-SpecificVariables。6.5.7任务删除和安全删除任务可以在系统中动态删除。Table6-7列出的函数用于产生任务和防止任务异常删除。exit()任务隐式调用,若任务创建时指定的入口函数返回时。当删除一个任务时,无须其它任务这个删除操作。函数taskSafe()和taskUnsafe()解决了任务的异常删除问题。taskSafe()函数保护重其它任务删除受保护的任务。当一个任务在一个关键区域执行或正在使用关键资源时,通常需要保护。如,一个任务可能用一个信号量来互斥访问以下数据结构。当执行在临界代码区域,任务可能被另外一个任务删除。因为任务不能使能完成关键区域,数据结构可能处于不一致或破坏状态。更进一步说,使用的信号量永远得不到释放,其它任务也不能使用临界资源,处于冻结状态。使用taskSafe()函数防止持有信号量的任务立即被删除。任何尝试删除使用taskSafe()函数保护的任务会被阻塞。当完成临界资源后,受保护任务可以使用taskUnsafe()自己释放自己,为任何需要删除的任务准备。为了支持网状安全删除区域,acount保存taskSafe()和taskUnsafe()的调用次数。当count为0时,允许删除,也就是说有多少unsafes,就有多少safes。仅被调用的任务受保护。一个任务不能使能另外一个任务安全或非安全删除。如下代码段展示了如何使用taskSafe()和taskUnsafe()来保护一段临界代码:taskSafe();semTake(semId,WAIT_FOREVER);/*Blockuntilsemaphoreavailable*/../*criticalregioncode*/.semGive(semId);/*Releasesemaphore*/taskUnsafe();安全删除通常和互斥耦合,如这个例子。为了方便和效率,一个特别类型的信号量,互斥信号量,为安全删除提供选项。更多信息,参考7.6.4Mutual-ExclusionSemaphores。6.5.8任务执行控制Table6-8列出的函数提供直接控制一个任务执行。任务执行过程中,若出现一些重要错误,则要求重启任务。重启机制,taskRestart(),用原来的创建参数重新创建一个任务。延迟操作为任务提供固定休眠的一个简化机制。任务延迟通常用于轮询应用。如延迟一个任务半秒,无须考虑时钟频率。调用taskDelay()如下:taskDelay(sysClkRateGet()/2);sysClkRateGet()函数返回系统速率:ticks/s。取代taskDelay(),你可以使用POSIXnanosleep()函数指明具体的延迟。仅单位不同;延迟函数是一样的,取决于系统时钟。详细,参考9.6POSIXClocksandTimers。注明调用taskDelay()从ready队列中删除调用任务。当任务再次准备好运行,位于对应优先级就绪队列最后。这个行为用于通过使用0ticks延迟函数放弃CPU给其它对等优先级的任务。taskDelay(NO_WAIT);/*allowothertasksofsameprioritytorun*/只有taskDelay()函数可以使用0参数。nanosleep()函数不可以。关于就绪队列更多信息,参考SchedulingandtheReadyQueue。注:ANSI和POSIXAPIs相似。系统时钟分辨率通常是60Hz(一秒钟60下)。这是一个时钟tick比较长的时间,可以是100Hz或120Hz。因此,因为周期延迟是有效轮询,你会考虑使用使用事件驱动的技术。6.5.9任务调度控制任务在创建时被指派一个优先级(参考6.5.1TaskCreationandActivation)。在运行过程中,可以通过调用taskPrioritySet()改变一个任务的优先级。动态改变优先级的功能允许应用跟踪实际操作流程的变化。小心使用taskPrioritySet()函数改变一个任务的优先级,改变后任务位于对应优先级就绪队列最后。关于就绪对象相关信息,参考SchedulingandtheReadyQueue。Table6-9列出了用于控制任务块的一组函数。6.5.10任务扩展:钩子函数为了允许额外任务相关的工具增加到系统,VxWorks提供了钩子函数,允许当任务创建时,一个任务上下文切换时,或一个任务被删除时,增加的函数被触发。TCB中有空闲域来为一个任务的上下文应用扩展。这些钩子函数在Table6-10列出;更多信息,参考taskHookLib相关API。任务创建的钩子函数在创建任务的上下文中执行。任务创建钩子必须考虑钩子函数中创建的任何内核对象的所有权关系(如看门狗定时器,信号量等)。因为创建钩子函数在创建任务的上下文中执行,新任务的进程拥有新的内核对象。可能必须要指派这些对象的所有权给新的任务进程。这会防止当进程创建的任务中止时,不期望的对象被回收。当使用任务切换钩子函数时,要意识到如下限制:·不要假设任何虚拟内存上下文为当前内存上下文,处理内核上下文(用ISRs)。·不要依赖目前任务的识别或依赖这个信息触发任何函数,如taskIdSelf()。·不要依赖taskIdVerify(pOldTcb)来确定是否为自我析构任务情况下删除执行的钩子。取而代之的是,删除钩子中其它状态信息当切换钩子时要被检测到(通过设置NULL指针)。内核上下文中用户安装的切换钩子被调用,因此不能访问所有VxWorks设施。Table6-11总结了一个任务切换钩子中可以调用的函数;通常任何不涉及内核调用的函数。注:关于POSIX扩展信息,参考9.POSIXFacilities。6.6任务错误状态:errno按照惯例,C库函数设置一个单一全局整数变量errno为一个正确的错误号,当函数遇到错误时。这个惯例指定为ANSIC标准的一部分。注:这一部分描述了VxWorks单处理器配置中errno的实现和用法。关于SMP的errno和其他全局变量的信息,参考24.17.8SMPCPU-SpecificVariablesandUniprocessorGlobalVariables,关于移植的信息,参考24.17MigratingCodetoVxWorksSMP。6.6.1errno的分层定义VxWorks中,errno同时以两种方式定义。ANSIC标准,一个潜在称为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。因此,被保存,通过内核回复,作为每次一个上下文切换产生时每个任务上下文一部分。同样,interruptserviceroutines(ISRs)查看自身的errno版本号。通过保存和恢复中断栈上的errno,作为内核自动提供的进入和退出中断代码的一部分(8.3.1ConnectingRoutinestoInterrupts)。因此,不管VxWorks上下文,不用直接操作全局变量errno的情况下一个错误码可以保持和提交。6.6.3错误返回惯例几乎所有的VxWorks函数都遵循一个规范,通过实际函数返回值来表示函数操作正确与否。很多函数仅返回OK(0)或ERROR(-1)。一些正常返回一个非负数的函数(如,open()函数返回一个文件描述符)也返回ERROR表示一个错误。返回一个指针的函数,通常返回NULL(0)表示一个错误。大多数情况下,一个函数返回如一个错误表示也设置errno为特别的错误码。全局变量errno从来不会被VxWorks函数清除。因此,它的值总表示上一次设置的错误状态。当一个VxWorks子函数在调用其他函数时得到一个错误提示,通常无须修改errno,返回自身的错误提示。因此,errno值在低层次函数中设置保持为错误类型的提示。6.6.4错误状态值指派VxWorkserrno值编码产生错误的模块,使用最重要的两个字节为独立错误计数。所有VxWorks模块数范围是1-500;一个0模块数errno值用于源码兼容性。其它errno值(也就是说,所有的正值)提供给应用使用。参考相关VxWorksAPI,获取定义和编码errno值规范的更多信息。6.7任务异常处理程序代码或数据中的错误会导致硬件异常条件如非法指令,总线或地址错误,除数为0,等。VxWorks异常处理报关注这些异常(参考19.ErrorDetectionandReporting)。任务可以通过信号工具关联它们的句柄来处理特定的硬件异常。若一个任务针对一个异常提供了一个信号回调,上面描述的默认异常回调是不会执行的。一个用户定义的信号回调在从重要错误中恢复时非常重要。通常,调用setjmp()函数定义函数中的恢复点,在回调中调用longjmp()函数来恢复上下文。注明longjmp()恢复任务的信号屏蔽的状态。信号也用于通知软件异常,还有硬件异常。在8.2Signals描述,和sigLib中相关API。6.8共享代码和可重入性7任务间和进程间通讯7.1介绍任务间通讯工具允许任务同步为了协调任务的行为。VxWorks中任务间通讯工具包括中断锁,任务锁,各种类型信号量,消息队列,管道,VxWorks事件和消息通道。对于进程间和内核进程通信,VxWorks信号量和消息队列,管道和事件(还有POSIX信号量和事件)可以创建为公共对象来提供跨越内存边界的任务间通讯(内核和进程之间,不同进程之间)。另外,消息通道提供基于socket的进程间通讯和进程间通讯机制。注:这一章提供了VxWorks内核中存在的设备信息。关于实时进程中存在的相关信息,参考VxWorksApplicationProgrammer’sGuide相关章节。注:这一章提供了通用于UP和SMP VxWorks配置的多任务设施。也提供了具体与UP的相关设施信息。后面,注明了SMP系统相关信息。正常情况下,VxWorks的SMP和UP配置共享相同的API——仅有一小部分函数不同。注明一些编程实践——隐式同步技术依赖任务优先级取代显式锁——SMP系统不合适。关于SMP编程更多信息,参考24.VxWorksSMP。关于具体移植相关信息,参考24.17MigratingCodetoVxWorksSMP。7.2关于任务间和进程间通讯VxWorks任务间和进程间设备为不同任务行为和通讯提供了同步机制。最简单的情况,执行在相同内存空间中(内核或进程)的任务可以通过访问共享数据结构轻易实现通讯和数据交换。(相关信息,参考7.3SharedDataStructures。)然而,它们的访问应该使用设计用于互斥访问一个共享资源的工具,如通用目的二进制信号量。很多专业的同步和互斥机制被提供用于处理更复杂和需要更高性能的场景,还有直接进行数据交换的机制和事件通知机制。VxWorkst提供的进程间和任务间通讯工具如下:中断锁提供禁用中断的方法,防止被ISRs抢占。中断锁不上一个通用目的机制,应该谨慎使用,参考7.4InterruptLocks。任务锁提供禁用被其它任务抢占的方法。任务锁不上一个通用目的机制,应谨慎使用。参考7.5TaskLocks。信号量提供任务同步和互斥的主要方法,如7.6Semaphores描述。信号量可以用公共对象创建,用于进程间通讯;相关信息,参考7.10Inter-ProcessCommunicationWithPublicObjects。关于POSIX信号量信息,参考9.13POSIXSemaphores。消息队列为任务间直接消息通讯提供高水平的机制。参考7.7MessageQueues。消息队列创建为公共对象,允许进程间通讯使用;相关信息,参考7.10Inter-ProcessCommunicationWithPublicObjects。管道提供给消息队列工具的一个可选消息接口。管道操作通过I/O系统操作,考虑用于标准I/O函数和select()。参考7.8Pipes。VxWorks事件提供任务和其它任务间的通讯和同步方法,中断服务程序(ISRs)和任务,信号量和任务,消息队列和任务,参考7.9VxWorksEvents。关于信号的信息,用于通用目的任务间和进程间通讯,参考8.2Signals。关于设计专用于多CPU系统的同步和通讯相关信息,参考see24.VxWorksSMP,25.OverviewofVxWorksAMP,31.Shared-MemoryObjects:VxMP和32.DistributedSharedMemory:DSHM和33.MessageChannels。注明:通常情况下,SMP和UP的VxWorks配置为任务间和进程间通讯共享相同的设备——仅很小一部分程序有差别。这一部分提供了通用两种配置的API信息,和具体与AP配置相关信息。后者也注明了SMP有的信息。关于VxworksSMP系统配置相关信息,参考24.VxWorksSMP;和移植相关信息,参考24.17MigratingCodetoVxWorksSMP。7.3共享数据结构相同内存空间(一个单一进程或内核中)中不同任务通讯的最基本的方法是通过访问共享数据结构。因为单一进程中或内核中存在的所有任务存在在一个单一线性地址空间,任务间通过共享数据结构通讯;参考Figure7-1。全局变量,线性缓存,环形缓存,链表,指针可以通过代码在不同的任务上下文运行。然而,访问共享数据结构可以通过一个互斥体控制,如信号量。(7.6Semaphores)关于用于进程间通讯的共享数据域相关信息,参考VxWorksApplicationProgrammer’sGuide。7.4中断锁intLock()函数禁用中断,因此防止被ISRs抢占。可以在一个任务或ISR上下文中调用。intUnLock()函数重新使能中断。如下代码用于包含一个临界区域:foo(){intlockKey=intLock();../*criticalregionofcodethatcannotbeinterrupted*/.intUnlock(lockKey);}当一个任务和中断共同访问一个变量或数据结构时,使用intLock()函数防止抢占。保护的代码量要最小化,意味着代码行数少,没有函数调用。若调用太长,会直接影响中断延迟和导致系统变得不确定性。为了防止一个任务被其它任务或中断抢占,使用intLock()和taskLock();更多信息,参考intLock()相关API。关于ISRs相关信息,参考8.3InterruptServiceRoutines:ISRs。警告:使用中断锁触发一个VxWorks系统函数可能会导致在一个未指明时钟段上重新使能中断。若调用的函数块,或导致一个高优先级任务变为就绪状态,当另外一个任务执行时,中断会被重新使能,或内核空闲时。相关信息,参考VxWorksAPIreferenceentryforintLock().。注明:intLock()函数针对APVxWorks配置提供,不适用于SMP。SMP有几个备选,保留ISR调用自旋锁,默认域UP系统中的intLock()行为。更多信息,参考24.7.1ISR-CallableSpinlocks和24.17MigratingCodetoVxWorksSMP。7.5任务锁taskLock()函数禁用其它任务抢占正在调用的任务。正在调用的任务是仅被允许执行的任务——处于就绪状态。若调用任务阻塞或挂起,调度选择下一个高优先级准备就绪任务执行。当调用任务解除阻塞,恢复执行,抢占再次被禁用。(关于任务状态的信息,参考6.2.1TaskStatesandTransitions。)taskUnlock()函数使能其它任务抢占。示例如下:foo(){taskLock();../*criticalregionofcodethatcannotbeinterrupted*/.taskUnlock();}任务锁可以被嵌套(使用一个count变量实现),这种情况下不会使能抢占直到调用count个taskUnlock()。任务锁可能导致不可接受的实时应答时间。高优先级任务不能执行直到加锁任务离开临界资源区,甚至高优先级任务本身不能用临界区激活。而这种互斥方式是非常简单的,确信保存短暂的延时。通常,信号量给互斥提供更好的机制;参考7.6Semaphores。当使用taskLock()时,中断不会被阻塞。可以成对使用intLock()来防止任务和ISRs抢占。更多信息,参考taskLock()和taskUnLock()。注明:函数taskLock()和taskUnlock()针对UPVxWorks配置提供,不适用于SMPVxWorks配置。SMP系统有几种备选方案,如仅用于任务的自旋锁,默认和taskLock()和taskUnlock()行为一样。更多信息,参考24.7.2Task-OnlySpinlocks和24.17MigratingCodetoVxWorksSMP。7.6信号量VxWorks信号量是高度优化的,提供非常快速的任务间通讯机制。信号量是解决任务间同步和互斥的主要方法,如下描述:·对于mutualexclusion,信号量内部锁访问共享资源。提供粒度很小的互斥机制,和中断锁、任务锁比较。·对于ynchronization,信号量使用外部事件协调一个任务的执行。注:信号量提供完整的内存边界,针对SMPVxWorks配置也是一个很好的功能。更多信息,参考24.9MemoryBarriers。VxWorks提供了如下几种信号量类型,可以用于如下几种类型优化:binary最快速,最通用的信号量。为同步或互斥优化。更多信息,参考7.6.3BinarySemaphores。mutualexclusion一个专门的二进制信号量针对解决互斥问题优化:优先级翻转,安全删除和递归。更多信息,参考7.6.4Mutual-ExclusionSemaphores。counting如二进制信号量,跟踪信号量释放的次数。为了保护资源的多个实例优化。更多信息,参考7.6.5CountingSemaphores。read/write一个特别的信号量类型,为需要写访问一个对象的任务,或需要并发访问仅需要读访问对象的任务提供互斥机制。这种类型的信号量对于SMP系统比较重要。更多信息,参考7.6.6Read/WriteSemaphores。VxWorks不仅仅提供了针对VxWorks设计的信号量,还为了考虑移植性提供了POSIX信号量。一个可选择信号量库提供了POSIX兼容的信号量接口;参考9.13POSIXSemaphores。注明:这儿设计的信号量可用于UP和SMP的VxWorks配置。可选产品VxMP提供了在VxWorks内核中可以用于AMP系统的信号量。更多信息,参考31.Shared-MemoryObjects:VxMP。7.6.1进程间使用公共信号量通讯VxWorks信号量作为私有对象创建,仅可以在被创建的内存空间中访问(内核或进程);作为公共对象创建,可以在整个系统范围内访问。作为一个公共对象创建一个信号量,semOpen()函数必须使用,信号量名必须以反斜杠开始(如/mySem)。信号量的类型有信号量类型参数指定。更详细的信息,参考7.10Inter-ProcessCommunicationWithPublicObjects,和VxWorksKernelAPIReference中semOpen入口。7.6.2信号量创建和使用大多数情况下,VxWorks为信号量的控制提供了一个单一的,通用的接口——取代定义一整组指明每一个信号量类型的信号量控制函数。这个规则的例外如下:·创建函数,具体于每一类信号量类型。·Read/write信号量的give和take函数,支持每一种操作的读写模式。·针对二进制和互斥信号量的扩展和内联版本,提供标准函数的优化选项。Table7-1列出了信号量控制函数。Table7-2列出的选项可用于扩展和内联函数。关于扩展和内联函数的更多信息,参考ScalableandInlineSemaphoreTakeandGiveRoutines。创建函数返回一个信号量ID,用于其它信号量控制函数的参数句柄。当创建一个信号量时,指定队列类型。信号量挂起的任务可以按照优先级排序(SEM_Q_PRIORITY)或先进先出排序(SEM_Q_FIFO)。警告:调用semDelete()函数中止一个信号量,并释放所有关联的内存。小心删除信号量操作,尤其是用于互斥的信号量,避免删除一个正在被其它任务使用的信号量。不要删除一个信号量,除非在相同的任务中使用这个信号量。针对扩展和内联信号量函数的选项Table7-2列出了用于扩展和内联函数的选项。关于扩展和内联函数更多信息,参考ScalableandInlineSemaphoreTakeandGiveRoutines。静态信号量初始化列出的信号量创建函数执行一个动态、两步操作,在运行时为信号量对象分配内存,之后初始化对象。信号量(其它VxWorks对象)也可以静态实例化——意味着在编译时分配内存——在运行时初始化对象。对于静态实例化通用信息,参考4.6StaticInstantiationofKernelObjects。关于信号量静态实例化信息,参考4.6.5StaticInstantiationOfSemaphores。可扩展和内联信号量的Take和Give函数除了标准的信号量give和take函数,VxWorks提供了用于二进制和互斥信号量的可扩展和内联版本。这些函数增加了如下优势:·对于UP和SMPVxWorks配置都可以提高性能——使用扩展和内联选项。·对于SMP,附加的性能提高,和标准函数相比,因为针对SMP系统中非竞争take和give操作提供了优化。可扩展函数设计用于轻量级竞争资源可以极大的提供性能和标准函数相比,标准函数可以提供更强的鲁棒性(如各种形式的错误检查)。可提供多种de-selecting选择的功能,将针对give和take操作执行。Take和give函数的inline版本提供了和扩展函数一样的选项,但是也避免了一个函数调用的过负荷。至于任何内联编码,重复增加到系统中。若一个应用多次调用一个函数的inline版本,或应该创建一个inline函数的封装,或用扩展函数代替。Table7-1列出了扩展和内联函数版本;Table7-2列出了扩展和内联函数选项。注明这些函数的使用方法,必须包含semLibInline.h头文件。7.6.3二进制信号量通用目的二进制信号量可以解决互斥和同步任务协调方式。二进制信号量关联最小的负荷,使得可以满足高性能的要求。互斥信号量描述在7.6.4Mutual-ExclusionSemaphores中,也是一个二进制信号量,但是已经被优化用于解决互斥一致性问题。同样,若互斥信号量的高级功能不需要,二进制信号量可用于互斥。一个二进制信号量可以认为为一个标准,存在(full),或不存在(empty)。当一个任务takes一个二进制信号量时,使用semTake(),输出结果取决于此时信号量存在与否;参考Figure7-2。若信号量存在(full),任务执行完,变的不存在。若信号量不存在(empty),任务排在阻塞任务等待队列中,并进入等待信号量的挂起状态。当一个任务give一个二进制信号量,使用semGive(),输出结果也取决于调用时信号量是否存在;参考Figure7-3。若存在(full),则操作无效,之后信号量变为存在(full)。若信号量不存在(empty),且一个或多个任务挂起在这个信号量上,等待队列中的第一个任务解除阻塞,信号量变为存在(full)。互斥二进制信号量内部锁高效的访问一个共享资源。不像禁用中断或抢占锁,二进制限制了关联资源的互斥范围。这个技术,一个创建的信号量用于保护资源。初始化信号量为存在(full)。/*includes*/#include<vxWorks.h>#include<semLib.h>SEM_IDsemMutex;/*Createabinarysemaphorethatisinitiallyfull.Tasks**blockedonsemaphorewaitinpriorityorder.*/semMutex=semBCreate(SEM_Q_PRIORITY,SEM_FULL);当一个任务想访问资源时,必须take信号量。之后任务持有这个信号量,其它需要这个信号量的任务会阻塞。当任务完成这个资源后,give信号量。允许其它任务使用这个资源。因此demo如下:semTake(semMutex,WAIT_FOREVER);../*criticalregion,onlyaccessiblebyasingletaskatatime*/.semGive(semMutex);同步当用于任务同步时,一个信号量表示一个任务等待的条件或事件。最初,信号量不存在(empty)。一个任务或ISR通过give信号量产生事件发信号。另外一个等待此信号量的任务,通过调用函数semTake(),获取信号量。等待信号量的任务一直阻塞,直到事件产生,并信号量释放。(关于ISR的详细讨论,参考8.3InterruptServiceRoutines:ISRs。)注明用于互斥的信号量和用于同步的信号量的顺序不同。对应互斥,信号量初始化为full,每个任务首先take,其次give。对于同步,信号量初始化为empty,一个任务等待其他任务give的信号量。在Example7-1中,init()函数创建二进制信号量,关联一个ISR到一个事件,并发起一个任务来处理事件。函数task1()运行直到调用semTake()。保持阻塞一直到一个事件导致ISR调用semGive()。当ISR完成task1()执行事件的处理。在一个专门的任务中处理事件过程非常有优势:很少发生在中断级别,因此降低了中断延迟。这种事件处理过程推荐在实时应用中使用。Example7-1UsingSemaphoresforTaskSynchronization/*Thisexampleshowstheuseofsemaphoresfortasksynchronization.*//*includes*/#include<vxWorks.h>#include<semLib.h>#include<arch/arch/ivarch.h>/*replacearchwitharchitecturetype*/SEM_IDsyncSem;/*IDofsyncsemaphore*/init(intsomeIntNum){/*connectinterruptserviceroutine*/intConnect(INUM_TO_IVEC(someIntNum),eventInterruptSvcRout,0);/*createsemaphore*/syncSem=semBCreate(SEM_Q_FIFO,SEM_EMPTY);/*spawntaskusedforsynchronization.*/taskSpawn("sample",100,0,20000,task1,0,0,0,0,0,0,0,0,0,0);}task1(void){...semTake(syncSem,WAIT_FOREVER);/*waitforeventtooccur*/printf("task1gotthesemaphore\n");.../*processevent*/}eventInterruptSvcRout(void){...semGive(syncSem);/*lettask1processevent*/...}广播同步允许所有的进程阻塞在同一个信号量上,要原子解除阻塞。正确的应用行为通常是在任何一组任务由机会处理下一步任务前请求一组任务来处理一个事件。函数semFlush()通过解除挂起在一个信号量上的任务解决同步问题。7.6.4互斥信号量互斥信号量是一个专门的二进制信号量设计用于解决固有的互斥问题,包括优先级转换,安全删除,和递归访问资源等。互斥信号量的基本行为和二进制信号量一样,附加如下几个功能:·仅用于互斥。·仅当一个任务take后,用于give。·不能再中断中give。·semFlush()操作无效。优先级翻转和优先级继承Figure7-4说明了优先级转换的情况。Priorityinversion产生,当一个高优先级任务被强制等待一个不确定时间来让一个低优先级任务是否信号量。考虑场景Figure7-4:t1,t2和t3分别是高、中,低任务。T3通过take相关二进制保护信号量,获取了一些资源。当t1抢占t3,并竞争T3持有的资源时,阻塞。若t1不阻塞,,正常T3完成这个资源,这不会发送抢占问题。然而,低优先级任务对于中优先级任务的抢占也是非常脆弱的,会阻止t3放弃资源。这种情况可能会保存,阻塞T1不确定时间。优先级继承策略互斥信号量有SEM_INVERSION_SAFE选项,使能priority-inheritance策略。优先级继承策略假如高优先级任务会阻塞到持有资源的低优先级任务上。一旦任务的优先级被提高,就会一直保持在高级别上直到任务需要的所有的互斥信号量被释放。因此,inheriting任务用于防止被其它立即优先级任务抢占。这个选项必须混合用于一个优先级队列(SEM_Q_PRIORITY)。注明继承任务在提高的优先级级别上完成指针后,返回到原来优先级就绪队列后面。(关于就绪队列更多信息,参考SchedulingandtheReadyQueue。)Figure7-5中,优先级继承解决了通过提供t3任务优先级到T1在t1阻塞的时间段来解决优先级转换问题。这个保护t3,防止被t1直接抢占,之后被t2抢占。如下例子,创建一个互斥信号量,使用优先级继承策略:semId=semMCreate(SEM_Q_PRIORITY|SEM_INVERSION_SAFE);安全删除互斥的另外一个问题涉及任务删除。在用信号量保护的临界区内,通常期望保护执行的任务不被意外删除。删除一个执行在临界区的任务可能是灾难性的。资源可能处于破坏状态,保护资源的信号量不存在了,有效的防止对资源的所有访问。原语taskSafe()和taskUnsafe()提供一个任务删除的解决方案。然而,互斥信号量提供SEM_DELETE_SAFE选项,每个semTake()使能一个taskSafe()隐式,每个semGive()使能一个taskUnsafe()。这种方式,保护一个持有信号量的任务不会被删除。这个选项比taskSafe()和taskUnsafe()有效。示例代码如下:semId=semMCreate(SEM_Q_FIFO|SEM_DELETE_SAFE);递归资源访问互斥信号量可以递归take。这个方法即一个任务可以多次take一个信号量,在释放之前。递归take用于一组互相调用的函数,每个函数需要访问互斥资源。这是可能的,因为系统要跟踪任务目前持有的互斥信号量数目。在释放之前,take几次,give几次。这由一个计数器跟踪。Example7-2RecursiveUseofaMutual-ExclusionSemaphore/*FunctionArequiresaccesstoaresourcewhichitacquiresbytaking*mySem;*FunctionAmayalsoneedtocallfunctionB,whichalsorequiresmySem:*//*includes*/#include<vxWorks.h>#include<semLib.h>SEM_IDmySem;/*Createamutual-exclusionsemaphore.*/init(){mySem=semMCreate(SEM_Q_PRIORITY);}funcA(){semTake(mySem,WAIT_FOREVER);printf("funcA:Gotmutual-exclusionsemaphore\n");...funcB();...semGive(mySem);printf("funcA:Releasedmutual-exclusionsemaphore\n");}funcB(){semTake(mySem,WAIT_FOREVER);printf("funcB:Gotmutual-exclusionsemaphore\n");...semGive(mySem);printf("funcB:Releasesmutual-exclusionsemaphore\n");}7.6.5Counting信号量Counting信号量是实现任务同步和互斥的另外一个方法。Counting信号量和二进制信号量工作原理一样,除了要跟踪一个信号量give的次数。每give一个信号量一次,count递增;没take信号量一次,count递减。当count达到零时,take时会阻塞。对于二进制信号量,若一个give的信号量,任务阻塞,之后取消阻塞。然而,counting信号量和二进制信号量不一样,若一个信号量give,没有任务阻塞,之后count递增。这意味着,一个无须阻塞情况下,可以give两次,take两次。Table7-3展示了任务take和give一个counting信号量的例子。Counting信号量对于保护多个资源版本比较有用。如使用5个磁盘驱动器使用一个counting计数器,初始化count为5,一个带有256个入口的环形缓存可能用一个初始化count为256的counting信号量来实现。初始化count用semCCreate()函数的一个参数指定。7.6.6Read/Write信号量Read/write信号量为应用提供增强功能,可以有效充分的利用读写访问一个资源的区别。一个read/write信号量可以在读或或者写模式take。也适应于SMP系统(关于SMP的VxWorks配置,参考24.VxWorksSMP)。一个在写模式下持有一个read/write信号量的任务可以独自访问资源。另一方面,一个在读模式下持有一个read/write信号量的任务不能独自访问资源。读模式下多个任务可以take一个read/write信号量,可以访问相同的资源。因为是独占的,写模式仅允许串行访问一个资源,而读模式允许共享或并发访问。在一个多处理器系统中,多个任务(运行在不同的处理器上)可以以真正并发方式读模式下访问一个资源。然而,在一个单处理器系统中访问是共享的,但并发是虚拟的。多个任务可以在相同的时间读模式访问一个资源,但是因为任务不是同时执行的,访问是有效的多路复用的。所有在读模式下持有一个read/write信号量的任务必须在所有任务可以在写模式take前give。读写模式规范一个read/write信号量和其它类型的信号量不同在take时必须指明在那种模式下。这个模式确定了是否访问时独自的(写模式),或者若允许并发访问(读模式)。不同的APIs对应不同的访问模式,如下:·semRTake()对应read模式(并发)·semWTake()对应写模式(独占)你也可以在一个read/write信号量上使用semTake(),行为和semWTake()一样。你在一个read/write信号量上使用semGive(),只要拥有的任务处于相同的模式。关于read/write信号量APIs更多信息,参考Table7-1和VxWorksAPIreferences。当一个任务在写模式下take一个read/write信号量,其行为和一个互斥信号量一样。任务独占拥有信号量。若其它任务尝试give这样一个信号量,则返回值为ERROR。当一个任务在读模式下take一个read/write信号量,其行为和其它信号量不一样。不提供对一个资源的独占访问(不保护临界区),信号量可能被多个任务并发持有。读模式下可以take一个read/write信号量的最多任务数在信号量使用创建函数创建时已经指明。对于所有的read/write信号量,系统默认的最多任务用组件参数SEM_RW_MAX_CONCURRENT_READERS设置。默认是32个。若当信号量创建时没有指明任务数,默认是32个。Read/write信号量可以在读写模式递归take。可选,每种模式下存在优先级继承和安全删除功能。写访问操作的优先级当存在一个read/write信号量时,优先给请求写访问的挂起任务,不管请求读访问的优先级相同任务。也就是说高优先级任务尝试一个semWTake()操作获取信号量,即使在一个更高优先级的任务尝试semRTake()。写访问的优先级帮助确定保护的资源目前保存,因为在一个挂起写操作发生前读操作发生导致没有延迟。然而,注:所有读模式take必须在一个read/write信号量在写模式下take前give。Read/write信号量和系统性能在系统中实现read/write信号量会增强系统的性能,尤其是SMP系统。然而,归因于跟踪多个读模式拥有者的附近记录开销,性能会受到影响。尤其是,UP中的中断延迟和SMP中的内核延迟将会有副作用。7.6.7特别信号量选项统一VxWorks信号量接口包括三个特别选项:超时,排队,使用VxWorks事件。这些选项对于7.6.6Read/WriteSemaphores描述的读写信号量是不存在的,或9.13POSIXSemaphores描述的POSIX兼容信号量。信号量超时当一个信号量不存在时,一个任务一直阻塞,信号量take操作可以设定一段超时事件。若在此期间没有take成功,则返回失败。这个行为有semTake()函数的参数控制,针对read/write信号量的take操作指明了任务挂起等待信号量的tick数。若任务在指定时间内take成功,返回OK。否则,返回ERROR。一个带参数NO_WAIT(0)的semTake(函数,意味着不等待,设置errno为S_objLib_OBJ_UNAVAILABLE。一个带正超时值的semTake(函数,返回S_objLib_OBJ_TIMEOUT。一个超时时间WAIT_FOREVER(-1),表示无限等待。信号量和排队VxWorks信号量包括选择用于排队阻塞在一个信号量上的任务机制的能力。基于两个标准:FIFO,或优先级排序,参考Figure7-6。优先级排序更好的保存了意向的系统优先级结构,但是优先级排序操作产生了很大的系统开销。FIFO排序开销很小,且导致常量性能。在创建信号量时,选择排队类型。使用继承选项的信号量(SEM_INVERSION_SAFE)必须选择优先级排队。信号量和VxWorks事件信号量可以发送VxWorks事件到指定任务,当空闲时。更多信息,参考7.9VxWorksEvents。7.7消息队列现代实时应用由一组独立且协作交互的任务构建。而信号量为任务的同步和加锁提供了高级别机制,通常一个高级别机制必须允许任务间可以相互协同通讯。VxWorks中,单处理器中主要的任务间通讯机制是messagequeues。关于跨越内存空间的多节点socket通讯,参考33.MessageChannels。消息队列允许多个不同的消息,每类消息的长度,被排队。任务和ISR发送消息到一个消息队列,任务从一个消息队列中接收消息。多个任务可以在相同的消息队列中发送和接收消息。两个任务的全双工通讯通常需要两个消息队列,一个方向一个;参考Figure7-7。VxWorks中共有两个消息队列子函数库。第一个,msgQLib,提供VxWorks消息队列,为VxWorks设计;第二个,mqPxLib,和thePOSIXstandard(1003.1b)forreal-timeextensions兼容。参考9.13.1ComparisonofPOSIXandVxWorksSemaphores。7.7.1进程间使用公共消息对象通讯VxWorks消息队列可以创建为私有对象,可以在创建的内存空间中被访问(内核或进程);或作为一个公共对象,可以在整个系统范围内访问。为了创建一个消息队列作为公共对象,必须使用msgQOpen()函数,消息队列名必须以反斜杠开始(如,/myMsgQ)。更详细的信息,参考7.10Inter-ProcessCommunicationWithPublicObjects和VxWorksKernelAPIReference入口msgQOpen。7.7.2消息创建和使用Table7-4中展示了VxWorks消息队列的创建,使用,删除函数。这个库提供了以FIFO排序的消息信息,有个例外,有两个优先级级别,标志位高优先级的消息关联在队列头上。一个使用msgQCreate()创建的消息队列。它的参数指明了消息队列中可以容纳的最多消息数,和每个消息的最多字节数。为消息队列分配足够的内存空间。一个任务或ISR通过调用msgQSend()函数发送一个消息到消息队列。若没有任务等待那个消息队列,消息增加到消息队列缓存中。若有个任务已经在等待那个消息队列,则消息立即传递给第一个等待的任务。一个任务使用msgQReceive()函数接收来自一个消息队列的消息。若消息队列中已经有一个消息,则第一个效率立即出队,返回给调用者。若消息队列中没有消息,则调用任务阻塞,增加到等待消息任务队列中。等待消息任务队列以任务优先级或FIFO排序,在队列创建时,使用参数指明。静态初始化消息队列msgQCreate()函数执行一个动态,二步操作,在运行时为消息队列分配内存,之后初始化对象。消息队列(其它VxWorks对象)也可以静态创建——这意味着在编译时为消息队列分配内存——在运行时初始化对象。关于静态初始化通用信息,参考4.6StaticInstantiationofKernelObjects。关于消息队列静态初始化相关信息,参考4.6.6StaticInstantiationofMessageQueues。消息队列超时msgQSend()和msgQReceive()函数可以设置超时参数。当发送一个消息时,超时时间指明消息到消息缓存要等待的ticks数,若没有空间入队。当接收一个消息时,超时时间指明消息等待消息缓存中存在消息的ticks数,若当前缓存没有消息。超时值规定和信号量一样,NO_WAIT(0)意味着立即返回,WAIT_FOREVER(-1)意味着永久等待。消息队列紧急消息msgQSend()函数允许指明消息的优先级参数:正常(MSG_PRI_NORMAL)和紧急(MSG_PRI_URGENT)。正常的消息增加到消息队列最后,紧急的消息增加到消息队列前面。Example7-3VxWorksMessageQueues/*Inthisexample,taskt1createsthemessagequeueandsendsamessage*totaskt2.Taskt2receivesthemessagefromthequeueandsimply*displaysthemessage.*//*includes*/#include<vxWorks.h>#include<msgQLib.h>/*defines*/#defineMAX_MSGS(10)#defineMAX_MSG_LEN(100)MSG_Q_IDmyMsgQId;task2(void){charmsgBuf[MAX_MSG_LEN];/*getmessagefromqueue;ifnecessarywaituntilmsgisavailable*/if(msgQReceive(myMsgQId,msgBuf,MAX_MSG_LEN,WAIT_FOREVER)==ERROR)return(ERROR);/*displaymessage*/printf("Messagefromtask1:\n%s\n",msgBuf);}#defineMESSAGE"GreetingsfromTask1"task1(void){/*createmessagequeue*/if((myMsgQId=msgQCreate(MAX_MSGS,MAX_MSG_LEN,MSG_Q_PRIORITY))==NULL)return(ERROR);/*sendanormalprioritymessage,blockingifqueueisfull*/if(msgQSend(myMsgQId,MESSAGE,sizeof(MESSAGE),WAIT_FOREVER,MSG_PRI_NORMAL)==ERROR)return(ERROR);}消息队列和排队选项VxWorks消息队列包括为阻塞域一个消息队列的任务选择排队机制的功能。MSG_Q_FIFO和MSG_Q_PRIORITY选项提供用于选择(msgQCreate()和msgQOpen()函数)排队机制,用于挂起于msgQSend()和msgQReceive()的任务。7.7.3展示消息队列属性VxWorks提供show命令产生每一类消息队列的关键消息队列信息。如myMsgQId是一个VxWorks消息队列,输出发送到标准输出设备上,在shell中执行如下:->showmyMsgQIdMessageQueueId:0x3adaf0TaskQueuing:FIFOMessageByteLen:4MessagesMax:30MessagesQueued:14ReceiversBlocked:0Sendtimeouts:0Receivetimeouts:07.7.4使用消息队列的服务器和客户端实时系统通常使用任务的CS模式构建。这种模式下,服务器任务接收客户端的请求,来执行一些复位,通常有个回复。请求和回复通常由任务间消息组成。Vxworks中,消息队列或管道都可以实现这个功能。如,CS通讯模式实现如下Figure7-8。每个服务器任务创建一个消息队列来接收来自客户端的请求。每个客户端创建一个消息队列来接收来之服务器的回复。每个请求消息包含一个客户端回复消息队列的msgQId。一个服务任务的主循环包含从请求消息队列中读消息,执行请求,发送回复给客户端的回复消息队列。相同的体系结构可以用管道完成,或根据特殊要求由其他方式实现。7.7.5消息队列和Vxworks事件消息队列可以发送VxWorks事件到指定任务,当一个消息到达一个没有任务等待的队列时。更多信息,参考VxWorks消息7.9VxWorksEvents。7.8管道管道通过VxWorksI/O系统提供了对消息队列工具的备选接口。管道是虚拟I/O设备,有驱动pipeDrv管理。pipeDevCreate()函数创建了一个管道设备和管道关联的潜在消息队列。这个调用指明了已创建管道的名,消息队列中最多容纳消息数,和每个消息的最大长度:status=pipeDevCreate("/pipe/name",max_msgs,max_length);已创建的管道通常是一个命名I/O设备。任务可以使用标准I/O函数打开,读写管道,和使用ioctl函数。至于和其它I/O设备的交互,若缓存中没有数据,读数据会一直阻塞,直到有数据,若没有缓存,写数据会一直阻塞,直到有缓存。如消息队列,ISRs可以写一个管道,但是不能读一个管道。作为I/O设备,管道提供消息队列不能提供的一个重要功能——使用select()。这个函数运行一个任务等待一组I/O设备。Select()函数和其它异步I/O设备一块工作,如网络套接字和串行设备等。因此,通过使用select(),一个任务可以监控几个管道,套接字和串行设备;参考10.5.9PendingonMultipleFileDescriptorswithselect()。管道允许你实现一个CS模式的任务间通讯;参考7.7.4ServersandClientswithMessageQueues。7.9VxWorks事件VxWorks事件提供了任务间,中断服务程序(ISRs)和任务间,信号量和任务间,消息队列和任务间的通讯和同步方法。事件可以针对任务间和ISR-to-task间同步被用于一个轻量级的二进制信号量备选方案(因为没有对象必须创建)。也可用于信号量存在时通知任务,或消息到达消息队列时,任务通知。事件工具提供了一个针对任务行为协调的机制,最多有32个events可以用于被其它任务,信号量,ISRs,和消息队列发送。一个任务可以等待来之多个源的事件。因此,事件在无需分配额外系统资源的同时可以提供解决复杂任务行为协调的方法。每个任务由32个事件标志,32位字位操作编码(位25到32为风河预留)。这些标志存储在任务的eventregister。注明一个事件标志本身没具体意义。32个事件标志的任一个的重要性取决于当下任务的编码和设置。当下任务接收当下事件的次数没有记录。一旦设置一个标志,相同或不同的发送者再次设置基本是一个不可见操作。事件和异步发送给任务的信号相似;但是区别是异步接收。也就是说,接收任务必须调用一个接收函数,等待事件到来时选择挂起。因此,和信号不一样,事件不需要一个句柄。事件使用的编码方法,参考eventLibAPIreference。注明:VxWorks事件,指本章描述的events,不要和SystemViewer事件混淆。配置VxWorks事件为了提供事件工具,VxWorks必须配置INCLUDE_VXEVENTS组件。7.9.1为接收事件准备一个任务一个任务可以在一个或多个事件上挂起,或简单检查接收的事件,调用eventReceive()函数。这个函数指明了等待的事件,为等待一个或几个事件提供选项。也为随机事件提供不同选项。然而,为了让一个任务接收来自一个信号量或一个消息队列的事件,必须首先用具体的对象注册,信号量对应semEvStart()函数,消息队列对应msgQEvStart()函数。一个任务仅为任何当下信号量或消息队列注册一次。semEvStart()函数识别信号量和当信号量空闲时发送给任务的事件。也为指明是否信号量释放时第一个发送事件或每次;是否是信号量释放注册时发送事件;是否从另外一个任务调用semEvStart()允许生效(注销先前注册的任务)。一旦一个任务使用一个信号量注册,每次信号量使用semGive()释放时,若没有其他任务在这个信号量上挂起,信号量发送事件给注册任务。为了请求信号量停止发送事件,注册任务调用函数semEvStop().。一旦一个任务使用一个消息队列注册,每次消息队列接收到一个消息时,且没有其他任务挂起到这个消息,消息队列发送一个事件给注册任务。为了请求消息队列停止发送事件,注册任务,调用msgQEvStop()函数。7.9.2发送事件到一个任务任务和ISRs可以使用eventSend()函数发送具体事件到一个任务,无论接收任务是否准备好使用它们。信号量和消息对象自动发送消息给任务,但是必须分别用semEvStart()和msgQEvStart()注册。这些对象当空闲时发送事件。对象空闲的条件如下:互斥信号量一个互斥信号量当不在拥有信号量时,且没有任务挂起时被认为是空闲的。如,在调用semGive()之后,若另外一个任务挂起在同一个信号量(semTake()),信号量不会发消息。二进制信号量一个二进制信号量当没有任务拥有它,没有任务等待时为空闲状态。Counting信号量一个counting信号量当count非零,没有任务挂起时为空闲状态。因此,事件不能用于计算一个信号量give或take的次数。消息队列一个消息队列当目前消息队列中有一个消息,且没有任务挂起,被任务是空闲状态。因此,事件不能用于计算发送到消息队列中的消息数。注明:仅因为一个对象已经被释放不意味处于空闲状态。如give一个信号量后,释放;若另外一个任务在释放时等待一个信号量,这不是空闲状态。当两个或更多任务频繁交换一个对象的所有权,因此可能对象永远得不到释放,因此永远也不会发消息。也注明当一个信号量或消息发送事件到一个任务时来提示空闲,这不意味着这个对象以任何方式为任务保存。一个等待事件的任务当资源释放时,一个任务解除挂起状态,但是对象可能在通知和解挂此间take。对象可能被一个高优先级任务抢占若任务接收到挂起在eventReceive()的事件。若一个低优先级任务可能会窃取这个对象:若任务接收到导致其他任务挂起的事件,而不是eventReceive(),一个低优先级任务可能会执行,且在事件发送之后,执行一个semTake(),但是在接收任务解除挂起状态之前。因此,不保证随后任务可能获取到资源的所有权。警告:因为事件不会以任何方式为应用预留,小心使用,避免混淆。注明事件25到32为风河系统预留,不能定制使用。事件和任务删除若一个任务在一个信号量或消息队列发送消息给它之前删除,事件依然被发送,但是没有接收方。默认,VxWorks默认处理这种情况。然而,一个应用创建一个对象用于通知已经发送的事件,没有成功接收的情况是非常有用的。这种情况下,信号量和消息对象创建时使用一个选项,事件接收失败后,导致错误返回(SEM_EVENTSEND_ERROR_NOTIFY和MSG_Q_EVENTSEND_ERROR_NOTIFY)。semGive()和msgQSend()调用,之后当对象空闲时返回ERROR。错误不意味着信号量没有give或消息没有传递。简化表示资源不能发送消息到注册任务。注明发送一个消息或give一个信号量失败优于一个失败事件。7.9.3访问事件标志当事件被发送给任务时,它们存储在任务的事件寄存器中(参考7.9.5TaskEventsRegister),任务本身不直接访问。当调用eventReceive()函数指定事件到来时,任务解除挂起,事件寄存器中的被拷贝到一个任务可访问的变量中。当eventReceive()函数使用EVENTS_WAIT_ANY选项——这意味着任务接收到任何指定事件后,任务解除挂起——检查导致任务解除挂起的事件内容变量。eventReceive()函数也提供允许检查接收事件的类型优于对正在接收的完全设置。7.9.4事件函数Table7-5列出了用于事件工作的函数:关于这些函数的更多信息,参考VxWorksAPIreferencesforeventLib,semEvLib,andmsgQEvLib。7.9.5任务事件注册每个任务由自己的taskeventsregister。任务事件寄存器是一个32位域用于存储来自其它任务,ISRs,信号量,和消息队列的消息。事件25到32(VXEV25or0x01000000toVXEV32or0x80000000)为风河预留,不能定制使用。如上注明的((7.9.3AccessingEventFlags),一个任务不能直接访问事件寄存器内容。Table7-6描述了影响事件寄存器内容的函数。7.9.6展示函数和事件为了利用事件调试系统,taskShow,semShow,和msgQShow库展示了事件信息。taskShow库展示了如下信息:·事件寄存器的内容·期望的事件·调用eventReceive()函数指定的选项semShow和msgQShow库展示如下信息:·任务注册的接收事件·发送到任务的资源·传递给semEvStart()或msgQEvStart()的选项。7.10公共对象用于进程中通讯内核对象如信号量和消息对象可以创建为私有对象或公共对象。这提供了对它们访问的控制范围——作为私有,控制在创建它的虚拟内存上下文,作为公共,整个系统都可以访问。公共对象在整个系统中都是可见的,因此可用于进程间通讯。公共和私有对象的性能没什么差别。一个对象仅在创建的时候指定是共有还是私有对象——之后不能改变。公共对象必须在创建时命名,名称以反斜杠开始;如/foo。私有对象不需要命名。关于本章附加命名任务的信息,参考6.5.2TaskNamesandIDs。创建和命名公共和私有对象公共对象总是被命名,名称必须以一个反斜杠开始。私有对象命不命名无所谓。若命名,名称不能以反斜杠开始。仅当一个当下类的一个公共对象和名称被创建。也就是说,只有一个名称为/foo的公共信号量。但是可能有一个公共命名信号量/foo和公共命名消息队列/foo。显而易见,区分命名最好(如/fooSem和/fooMQ)。系统允许在任何当下内存上下文中仅创建一个当下类的私有对象和名称;也就是说,当下进程和内核中。如:·若进程a创建一个私有信号量命名为bar,不能创建第二个命名为bar的信号量。·然而,进程B可以创建一个命名为bar的私有信号量,尽管其它进程中已经有一个相同名字的信号量。注明私有任务例外——重名在私有任务中允许;参考6.5.2TaskNamesandIDs。为了创建一个命名对象,要使用正确的xyzOpen()API,如semOpen()。当以反斜杠开始命名时,是一个公共对象。为了删除公共对象,xyzDelete()API不能使用(仅用于私有对象)。取而代之的是,xyzClose()和xyzUnlink()API必须依据POSIX标准。也就是说,必须从命名空间解除,之后在最后一个关闭操作中删除对象(如使用semUnlink()和semClose()API针对公共信号量)。同样,必须首先执行所有的关闭操作,之后执行unlink操作,之后对象删除。注明若一个对象使用OM_DELETE_ON_LAST_CLOSE标志创建,会在最后一次关闭操作时自动删除,不管是否unlinked。用于创建公共内核对象的相关API信息,参考VxWorksKernelAPIReference中msgQOpen,semOpen,taskOpen,andtimerOpen入口。7.11对象所有权及资源回收所有的对象(如信号量)属于进程中创建此对象的任务,或一个内核任务。当所有权改变时,如在一个进程创建钩子上,可以使用objOwnerSet()函数。然而,使用受限——新的拥有者必须是一个进程或内核。进程拥有的所有对象,在进程消亡时,所有对象会自动销毁。所有父对象的子对象,在父对象消亡时,所有的子对象会自动销毁。进程可以通过一个通过名称查询对象共享公共对象(使用xyzOpen()函数)。进程间的共享对象仅可以用名称完成。当一个进程中止时,拥有的所有的私有对象删除,不管是否命名。进程中所有引用的公共对象被关闭(执行xyzClose()操作)。因此,在资源回收时所有的公共对象被删除,不管是哪个进程创建的,若没有特别xyzOpen()调用(没有其他进程或内存引用),已经卸载的队列或用OM_DELETE_ON_LAST_CLOSE选项创建的对象。这个规则的例外是任务,当创建的进程消亡时,总是被回收。objShowAll()展示函数用于展示对象间的所有权关系。8信号,ISRs,和看门狗定时器9POSIX工具9.1介绍VxWorks在很多自身内核库中支持扩展POSIX。为了便于应用移植,VxWorks以可选组件方式提供了附加的POSIX接口。在内核中,VxWorks实现了POSIXstandardIEEEStd1003.1(POSIX.1)标准中描述的一些传统的接口,和一些POSIX.1可选功能中的实时接口。关于POSIX标准和设施的详细信息,参考http://www.opengroup.org/和http://www.unix.org/。POSIX和实时系统当VxWorks提供很多POSIX兼容APIs,并不是所有的POSIXAPIs适合嵌入式或实时系统,或整体兼容于VxWorks操作系统体系结构。很少情况下,风河对使用的POSIX功能为实时系统或Vxworks兼容做很小限制。如:内存切换到磁盘不适合在实时系统,VxWorks不提供这个功能。然而,VxWorks提供了POSIX页锁函数来方便移植代码到VxWorks。这个函数不提供其它有用的功能——页已经锁存在VxWorks系统中(参考9.9POSIXPage-LockingInterface)。VxWorks任务在系统范围基础上调度;进程本身不会调度。所以,当POSIX访问函数允许两个值竞争((PTHREAD_SCOPE_SYSTEM和PTHREAD_SCOPE_PROCESS),对于这些函数仅系统范围在VxWorks中实现(参考9.10POSIXThreads和9.12POSIXandVxWorksScheduling)。关于POSIX功能的任何限制在本章标注,或在相关的POSIXAPIs提供更详细的描述。POSIX和VxWorks设施这一章描述了VxWorks和具体VxWorksPOSIX扩展的POSIX支持。另外,比较了固有VxWorks工具和VxWorks有的POSIX设施。用于本章节的VxWorks限定符用于标识固有非POSIXAPIs,也用于和POSIXAPIs比较。如,你可以发现讨论POSIX信号量(9.13.1ComparisonofPOSIXandVxWorksSemaphores)和VxWorks信号量的比较信息,尽管POSIX信号量也在VxWorks中实现。VxWorks扩展对于POSIX也这样标识。注:这一章提供了内核中存在的POSIX设施相关信息。关于RTPs相关设施信息,参考VxWorksApplicationProgrammer’sGuide对应章节。9.2配置VxWorksPOSIX设施VxWorks对于带有很多库(9.3GeneralPOSIXSupport)的内核提供扩展POSIX支持,但是默认VxWorks的默认配置不包含可能使用的POSIX工具。提供私有POSIX库支持的可选VxWorks组件在本章节描述。内核空间的通用POSIX支持由BUNDLE_POSIX组件包提供。若内存要求一个更细粒度配置,个体组件可以用于功能选择。参考独立POSIX功能的配置指导。提醒:内核空间的POSIX支持组件组和用户控制的POSIX支持组件组不一样。关于用户空间的相关组件信息,参考VxWorksApplicationProgrammer'sGuide:POSIXFacilitiesfortheappropriatecomponentbundle9.2.1针对POSIX设备的VxWorks组件Table9-1表提供了必须配置在内核中来支持指定POSIX设施的独立VxWorks组件概述。网络设置在WindRiverNetworkStackProgrammer’sGuide描述。9.3通用POSIX支持VxWorks支持很多POSIX兼容库。如所示;参考对于这些的库的API参考获取更详细信息。如下章节描述了附加固有VxWorksAPIs提供的可选POSIXAPI组件。提醒:风河建议我们不要同时使用提供相似功能的POSIX库和固有VxWorks库。否则,会导致不可预料的结果。如你已经使用了clockLib函数,则不要使用tickLib函数来操作系统的tick计数;不要使用taskLibAPI来改变一个pthreadAPI的POSIX线程的优先级,等。一个POSIX应用可以在运行时来使用如下APIs来确定系统中POSIX支持的状态。·sysconf()函数返回配置系统变量的目前值,允许应用确定是否支持一个可选工具,及系统限制的精确值。·confstr()函数返回和一个系统变量相关的字符串。本版本,函数仅返回系统默认路径的字符串。uname()函数使得应用获取关于系统运行的信息。VxWorks提供的识别信息是系统名(VxWorks),系统的网络名,系统的发布版本号,机器名(BSP型号),体系结构的字节顺序,内核版本号,处理器型号(CPU家族),BSP矫正版本号,和系统编译日期。9.4POSIX头文件POSIX1003.1标准定义了一组头文件,作为应用开发环境的一部分。VxWorks用户端开发环境提供比内核端更多的POSIX头文件,它们的内容和内核头文件的内核比较,和POSIX标准更一致。内核应用开发的POSIX头文件如下Table9-3.所示:9.5POSIX名称空间POSIX命名空间由用户模式RTP应用提供隔离。相关更多信息,参考VxWorksApplicationProgrammer’sGuide:POSIXFacilities。9.6POSIX时钟和定时器VxWorks提供了POSIX1003.1b标准的clock和timer接口。POSIXClocksPOSIX定义了多种软件(虚拟)时钟,标识为时钟,时钟,处理器CPU-time时钟,线程CPU-time时钟。这些时钟都使用系统的硬件timer。实时时钟和单时钟是系统范围内时钟,因此VxWorks内核和进程都支持。进程CPU-time时钟在VxWorks中不支持。线程CPU-time在进程中运行的POSIX线程支持。一个POSIX线程可以针对应用使用实时时钟,单时钟,和一个线程CPU-time时钟。关于线程CPU-time时钟更多信息,参考VxWorksApplicationProgrammer’sGuide:POSIXFacilities。实时时钟可能被复位(仅来之内核)。单时钟不能复位,提供从系统启动,系统运行的时间。实时时钟可以用clock_id参数CLOCK_REALTIME通过POSIX时钟和timer函数访问。一个实时时钟可以在运行时通过在内核中(不是进程中)调用函数clock_settime()复位。单时钟可以通过调用带一个的clock_id参数的CLOCK_MONOTONICclock_gettime()函数调用访问。一个单时钟跟踪系统启动后,系统运行的时钟。一个单时钟不会复位。应用可以依据这个函数计算系统出错停止运行时间。CLOCK_REALTIME和CLOCK_MONOTONIC都定义在time.h中。参考Table9-4POSIX时钟函数列表。过时的指定VxWorksPOSIX扩展时钟函数clock_setres()是为了前向兼容提供。关于时钟函数更多信息,参考clockLib库相关API。为了包含库到系统,配置INCLUDE_POSIX_CLOCKS组件。对于线程CPU-timer,必须包含INCLUDE_POSIX_PTHREAD_SCHEDULER和INCLUDE_POSIX_THREAD_CPUTIME组件。POSIXTimersPOSIXtimer设备为任务本身在未来一段时间后自身产生信号的函数。提供创建,设置,产生一个timer的函数。Timers基于时钟创建。内核中CLOCK_REALTIME和CLOCK_MONOTONIC时钟支持timer。进程中,支持CLOCK_REALTIME时钟,CLOCK_MONOTONIC时钟,threadCPU-timeclocks时钟(包含CLOCK_THREAD_CPUTIME_ID时钟)。当一个timer到期时,默认信号,SIGALRM,发送给任务。当timer到期时,回调被调用,使用sigaction()函数(参考8.2Signals)。参考Table9-5,一系列POSIXtimer函数。VxWorkstimerLib库包含一组具体VxWorksPOSIX扩展:timer_open(),timer_close(),timer_cancel(),timer_connect(),和timer_unlink()。这些函数允许在VxWorks更简单,更有效的使用POSIXtimer。更多信息,参考timerLib库的相关API。注:VxWorks中,一个timer名不是以斜线字符开始(/)的POSIXtimer被任务私有于打开这个timer的进程,不能被其它进程访问。一个用斜线(/)字符开始的timer是一个公共对象,其它进程可以访问(根据POSIX标准)。参考7.10Inter-ProcessCommunicationWithPublicObjects。POSIXnanosleep()函数提供具体秒,和纳秒休眠或延迟时间,对比于VxWorks提供的taskDelay()函数。尽管如此,精确度是一样的,由系统时钟精度确定;仅单位不同。为了在系统中包含timerLib库,配置INCLUDE_POSIX_TIMERS组件。9.7POSIX异步I/OPOSIX异步I/O(AIO)函数由aioPxLib库支持。VxWorksAIO实现满足POSIX1003.1标准规格。更多信息,参考10.8AsynchronousInput/Output。9.8POSIXAdvisory文件锁POSIXadvisory文件锁提供针对POSIX兼容文件的字节范围锁(对于VxWorks,这意味着HRFS文件系统中的文件)。VxWorks实现满足POSIX1003.1标准规格。POSIXadvisoryfilelocking通过fcntl()文件控制函数提供。为了在VxWorks中包含POSIXadvisoryfilelockingfacilities设备,配置INCLUDE_POSIX_ADVISORY_FILE_LOCKING组件。advisoryfilelocking的VxWorks实现涉及和死锁检测相关的一个行为差别,因为VxWorks经常不是调度的。注明这个差别仅体现在一个进程(RTP)内多线程操作情况下。根据POSIX,advisorylocks由一个进程ID标识,当一个进程退出时,所有的advisorylocks销毁,VxWorks也是这样。但是因为VxWorks进程本身不能调度,可能individualadvisoryLocks在当下一个文件中字节范畴上有两个值:线程(任务)实际拥有锁,进程包含线程。另外,在线程基础上,使用一个锁导致另外一个锁死锁,而不是在进程基础上。这意味着若线程请求一个新的锁时可能会阻塞目前正阻塞的其它线程(在当下进程中),若请求的线程持有任何advisorylock,此时要检查是否存在死锁。9.9POSIX页锁接口9.10POSIX线程POSIX线程(也称为pthreads)和VxWorks任务类同,但是有附近功能。VxWorks中pthreads实现在固有任务上面,维持的pthreadsIDs和潜在的任务ID不一样。VxWorks中包含POSIX线程支持的主要原因如下:·为了移植POSIX应用到VxWorks。·充分在实时应用中使用POSIX任务调度(包含并发的调度策略)。关于POSIX线程调度更多信息,参考9.12POSIXandVxWorksScheduling。提醒:POSIX线程APIs在定制系统回调中不能触发——直接由系统调用回调调用或系统调回中函数调用。在系统调用回调中使用POSIX线程API会导致不可预料的结果。更多信息,参考21.CustomSystemCalls。9.10.1POSIX线程属性VxWorks任务和POSIX线程的主要区别是使用的选项和设置方法不同。对于VxWorks任务,这个选项通过任务创建API设置,通常是taskSpawn()。另一方面,POSIX线程,有称为attributes的功能。每个属性包含一组值,一组设置和获取这些值的accessroutines。你要在属性对象pthread_attr_tpthread创建之前,指明所有的线程属性。很少情况下,你可以在线程创建之后修改相关属性值。9.10.2具体VxWorksPthread属性VxWorksPOSIX线程的实现提供两个附加的pthread属性(POSIX扩展)——pthreadname和pthreadoptions——和对应的访问函数。PthreadName当POSIX线程没有命名实体时,VxWorks任务依据基本的名字。默认潜在任务元素被命名为pthrNumber(如pthr3)。名字中的数字,没创建一个新的线程,递增一次(0到2^32-1)。然而,尽可能用如下命名方式命名:·属性名:threadname·可能的值:一个null中止字符串·默认值:none(使用的默认命名策略)·访问函数(具体VxWorksPOSIX扩展):pthread_attr_setname()和pthread_attr_getname()PthreadOptionsPOSIX线程是和任务体系结构相关不可知论的。另一方面,一个VxWorks任务用具体的选项创建是为了更好的兼容体系结构。如,对于Altivec-capablePowerPC体系结构,任务必须用VX_ALTIVEC_TASK选项创建来使用Altivec处理器。Pthread选项根据POSIX线程基于的属性可用于设置VxWorks任务。·属性名:threadoptions·可能的值:和VxWorks任务选项一样,参考taskLib.h·默认值:none(默认任务使用的选项)·访问函数(具体VxWorksPOSIX扩展)pthread_attr_setopt()和pthread_attr_getopt()。9.10.3当创建Pthreads时指定属性如下例子使用默认属性和使用显式属性创建一个线程:9.10.4POSIX线程创建和管理VxWorks提供了很多POSIX线程函数。表列出了一部分和线程创建或执行相关的函数。9.10.5POSIX线程属性访问POSIX属性访问函数如Table9-8描述。具体VxWorksPOSIX扩展函数在9.10.2VxWorks-SpecificPthreadAttributes描述。9.10.6POSIX线程私有数据POSIX线程可以存储和访问私有数据;也就是说,具体线程的数据。它们通过线程库为每一个线程维持一个key,来访问线程的数据。一个key对应关联数据的位置。通过调用pthread_key_create()创建,调用pthread_key_delete()释放。位置通过调用pthread_getspecific()和pthread_setspecific()访问。这个位置代表一个指向数据的指针,不是数据本身,所有对key关联的数据的大小和内容没有限制。Pthread库为内核中所有线程支持最大256个key。pthread_key_create()函数针对一个析构函数有一个选项,当创建线程退出或取消时被调用,若key关联的数据非空。这个析构函数释放数据本身关联的空间,没有key信息。设置一个析构函数非常重要,避免当为线程分配内存时产生内存泄露。Key本身也会释放,通过调用pthread_key_delete()函数,否则key不能用于其它线程库。9.10.7POSIX线程取消POSIX提供了一个cancellation机制,来中止一个线程。有两种类型的中止:deferred和asynchronous。推迟取消导致线程显式检查是否已经取消。这种类型以如下其中一种方式产生:·线程代码间隔调用pthread_testcancel()。·线程调用一个包含acancellationpoint的函数,在此期间线程可能会自动取消。异步取消导致线程执行立即中断,调用一个回调,如一个信号。自动取消点是在一段长时间内阻塞线程执行的库函数。注:当msync(),fcntl(),和tcdrain()函数被授权取POSIX1003.1消点时,本版本不提供。POSIX取消掉在VxWorks库在Table9-9中描述。用于线程取消点的函数如下表Table9-10所示:9.11POSIX线程互斥和条件变量提供的Pthread互斥锁(互斥变量)和条件变量和POSIX1003.1c标准兼容。如POSIX线程,互斥锁和条件变量有关联的attributes。互斥锁属性保存在一个称为pthread_mutexattr_t的数据类型里。包含两个属性,protocol和prioceiling.。用于管理这些属性的信息描述如下。关于这些和其它互斥锁函数相关信息,参考pthreadLib中相关API。9.11.1线程互斥锁直接用于操作一个互斥体对象和互斥体属性对象的函数在Table9-11和Table9-12列出。ProtocolMutexAttributeProtocolMutexAttribute定义了互斥锁变量如何处理优先级变化问题呢(在VxWorks互斥信号量章节描述;参考7.6.4Mutual-ExclusionSemaphores)。属性名:protocol可能的值:PTHREAD_PRIO_NONE,PTHREAD_PRIO_INHERIT和PTHREAD_PRIO_PROTECT访问函数:pthread_mutexattr_getprotocol()和pthread_mutexattr_setprotocol()PTHREAD_PRIO_INHERIT选项是针对内核中创建的线程协议属性的默认值(不像进程中创建的线程,默认是PTHREAD_PRIO_NONE)。THREAD_PRIO_INHERIT值用于创建一个带优先级继承的互斥体——等同于用于semMCreate()函数的SEM_Q_PRIORITY和SEM_INVERSION_SAFE选项。一个线程拥有一个带PTHREAD_PRIO_INHERIT值的互斥体变量,继承之任何等于互斥体和执行在这个优先级上的任何高优先级线程,直到是否互斥体,此时返回原来的优先级。因为不期望提高一个低优先级线程到高于既定优先级的优先级,POSIX定义优先级限定值,描述如下。用priorityprotection创建的互斥变量使用PTHREAD_PRIO_PROTECT值。PriorityCeilingMutexAttribute属性是针对用设置为PTHREAD_PRIO_PROTECT的protocol属性创建的互斥体变量的POSIX优先级阀值。·属性名:prioceiling·可能值:0到255之间任何值·访问函数:pthread_mutexattr_getprioceiling()和pthread_mutexattr_setprioceiling()·动态访问函数:pthread_mutex_getprioceiling()和pthread_mutex_setprioceiling()注明POSIX优先级排序机制和VxWorks机制相反。更多信息,参考9.12.2POSIXandVxWorksPriorityNumbering。当如下情况下定义优先级ceiling:·任何想获取一个互斥体的线程,它的优先级高于ceiling,不能获取互斥体。·任何优先级低于ceiling值的线程在持有互斥体期间提高优先级到ceiling值。·当互斥体释放时,线程的优先级恢复到先前值。9.11.2条件变量一个线程条件变量对应于允许线程同步的一个对象,当一个变量值表示一个事件或状态时。这是一个更复杂的同步类型和仅信号量允许的同步。主要优势是考虑被动等待(和主动等待和轮询相反)变量值变化。条件变量可以和互斥体混合使用(每个条件变量一个互斥体)。操作在一个条件变量和条件变量属性对象的函数在Table9-11和Table9-12列出:9.12POSIX和VxWorks调度VxWorks可以配置为传统的(固有)VxWorks调度或一个POSIX线程调度。不能用于进程调度。VxWorks中仅有的调度实体是任务和线程。一个POSIX线程调度的VxWorks实现是传统VxWorks调度的增强功能,提供了在进程中运行的额外线程调度设备。无论哪种调度方式,VxWorks任务和线程共享一个单一优先级范围和相同的全局调度机制。然而,在进程中使用POSIX线程调度可能有独立的(并发)的调度策略。注明VxWorks必须配置POSIX线程调度来在进程中运行线程。注:风河推荐在相同的应用中不要同时使用POSIXAPI和VxWorksAPIs。否则会导致POSIX应用非兼容。Table9-15提供了任务和线程是如何调度工作的,在内核和进程中。关键区别如下:·POSIX线程调度提供了进程中的POSIX线程调度支持。·其它情况下,POSIX线程调度和VxWorks调度方式一样(在优先级处理上有微小区别,参考DifferencesinRe-QueuingPthreadsandTasksWithLoweredPriorities)·传统的VxWorks调度不能在进程中使用Pthread调度。事实上,pthread在进程中不能启动除非VxWorks配置了POSIX线程调度。Table9-15提供的信息在随后部分详细讨论。9.12.1POSIX和VxWorks调度区别通常,POSIX调度模型和VxWorks系统的调度区别在如下方式——无论系统是否配置风河POSIX线程调度或传统的VxWorks调度:POSIX支持一个两级调度模式,包括contentionscope的概念,线程调用可以应用在系统范围或异常进程基础上。另一方面,VxWorks中,RTPs不能自身调度,任务和线程在系统范围基础上调度(内核和进程)。POSIX在process-by-process和thread-by-thread基础上应用调度策略。VxWorks在系统范围基础上应用调度策略。这意味着所有的任务和线程使用一个抢占优先级模式或一个轮询模式。这个规则的仅有例外是执行在进程中的线程可以使用并发(独立)调度策略,包含零星调度(注明POSIX线程调度必须用于这种情况)。POSIX支持schedulingallocationdomain的概念;也就是说,进程或线程和处理器的关联。因为VxWorks不支持多处理器硬件,所以VxWorks只有一个域,所有的任务和线程和这个域关联。VxWorks不支持POSIX线程并发功能,如所有线程调度一样。POSIX线程并发API仅提供应用移植,但是没有用。9.12.2POSIX和VxWorks优先级排序POSIX优先级排序机制和VxWorks优先级排序机制相反。POSIX中,数字越大,优先级越高。VxWorks中,数字越小,优先级越高,0是最高优先级。优先级数字用于POSIX调度库,和所有其它使用和报告的VxWorks组件不匹配。你可以通过设置全局变量posixPriorityNumbering为FALSE.,来改变默认POSIX优先级排序模式。若你这样做,schedPxLib使用VxWorks排序模式,这个优先级排序兼容其它使用的VxWorks组件。在如下部分,讨论相同优先级的线程和任务,指功能类同的优先级级别,不是优先级数字。9.12.3默认调度策略所有的VxWorks任务和线程依据系统范围内默认调度策略调度。这个规则的唯一例外是运行在用户模式(进程)的线程。这种情况下,并发调度策略和应用于线程的默认系统调度策略不同。注明仅当VxWorks配置POSIX线程调度的情况下线程可以允许在进程中;若VxWorks配置为传统的调度,则线程不能运行在进程中。针对VxWorks的系统范围默认调度策略——无论采用哪种调度策略,优先基于抢占调度——对应于POSIXSCHED_FIFO调度策略。运行时激活的系统范围调度策略可以通过kernelTimeSlice()函数改变为轮询调度。也可以改变回来通过调用带参数为0的kernelTimeSlice()函数。VxWorks改变为轮询调度对应SCHED_RR策略。kernelTimeSlice()函数不能再用户模式调用(也就是说一个进程中)。传递一个非零参数,影响所有非零内核和用户任务,所有内核线程,所有使用SCHED_OTHER策略的用户线程。任何使用SCHED_RR策略运行的用户线程在调用中不会生效;但是在使用新定义的时间片后,开始生效。9.12.4VxWorks传统调度VxWorks传统调度可以用于内核任务和线程。不能用于进程中的线程。若VxWorks配置为传统调度,在进程中调用pthread_create()会失败,错误号设置为ENOSYS。传统的VxWorks调度像任务一样调度线程。所有的任务和线程在一个系统中执行,所以使用目前默认的调度策略(基于优先级抢占策略或轮询调度策略,参考9.12.3DefaultSchedulingPolicy),和不能应用于个体线程的并发策略。关于传统调度和任务工作原理的信息,参考6.4TaskScheduling。传统VxWorks调度提供的调度选项和POSIX选项类同。如下线程调度策略对应传统的VxWorks调度策略:·SCHED_FIFO和VxWorks基于优先级抢占调度类同。区别是若低优先级情况下在准备就绪队列中的任务或线程位置不同;参考CaveatsAboutSchedulingBehaviorwiththePOSIXThreadScheduler。·SCHED_RR对应VxWorks轮询机制。·SCHED_OTHER对应目前系统范围默认调度策略。SCHED_OTHER策略是VxWorks中针对线程的默认策略。没有VxWorks传统调度策略对应SCHED_SPORADIC。配置VxWorks为传统调度VxWorks默认配置为传统调度。这个调度由INCLUDE_VX_TRADITIONAL_SCHEDULER组件提供。VxWorks传统调度调度行为说明并发调度策略在内核线程中不支持,必须小心使用线程scheduling-inheritance和schedulingpolicy属性。若scheduling-inheritance属性设置为PTHREAD_EXPLICIT_SCHED和schedulingpolicy设置为SCHED_FIFO或SCHED_RR,且这个策略和目前系统范围默认调度策略不匹配,线程创建失败。风河推荐总是使用PTHREAD_INHERIT_SCHED作为scheduling-inheritance属性。这种情况下目前的VxWorks调度策略应用,和使用父线程优先级。否则,若线程必须用和不同父线程优先级的优先级启动,scheduling-inheritance属性应该设置为PTHREAD_EXPLICIT_SCHED和schedulingpolicy属性设置为SCHED_OTHER(这对应于目前系统范围默认调度策略)。为了充分利用POSIX调度模型,VxWorks必须配置POSIX线程调度,运行在进程中的线程有问题。参考9.12.5POSIXThreadsScheduler。9.12.5POSIX线程调度POSIX线程调用可以用于调度VxWorks系统中的线程和任务。注明POSIX线程调度的目的是为运行在进程中的线程提供POSIX线程调度。若不需求在进程中支持POSIX线程调度支持,则不需要在系统中使用这种调度方式(仅内核系统;带进程内核系统,但是没有线程)。POSIX线程调度要求线程在进程中运行,为线程调度提供POSIX1003.1兼容(包括并发调度策略)。若VxWorks没有配置POSIX线程调度,线程不能再进程中创建。内核中调度POSIX线程调度调度内核任务和内核线程的方式和传统VxWorks任务调度方式一样。6.4TaskScheduling,VxWorks传统调度和任务工作原理,和POSIX调度策略对应的传统VxWorks调度策略相关信息,参考9.12.4VxWorksTraditionalScheduler。进程中调度当VxWorks配置POSIX线程调度,执行在进程中的任务根据系统范围内调度策略调度。另一方面,执行在进程中的线程根据POSIX1003.1调度策略指派给每个线程的策略调度,也可以依据标准动态修改。调度策略如下:·SCHED_FIFO是一个抢占优先级调度策略。对于当下优先级水平,使用这个策略的线程调度和具有相同任务级别的任务调度一样。存在微小差别是若低优先级时候(参考DifferencesinRe-QueuingPthreadsandTasksWithLoweredPriorities)。·SCHED_RR是一个优先级轮询调度策略。针对当下优先级水平,所有使用这个策略的线程在放弃CPU之前给予相同的执行时间。·SCHED_SPORADIC是用于非周期性行为的调度策略,保证使用这个策略的线程周期性服务高优先级的线程,低优先级任务在其它时间服务。·SCHED_OTHER对应VxWorks目前使用的调度策略,抢占优先级策略或轮询。使用这个策略的线程服从系统的全局调度策略,如VxWork任务或内核线程。注明如下关于SCHED_SPORADIC策略的VxWorks实现系统周期性时钟用于时间计数。不支持动态改变SCHED_SPORADIC调度策略;然而支持动态改变SCHED_SPORADIC策略到其它策略。VxWorks不针对SS_REPL_MAX宏的补充事件最大数设上限。默认40个事件设置给线程属性结构的sched_ss_max_repl域,要改变。配置VxWorksPOSIX线程调度配置VxWorksPOSIX线程调度,增加INCLUDE_POSIX_PTHREAD_SCHEDULER组件到内核。注明INCLUDE_POSIX_PTHREAD_SCHEDULER组件仅提供SCHED_FIFO,SCHED_RR,和SCHED_OTHER调度策略。对于SCHED_SPORADIC调度策略,必须增加INCLUDE_PX_SCHED_SPORADIC_POLICY组件。包BUNDLE_RTP_POSIX_PSE52包括INCLUDE_PX_SCHED_SPORADIC_POLICY和INCLUDE_POSIX_PTHREAD_SCHEDULER组件。配置POSIX_PTHREAD_RR_TIMESLICE参数可能用于为线程配置使用SCHED_RR策略的默认轮询时间片间隔。为了在运行时修改时间片间隔,调用kernelTimeSlice()函数,带有不同时间片值。新的时间片值仅影响kernelTimeSlice()调用之后创建的线程。注明:INCLUDE_POSIX_PTHREAD_SCHEDULER组件是一个独立组件。不依赖任何POSIX组件。POSIX线程调度必须显式增加INCLUDE_POSIX_PTHREAD_SCHEDULER组件或BUNDLE_RTP_POSIX_PSE52包。POSIX线程调度组件是独立的,因为它的目的是仅用于进程中线程;仅使用线程的内核系统,不需要改变默认VxWorks传统调度。POSIX线程调度调度行为说明使用POSIX线程调度涉及一些复杂性要在设计系统中考虑在内。小心如下:·使用轮询和基于优先级抢占调度策略。·使用SCHED_OTHER独立策略运行线程。·低优先级时,任务和线程在准备就绪队列中的位置不同。·POSIX应用的前向兼容问题设计用于VxWorks传统调度。使用轮询和基于优先级抢占策略针对相同优先级级别的任务和线程使用混合轮询和基于优先级抢占策略可能会导致任务或线程针对使用轮询策略的运行实体为产生CPU饥饿状态。当VxWorks使用轮询机制运行作为系统默认模式,若线程以相同的优先级使用并发(独立)SCHED_FIFO策略,则任务可能不能得到期望的时间片运行。这是因为其中一个线程可能会控制CPU,导致任务处于饥饿状态。若抢权者线程被抢占,仍保持在已就绪优先级列表的前面(作为POSIX授权),持续的统治CPU,直到对应优先级级别可以再次运行。使用SCHED_RR或SCHED_OTHER策略对于使用轮询策略的任务都存在这样的缺陷。同样,当VxWorks使用抢占模式允许作为默认,任务会导致使用相同优先级级别的线程处于饥饿状态,若后者使用并把(独立)SCHED_RR策略。使用SCHED_OTHER并发策略运行线程使用并发(独立)SCHED_OTHER策略的创建的线程使用系统范围默认调度策略,这意味着:·若系统默认是目前基于优先级的抢占调度,SCHED_OTHER线程使用抢占策略运行。·若系统默认是目前轮询机制,SCHED_OTHER线程使用轮询策略运行。当从基于优先级抢占模式改变默认系统策略为轮询模式(或相反)为使用SCHED_OTHER创建的线程改变有效的调度模式;对于使用SCHED_RR或SCHED_FIFO.创建的线程无效。使用低优先级线程和任务重新排队的区别POSIX线程调度重定向低优先级线程在准备就绪队列中的位置和低优先级任务不同。区别如下:·当一个低优先级线程——使用pthread_setschedprio()函数——POSIX线程调度会把这个线程放在相对优先级队列前面。·当低优先级任务——使用taskPrioritySet()函数——POSIX线程调度为把对应优先级任务放在就绪队列后面,传统的VxWorks调度也会这么做。这意味着降低一个任务和一个线程的优先级会对运行产生不同的影响(若就绪队列中有相同优先级的其它任务或线程)。如一个任务和一个线程每次降低他们的优先级来作用相同的级别,线程在队列前面,任务在队列后面。线程会在任何其它这个级别上的线程或任务之前运行,任务在之后运行。注明:风河推荐不要再同一个应用中同时使用POSIXAPI和VxWorksAPI。否则会导致POSIX应用不兼容。关于就绪队列的更多信息,参考SchedulingandtheReadyQueue。应用的后向兼容问题使用POSIX线程调度改变了POSIX应用的行为,对于用传统VxWorks调度编写的应用。对于存在的要求后向兼容的POSIX应用,所有线程的调度策略可能改变为CHED_OTHER。这会导致它们的策略默认激活VxWorks调度策略(在POSIX线程调度介绍之前是这种情况)。9.12.6POSIX调度函数POSIX1003.1b调度函数用schedPxLib库提供,针对VxWorks在Table9-16描述。关于这些函数的更多信息,参考schedPxLibAPI引用。注:schedPxLib提供针对VxWorks5.x和VxWorks6.x早期版本的一些调度函数是POSIX不兼容的,仅为内核中后向兼容维护。这些函数已经弃用:sched_setparam(),sched_getparam(),sched_setscheduler(),和sched_getscheduler()。固有的VxWorks函数taskPrioritySet()和taskPriorityGet()应用于任务优先级操作。POSIX函数pthread_setschedparam()和pthread_getschedparam()用于线程优先级操作。关于改变默认系统调度策略的信息,参考9.12.3DefaultSchedulingPolicy。关于并发调度策略信息,参考9.12.5POSIXThreadsScheduler。注明POSIX优先级排序机制和VxWorks相反。更多信息,参考9.12.2POSIXandVxWorksPriorityNumbering。在系统中包含schedPxLib库,配置INCLUDE_POSIX_SCHED组件。9.12.7获取调度参数:优先级限制和时间片函数sched_get_priority_max()和sched_get_priority_min()返回可能的POSIX优先级的最大值和最小值。若使能轮询调度,你可以使用sched_rr_get_interval()来确定目前时间片间隔。这个函数使用一个指向timespec结构的参数(在time.h中定义),写每个时间片的秒和纳秒数来确定这个结构的元素。9.13POSIX信号量POSIX定义了named和unnamed信号量,具有相同的属性,接口有很小差别。POSIX信号量库针对命名和未命名的信号量提供了创建,打开,销毁函数。当打开一个命名信号量时,你指派一个特征命名,被其它命名信号量函数当做一个参数。semPxLib库提供其它函数,如Table9-17。在系统中包含POSIXsemPxLib库信号量函数,配置INCLUDE_POSIX_SEM组件。VxWorks也提供了semPxLibInit(),一个非POSIX(仅内核)函数,初始化内核的POSIX信号量库。当内核增加INCLUDE_POSIX_SEM组件时,默认在系统启动时调用。9.13.1POSIX和VxWorks信号量的比较POSIX信号量是counting信号量;也就是说,保持跟踪调用的次数。VxWorks信号量机制和POSIX信号量机制类似,除了VxWorks信号量提供附加功能:·优先级继承·任务删除安全·单任务多长使用一个信号量的功能·互斥信号量所有权·排队机制选项当这些功能很重要时,VxWorks信号量优于POSIX信号量(更多信息,参考6.TasksandMultitasking)。POSIX属性wait(orlock)和post(orunlock)分别对应VxWorks的take和give。POSIX的locking,unlocking和得到信号量值函数用于命名和未命名信号量。函数sem_init()和sem_destroy()仅用于未命名信号量的初始化和销毁。sem_destroy()调用中止一个未命名信号量和释放所有关联的资源。函数sem_open(),sem_unlink(),和sem_close()仅用于命名信号量的打开和关闭。针对命名信号量混合使用sem_close()和sem_unlink()和针对未命名信号量使用sem_destroy()有同样的效果。也就是说,这个函数中止信号量和释放关联的内存。警告:当删除信号量时,尤其是互斥信号量,避免在其它任务正在使用的情况下删除。不要删除一个信号量除非删除任务第一次对信号量加锁。通用对于命名信号量,仅当其它任务打开信号量的时候,才关闭信号量。9.13.2使用未命名信号量当使用未命名信号量时,通常一个任务为信号量分配内存,并初始化。一个信号量用数据结构sem_t表示,定义在semaphore.h。信号量初始化函数,sem_init(),指定初始化值。一旦信号量初始化完成后,任何任务可以通过sem_wait()(blocking)orsem_trywait()(non-blocking),andunlockingitwithsem_post()使用信号量。信号量用于同步和互斥。因此,当一个信号量用于同步时,通常初始化为0(锁)。等待同步的任务阻塞在sem_wait()。任务完成同步后通过sem_post()释放信号量。若被信号量阻塞的任务是仅有等待那个信号量的任务,任务解锁后,开始运行。若其他任务也被信号量阻塞,高优先级的任务被解锁。当一个信号量用于互斥时,通常初始化为一个大于0的值,意味着资源存在。因此,第一个任务非阻塞加锁信号量,设置信号量为0(加锁)。随后任务将会阻塞直到信号量释放。对于先前的场景,高优先级的任务释放信号量,解除阻塞。当用于用户模式应用中,未命名信号量仅可以被属于执行应用进程的任务访问。注明:VxWorks中,一个不是以斜线(/)字符开始的POSIX信号量被认为私有于打开它的进程,不能被其它进程访问。一个以斜线(/)字符开始的信号量是一个公共对象,其它进程可以访问(根据POSIX标准)。参考7.10Inter-ProcessCommunicationWithPublicObjects。Example9-6POSIXUnnamedSemaphores/**Thisexampleusesunnamedsemaphorestosynchronizeanactionbetweenthe*callingtaskandataskthatitspawns(tSyncTask).Torunfromtheshell,*spawnasatask:**->spunnameSem*//*includes*/#include<vxWorks.h>#include<semaphore.h>/*forwarddeclarations*/voidsyncTask(sem_t*pSem);/*************************************************************************unnameSem-testcaseforunamedsemaphores**Thisroutinetestsunamedsemaphores.**RETURNS:N/A**ERRNOS:N/A*/voidunnameSem(void){sem_t*pSem;/*reservememoryforsemaphore*/pSem=(sem_t*)malloc(sizeof(sem_t));if(pSem==NULL){printf("pSemallocationfailed\n");return;}/*initializesemaphoretounavailable*/if(sem_init(pSem,0,0)==-1){printf("unnameSem:sem_initfailed\n");free((char*)pSem);return;}/*createsynctask*/printf("unnameSem:spawningtask...\n");if(taskSpawn("tSyncTask",90,0,2000,syncTask,pSem)==ERROR){printf("FailedtospawntSyncTask\n");sem_destroy(pSem);free((char*)pSem);return;}/*dosomethingusefultosynchronizewithsyncTask*//*unlocksem*/printf("unnameSem:postingsemaphore-synchronizingaction\n");if(sem_post(pSem)==-1){printf("unnameSem:postingsemaphorefailed\n");sem_destroy(pSem);free((char*)pSem);return;}/*alldone-destroysemaphore*/if(sem_destroy(pSem)==-1){printf("unnameSem:sem_destroyfailed\n");return;}free((char*)pSem);}voidsyncTask(sem_t*pSem){/*waitforsynchronizationfromunnameSem*/if(sem_wait(pSem)==-1){printf("syncTask:sem_waitfailed\n");return;}elseprintf("syncTask:semlocked;doingsync’edaction...\n");/*dosomethingusefulhere*/}9.13.3使用命名信号量sem_open()函数打开一个已经存在的信号量或可选创建一个新的信号量。你可以指明如下标志值:O_CREAT若不存在,创建信号量。若存在,fail或打开信号量,依据是否指定O_EXCL标志。O_EXCL若新创建的信号量,打开信号量;若信号量存在,返回fail。基于标志和使用信号量已经存在的结果,如Table9-18所示。一旦初始化后,一个信号量保持可用直到销毁。任务可以在任何时间销毁一个信号量,但是系统仅在没有任务打开信号量的情况下,销毁信号量。若VxWorks配置,你在shelll中可以使用show()来展示一个POSIX信号量的信息。这个例子展示了关于POSIX信号量mySem的信息,有两个任务阻塞,等待这个信号量。->showsemIdvalue=0=0x0Semaphorename:mySemsem_open()count:3Semaphorevalue:0No.ofblockedtasks:2注明show()使用信号量ID作为参数。对于一组任务使用一个命名信号量,其中一个任务第一次创建和初始化了信号量,通过sem_open()调用函数,使用O_CREAT参数。任何使用此信号量的任务,都必须通过调用sem_open()函数,不带O_CREAT参数,打开信号量后,才能使用。任何打开信号量的任务可以通过调用sem_wait()(blocking)orsem_trywait()(non-blocking)使用此信号量,之后当使用完信号量后通过调用sem_post()函数解锁信号量。移除一个信号量,所有使用信号量的任务必须使用sem_close()关闭信号量,其中一个任务必须unlink。使用sem_unlink()unlink一个信号量从命名表中删除信号量。名字从名字表中移除后,目前打开该信号量的任务依然可以正常使用,但是新的任务不能再次打开该信号量。若任务尝试打开信号量,不带O_CREAT标志,操作失败。一个unlinked的信号量当最后一个任务关闭后,被系统删除。注:POSIX命名信号量可以在进程间共享仅当信号量名称以斜线(/)字符开始。否则私有于创建它的进程,其它进程不能访问,参考7.10Inter-ProcessCommunicationWithPublicObjects。Example9-7POSIXNamedSemaphores/**Inthisexample,nameSem()createsataskforsynchronization.The*newtask,tSyncSemTask,blocksonthesemaphorecreatedinnameSem().*Oncethesynchronizationtakesplace,bothtasksclosethesemaphore,*andnameSem()unlinksit.Torunthistaskfromtheshell,spawn*nameSemasatask:*->spnameSem,"myTest"*//*includes*/#include<vxWorks.h>#include<taskLib.h>#include<stdio.h>#include<semaphore.h>#include<fcntl.h>/*forwarddeclaration*/voidsyncSemTask(char*name);/******************************************************************************nameSem-testprogramforPOSIXsemaphores**Thisroutineopensanamedsemaphoreandspawnsatask,tSyncSemTask,which*waitsonthenamedsemaphore.**RETURNS:N/A**ERRNO:N/A*/voidnameSem(char*name){sem_t*semId;/*createanamedsemaphore,initializeto0*/printf("nameSem:creatingsemaphore\n");if((semId=sem_open(name,O_CREAT,0,0))==(sem_t*)-1){printf("nameSem:sem_openfailed\n");return;}printf("nameSem:spawningsynctask\n");if(taskSpawn("tSyncSemTask",90,0,4000,(FUNCPTR)syncSemTask,(int)name,0,0,0,0,0,0,0,0,0)==ERROR){printf("nameSem:unabletospawntSyncSemTask\n");sem_close(semId);return;}/*dosomethingusefultosynchronizewithsyncSemTask*//*givesemaphore*/printf("nameSem:postingsemaphore-synchronizingaction\n");if(sem_post(semId)==-1){printf("nameSem:sem_postfailed\n");sem_close(semId);return;}/*alldone*/if(sem_close(semId)==-1){printf("nameSem:sem_closefailed\n");return;}if(sem_unlink(name)==-1){printf("nameSem:sem_unlinkfailed\n");return;}printf("nameSem:closedandunlinkedsemaphore\n");}/******************************************************************************syncSemTask-waitsonanamedPOSIXsemaphore**ThisroutinewaitsonthenamedsemaphorecreatedbynameSem().**RETURNS:N/A**ERRNO:N/A*/voidsyncSemTask(char*name){sem_t*semId;/*opensemaphore*/printf("syncSemTask:openingsemaphore\n");if((semId=sem_open(name,0))==(sem_t*)-1){printf("syncSemTask:sem_openfailed\n");return;}/*blockwaitingforsynchronizationfromnameSem*/printf("syncSemTask:attemptingtotakesemaphore...\n");if(sem_wait(semId)==-1){printf("syncSemTask:takingsemfailed\n");return;}printf("syncSemTask:hassemaphore,doingsync'edaction...\n");/*dosomethingusefulhere*/if(sem_close(semId)==-1){printf("syncSemTask:sem_closefailed\n");return;}}9.14POSIX消息队列POSIX消息队列函数,由mqPxLib,提供,如Table9-19所示。注明内核和用户空间的mq_open()版本有些区别。内核版本允许为用oflags参数指明的任何权限创建一个消息队列。用户空间版本服从POSIXPSE52原型,所以在第一次调用后,若一个等同的或更低的权限被指定,则任何随后相同的进程中的调用时允许的。关于用户空间mq_open()版本的使用权限信息,参考VxWorksApplicationProgrammer’sGuide:POSIXFacilities。VxWorks初始化函数mqPxLibInit()初始化内核的POSIX消息队列库(这是一个仅用于内核的函数)。当配置INCLUDE_POSIX_MQ组件后,系统在启动时刻自动调用。关于VxWorks消息队列库信息,参考msgQLibAPI。9.14.1POSIX和VxWorks消息队列的比较POSIX消息队列和VxWorks消息队列类似,除了POSIX消息队列提供带优先级的消息。不同之处汇总如下Table9-20。9.14.2POSIX消息队列属性一个POSIX消息队列有如下属性:·一个可选的O_NONBLOCK标志,若消息队列为空,防止mq_receive()阻塞调用。·消息队列中消息最大数·消息最大大小·目前队列中消息数任务可以使用mq_setattr()设置或清除O_NONBLOCK标志。使用mq_getattr()得到所有属性值。(如POSIX允许的,消息队列的这个实现充分使用一定数量不公开的内部标志数。)Example9-8SettingandGettingMessageQueueAttributes/**ThisexamplesetstheO_NONBLOCKflagandexaminesmessagequeue*attributes.*//*includes*/#include<vxWorks.h>#include<mqueue.h>#include<fcntl.h>#include<errno.h>/*defines*/#defineMSG_SIZE16intattrEx(char*name){mqd_tmqPXId;/*mqdescriptor*/structmq_attrattr;/*queueattributestructure*/structmq_attroldAttr;/*oldqueueattributes*/charbuffer[MSG_SIZE];intprio;/*createreadwritequeuethatisblocking*/attr.mq_flags=0;attr.mq_maxmsg=1;attr.mq_msgsize=16;if((mqPXId=mq_open(name,O_CREAT|O_RDWR,0,&attr))==(mqd_t)-1)return(ERROR);elseprintf("mq_openwithnon-blocksucceeded\n");/*changeattributesonqueue-turnonnon-blocking*/attr.mq_flags=O_NONBLOCK;if(mq_setattr(mqPXId,&attr,&oldAttr)==-1)return(ERROR);else{/*paranoiacheck-oldAttrshouldnotincludenon-blocking.*/if(oldAttr.mq_flags&O_NONBLOCK)return(ERROR);elseprintf("mq_setattrturningonnon-blockingsucceeded\n");}/*tryreceiving-therearenomessagesbutthisshouldn'tblock*/if(mq_receive(mqPXId,buffer,MSG_SIZE,&prio)==-1){if(errno!=EAGAIN)return(ERROR);elseprintf("mq_receivewithnon-blockingdidn’tblockonemptyqueue\n");}elsereturn(ERROR);/*usemq_getattrtoverifysuccess*/if(mq_getattr(mqPXId,&oldAttr)==-1)return(ERROR);else{/*testthatwegotthevalueswethinkweshould*/if(!(oldAttr.mq_flags&O_NONBLOCK)||(oldAttr.mq_curmsgs!=0))return(ERROR);elseprintf("queueattributesare:\n\tblockingis%s\n\tmessagesizeis:%d\n\tmaxmessagesinqueue:%d\n\tno.ofcurrentmsgsinqueue:%d\n",oldAttr.mq_flags&O_NONBLOCK?"on":"off",oldAttr.mq_msgsize,oldAttr.mq_maxmsg,oldAttr.mq_curmsgs);}/*cleanup-closeandunlinkmq*/if(mq_unlink(name)==-1)return(ERROR);if(mq_close(mqPXId)==-1)return(ERROR);return(OK);}9.14.3显示消息队列属性函数用于显示关于POSIX消息队列的信息。注明如下:->mq_open("mymq4",0x4201,0)value=8380448=0x7fe020->mqPxShow0x7fe020Messagequeuename:mymq49POSIXFacilities9.14POSIXMessageQueues207No.ofmessagesinqueue:0Maximumno.ofmessages:16Maximummessagesize:16Flags:O_WRONLYO_NONBLOCK(0x4001)9.14.4通过消息队列通讯在一组任务通过一个POSIX消息队列通讯之前,其中一个任务必须通过调用mq_open(),使用O_CREAT参数创建消息队列。一旦消息队列创建后,其它任务可以通过队列名在队列上发送和接收消息。仅第一个任务用标志打开队列;随后任务可以打开队列接收消息(O_RDONLY),仅发送((O_WRONLY),或发送和接收(O_RDWR)。mq_send()使用发送消息到队列。当队列满时,若一个任务想发送消息到队列,任务阻塞直到其它任务从队列中读一个消息,产生空间。为了避免mq_send()函数阻塞,当打开消息队列时,设置O_NONBLOCK标志。那种情况下,当队列为满时,mq_send()函数返回-1,设置errno为EAGAIN代替挂起,允许再次尝试或采取其它措施。mq_send()函数的其中一个参数指明一个消息优先级。优先级范围从0(最低)到31(最高)。当一个任务使用mq_receive()接收一个消息时,任务接收目前队列中优先级高的消息。若队列中所有消息优先级一样,则按照FIFO处理。若队列是空的,任务阻塞直到队列中接收到一个消息。为了避免挂起在mq_receive()函数上。用O_NONBLOCK参数,打开消息队列;那种情况下,当一个任务尝试读一个空队列时,mq_receive()函数返回-1,设置errno为EAGAIN。为了关闭一个消息队列,调用mq_close()。关闭队列不是销毁队列,仅验证任务不再使用队列。为了请求销毁队列,调用mq_unlink()。Unlinking一个消息队列不是立即销毁队列,为了预防其它任务操作队列,从队列名称表中移除队列名。正在使用此队列的任务依然正常使用。当最后一个任务关闭一个unlinked队列后,队列被销毁。注:VxWorks中,一个POSIX消息队列,若名称不是以斜线(/)字符开始,则被认为私有于打开它的进程,其它进程不能访问。若名称是以斜线(/)字符开始,则是一个公共对象,其它进程可以访问它(根据POSIX标准)。参考7.10Inter-ProcessCommunicationWithPublicObjects。Example9-9POSIXMessageQueues/**Inthisexample,themqExInit()routinespawnstwotasksthat*communicateusingthemessagequeue.*Torunthistestcaseonthetargetshell:**->spmqExInit*//*mqEx.h-messageexampleheader*//*defines*/#defineMQ_NAME"exampleMessageQueue"/*forwarddeclarations*/voidreceiveTask(void);voidsendTask(void);/*testMQ.c-exampleusingPOSIXmessagequeues*//*includes*/#include<vxWorks.h>#include<taskLib.h>#include<stdio.h>#include<mqueue.h>#include<fcntl.h>#include<errno.h>#include<mqEx.h>/*defines*/#defineHI_PRIO31#defineMSG_SIZE16#defineMSG"greetings"/******************************************************************************mqExInit-mainformessagequeuesendandreceivetestcase**Thisroutinespawnstotaskstoperformthemessagequeuesendandreceive*testcase.**RETURNS:OK,orERROR**ERRNOS:N/A*/intmqExInit(void){/*createtwotasks*/if(taskSpawn("tRcvTask",151,0,4000,(FUNCPTR)receiveTask,0,0,0,0,0,0,0,0,0,0)==ERROR){printf("taskSpawnoftRcvTaskfailed\n");return(ERROR);}if(taskSpawn("tSndTask",152,0,4000,(FUNCPTR)sendTask,0,0,0,0,0,0,0,0,0,0)==ERROR){printf("taskSpawnoftSendTaskfailed\n");return(ERROR);}return(OK);}/******************************************************************************receiveTask-receivemessagesfromthemessagequeue**Thisroutinecreatesamessagequeueandcallsmq_receive()towaitfor*amessagearrivinginthemessagequeue.**RETURNS:OK,orERROR**ERRNOS:N/A*/voidreceiveTask(void){mqd_tmqPXId;/*msgqueuedescriptor*/charmsg[MSG_SIZE];/*msgbuffer*/intprio;/*priorityofmessage*//*openmessagequeueusingdefaultattributes*/if((mqPXId=mq_open(MQ_NAME,O_RDWR|O_CREAT,0,NULL))==(mqd_t)-1){printf("receiveTask:mq_openfailed\n");return;}/*tryreadingfromqueue*/if(mq_receive(mqPXId,msg,MSG_SIZE,&prio)==-1){printf("receiveTask:mq_receivefailed\n");return;}else{printf("receiveTask:Msgofpriority%dreceived:\n\t\t%s\n",prio,msg);}}/******************************************************************************sendTask-sendamessagetoamessagequeue**Thisroutineopensanalreadycreatedmessagequeueand*callsmq_send()tosendamessagetotheopenedmessagequeue.**RETURNS:OK,orERROR**ERRNOS:N/A*/voidsendTask(void){mqd_tmqPXId;/*msgqueuedescriptor*//*openmsgqueue;shouldalreadyexistwithdefaultattributes*/if((mqPXId=mq_open(MQ_NAME,O_RDWR,0,NULL))==(mqd_t)-1){printf("sendTask:mq_openfailed\n");return;}/*trywritingtoqueue*/if(mq_send(mqPXId,MSG,sizeof(MSG),HI_PRIO)==-1){printf("sendTask:mq_sendfailed\n");return;}elseprintf("sendTask:mq_sendsucceeded\n");}9.14.5消息到来的通知机制一个线程(或任务)可以使用函数在一空队列上请求一个消息到来的通知机制。因此避免线程阻塞或轮询等待一个消息。每个队列仅可在一个线程上注册通知机制。一旦一个队列有一个通知线程,不用尝试用mq_notify()注册可以成功直到通知请求满足或取消。一旦一个队列发送通知到一个线程,通知请求满足,队列和指定的线程没有其他特殊的关系;也就是说,队列为每一个mq_notify()请求发送一个通知信号。为了安排一个具体的线程持续的接收通知信号;最好的方法是从相同的接收通知信号回调中调用mq_notify()函数。为了取消一个通知请求,指定NULL取代一个通知信号。仅目前注册的线程可以取消他的通知请求。mq_notify()机制不能发送通知:·当附件信息到达一个非空队列。也就是说,当一个消息到达一个空队列时,通知才会发送。·若另外一个线程阻塞在mq_receive().函数上。·回复调用函数mq_notify()后。也就是说每次mq_notify()调用只有一个通知。Example9-10MessageQueueNotification/**Inthisexample,ataskusesmq_notify()todiscoverwhenamessage*hasarrivedonapreviouslyemptyqueue.Torunthisfromtheshell:**->ld<mq_notify_test.o*->spexMqNotify,"greetings"*->mq_send**//*includes*/#include<vxWorks.h>#include<signal.h>#include<mqueue.h>#include<fcntl.h>#include<errno.h>#include<stdio.h>#include<string.h>/*defines*/#defineQNAM"PxQ1"#defineMSG_SIZE64/*limitonmessagesizes*//*forwarddeclarations*/staticvoidexNotificationHandle(int,siginfo_t*,void*);staticvoidexMqRead(mqd_t);/*****************************************************************************exMqNotify-exampleofhowtousemq_notify()**Thisroutineillustratestheuseofmq_notify()torequestnotification*viasignalofnewmessagesinaqueue.Tosimplifytheexample,a*singletaskbothsendsandreceivesamessage.**RETURNS:0onsuccess,or-1**ERRNOS:N/A*/intexMqNotify(char*pMessage,/*textformessagetoself*/intloopCnt/*numberoftimestosendamsg*/){structmq_attrattr;/*queueattributestructure*/structsigeventsigNotify;/*toattachnotification*/structsigactionmySigAction;/*toattachsignalhandler*/mqd_texMqId;/*idofmessagequeue*/intcnt=0;/*Minorsanitycheck;avoidexceedingmsgbuffer*/if(MSG_SIZE<=strlen(pMessage)){printf("exMqNotify:messagetoolong\n");return(-1);}/**Installsignalhandlerforthenotifysignalandfillin*asigactionstructureandpassittosigaction().Becausethehandler*needsthesiginfostructureasanargument,theSA_SIGINFOflagis*setinsa_flags.*/mySigAction.sa_sigaction=exNotificationHandle;mySigAction.sa_flags=SA_SIGINFO;sigemptyset(&mySigAction.sa_mask);if(sigaction(SIGUSR1,&mySigAction,NULL)==-1){printf("sigactionfailed\n");return(-1);}/**Createamessagequeue-fillinamq_attrstructurewiththe*sizeandno.ofmessagesrequired,andpassittomq_open().*/attr.mq_flags=0;attr.mq_maxmsg=2;attr.mq_msgsize=MSG_SIZE;if((exMqId=mq_open(QNAM,O_CREAT|O_RDWR|O_NONBLOCK,0,&attr))==(mqd_t)-1){printf("mq_openfailed\n");return(-1);}/**Setupnotification:fillinasigeventstructureandpassit*tomq_notify().ThequeueIDispassedasanargumenttothe*signalhandler.*/sigNotify.sigev_signo=SIGUSR1;sigNotify.sigev_notify=SIGEV_SIGNAL;sigNotify.sigev_value.sival_int=(int)exMqId;if(mq_notify(exMqId,&sigNotify)==-1){printf("mq_notifyfailed\n");return(-1);}/**Wejustcreatedthemessagequeue,butitmaynotbeempty;*ahigher-prioritytaskmayhaveplacedamessagetherewhile*wewererequestingnotification.mq_notify()doesnothingif*messagesarealreadyinthequeue;thereforewetryto*retrieveanymessagesalreadyinthequeue.*/exMqRead(exMqId);/**Nowweknowthequeueisempty,sowewillreceiveasignal*thenexttimeamessagearrives.**Wesendamessage,whichcausesthenotifyhandlertobeinvoked.*Itisalittlesillytohavethetaskthatgetsthenotification*betheonethatputsthemessagesonthequeue,butwedoithere*tosimplifytheexample.Arealapplicationwoulddootherwork*insteadatthispoint.*/if(mq_send(exMqId,pMessage,1+strlen(pMessage),0)==-1){printf("mq_sendfailed\n");}/*Cleanup*/if(mq_close(exMqId)==-1){printf("mq_closefailed\n");return(-1);}/*Morecleanup*/if(mq_unlink(QNAM)==-1){printf("mq_unlinkfailed\n");return(-1);}return(0);}/*****************************************************************************exNotificationHandle-handlertoreadinmessages**Thisroutineisasignalhandler;itreadsinmessagesfroma*messagequeue.**RETURNS:N/A**ERRNOS:N/A*/staticvoidexNotificationHandle(intsig,/*signalnumber*/siginfo_t*pInfo,/*signalinformation*/void*pSigContext/*unused(requiredbyposix)*/){structsigeventsigNotify;mqd_texMqId;/*GettheIDofthemessagequeueoutofthesiginfostructure.*/exMqId=(mqd_t)pInfo->si_value.sival_int;/**Requestnotificationagain;itresetseachtime*anotificationsignalgoesout.*/sigNotify.sigev_signo=pInfo->si_signo;sigNotify.sigev_value=pInfo->si_value;sigNotify.sigev_notify=SIGEV_SIGNAL;if(mq_notify(exMqId,&sigNotify)==-1){printf("mq_notifyfailed\n");return;}/*Readinthemessages*/exMqRead(exMqId);}/*****************************************************************************exMqRead-readinmessages**Thissmallutilityroutinereceivesanddisplaysallmessages*currentlyinaPOSIXmessagequeue;assumesqueuehasO_NONBLOCK.**RETURNS:N/A**ERRNOS:N/A*/staticvoidexMqRead(mqd_texMqId){charmsg[MSG_SIZE];intprio;/**Readinthemessages-usesalooptoreadinthemessages*becauseanotificationissentONLYwhenamessageissenton*anEMPTYmessagequeue.Therecouldbemultiplemsgsif,for*example,ahigher-prioritytaskwassendingthem.Becausethe*messagequeuewasopenedwiththeO_NONBLOCKflag,eventually*thisloopexitswitherrnosettoEAGAIN(meaningwedidan*mq_receive()onanemptymessagequeue).*/while(mq_receive(exMqId,msg,MSG_SIZE,&prio)!=-1){printf("exMqRead:mqId(0x%x)receivedmessage:%s\n",exMqId,msg);}if(errno!=EAGAIN){printf("mq_receive:errno=%d\n",errno);}}9.15POSIX信号VxWorks提供POSIX信号函数,和内核中BSD兼容函数和固有VxWorks函数一样。关于这些函数更多信息,参考8.2Signals。9.16POSIX内存管理VxWorks内核提供了POSIX内存管理支持,支持calloc(),malloc(),realloc(),和free()。相关更多信息,参考15.7KernelHeapandMemoryPartitionManagement。内核也提供了如下POSIX内存锁函数:mlock(),munlock(),mlockall(),和munlockall()。然而,VxWorks中的内存映射通常是驻留内存。这为映射文件确保了确定的内存访问,但是也意味着映射的物理内存不是连续的,直到解除映射。因此,这些POSIX内存锁函数什么也不做,用于方便应用移植。关于POSIX内存管理工具进程相关信息,参考VxWorksApplicationProgrammer’sGuide:POSIXFacilities。10I/O系统10.1介绍VxWorksI/O系统设计用于引进一种针对不同设备的简化,通用,设备独立的接口。包括:·面向字符型设备,如终端或通讯线路·随机访问块设备,如磁盘·虚拟设备如任务间管道和socket·监控和控制设备,如数字和模拟I/O设备·可以访问远程设备的网络设备VxWorksI/O系统为基本和缓存I/O提供了标准的C库。基本的I/O库是UNIX兼容的;缓存I/O库是ANSIC兼容的。关于Vxworks设备的更多信息,参考11.Devices。注:本章提供了VxWorks内核中存在的设备信息。关于实时处理的信息,参考VxWorksApplicationProgrammer’sGuide中的对应章节。10.2关于VxworksI/O系统内部,VxWorksI/O系统具备独特的设计,和大多数I/O系统相比更快速、更灵活。在实时系统中,这些都是很重要的属性。表Figure10-1说明了VxWorksI/O系统中不同元素间的关系。所有这些元素在本章讨论,除了文件系统函数(在12.LocalFileSystems讨论),和网络元素(WindRiverNetworkStackProgrammer’sGuide)。注:图中虚线表示某些文件系统要求XBD,如HRFS和dosFs要求XBD,而ROMFS有自己独立的驱动接口。参考11.8ExtendedBlockDeviceFacility:XBD。VxWorks和主系统I/O间的区别大多数的VxWorks中I/O使用和UNIX和WindowsI/O使用时完全源代码兼容的。■FileDescriptors在VxWorks中,文件描述符在内核和每一个进程中是唯一的——和UNIX和WINDOW一样。内核和每一个进程有自己的文件描述符空间,各不相同。当进程创建后,它的文件描述符空间通过负责创建者的文件描述符初始化。(这个应用仅当创建者是一个进程时。若创建者是一个内核任务,仅有三个标准I/O描述符被复制,0,1,2。)此后,所有打开,关闭,或复制行为仅影响进程的描述符空间。在内核和每一个进程中,文件描述符全局可见,意味着可以被运行在进程上的任何任务访问。然而,在内核中,标准输入,标准输出(0,1,和2),标准错误是具体与任务的。文件描述符的更多信息,参考10.5.1FileDescriptors,和10.5.3StandardI/ORedirection。■I/OControl传递给函数ioctl()的参数,UNIX和VwWork可能不同。■DeviceConfigurationVxWorks中,设备驱动可以动态安装和删除。但是仅在内核空间。■DeviceDriverRoutinesUNIX下,设备驱动执行在系统模式,不能抢占。Vxworks下,驱动程序可以被抢占,因为他们执行在任务上下文中。10.3配置VxworksI/O设备配置VxWorksI/O设备的主要组件如下:■INCLUDE_IO_BASIC—providesbasicI/Ofunctionality.■INCLUDE_IO_FILE_SYSTEM—providesfilesystemsupport.■INCLUDE_POSIX_DIRLIB—providesPOSIXdirectoryutilities.■INCLUDE_IO_REMOVABLE—providessupportforremovablefilesystems.■INCLUDE_IO_POSIX—ProvidesPOSIXI/Osupport.■INCLUDE_IO_RTP—providesI/OsupportforRTPs.■INCLUDE_IO_MISC—miscellaneousIOfunctionsthatarenolongerreferencedbutareprovidedforbackwardscompatibility.INCLUDE_IO_SYSTEM组件提供是为了前向兼容。包括了上面列出的所有组件。提供其它功能的组件,将会在本章描述。10.4文件、设备、驱动VxWorks中,应用通过打开文件名files访问I/O设备。一个files可以指如下两种事务:一个没有结构化的raw设备如一个串行通讯通道或一个内部任务管道。一个结构体,随机访问设备上的logicalfile,包含一个文件系统。考虑如下命名文件:/usr/myfile/pipe/mypipe/tyCo/0第一个指称为/usr的一个磁盘上的一个文件myfile。第二个是一个命名管道(根据命名规范,管道名以/pipe开始)。第三个指一个物理串行通道。然而,I/O可以用同样的方式完成这些设备的操作。在Vxworks中,它们统称为files,即使它们是不同的物理对象。设备通过devicedrivers处理。通常,使用I/O系统,不需要深入理解设备和驱动的实现。注,然而,VxWorksI/O系统给予驱动相当大的灵活性来处理每个具体的设备。驱动遵守用户视角,但是每一个都各不相同。尽管所有的I/O都直接针对命名文件,但是完成操作需要两个不同水平:basic和basic。两种方法在数据缓存方式和调用类型上不同。这两种方式将会在随后章节介绍。文件名和默认设备具体的文件名是一个字符串。一个未结构化的设备通过设备名命名。如文件系统设备,设备名在一个文件名后面。因此,/tyCo/0名字可能命名一个特殊的串行I/O通道,名字DEV1:/file1表示文件file1在设备DEV1:上。当一个I/O调用中,使用一个具体的文件名,I/O系统查找一个和一个初始化的文件名子字符串匹配的设备名。I/O函数因此定位设备。如没有找到匹配的设备,则I/O函数定位一个defaultdevice。你可以设置这个默认设备为系统中的任何设备,即使没有设备,会返回一个错误。你可以通过使用ioDefPathGet()得到目前的默认路径。你可以使用ioDefPathSet()设置默认目前默认路径。非块设备当系统初始化时,设备增加到I/O系统时,命名。块设备当初始化使用一个具体的文件系统时,被命名。VxWorksI/O系统不对设备名增加任何限制,除了查找匹配设备和文件名过程中。为设备和文件名选择命名规范是很有用的:大多数设备名用(/)开始,除了非NFS网络设备,和VxWorks和dosFs文件系统设备。注:为了识别一个虚拟根文件系统,设备名必须开始于一个正斜线,且不包含任何斜线字符。更多信息,参考12.3VirtualRootFileSystem:VRFS。根据命名规范,基于NFS网络设备用开始于一个斜线的名字挂载。如:/usr非NFS网络设备用远程机器名加一个冒号:host:名称的剩余部分是远程系统上远程目录中的文件名。使用dosFs的文件系统设备通常大写字母的设备名加冒号:DEV1:注:dosFs设备上的文件名和目录名通常用(\)分离。这也可以替换使用正斜杠(/)。提醒:因为设备名通过I/O系统使用简单字符串匹配识别,一个斜线((/或\))不应该单独用于一个设备名,也不应该用于一个设备名本身的一部分。10.5基本I/O10.5.1文件描述符在基本I/O层次上,文件通过一个afiledescriptor引用。一个文件描述符是由调用open()或creat()函数返回的短整型值。其它基本I/O调用使用一个文件描述符作为一个指明一个文件的参数。文件描述符不是全局的。内核有自己的一组文件描述符,每个进程(RTP)有自己的一组。内核中的任务,或一个具体进程中的共享文件描述符。仅有共享的文件描述符可以跨越这些边界,当一个进程是另外一个进程的子进程时,或一个内核的子进程时(内核任务创建的进程仅发起任务的标准I/O文件描述符0,1,2)。如:若任务A和任务B运行在进程foo中,分别在文件描述符7上执行一个写操作,会写到相同的文件(和设备)。若进程bar独立于进程foo启动(不是foo的子),它的任务X,和Y往文件描述符7上执行写操作,它们会写到不同的文件。若进程foobar通过进程too启动(是foo的子),它的任务M和N分别在文件描述符7上执行一个写操作,会写到相同的文件里。然而,仅当文件没有关闭的前提下。若关闭,随后打开的文件描述符,会操作在不同的文件。当一个文件打开,一个文件描述符分配并返回。当文件关闭,文件描述符释放。文件描述符表内核中存在的文件描述符数量用宏NUM_FILES定义。这个明确了文件描述符表的大小,控制同时可以打开多少个文件描述符。默认是50个,可以根据系统需要改变。为了避免使用越界的文件描述符,如创建错误,应用应该关闭不使用的文件描述符。内核中文件描述符表大小可以通过编程方式改变。rtpIoTableSizeGet()函数读文件描述符表大小,rtpIoTableSizeSet()函数改变。注明这两个函数仅用于内核和进程中(I/O系统对待内核为一个特别的进程)。调用实体——是否内核或进程——通过指明rtpIoTableSizeSet()函数的第一个参数为0,第二个参数为文件描述符表的大小。注明只能增加。10.5.2标准Input,标准output,和标准错误三个文件描述符有特殊的意义:■0isusedforstandardinput(stdin).■1isusedforstandardoutput(stdout).■2isusedforstandarderroroutput(stderr).所有的任务读它们的标准输入——如getchar()——从文件描述符0。同样,文件描述符1用于标准输出——如printf()。文件描述符2用于输出错误信息。使用这些描述符,一旦重定向文件和描述符后,你可以为多个任务操作输入和输出。这个标准的文件描述符用于使得任务和模块独立于它们实际的I/O指派。若一个模块发送输出到一个标准输出(文件描述符1),它的输出可以被重定向到任何文件或设备,无需通知这个模块。VxWorks允许两级重定向。第一,针对三个标准文件描述符的全局指派。第二,个别任务可以覆盖全局描述符的指派,仅限于本任务使用。10.5.3标准I/O重定向当Vxworks初始化后,全局标准I/O文件描述符stdin(0),stdin(1)和stdin(2),被默认设置为系统控制台设备文件描述符,通常是串行tty设备。每个内核任务默认使用这些全局标准I/O文件描述符。因此,任何标准I/O操作如调用printf()和getchar()使用全局标准i/O描述符。编制的I/O可以重定向,然而,或者在私有任务级别,或内核全局级别。一个特别任务的标准I/O可以通过ioTaskStdSet()函数改变。这个函数的参数是改变的任务的任务ID(0表示调用任务本身),要重定向的标准文件描述符,要定向的文件描述符。如,一个任务可以调用如下函数,重定向标准输出到fileFd文件描述符。ioTaskStdSet(0,1,fileFd);第三个参数可以是任何打开的有效文件描述符。若是一个文件系统文件,所有任务的随后的标准输出,如printf(),写入那个文件里面。复位任务标准I/O到全局标准I/O,第三个参数应该是0,1,2。全局标准I/O文件描述符也可以改变默认配置,这会影响所有的内核任务,除了由原来全局改变为使用具体任务标准I/O文件描述符的任务。全局标准I/O文件描述符通过调用ioGlobalStdSet()函数改变。这个函数的参数是要重定向的标准I/O文件描述符和要定向的文件描述符。如:ioGlobalStdSet(1,newFd);这个调用设置了全局标准输出到newFd,是一个有效的任何打开的文件描述符。所有没有自身标准输出定向的任务会受到影响,随后所有的标准I/O输出经过newFd。目前的全局的和任何任务任务标准I/O可以通过调用ioGlobalStdGet()和ioTaskStdGet()确定。更多信息,参考这些函数的API。标准I/O重定向的问题要小心在任务中使用任务标准I/O重定向,要保证不会造成数据破坏。在任意任务的标准I/O文件描述符关闭前,可以通过调用ioTaskStdSet()用新的描述符代替。若一个任务的标准I/O使用ioTaskStdSet()设置,文件描述符号飙车在任务的内存中。某些情况下,文件描述符可能关闭,被其它任务释放或被其它任务打开。一旦释放,可能被重新使用,打开连接到不同的文件。原来的任务依然把这个文件描述符作为标准I/O描述符使用,数据破坏是不可避免的。作为一个例子,考虑从一个telnet或rlogin连接发起一个任务。任务继承了网络连接任务的标准I/O文件描述符。若连接退出,网络连接任务的标准I/O文件描述符被关闭。然而,发起的任务依然把这些文件描述符作为任务标准I/O描述符。若关闭的描述符通过调用open()被重新循环使用,导致数据破坏,会给系统造成严重的后果。为了防止这种情况出现,所有发起的任务必须在网络连接中断之前重定向它们的标准I/O描述符。如下例子说明了这个场景,通过shell命令重定向一个发起任务的标准I/O到全局标准I/O文件描述,在登录前。taskspawn()调用是简化表示。->taskSpawn"someTask",......Taskspawned:id=0x52a010,name=t4value=5414928=0x52a010->ioTaskStdSet0x52a010,0,0value=0=0x0->ioTaskStdSet0x52a010,1,1value=0=0x0->ioTaskStdSet0x52a010,2,2value=0=0x0->logout下一个任务说明了任务标准I/O重定向到其它文件描述符。->taskSpawn"someTask",......Taskspawned:id=0x52a010,name=t4value=5414928=0x52a010->ioTaskStdSet0x52a010,0,someOtherFdxvalue=0=0x0->ioTaskStdSet0x52a010,1,someOtherFdyvalue=0=0x0->ioTaskStdSet0x52a010,2,someOtherFdzvalue=0=0x0->logout10.5.4Open和Close10.5.5Create和Remove10.5.6Read和Write10.5.7FileTruncation有时候很方便的丢失文件中的部分数据。针对写打开一个文件后,你可以使用ftruncate()函数截断文件为具体大小。这个函数的参数是一个文件描述符和期望的文件长度字节数。status=ftruncate(fd,length);若成功截断文件,返回OK。若文件描述符指向的设备不能截断,文件返回一个错误,错误号为EINVAL。若指定的大小大于文件大小,结果取决于文件系统,dosFS和HRFS,将文件大小扩展为指定大小;然而,其它文件系统,返回错误。ftruncate()函数是POSIX1003.1b标准的一部分。完全由HRFS支持,然而,部分兼容dosFs:创建和修改次数不发生变化。注明HRFS的seek位置不会被这个函数修改,但是dosFs的seek位置会被这个函数设置到文件尾。10.5.8I/O控制10.5.9PendingonMultipleFileDescriptorswithselect()10.5.10POSIX文件系统函数POSIXfsPxLib库针对不同的文件操作提供了I/O和文件系统函数。这些函数描述在表Table10-4中。Table10-4FileSystemRoutinesRoutineDescriptionunlink()Unlinkafile.link()Linkafile.fsync()Synchronizeafile.fdatasync()Synchronizethedataofafile.rename()Changethenameofafile.fpathconf()Determinethecurrentvalueofaconfigurablelimit.pathconf()Determinethecurrentvalueofaconfigurablelimit.access()Determineaccessibilityofafile.chmod()Changethepermissionmodeofafile.fcntl()Performcontrolfunctionsoveropenfiles.10.6标准I/OVxWorks提供了一个标准I/O包(stdio.h),完全支持ANSIC,兼容UNIX和WindowsstandardI/O包。10.6.1配置VxWorks标准I/O对于Vxworks内核,大多数传统的标准I/O函数由一个VxWorks组件提供,小的子程序由其他组件提供。这种模块化的方法允许定制组件,最大化提高系统性能——如printf(),sprintf(),和sscanf()——通过只包含这些组件来提供这些函数。如下组件提供了标准I/O函数:INCLUDE_ANSI_STDIO提供ansiStdio库,包含大多数A’SI标准I/O函数,除了printf(),sprintf(),和sscanf()。INCLUDE_ANSI_STDIO_EXTRA提供VxWorksANSI标准I/O扩展标准,在ansiStdio.。INCLUDE_FORMATTED_OUT_BASIC提供fioBaseLib库,包含printf(),sprintf(),和一些相关函数。INCLUDE_FORMATTED_IO提供fioLib库,包含sscanf()和一些相关函数。fioBaseLib和fioLib库中的函数不使用标准I/O库ansiStdio提供的缓存I/O设备。因此可以使用它们,即使ansiStdio库没有包含在VxWorks配置中。10.6.2关于printf(),sprintf(),scanf()VxWorks用ANSI标准实现函数printf(),sprintf(),和sscanf()。注明如下:printf()函数不是缓存的。用这种方式实现,fioBaseLib库不需要缓存工具的开销。缓存的优势对于printf().函数来讲无关紧要。需要缓存类型printf()-style输出的应用可以调用fprintf()到stdout。而sscanf()函数由INCLUDE_FORMATTED_IO组件提供,scanf()函数由INCLUDE_ANSI_STDIO组件提供。为了使用scanf(),必须包含INCLUDE_ANSI_STDIO组件提供ansiStdio库。10.6.3关于标准I/O和缓存当应用执行很多小的读写操作时,带缓存I/O函数的使用比不带缓存I/O函数使用更具优势。(更多信息,参考10.5BasicI/O。)。尽管VxWorksI/O系统是高效的,但是和每一个低水平(basicI/O)调用会带来一定的开销。首先,I/O系统必须从设备独立的用户调用(read(),write(),等)到具体驱动的函数调度。第二,大多数驱动使用一个互斥机制或队列机制来防止多个用户的并发请求。这个开始非常小,因为VxWorks原语非常快。然而,一个应用一次从一个文件处理一个字符,这样会带来很大的开销。如:n=read(fd,&char,1);为了使得这种I/O类型更高效和灵活,标准I/O设施使用一个缓存机制,进行读写操作。这个缓存对于用户是透明的;由标准I/O程序和宏自动处理。为了使用标准I/O访问一个文件,用fopen()打开open()代替,如下:fp=fopen("/usr/foo","r");返回值,是指向一个打开文件和关联缓存的指针。一个文件指针实际上指向一个FILE类型的结构(FILE*)。通过对比,低水平的I/O函数通过一个文件描述符识别一个文件,是一个短整型。事实上,文件指针指向的FILE结构包含潜在打开文件的文件描述符。一个已经打开的文件描述符可以通过调用fdopen()和一个缓存FILE关联。如下:fp=fdopen(fd,"r");用fopen()打开一个文件后,通过fread()读数据,通过getc()一次获取一个字符,通过fwrite()写数据,或通过putc()一次写一个字符。当fclose()调用时,FILE结构释放。写入文件或提取文件的函数和宏极其高效。通过直接移动指针访问缓存,如用户读写数据一样。暂停调用低级别的读写函数,仅当读缓存空,或写缓存满时。警告:标准I/O缓存和指针是任务private的。内部没有信号量或其他互斥机制,因为为了提供一个高效的私有缓存机制。因此,多任务不能在相同的stdioFILEpointer同时执行I/O。注:VxWorks内核printf()实现是一个ANSI标准,非缓存的。这个实现考虑了VxWorks小型机配置,仍然提供了关键的标准I/O函数。缓存的性能优势和printf()函数无关。10.6.4关于标准Input,标准Output,和标准错误如10.5BasicI/O讨论的,有三个特殊的文件描述符(0,1,2)为标准Input,标准输出,标准错误保留。当任务使用标准文件描述符,stdin,stdout,和stderr,缓存I/O到表中文件描述符时,三个对象的stdioFILE缓存被自动创建。每个使用标准I/O文件描述符的任务由自己的stdioFILE缓存。当任务退出时,stdioFILE缓存被释放。10.7其它格式化I/O这一部分描述了额外的格式化I/O设备。10.7.1串行I/O轮询模式输出:kprintf()kprintf()函数以和printf()函数相似的方式执行格式化输出,除了输出字符以轮询方式被发送到目标的串口上。这个函数的设计初衷是用于调试工具。也可以在内核启动顺序过程中(在中断使能前或I/O系统初始化完成之后)和ISRs中使用,通过调用printf()函数输出调试信息到目标控制台。更多信息,参考APIkprintf()。由组件INCLUDE_DEBUG_KPRINTF提供。kputs()函数提供为格式化字符输出功能。更多信息,参考相关API。由组件INCLUDE_DEBUG_KPUTS提供。可选INCLUDE_DEBUG_KWRITE_USER组件用于实现输出到存储媒介,参考WritingtoUser-DefinedStorageMediaWithkprintf()andkputs()。WritingtoUser-DefinedStorageMediaWithkprintf()andkputs()kprintf()和kputs()函数可以用于写用户自定义存储媒介。如:#include<vxWorks.h>#include<string.h>#include<sysLib.h>STATUSusrRtn(char*buffer,size_tlen){staticunsignedintoffset;char*destAddress;destAddress=sysMemTop()+offset;bcopy(buffer,destAddress,len);offset=offset+len+1;returnOK;}为了充分使用这些函数,执行如下步骤:1增加代码到任何目录下C文件。2创建VIP镜像,基于合适的BSP。3增加c文件到工程。4增加INCLUDE_DEBUG_KPRINTF和INCLUDE_DEBUG_KWRITE_USER组件。5设置配置参数,如下:DEBUG_KWRITE_USR_RTNusrRtnUSER_RESERVED_MEMsomeSize6编译工程,启动Vxworks。当调用kprintf()和kputs()时,输出到用户自定义内存。消息被一条接一条写,从sysMemTop()开始。消息可以从sysMemTop()开始内存读。10.7.2附加的格式化I/O函数fioLib和fioBaseLib库提供了额外的格式化函数,但是未缓存输出和扫描函数(非ANSI)。如,printErr()函数模拟printf()函数,输出格式化信息到标准错误描述符(2)。fdprintf()函数输出格式化字符串到指定文件描述符。10.7.3信息日志另外一个高级别I/O工具由logLib库提供,允许格式化信息被记录,不用在任务上下文处理。信息的格式和参数被发送到日志任务的消息队列上,后格式化,输出信息。这在中断处理函数中非常有用,不会造成I/O延迟(会占用很多栈空间)。使用logInit()或logFdSet().将信息显示在控制台上。logLib工具由INCLUDE_LOGLIB组件提供。10.8异步Input/OutputAsynchronousInput/Output(AIO)是和正常的内部处理并发执行输入和输出操作的功能。AIO使得当逻辑独立时,解耦IO操作和特殊任务的行为。VxworkAIO实现满足了POSIX1003.1b标准规格。AIO的优点是提高处理效率:允许无论资源是否存在,I/O操作发生,而不是等待独立操作的完成产生的随机事件。AIO消除了正常的同步I/O导致的不必要的任务块;这减少了输入输出和内部处理间的资源竞争,并加快了吞吐量。增加INCLUDE_POSIX_AIO和INCLUDE_POSIX_AIO_SYSDRV组件,包括AIO到Vxworks配置。第二个配置常量使能辅助AIO驱动,要求目前所有Vxworks设备上进行异步I/O。10.8.1POSIXAIO函数10.8.2AIO控制块10.8.3使用AIO11设备11.1概述VxWorksI/O系统允许不同的设备驱动处理七个基本I/O函数。VxWorks提供了串行设备(伪和伪终端),管道,一个针对直接访问内存的伪I/O设备,null设备,块设备,和支持PCMCIA和PCI设备。所有的这些设备都在本章描述,伴随扩展块设备(XBD)层,提供块设备驱动和文件系统的接口。独立于设备的I/O系统和设备驱动之间的关系在此被讨论。VxWorks提供了NFS和非NFS网络设备和socket,本章简要描述,详细描述,参考14.NetworkFileSystem:NFS和WindRiverNetworkStackProgrammer’sGuide。关于I/O系统的更多信息,参考10.I/OSystem。11.2关于VxWorks中的设备VxWorksI/O系统是灵活的,允许不同的设备驱动处理7个基本的I/O函数(参考10.5BasicI/O)。所有的VxWorks设备驱动遵循10.2AbouttheVxWorksI/OSystem所示规范,和10.4Files,Devices,andDrivers,不同规格。Table11-1表列出了支持的设备。警告:设备不能给予相同的名字,或将会覆盖核心I/O中的其他设备名。注:仅VxBus兼容驱动可以用于对称多处理器(SMP)配置。相关信息参考24.VxWorksSMP和24.17MigratingCodetoVxWorksSMP。11.3串行I/O设备:终端和伪终端设备VxWorks提供终端和伪终端设备(tty和pty)。Tty设备是实际的终端设备;pty是模拟终端的设备。这些伪终端在应用中非常有用,如远程登录工具。VxWorks串行I/O设备缓存串行字节流。每个设备有针对输入和输出有环形缓存。从输入环形缓存中读数据,往输出环形缓存中写数据。当系统初始化过程中,创建设备时指明环形缓存的大小。注:本节剩余部分,词tty用于表示tty和pty设备。ttyOptionstty设备有影响设备行为的大范围选项。这些选项通过使用带有FIOSETOPTIONS参数的ioctl()函数设置设备选项的相关位设置。如设置所有的tty选项,除了OPT_MON_TRAP。status=ioctl(fd,FIOSETOPTIONS,OPT_TERMINAL&~OPT_MON_TRAP);更多信息,参考11.3.3I/OControlFunctions。Table11-2是已有选项的概要。列出来的选项定义在头文件ioLib.h中。更多信息,参考相关tyLib的API。11.3.1Raw模式和Line模式一个tty设备操作在两种模式中的其中一种模式:rawmode(unbuffered)或linemode。Raw模式是默认的。Line模式通过设备选项的字的OPT_LINE位选择,参考ttyOptions。Raw模式,只要从设备输入一个字符,读者就可以操作这个字符。Raw模型下,从一个tty设备读数据,导致尽可能多的数据从输入环形缓存中提取,直到达到用户读缓存的上限。输入不能修改,处理直接修改tty选项位。Line模式,所有的输入字符被保存直到NEWLINE输入字符;之后整个字符行,包括NEWLINE,一次性输入到环形缓存中。Line模式下从tty设备上读数据,导致字符结束到下一行,从输入环形缓存中提取,达到用户读缓存的上限。可以通过具体的字符修改输入CTRL+H(backspace),CTRL+U(line-delete),andCTRL+D(end-of-file),在11.3.2ttySpecialCharacters,讨论。11.3.2tty特别字符11.3.3I/O控制函数11.4管道设备11.5伪I/O设备11.6Null设备VxWorks提供了针对null设备的/null和/dev/null设备。/null设备是传统的Vxworksnull设备,默认向前兼容。设备由BUNDLE_RTP_POSIX_PSE52组件包提供,要求配置POSIXPSE52原型。注明:devsshell命令列出了/null和/dev/null等其他设备,但是ls命令不会在VRFS根目录下,列出/dev/null(因为名字违背了VRFS命令规则)。应用可以按要求使用/null或/dev/null设备。针对POSIXPSE52的信息,参考VxWorksApplicationProgrammer’sGuide:POSIXFacilities,针对VRFS的信息,参考VxWorksKernelProgrammer’sGuide:LocalFileSystems。11.7块设备11.8扩展块设备设施:XBD扩展块设备(XBD)设备传达文件系统和块设备间的I/O行为。在文件系统和块设备间提供一个标准接口,调度来自一个文件系统的I/O请求到合适的设备驱动。XBD设备也提供可移除文件系统支持,自动文件系统检测和多个文件系统支持。更多信息,参考12.LocalFileSystems。注:XBD设备由INCLUDE_XBD组件提供,也提供针对下面可选组件的通用服务:INCLUDE_XBD_RAMDRV提供RAM磁盘执行。参考11.7.1XBDRAMDisk。INCLUDE_XBD_PART_LIB提供磁盘分区工具。参考11.8.1XBDDiskPartitionManager。INCLUDE_XBD_BLK_DEV提供传统块设备驱动支持,传统块设备驱动设计使用XBD的前辈——缓存块I/O(CBIO工具)。这些设备包括软盘驱动,SCSI和TrueFFS(flash磁盘访问仿真)。INCLUDE_XBD_TRANS提供一个基于事务的文件系统工具(TRFS),可以用于dosFs。提供容错文件系统一致性和因为响应功率损耗造成的快速恢复。参考12.6Transaction-BasedReliableFileSystemSupportfordosFs:TRFS。11.8.1XDB磁盘分配管理器VxWorks提供INCLUDE_XBD_PART_LIB组件支持PC一样的磁盘分区功能,便于VxWorks目标系统和运行Windows操作系统的PC共享固定磁盘和可移除存储设备。这个组件包括两个模块:xbdPartition和partLib.。xbdPartitionModulexbdPartition工具为媒介上检测的每一个分区创建一个设备。检测到的每一个分区通过文件系统监控探测,一个I/O设备增加到VxWorks中。这个设备是通过文件系统监控(或若文件系统没有被识别或检测到,是rawFs)找到的文件系统实例。若没有分区检测到,创建一个的单一的设备表示整个媒介。一个单一媒介上最多可以达到4个分区。关于文件系统监控的更多信息,参考12.2FileSystemMonitor。分区工具也命名这些分区。名称衍生之基设备名和分区号。基设备名衍生之设备驱动名字(更多信息,参考VxWorksDeviceDriverDeveloper’sGuide。)如,针对一个ATA硬盘的XBD兼容设备将会有一个名称:/ata00,若有四个分区,命名如下:/ata00:1/ata00:2/ata00:3/ata00:4若没有分区,名称为/ata00:0。工具如何使用,参考Example12-1。partLibLibrary提供了创建类似PC磁盘分区功能的工具。一个媒介上最多可以创建4个分区。注明当创建分区时,磁盘上存在的任何信息丢失。更多信息,参考VxWorksAPIreferenceforxbdCreatePartition()。11.8.2XBD块设备封装INCLUDE_XBD_BLKDEV组件提供传统块设备驱动支持,设计用于和XBD的前辈一块工作——缓存块I/O(CBIO)工具。提供一个封装XBD工具,转换基于BLK_DEV逻辑块设备结构块I/O驱动接口为一个XBDAPI兼容接口。注:要求包含INCLUDE_XBD_BLKDEV和INCLUDE_XBD组件的风河设备是软盘,SCSI,TrueFFS(flash的磁盘访问仿真器)驱动。任何基于BLK_DEV接口的第三方设备驱动必须包含INCLUDE_XBD_BLKDEV组件。不要求INCLUDE_XBD_BLK_DEV组件的风河驱动是USB块存储,和XBDRAM磁盘。提醒:根据驱动的实现,INCLUDE_XBD_BLK_DEV组件可能不能正确的检测媒介插入和删除。可能媒介删除时,删除文件系统,或媒介插入时,实例化一个文件系统。11.8.3XBDTRFS组件INCLUDE_XBD_TRANS组件是一个XBD兼容的基于事务的可依赖文件系统设备。TRFS是一个针对dosFs文件系统提供容错文件系统层的I/O工具。参考12.6Transaction-BasedReliableFileSystemSupportfordosFs:TRFS。11.9PCMCIA一个PCMCIA卡可以插入到笔记本电脑中用于连接设备,如调制解调器和外部硬盘。VxWorks提供针对pcPentium,pcPentium2,和pcPentium3BSPs和允许VxWorks运行在这些目标上来支持PCMCIA硬件的PCMCIA驱动的PCMCIA设备。PCMCIA支持是在PCMCIARelease2.1level。不包括VxWorks不要求的socket服务或卡服务。包括芯片驱动和库。PCMCIA库和驱动包含在源码中,形成一个基于CPU架构的Vxworks系统,除了IntelPentium。为了在系统中包含PCMCIA支持,配置INCLUDE_PCMCIA组件。关于PCMCIA设施更多的信息,参考相关pcmciaLib和pcmciaShowAPI。11.10外设组件互连:PCIPeripheralComponentInterconnect(PCI)是一个连接外设到一个PC的总线标准,用于系统。PCI包括使用解耦cpu和相对比较低功能外设的缓存,允许它们异步操作。关于PCI设备的信息,参考pciAutoConfigLib,pciConfigLib,pciInitLib,andpciConfigShow相关API。11.11网络文件系统(NFS)设备NetworkFileSystem(NFS)设备允许使用NFS协议访问远程主机上的文件。NFS协议指明了client软件,从远程主机上读文件,server软件,导出文件到远程机器。驱动nfsDrv扮演一个VxWorksNFSclinet去访问网络上任何NFS服务器上的文件。VxWorks也允许你运行一个NFSserver导出文件到其它系统上。使用NFS设备,你可以创建,打开,访问远程文件,尽管他们在一个本地磁盘上文件系统上。这称为networktransparency。关于VxWorks应用NFS更多信息,参考14.NetworkFileSystem:NFS。11.11.1从VxWorks系统上挂载一个远程NFS文件系统访问一个远程NFS文件系统通过本地挂载文件系统和使用函数nfsMount()创建一个I/O设备来建立。参数是(1)NFS服务器主机名,(2)主机文件系统名,和(3)文件系统本地名。如,如下调用挂载主机mars的/usr为本地/vxusr:nfsMount("mars","/usr","/vxusr");这个创建了一个带具体本地名的VxWorksI/O设备(/vxusr)。若本地名指明为NULL,本地名和远程名一样。一个远程文件系统挂载之后,通过本地文件系统访问文件。因此,先前例子之后,打开文件/vxusr/foo,同时打开了mars主机上文件/usr/foo。远程文件系统必须通过实际驻留的系统上导出。然而,NFS服务器可以仅从本地文件系统导出。在服务器上使用合适的命令来查询那个文件系统是本地的。NFS要求authentication参数来表示用户发起的远程访问。为了设置这些参数,使用nfsAuthUnixSet()和nfsAuthUnixPrompt()函数。增加INCLUDE_NFS组件,支持NFS客户端。导出和挂载NFS文件系统和授权访问允许在14.NetworkFileSystem:NFS详细讨论。参考nfsLib和nfsDrv相关入口。11.11.2NFS客户端的I/O控制函数11.12非-NFS网络设备Vxworks也提供通过RemoteShellprotocol(RSH)或FileTransferProtocol(FTP)访问远程主机上的文件。这些网络设备的实现使用驱动netDrv,包含在风河网络协议栈中,你可以打开,读,写,关闭位于远程主机上的文件,不需要管理用于影响信息传输的潜在协议细节。当一个远程文件使用RSH或FTP时,整个文件被拷贝到本地内存。最终,打开的最大文件收到本地存在内存的限制。读写操作在驻留内存上进行拷贝操作。当关闭时,文件被拷贝到最初的远程文件,若被修改。通常,NFS设备从性能角度考虑,选择RSH和FTP设备,因为NFS不会拷贝整个文件到本地内存。然而,并不是所有的主机系统支持NFS。11.12.1创建网络设备11.12.2I/O控制函数11.13SocketsVxWorks中,网络通讯的潜在基础是socket。一个socket是一个任务间通讯的终结点;数据从一个socket到另外一个socket发送。Socket不是通过标准的I/O函数创建或打开的。取而代之,它们通过调用socket()函数创建,通过sockLib中的其它函数连接和访问。然而,一个streamsocket(使用TCP)被创建和连接后,也可以通过一个标准I/O访问,使用read(),write(),ioctl(),和close()。Socket()返回的值是一个socket句柄用于一个I/O系统文件描述符。VxWorkssocket程序源代码兼容于BSD4.4UNIXsocket函数和BSD4.4UNIXsocket网络标准。这些函数的使用参考WindRiverNetworkStackProgrammer’sGuide。11.14内部I/O系统结构VxWorksI/O系统和大多数I/O系统工作的方式不同,VxWorks执行用户I/O请求在独立于I/O系统和设备驱动本身间分配。在大多数系统中,设备驱动提供一些函数,执行低级别I/O函数,如读字节序,写字节序,面向字符的设备。高级别的协议,如面向字符的通讯协议,应用于独立于设备的I/O系统部分。用户请求在驱动程序控制之前由I/O系统处理。而这个方法的设计初衷是使得驱动的开发跟简单,保证设备行为尽可能相似,有一些缺点。驱动开发者当要实现当前I/O系统不存在的协议时,比较困难。在实时系统中,有时希望为当前吞吐量比较重要的设备提供可选择的协议,或者当前设备不适合标准模型时,比较麻烦。VxWorksI/O系统中,在设备驱动控制设备前用户I/O请求完成最小化处理。VxWorksI/O系统角色相当于一个交换机,路由用户请求到合适的驱动提供的程序。每个驱动可以处理最初用户请求作为自己的设备。另外,然而,高级别子路由库存在,可以被驱动开发者使用针对字符型或块设备实现标准协议。因此,VxWorksI/O系统提供两种方法:一种是针对大多数设备,只用编写少量代码,简单实现一个驱动;驱动开发者可以以非标准方式自由执行用户请求。有两种基本的设备类型:block和character(或non-block,参考Figure11-1))。块设备用于存储文件系统。它们是数据转移到块上很少访问的设备。字符型设备通常是tty/sio类型。如早期部分讨论的,VxWorksI/O系统的三个基本元素是驱动,设备和文件。如下部分详细的描述了这三个部分。关于字符型驱动的讨论,很多可以应用到块设备,组织上仅有微小区别。注:这个讨论设计用于澄清VxWorksI/O设施结构和强调编写VxworksI/O设备驱动应该注意的问题。详细信息,参考VxWorksDeviceDriverDeveloper’sGuide。Example11-5展示了一个驱动的简化代码,用于贯穿如下的一个例子。这个例子驱动是面向字符型设备的。Example11-5HypotheticalDriver/**xxDrv-driverinitializationroutine*xxDrv()init’sthedriver.ItinstallsthedriverviaiosDrvInstall.*Itmayallocatedatastructures,connectISRs,andinitializehardware*/STATUSxxDrv(){xxDrvNum=iosDrvInstall(xxCreat,0,xxOpen,0,xxRead,xxWrite,xxIoctl);(void)intConnect(intvec,xxInterrupt,...);...}/*************************************************************************xxDevCreate-devicecreationroutine**Calledtoaddadevicecalled<name>tobesvcedbythisdriver.Other*driver-dependentargumentsmayincludebuffersizes,deviceaddresses.*TheroutineaddsthedevicetotheI/OsystembycallingiosDevAdd.*Itmayalsoallocateandinitializedatastructuresforthedevice,*initializesemaphores,initializedevicehardware,andsoon.*/STATUSxxDevCreate(name,...)char*name;...{status=iosDevAdd(xxDev,name,xxDrvNum);...}/***ThefollowingroutinesimplementthebasicI/Ofunctions.*ThexxOpen()returnvalueismeaningfulonlytothisdriver,*andispassedbackasanargumenttotheotherI/Oroutines.*/intxxOpen(xxDev,remainder,mode)XXDEV*xxDev;char*remainder;intmode;{/*serialdevicesshouldhavenofilenamepart*/if(remainder[0]!=0)return(ERROR);elsereturn((int)xxDev);}intxxRead(xxDev,buffer,nBytes)XXDEV*xxDev;char*buffer;intnBytes;...intxxWrite(xxDev,buffer,nBytes)...intxxIoctl(xxDev,requestCode,arg).../**xxInterrupt-interruptserviceroutine**Mostdrivershaveroutinesthathandleinterruptsfromthedevices*servicedbythedriver.Theseroutinesareconnectedtotheinterrupts*bycallingintConnect(usuallyinxxDrvabove).Theycanreceivea*singleargument,specifiedinthecalltointConnect(seeintLib).*/VOIDxxInterrupt(arg)...11.14.1驱动一个针对非块设备驱动通常实现7个基本I/O函数——creat(),remove(),open(),close(),read(),write(),ioctl()——针对一个特别类型的设备。驱动使用对应具体驱动函数实现这些通用函数,使用iosDrvInstall()函数安装。注:仅VxBus兼容驱动可以用于对称多处理器(SMP)的VxWorks配置。关于VxWorksSMP和移植的相关信息,参考24.VxWorksSMP和24.17MigratingCodetoVxWorksSMP。并不是所有的通用I/O函数要实现,若一个特殊的设备不支持。如remove()函数通常设备不支持,不用于文件系统。若七个基本I/O函数的其中任一个没有被驱动实现,当驱动安装时,一个null函数指针用于对应的iosDrvInstall()参数。调用一个不支持的函数,将会返回fail,返回一个ENOTSUP错误。驱动可能(可选)允许任务等待多个文件描述符激活。这个功能应用于驱动的ioctl()函数;参考Implementingselect()。一个针对块设备接口文件系统的驱动,而不是直接连接I/O系统。文件系统轮流实现大多数I/O函数。驱动仅需要支持读、写块函数,复位设备,执行I/O控制,和检查设备状态函数。当一个应用触发其中一个基本I/O函数时,I/O系统路由请求到具体驱动的对应函数,如下面部分描述。驱动的函数运行在调用任务的上下文中,尽管也可以直接从应用调用。因此,驱动可以自由的使用任务可以使用的设施,包括到其它设备的I/O。这意味着,大多数驱动必须提供一中机制来提供关键代码的互斥。常用的机制是semLib提供的信号量设施。·一个安装驱动到I/O系统的初始化程序,连接任何通过驱动提供给设备服务的中断,并执行任何必要的初始化。这个函数通常命名为xxDrv()。·一个函数增加驱动服务的设备到I/O系统。这个函数通常命名为xxDevCreate()。·中断服务程序,连接到驱动提供服务的设备的中断。驱动表和安装驱动I/O系统的函数路由用户I/O请求到合适驱动的合适程序。I/O系统通过一张包含每个驱动每个函数地址的表类维护这些。驱动通过调用I/O系统内部iosDrvInstall()函数来动态安装。这个函数的参数是新驱动的其它I/O函数的地址。iosDrvInstall()函数输入这些地址到驱动表的空闲槽中,并返回这个槽的索引。这个索引被称为drivernumber,随后用于关联具体设备和驱动。Null(0)地址可以指派给七个基本I/O函数的任何一个函数指针,若设备不支持这个函数时。如remove()函数,非文件系统的设备通常不支持,null指针指派给驱动的remove函数。当一个用户I/O调用匹配一个null驱动函数,调用失败,返回ENOTSUP。VxWorks文件系统(如dosFsLib)在驱动表中包含他们自己的入口,在文件系统初始化时创建。安装一个驱动的例子展示了当初始化函数运行时,例子驱动和I/O系统执行的步骤:驱动调用,指明了驱动的七个基本I/O函数的地址。后,I/O系统:1定位驱动表中下一个可用槽,如槽2.2在驱动表中输入驱动函数的地址。3返回槽号为最新安装驱动的驱动号。11.14.2设备一些驱动可以服务一个特别类型设备的多个实例。如,针对一个串行通讯设备的单一驱动可以处理多个分离的通道,仅一些参数不同,如设备地址。在VxWorksI/O系统中,设备通过一个数据结构deviceheader(DEV_HDR)定义。这个数据结构包含设备名字符串和服务于设备的驱动号。系统中所有设备的设备头保存在内存驻留链接表devicelist中。设备头是有单个驱动确定的一个大的数据结构的一部分。这个大的数据结构,称为adevicedescriptor,包含额外的具体设备的数据如设备地址,缓存和信号量。设备链表和增加的设备非块设备通过调用内部I/O函数iosDevAdd()动态增加到I/O系统中。iosDevAdd()函数的参数是新设备的设备描述符的地址,设备名,和服务于设备的驱动号。驱动指明的设备描述符包含任何独立设备的必要信息,如开始于一个设备头。驱动不需要填充设备头,仅设备独立的信息。iosDevAdd()函数输入具体的设备名和设备头中的驱动号,并增加到系统设备列表中。为了增加一个块设备到I/O系统,针对设备上要求的文件系统调用设备的初始化函数——如,dosFsDevCreate()。之后,自动调用iosDevAdd()。iosDevFind()函数可以用以定位设备结构(通过获取一个指向DEV_HDR的指针,是这个结构的第一个成员),且验证一个设备名是否存在于设备表中。如下是一个使用iosDevFind()的例子:char*pTail;/*pointertotailofdevName*/chardevName[6]="DEV1:";/*nameofdevice*/DOS_VOLUME_DESC*pDosVolDesc;/*firstmemberisDEV_HDR*/...pDosVolDesc=iosDevFind(devName,(char**)&pTail);if(NULL==pDosVolDesc){/*ERROR:devicenamedoesnotexistandnodefaultdevice*/}else{/**pDosVolDescisavalidDEV_HDRpointer*andpTailpointstobeginningofdevName.*CheckdevNameagainstpTailtodetermineifitis*thedefaultnameorthespecifieddevName.*/}增加设备的例子在Figure11-3中,例子驱动的设备创建函数xxDevCreate()通过调用函数iosDevAdd()增加设备到I/O系统。删除设备删除一个设备可以使用iosDevDelete()函数,删除管理的驱动使用函数iosDrvRemove()。注明一个删除操作导致打开设备的描述符invalidated,但是没有关闭。文件描述符必须通过应用调用API明确关闭。若不是这种情况,文件描述符通过I/O系统自动关闭,当文件描述符正在被应用使用,或应用尚未意识到设备已经删除时,描述符不能指派给其它新的文件。若,应用使用一个关联已经删除设备的文件描述符,到导致I/O错误,可能会造成数据崩溃。因为一个设备的文件描述符已经删除,且无效,任何随后使用此描述符的I/O调用——除了——close()会失败。I/O函数相关的行为如下:close()函数在I/O系统级别释放了文件描述符,和驱动关闭函数不在调用。read(),write()和ioctl()返回fail,errorENXIO(没有这个设备或地址)。当open(),remove(),和create()函数在没有打开一个文件描述符的情况下,使用时,调用失败,因为设备名在设备列表中不存在。注明即使设备已经删除,立即增加相同的设备名,删除时文件描述符无效,没有恢复到有效状态。文件描述符关联的I/O调用行为和设备没有增加的行为一样。可能遇到设备删除的应用应该在调用read(),write(),和ioctl()函数时,检查ENXIO错误,后关闭对应的文件描述符。使用回调函数管理设备删除对于设备动态安装和删除的情况,iosDevDelCallback()函数提供post-deletion处理方法,在所有的驱动调用结束。一个设备删除回调的正常用法是防止一个竞争条件,一个任务删除了一个设备的描述符,同时另外一个任务正在使用。属于一个应用的一个设备描述符,I/O系统不能控制它的创建和释放。是一个带有内嵌前置的数据结构DEV_HDR的数据结构,后面是具体的设备成员。它的指针用于给任何iosDevXyz()函数,作为一个DEV_HDR指针或用于用户设备处理的设备描述符。当删除一个设备时,应用不应该在iosDevDelete()和iosDrvRemove()调用之后立即释放设备描述符内存,因为删除设备的驱动程序调用可能正在进行,可能会导致严重错误。如,随后可能会产生一个竞争条件:任务A通过调用open()函数触发驱动xyzOpen()函数,xyzOpen()调用尚未返回,此时任务B删除了设备,并释放了设备描述符。然而,若任务B没有释放文件描述符,通过iosDevDelCallback()函数安装的回调函数处理,后仅在任务A驱动函数执行完后释放。当一个设备用iosDevDelete()或iosDrvRemove()函数时,回调函数立即调用,尽管正在运行的关联驱动没有执行任何行为(也就是说,设备驱动引用计数为0)。否则,回调函数不会执行,直到最后一个驱动调用退出(设备驱动引用计数达到0)。一个设备删除回调函数调用时,只要一个参数,指向的DEV_HDR数据结构指针。如:devDeleteCallback(pDevHdr)回调函数应该在iosDevAdd()调用之后用函数iosDevDelCallback()安装。如下代码片段说明了回调使用。文件系统描述符被安装到I/O设备列表中。它的设备删除回调,fsVolDescRelease()执行后置处理,包括释放设备卷描述符分配的内存空间。voidfsVolDescRelease(FS_VOLUME_DESC*pVolDesc){......free(pVolDesc->pFsemList);free(pVolDesc->pFhdlList);free(pVolDesc->pFdList);......}STATUSfsDevCreate(char*pDevName,/*devicename*/device_tdevice,/*underlyingblockdevice*/u_intmaxFiles,/*maxno.ofsimultaneouslyopenfiles*/u_intdevCreateOptions/*writeoption&volumeintegrity*/){FS_VOLUME_DESC*pVolDesc=NULL;/*volumedescriptorptr*/......pVolDesc=(FS_VOLUME_DESC*)malloc(sizeof(*pVolDesc));pVolDesc->device=device;......if(iosDevAdd((void*)pVolDesc,pDevName,fsDrvNum)==ERROR){pVolDesc->magic=NONE;gotoerror_iosadd;}/*Devicedeletioncallbackinstalledtoreleasememoryresource.*/iosDevDelCallback((DEV_HDR*)pVolDesc,(FUNCPTR)fsVolDescRelease);......}STATUSfsDevDelete(FS_VOLUME_DESC*pVolDesc/*pointertovolumedescriptor*/){....../**DeletethefilesystemdevicefromI/Odevicelist.Callback*fsVolDescReleasewillbecalledfromnowonata*safetimebyI/Osystem.*/iosDevDelete((DEV_HDR*)pVolDesc);......}应用应该检查一个删除设备的返回的错误,如下:if(write(fd,(char*)buffer,nbytes)==ERROR){if(errno==ENXIO){/*Deviceisdeleted.fdmustbeclosedbyapplication.*/close(fd);}else{/*writefailureduetootherreason.Dosomeerrordealing.*/......}}11.14.3文件描述符一个单独的设备一次可以打开多个文件描述符。一个设备驱动可以维护和一个文件描述符关联的额外信息,超出了I/O系统设备信息范围。尤其,那些一次可以打开多个文件描述符的设备有和每一个文件描述符关联的具体文件信息(如,文件偏移)。你也可以在一个非块设备上打开多个文件描述符,如tty;通常没有额外信息,因此往文件描述符上写任何信息会产生同样的结果。FileDescriptorTable用open()或creat()函数打开文件。I/O系统查找设备名设备链表,匹配调用者指明的文件名(或一个初始化子字符串)。若匹配成功,I/O系统使用包含在对应设备头中的驱动号来定位和调用驱动表中的驱动的打开函数。I/O系统必须建立调用者在后续I/O调用过程中使用的文件描述符和提供服务的驱动之间建立关联。另外,驱动必须为每个描述符关联一些数据结构。若是非块设备,这通常是I/O系统中的设备描述符。I/O系统在一个表中维护这些关联,称为filedescriptortable。这个表包含驱动号和一个额外驱动定义的4字节值。驱动值是驱动打开函数返回的内部描述符值,可以是要求识别文件的任意值。在后续的I/O调用过程中,这个值提供给驱动代替应用级别使用的文件描述符。打开一个文件的例子在Figure11-4和Figure11-5中,一个用户调用open()打开文件/xx0。I/O系统执行如下一系列行为:1查找设备列表中匹配具体文件名的设备名(或一个初始字符串)。这种情况下,一个完全的设备名匹配。2在文件描述符表中预留了一个槽,创建一个新的文件描述符对象,当open成功后,使用。3之后查找驱动的open函数的地址,xxOpen(),并调用这个函数。注明xxOpen()的参数通过I/O系统从用户最初参数转换给函数open()。xxOpen()函数的第一个参数是一个指向设备描述符I/O系统位于完整文件名搜索。下一个参数是用户指定文件名的remainder,删除匹配设备名字符串之后的字符。这种情况下,因为设备名匹配整个文件名,传递给驱动的是一个null字符串。驱动可以用任何方式解析这个字符串。若是块设备,这个remainder是设备上一个文件名。若是非块设备,通常是个错误,什么也不是,只是一个null字符串。第三个参数是文件访问标志,如O_RDONLY;也就是说,文件只读。上一个参数是模式,传递给最初的open()函数。4执行xxOpen(),返回一个值,随后用于识别新打开的文件。这种情况下,这个值指向设备描述符。这个值被提供给驱动,在随后的I/O调用中指打开的文件。注明若驱动仅返回设备描述符,驱动不能识别同一设备打开的多个文件。在非块设备驱动下,这通常是正确的。5I/O系统之后如是驱动号和xxOpen()返回的值。6最后,返回给用户文件描述符表中槽索引。从文件中读数据的例子在Figure11-6中,用户调用read()从文件中获取输入数据。指明的文件描述符是针对这个文件的文件描述符表中的索引。I/O系统使用包含在表中的索引号,定位驱动的读程序,xxRead()。I/O系统调用xxRead(),传递文件描述符中识别值——xxOpen()返回的值。此时的值是指向设备描述符。驱动的读函数从设备读数据。write()和ioctl()是同样的执行流程。关闭一个文件的例子用户通过调用close()函数中止文件的使用。如read()所示,I/O系统使用包含在文件描述符中的驱动号定位驱动的close函数。在例子驱动中,没有指明close函数;因此没有驱动函数调用。取而代之,I/O系统标志文件描述符表中槽存在。后续对此文件描述符的引用会导致错误。随后调用open()会重新使用这个槽。实现select()在你的驱动中支持select()允许任务从多个设备上等待输入或指明一个最大等待时间使得准备好I/O操作。写一个支持select()的驱动很简单,因为大多数功能由selectLib提供。若如下某种情况适合你,你可能想你的驱动支持select()。·一个想设置一个超市时间等待设备I/O的任务。如一个任务想在一个UDP套接字上,设置一超市时间,若报文一直没来的情况下。·驱动支持多个设备,任务想同时等待多个这样的设备。如多个管道用于不同的数据优先级。·一个在等待设备I/O,同时在等待其它设备I/O。如一个服务器任务,可能使用管道和套接字。为了实现select(),驱动必须保持一张等待设备活动的任务列表。当设备准备好事,驱动释放所有等待在设备上的任务。针对一个支持select()的设备驱动,必须声明一个SEL_WAKEUP_LIST结构(通常声明为设备描述符结构的一部分)和通过调用selWakeupListInit()初始化。这个在驱动的xxDevCreate()函数中完成。当一个任务调用select()时,调用驱动ioctl()的函数,使用FIOSELECT或FIOUNSELECT功能。若ioctl()函数使用功能FIOSELECT,驱动必须执行如下步骤:1通过调用selNodeAdd()函数增加SEL_WAKEUP_NODE(通过ioctl())函数第三个参数提供)到SEL_WAKEUP_LIST。2使用selWakeupType()函数检测是否有任务等待读数据(SELREAD),或是否有任务等待写数据(SELWRITE)。3若设备准备好(由selWakeupType()确定的读或写),驱动调用selWakeup()函数来确信select()调用在任务中没有挂起。这个避免任务阻塞,但是设备已经准备好。若使用FIOUNSELECT调用ioctl(),驱动调用selNodeDelete()删除唤醒list中提供的SEL_WAKEUP_NODE。当设备准备好时,selWakeupAll()用于解除阻塞在设备上的所有等待的任务。尽管这个通常不会在驱动的ISR中产生,随时会发生。如,一个管道驱动从它的xxRead()函数中调用selWakeupAll()来解除所有等待写的任务,尽管管道中有空间存数据。同样,管道的xxWrite()函数可能调用selWakeupAll()来解除所有等待读的任务,即使当前管道中有数据。Example11-6DriverCodeUsingtheSelectFacility/*Thiscodefragmentshowshowadrivermightsupportselect().Inthis*example,thedriverunblockstaskswaitingforthedevicetobecomeready*initsinterruptserviceroutine.*//*myDrvLib.h-headerfilefordriver*/typedefstruct/*MY_DEV*/{DEV_HDRdevHdr;/*deviceheader*/BOOLmyDrvDataAvailable;/*dataisavailabletoread*/BOOLmyDrvRdyForWriting;/*deviceisreadytowrite*/SEL_WAKEUP_LISTselWakeupList;/*listoftaskspendedinselect*/}MY_DEV;/*myDrv.c-codefragmentsforsupportingselect()inadriver*/#include<vxWorks.h>#include<selectLib.h>/*Firstcreateandinitializethedevice*/STATUSmyDrvDevCreate(char*name,/*nameofdevicetocreate*/){MY_DEV*pMyDrvDev;/*pointertodevicedescriptor*/...additionaldrivercode.../*allocatememoryforMY_DEV*/pMyDrvDev=(MY_DEV*)malloc(sizeofMY_DEV);...additionaldrivercode.../*initializeMY_DEV*/pMyDrvDev->myDrvDataAvailable=FALSEpMyDrvDev->myDrvRdyForWriting=FALSE/*initializewakeuplist*/selWakeupListInit(&pMyDrvDev->selWakeupList);...additionaldrivercode...}/*ioctlfunctiontorequestreadingorwriting*/STATUSmyDrvIoctl(MY_DEV*pMyDrvDev,/*pointertodevicedescriptor*/intrequest,/*ioctlfunction*/intarg/*wheretosendanswer*/){...additionaldrivercode...switch(request){...additionaldrivercode...caseFIOSELECT:/*addnodetowakeuplist*/selNodeAdd(&pMyDrvDev->selWakeupList,(SEL_WAKEUP_NODE*)arg);if(selWakeupType((SEL_WAKEUP_NODE*)arg)==SELREAD&&pMyDrvDev->myDrvDataAvailable){/*dataavailable,makesuretaskdoesnotpend*/selWakeup((SEL_WAKEUP_NODE*)arg);}if(selWakeupType((SEL_WAKEUP_NODE*)arg)==SELWRITE&&pMyDrvDev->myDrvRdyForWriting){/*devicereadyforwriting,makesuretaskdoesnotpend*/selWakeup((SEL_WAKEUP_NODE*)arg);}break;caseFIOUNSELECT:/*deletenodefromwakeuplist*/selNodeDelete(&pMyDrvDev->selWakeupList,(SEL_WAKEUP_NODE*)arg);break;...additionaldrivercode...}}/*codethatactuallyusestheselect()functiontoreadorwrite*/voidmyDrvIsr(MY_DEV*pMyDrvDev;){...additionaldrivercode.../*ifthereisdataavailabletoread,wakeupallpendingtasks*/if(pMyDrvDev->myDrvDataAvailable)selWakeupAll(&pMyDrvDev->selWakeupList,SELREAD);/*ifthedeviceisreadytowrite,wakeupallpendingtasks*/if(pMyDrvDev->myDrvRdyForWriting)selWakeupAll(&pMyDrvDev->selWakeupList,SELWRITE);}缓存一致性注:本部分描述的缓存工具仅限于单处理器(UP)Vxworks配置,有些不适合对称多处理器(SMP)。更多信息,参考cacheLibRestrictions。关于VxWorksSMP移植的更多信息,参考24.VxWorksSMP和24.17MigratingCodetoVxWorksSMP。为板卡编写带缓存的驱动必须保证cachecoherency。意味着缓存中的数据必须和RAM中的数据保持同步或一致性。数据缓存和RAM,当异步访问RAM时,可能不同步(如,DMA访问或VMEbus访问)。数据缓存通过减少内存访问的次数来提供系统性能。Figure11-7图展示了CPU,数据缓存,RAM,和一个DMA设备的关系。若一个CPU针对一个DMA设备写数据到RAM,数据可以首先写到数据缓存。当DMA设备从RAM转移数据时,不保证RAM上的数据更加RAM数据更新。因此,输出给设备的数据可能不是最新的——新数据可能还在缓存中。这个数据不一致性可以通过确信数据缓存中的数据已经更新的RAM,在数据被转移到DMA设备前。若一个CPU从RAM读数据,RAM中数据源自一个DMA设备。读到的数据可以来之缓存(若数据缓存没有标志无效),并不是设备刚转移给RAM的数据。解决这种数据不一致性的方式是确信缓存buffer已经标志无效,以至于从RAM读取数据,而不是缓存。驱动可以通过分配缓存安全buffers(标志非缓存的缓存)或flushing和任何时间向设备写入数据,或从设备读出数据的缓存入口无效。分配cache-safebuffers对于静态缓存比较有用;然而,这通常需要MMU支持。频繁的分配、释放Non-cacheable缓存会导致系统中出现大量的标志Non-cacheablebuffers的内存碎片。两种方法都可以寻仙;这允许动态缓存保持一致。函数cacheFlush()和cacheInvalidate()用于人为的flush和无效缓存缓冲区。在设备读数据前,使用函数,flush数据从缓存到RAM来保证设备对最新的数据。设备写数据到RAM之后,用函数cacheInvalidate()标志无效缓存缓冲区。这保证数据cpu已经准备好数据,缓存已经和RAM中数据同步。Example11-7DMATransferRoutine/*ThisasampleDMAtransferroutine.Beforeprogrammingthedevice*tooutputthedatatothedevice,itflushesthecachebycalling*cacheFlush().Onaread,afterthedevicehastransferredthedata,*thecacheentrymustbeinvalidatedusingcacheInvalidate().*/#include<vxWorks.h>#include<cacheLib.h>#include<fcntl.h>#include"example.h"voidexampleDmaTransfer/*1=READ,0=WRITE*/(UINT8*pExampleBuf,intexampleBufLen,intxferDirection){if(xferDirection==1){myDevToBuf(pExampleBuf);cacheInvalidate(DATA_CACHE,pExampleBuf,exampleBufLen);}else{cacheFlush(DATA_CACHE,pExampleBuf,exampleBufLen);myBufToDev(pExampleBuf);}}可能通过混合cache-safebufferallocation和cache-entryflushing或invalidation两种方法开发的驱动效率更高。仅当绝对必要时,flush或无效一个缓存入口。为了解决静态缓存的不一致性问题,使用cacheDmaMalloc()。这个函数初始化一个CACHE_FUNCS结构(在cacheLib.h中定义),指向flush和invalidate函数,可以用于保持缓存一致性。宏CACHE_DMA_FLUSH和CACHE_DMA_INVALIDATE使用这个结构优化flush和invalidate函数的调用。若CACHE_FUNCS结构中对应的函数指针为NULL,则不需要调用flush和invalidate函数,因为加入缓存时一致的。驱动代码使用一个虚拟地址和设备使用一个物理地址。无论何时设备的当下地址,必须是一个物理地址。无论何时驱动访问内存,必须使用虚拟地址。设备驱动在传递给设备之前,应该使用CACHE_DMA_VIRT_TO_PHYS来转换虚拟地址位物理地址。可以使用CACHE_DMA_PHYS_TO_VIRT转换物理地址到虚拟地址,——这个过程比较耗时,且具有不确定性,尽可能避免使用。Example11-8Address-TranslationDriver/*Thefollowingcodeisanexampleofadriverthatperformsaddress*translations.Itattemptstoallocateacache-safebuffer,fillit,and*thenwriteitouttothedevice.ItusesCACHE_DMA_FLUSHtomakesure*thedataiscurrent.Thedriverthenreadsinnewdataanduses*CACHE_DMA_INVALIDATEtoguaranteecachecoherency.*/#include<vxWorks.h>#include<cacheLib.h>#include"myExample.h"STATUSmyDmaExample(void){void*pMyBuf;void*pPhysAddr;/*allocatecachesafebuffersifpossible*/if((pMyBuf=cacheDmaMalloc(MY_BUF_SIZE))==NULL)return(ERROR);…fillbufferwithusefulinformation…/*flushcacheentrybeforedataiswrittentodevice*/CACHE_DMA_FLUSH(pMyBuf,MY_BUF_SIZE);/*convertvirtualaddresstophysical*/pPhysAddr=CACHE_DMA_VIRT_TO_PHYS(pMyBuf);/*programdevicetoreaddatafromRAM*/myBufToDev(pPhysAddr);…waitforDMAtocomplete……readytoreadnewdata…/*programdevicetowritedatatoRAM*/myDevToBuf(pPhysAddr);…waitfortransfertocomplete…/*convertphysicaltovirtualaddress*/pMyBuf=CACHE_DMA_PHYS_TO_VIRT(pPhysAddr);/*invalidatebuffer*/CACHE_DMA_INVALIDATE(pMyBuf,MY_BUF_SIZE);…usedata…/*whendonefreememory*/if(cacheDmaFree(pMyBuf)==ERROR)return(ERROR);return(OK);}12本地文件系统12.1介绍VxWorks提供了针对不同类型应用的文件系统。不同的文件系统可以同时使用,大多数情况下,一个单一的VxWorks系统,存在多个文件系统实例。大多数文件系统依赖扩展块设备(XBD)设施,针对文件系统和设备驱动间的一个标准IO接口。这个标准接口允许你写自己的VxWorks文件系统,自由的组合文件系统和设备驱动。文件系统用于可移除设备,充分使用文件系统监控,来自动检测设备插入和设备上安装正确的文件系统。应用,文件系统,I/O设施,设备驱动和硬件设备之间的关系如图Figure12-1所示。注明和HRFS,dosFS,rawFs,和cdromFs文件系统相关的信息。虚线框表示必须配置的元素,实例化创建一个具体功能运行时文件系统。关于XBD设施更多信息,参考11.8ExtendedBlockDeviceFacility:XBD。这一章讨论文件系统监控和下面的VxWorks文件系统,描述它们的组成,配置和用法:■VRFS一个虚拟根文件系统用于要求一个POSIX根文件系统的应用。VRFS简化为其它文件系统和设备可以访问的的根目录,参考12.3VirtualRootFileSystem:VRFS。■HRFS一个POSIX兼容的事务型文件系统设计用于块设备(disks)的实时使用。可以用于flash内存+TureFFS和XBD块封装组件。参考12.4HighlyReliableFileSystem:HRFS。■dosFs一个MS-DOS兼容文件系统设计用于实时使用块设备。可以用于flash内存+TrueFFS和XBD块封装组件。也可以用于基于事务的可依赖文件系统(TRFS)设施。参考12.5MS-DOS-CompatibleFileSystem:dosFs和12.6Transaction-BasedReliableFileSystemSupportfordosFs:TRFS。■rawFS提供一个简化raw文件系统,对待整个磁盘为一个单独的文件,参考12.7RawFileSystem:rawFs。■cdromFs允许应用根据ISO9660标准文件系统从格式化的CD-ROMs上读数据。■ROMFS设计用于捆绑应用和其它文件到一个VxWorks系统镜像中。除了存储VxWorks启动镜像外,不能用做它用。■TSFS使用主机目标服务器来给目标提供访问主机文件系统的文件系统,参考12.10TargetServerFileSystem:TSFS。XBD设施更多信息,参考11.8ExtendedBlockDeviceFacility:XBD。FileSystemsandFlashMemoryVxWorks可以针对flash内存设备配置文件系统使用TrueFFS和HRFS文件系统或dosFS文件系统,更多信息,参考12.5MS-DOS-CompatibleFileSystem:dosFs和13.FlashFileSystemSupport:TrueFFS。注:本章提供VxWorks内核有的设施信息。关于实时处理的更多信息,参考VxWorksApplicationProgrammer’sGuide:LocalFileSystems。12.2文件系统监控文件系统监控提供了设备的插入,和设备上正确文件系统实例化的检测功能。所有的文件系统都需要这个监控器用于XBD工具。由INCLUDE_FS_MONITOR组件提供。需要XBD和文件系统监控组件的文件系统是HRFS,dosFs,和cdromFs。检测到那个设备的过程,和创建的那个文件系统的过程如下:1当文件系统在启动时间初始化时,注册probe函数,和用文件系统监控实例化函数。2当检测到一个设备或插入一个设备(如,当初始化一个驱动时,或在一个存在的设备上插入一个媒介时——如软盘插入软驱),关联的块设备会产生一个插入事件。(参考DeviceInsertionEvents)3对应主要插入事件,若设备可以支持分区,文件系统创建一个XBD分区管理器。(关于分区管理器更多信息参考11.8.1XBDDiskPartitionManager)4若分区管理器发现了物理设备上的分区,会为每一个分区创建一个设备;是否找到分区,管理器都会产生第二次插入事件。5当文件系统监控检查到第二个事件时,所有注册的文件系统的探测函数运行。6当一个文件系统的探测函数返回成功,文件系统的实例化函数执行。若没有探测函数返回成功,或若文件系统实例化函数返回失败,默认一个rawFs文件系统创建在设备上。当移除一个设备时,如下发生:1块设备检测硬件设备关联的块设备移除,产生一个移除事件。2块设备删除本身,释放资源。3关联块设备的文件系统从核心I/O移除,无效本身文件句柄。4文件系统删除本身,释放资源。DeviceInsertionEvents文件系统监控反应的设备插入事件类型详细描述如下:XBDPrimaryInsertionEvent一个XBD兼容的块设备当可以支持分区的媒介插入时产生一个XBDPrimaryInsertionEvent(也就是说在分区表中发现)。对应,文件系统监控创建一个分区管理器,之后为轮流为媒介上发现的每一个分区产生secondaryinsertionevents。注:使用(INCLUDE_XBD_BLK_DEV组件的块设备,不管什么媒介,都会产生一个XBDPrimaryInsertionEvent。封装元素本质上是硬件未知的;不能识别设备是否包括分区。如设备可能是一个硬盘——预计的分区——或可能是一个软驱设备。注一个RAM磁盘设备可以产生primaryinsertionevent,取决于创建时使用的参数。(参考11.7.1XBDRAMDisk和APIreferenceforXbdRamDisk。)XBDSecondaryInsertionEvent会产生一个secondaryinsertionevent,或一个XBD分区管理器产生。secondaryinsertionevent提醒文件系统管理器运行probe函数,识别设备上的文件系统。若probe函数返回OK,执行关联的文件系统创建函数。若所有的探测函数没有识别到一个文件系统,或如一个文件系统的创建函数失败,默认创建rawFs文件系统。一个块设备上不支持分区的媒介XBDSoftInsertEvent不像其他事件,一个XBDSoftInsertEvent是通过应用程序指令产生的而不是通过物流介质交换产生的。当调用带有XBD_SOFT_EJECT参数ioctl()控制函数,通知文件系统管理器目前的文件系统已经移除,默认的rawFs文件系统应该创建。这个调用导致系统旁路正常的文件系统检测操作,保证rawFs实例化取代目前的文件系统。XBDNameMappingFacility文件系统监控命名映射工具允许XBD名字映射为更合适的名字。主要用于分区管理器,当检测到一个分区时,增加:x到基本的xbd名字前面。使用fsm命名工具映射分区名更有用。如,软盘驱动使用命名组件映射提供的软驱名+:0,分区管理器会增加到/fdx。x代表软驱驱动号。若没有做这些,你会看到默认的驱动名,通过devsshell命令查看。更多信息,参考fsmNameInstall(),fsmNameMap(),和fsmNameUninstall();也参考Example12-2。12.3虚拟根文件系统:VRFSVxWorks提供一个虚拟根文件系统(VRFS)用于要求一个POSIX根文件系统的应用。VRFS简化为一个“/”或根目录,其它文件系统和设备可以访问。VRFS不是一个真正的文件系统,文件和目录不能用和文件系统关联的命令创建,是只读的。只有名称以一个正斜杠开始的设备——不包含任何其它正斜杠字符——被VRFS识别。为了在Vxworks中包含VRFS,配置INCLUDE_VRFS组件到内核。若内核中包含了这个组件,VRFS被创建,且自动挂载。这个shell链接说明了设备名和访问设备和带VRFS文件系统的关系。->devsdrvname0/null1/tyCo/01/tyCo/12/aioPipe/0x18170406/romfs7/9yow-build02-lx:10/vio11/shm12/ram0value=25=0x19->cd"/"value=0=0x0->ll?---------0000Jan100:00nulldrwxrwxr-x01517910020Jan232098romfs/?---------0000Jan100:00viodrwxrwxrwx1000Jan100:00shm/drwxrwxrwx1002048Jan100:00ram0/value=0=0x0注明/tyCo/0,/tyCo/1,/aioPipe/0x1817040和yow-build02-lx没有显示在根目录下,因为他们不遵循VRFS命令规范。前三个设备名中包含斜线,四个设备的设备名不包含正斜线。注明:文件系统列表由一个正斜线字符。其它设备没有,权限列有问号,表示他们没有识别文件的权限。注:配置Vxworks支持POSIXPSE52一致性(使用BUNDLE_RTP_POSIX_PSE52)提供/dev/null设备。注明devsshell命令列出了带有其它设备的/dev/null,但是ll命令在VRFS目录下没有列出/dev/null设备(因为不遵从VRFS文件系统命名规范)。应用可以按要求使用/dev/null。Null设备更多信息,参考11.6NullDevices。关于POSIXPSE52的更多信息,参考VxWorksApplicationProgrammer’sGuide:POSIXFacilities。提醒:VRFS改变了其它文件系统的行为,因为它在VxWorks下提供了根目录。改变路径到主机文件系统上的绝对路径,当VRFS安装时,VxWorks设备名之前没有绝对路径时,系统不能正常工作。如,目前的工作路径是hostname,改变路径到/home/panloki将不会工作——必须命名为hostname:/home/panloki。12.4高依赖度文件系统:HRFS高依赖度文件系统(HRFS)是一个针对实时系统的事务型文件系统。这种文件系统的主要功能是:·容错。这种文件系统从来不会处于不一致状态,因此可以从功耗损耗情况下快速恢复。·可配置的提交策略。·层次文件和目录系统,允许卷上高效文件组织结构。·POSIX兼容。HRFS库的更多信息,参考VxWorksAPIreferencesforhrfsFormatLib,hrFsLib,andhrfsChkDskLib。HRFSandFlashMemory使用HRFS的flash内存相关信息,参考13.FlashFileSystemSupport:TrueFFS。12.4.1针对HRFS配置VxWorks为了让VxWorks支持HRFS,用合适的组件和可选的组件配置内核。RequiredComponents要求增加INCLUDE_HRFS组件或INCLUDE_HRFS_READONLY组件。顾名思义,后者是主HRFS组件的只读版本。提供没有针对磁盘修改的的工具的库要小一点。这些组件的配置参数如下:HRFS_DEFAULT_MAX_BUFFERS定义HRFS的缓存机制使用的缓存数。最小6个缓存,默认是16个。这个参数应用于所有的HRFS卷。注明当增加缓存数时,可以增加性能,但是损坏了大量的堆内存。关于配置参数提高性能的信息,参考12.4.5OptimizingHRFSPerformance。HRFS_DEFAULT_MAX_FILES定义在一个HRFS卷上,可以同时打开几个文件。最小是1,默认是10。注明这个数和文件描述符的最大数不一样。INCLUDE_HRFS_DEFAULT_WRITE_MODE组件自动包含,提供默认写模式,每一次写执行一次提交。(其它选择,参考INCLUDE_HRFS_HISPEED_WRITE_MODE。)增加INCLUDE_HRFS或INCLUDE_HRFS_READONLY组件,HRFS针对块设备要求合适的组件;如INCLUDE_ATA。若你使用一个设备驱动,不是设计用于XBD工具的,你必须使用INCLUDE_XBD_BLK_DEV组件和INCLUDE_XBD组件。参考11.8.2XBDBlockDeviceWrapper。OptionalHRFSComponentsHRFS可选组件如下:INCLUDE_HRFS_HISPEED_WRITE_MODE高速写模式,针对目前的事务,执行一个提交,当如下其中一个条件成立时:缓存使用超过40%,或5s流逝(若目前事务依然激活)。这个组件给默认INCLUDE_HRFS_DEFAULT_WRITE_MODE组件提供一个选择,每一次写,执行一次提交。针对性能,使用INCLUDE_HRFS_HISPEED_WRITE_MODE组件配置的更多信息,参考12.4.5OptimizingHRFSPerformance。事务和提交策略的更多信息,参考12.4.6TransactionalOperationsandCommitPolicies。INCLUDE_HRFS_FORMAT格式化HRFS媒介。INCLUDE_HRFS_CHKDSK文件系统一致性检查。INCLUDE_HRFS_ACCESS_TIMESTAMPHRFS卷的访问时间戳。注明这个组件包含在BUNDLE_RTP_POSIX_PSE52组件包中。OptionalXBDComponentsINCLUDE_XBD_PART_LIB(磁盘分区)和INCLUDE_XBD_RAMDRV(RAM磁盘)组件是可选的。关于XBD工具更多信息,参考11.8ExtendedBlockDeviceFacility:XBD。12.4.2配置HRFSHRFS提供了如下组件配置参数:HRFS_DEFAULT_MAX_BUFFERS定义HRFS的缓存机制使用的缓存数。最小6个缓存,默认是16个。这个参数应用于所有的HRFS卷。注明当增加缓存数时,可以增加性能,但是损坏了大量的堆内存。关于配置参数提高性能的信息,参考12.4.5OptimizingHRFSPerformance。HRFS_DEFAULT_MAX_FILES定义在一个HRFS卷上,可以同时打开几个文件。最小是1,默认是10。注明这个数和文件描述符的最大数不一样。12.4.3创建一个HRFS文件系统这一部分描述了创建一个HRFS文件系统过程。首先提供一个概要,后详细描述每一步。参考12.4.4HRFS,ATA,andRAMDiskExamples。HRFS文件系统创建概要操作系统的配置信息,参考12.4.1ConfiguringVxWorksforHRFS。文件系统在系统启动时自动初始化。创建一个HRFS文件系统步骤如下:1若你使用一个定制驱动,创建合适的块设备,参考Step1:CreateaBlockDevice。若你针对设备使用一个标准的VxWorks组件,自动创建。2若你使用一个不是基于XBD兼容设备驱动,创建一个XBD设备wrapper。参考Step2:CreateanXBDDeviceWrapper。(也参考11.8.2XBDBlockDeviceWrapper)3可选,创建挂载分区,参考Step3:CreatePartitions。4若你没有使用预格式化磁盘,格式化卷。参考Step4:FormattingtheVolume。HRFS文件系统创建步骤在执行任何操作之前,HRFS文件系统库,hrFsLib必须初始化。这个在系统启动时自动发生,有包含在系统中的HRFS组件触发。初始化HRFS涉及一个vnode层的创建。HRFS安装一个内部vnode操作数号到这一层。当检测到媒介时,Vnode层触发iosDrvInstall()函数,增加驱动到I/O驱动表。指派给vnode的驱动号——因此HRFS——记录在全局变量中,vnodeAffDriverNumber。这个表针对vnode文件操作指明了入口点,通过HRFS访问设备。Step1:创建一个块设备若你使用针对设备的一个标准的VxWorks组件,自动创建。若你使用一个定制的驱动,通过调用设备驱动的创建函数,创建合适的块设备。这个程序的格式是xxxDevCreate(),xxx代表设备驱动类型,如ataDevCreate().。驱动程序返回一个指向一个块设备描述结构的指针BLK_DEV,这个结构描述了设备的物理属性和指明了设备驱动提供的程序。返回的指针用于下一步创建一个XBD设备wrapper。Step2:创建一个XBD设备Wrapper若你使用一个不兼容XBD设备驱动,需要一个XBD设备wrapper。若内核中包含INCLUDE_XBD_BLK_DEV组件,则wrapper会自动创建。否则,使用xbdBlkDevCreate().为每一个块设备创建一个wrapper。XBDwrapper创建之后,物理设备自动探测一个文件系统和分区。若一个磁盘已经格式化,则磁盘挂载。若找到一个文件系统,已经挂载。若文件系统不是HRFS,必须格式化。(如下)Step3:创建分区如内核中包含了INCLUDE_XBD_PART_LIB组件。你可以在磁盘上创建分区,挂载卷到分区。使用xbdCreatePartition()函数创建分区。这一步只执行一次,当磁盘第一次初始化后。若分区已经写入到磁盘,执行这一步会破坏数据。Step4:格式化卷若你使用未格式化磁盘,想在磁盘上取代现在的文件系统,通过调用hrfsFormat()函数格式化磁盘。提醒:重新格式化一个磁盘,会破坏磁盘上数据。12.4.4HRFS,ATA和RAM磁盘例子这一部分提供了先前部分讨论的例子步骤。他们是相对通用的,步骤如下:·在ATA磁盘上用shell命令创建HRFS文件系统。·编写创建和格式化分区的代码。·创建和格式化一个RAM磁盘分区的代码。提醒:因为设备名被I/O系统使用简化substring匹配识别,文件系统不应该使用’/’单独作为一个名字;否则会导致未知情况发生。Example12-1CreateHRFSinPartitionsonanATADisk这个例子演示了如何用shell命令,初始化带HRFS的ATA磁盘为两个分区。而这些使用ATA设备的步骤,可以应用到其它块设备上。1若使用一个定制驱动,在主ATA控制器上(控制器0)创建一个控制主ATA硬盘(驱动号:0)ATA块设备。这个设备使用整个磁盘。->xbd=ataXbdDevCreate(0,0,0,0,"/ata")Newsymbol"xbd"addedtokernelsymboltable.Instantiating/ata:0asrawFsxbd=0xca4fe0:value=262145=0x40001变量xbd是device_t类型的。0值表示调用ataXbdDevCreate()函数时,有错误,通常指一个BSP配置或硬件配置错误。若你使用标准INCLUDE_ATA的设备组件,块设备自动创建。注明这种情况默认设备名是/ata0a。2显示关于设备信息->devsdrvname0/null1/tyCo/01/tyCo/18yow-grand:9/vio4/ata:0value=25=0x193新的ata驱动盘/ata:0列出。0表示没有检测到其它分区。注明若没有检测到设备上的文件系统,默认的rawFs文件系统字自动实例化,出现在设备列表中。准备第一次使用的磁盘,指明磁盘50%的空间属于第二个分区,留下50%为第一个分区的空间。这一步只能执行一次,当磁盘初始化时。若分区已经写入到磁盘,若执行这一步,会破坏数据。->xbdCreatePartition("/ata:0",2,50,0,0)value=0=0x0xbdCreatePartition()函数的四个参数是:·驱动名·分区数·第二个分区使用磁盘百分比·第三个分区使用磁盘百分比·第四个分区使用磁盘百分比剩余的用于第一个分区。4后列出新分区的磁盘->devsdrvname0/null1/tyCo/01/tyCo/18yow-grand:9/vio3/ata:13/ata:2注明/ata:0没有出现在列表中,/ata:1和/ata:2两个新的设备出现在列表中,表示新增加的分区。每个卷有rawfs实例,没有格式化。5格式化HRFS卷。这一步只执行一次,当卷第一次创建时。若卷已经格式化,则忽略这一步。这个例子用默认选项格式化文件系统。->hrfsFormat("/ata:1",0ll,0,0)Formatting/ata:1forHRFSInstantiating/ata:1asrawFsFormatting...OK.value=0=0x0->hrfsFormat("/ata:2",0ll,0,0)Formatting/ata:2forHRFSInstantiating/ata:2asrawFsFormatting...OK.value=0=0x0注明hrfsFormat()调用中,第二个参数ll必须指明给shell,数据类型为longlong。更多信息,参考hrfsFormatLib相关的API。6显示HRFS卷信息->ll"/ata:1"ListingDirectory/ata:1:drwxrwxrwx1008192Jan100:13./drwxrwxrwx1008192Jan100:13../value=0=0x0->ll"/ata:2"ListingDirectory/ata:2:drwxrwxrwx1008192Jan100:13./drwxrwxrwx1008192Jan100:13../value=0=0x0若你使用一个ATA硬盘或来自一个ATAAPICD-ROM驱动盘的一个CD-ROM文件系统,你可以有选择的使用usrAtaConfig()。这个函数立即处理几个步骤。更多信息,参考API。Example12-2CreatingandPartitioningaDiskandCreatingVolumes这部分代码传递你已经实例化的块设备名字,创建三个分区,创建这些分配的分区处理,创建处理他们的HRFS设备。后使用hrfsFormat().函数格式化分区。STATUSusrPartDiskFsInit(char*xbdName/*devicenameusedduringcreationofXBD*/){constchar*devNames[]={"/sd0a","/sd0b","/sd0c"};devname_txbdPartName;inti;/*Mappartitionnames*/for(i=1;i<=3;i++){sprintf(xbdPartName,"%s:d",devNames[i],i);fsmNameInstall(devNames[i-1],xbdPartName);}/*createpartitions*/if((xbdCreatePartition(xbdName,3,50,45))==ERROR)returnERROR;/*Formattingthefirstpartition*/if(hrfsFormat(devNames[0],0ll,0,0)==ERROR)returnERROR;/*Formattingthesecondpartition*/if(hrfsFormat(devNames[1],0ll,0,0)==ERROR)12LocalFileSystems12.4HighlyReliableFileSystem:HRFS295returnERROR;/*Formattingthethirdpartition*/if(hrfsFormat(devNames[2],0ll,0,0)==ERROR)returnERROR;returnOK;}注:大部分情况,你可以针对不同的文件系统格式化不同的分区。Example12-3CreatingandFormattingaRAMDiskVolumeandPerformingFileI/O如下代码创建一个RAM磁盘,使用HRFS格式化,执行文件系统操作。#include<vxWorks.h>#include<stdlib.h>#include<stdio.h>#include<string.h>#include<hrFsLib.h>#include<xbdPartition.h>#include<xbdRamDisk.h>#defineDEVNAME"/myram"/*nameoftheRAMdisk*/#defineBLOCKSIZE512#defineDISKSIZE(BLOCKSIZE*2000)STATUShrfsSetup(void){STATUSerror;device_txbd;/*CreateaRAMdisk.Don’tsupportpartitions*/xbd=xbdRamDiskDevCreate(BLOCKSIZE,DISKSIZE,0,DEVNAME);if(xbd==NULLDEV){printf("FailedtocreateRAMdisk.errno=0x%x\n",errno);return(ERROR);}/**FormattheRAMdiskforHRFS.Allowforuptoa1000files/directories*andletHRFSdeterminethelogicalblocksize.*/error=hrfsFormat(DEVNAME,0ll,0,1000);if(error!=OK){printf("FailedtoformatRAMdisk.errno=0x%x\n",errno);return(ERROR);}printf("%snowreadyforuse.\n",DEVNAME);return(OK);}STATUShrfsFileExample(void){intfd;charpath[PATH_MAX];char*testString="helloworld";intsize=strlen(testString)+1;/*sizeofteststringincludingEOS*/intlen;/*Createafileundertherootdirectory*//*Firstbuildthepath*/sprintf(path,"%s/myfile",DEVNAME);fd=open(path,O_RDWR|O_CREAT,0777);if(fd<0){printf("Couldn’tcreatefile%s.errno=0x%x\n",path,errno);return(ERROR);}/*Writetothefile*/printf("Writing%dbytestofile.\n",size);len=write(fd,testString,size);if(len!=size){printf("Couldn’twritetofile%s.errno=0x%x\n",path,errno);close(fd);return(ERROR);}/*Closeandre-openfile*/close(fd);fd=open(path,O_RDWR,0777);if(fd<0){printf("Couldn’tre-openfile%s.errno=0x%x\n",path,errno);return(ERROR);}/*Nowreadbackwhatwewrote*/printf("Reading%dbytesfromfile.\n",size);len=read(fd,path,size);if(len!=12){printf("Couldn’treadfromfile%s.errno=0x%x\n",path,errno);close(fd);return(ERROR);}/*Makesurewereadwhatwewrote*/if((len=strcmp(path,testString))!=0){printf("Readdatadifferentfromwrittendata.errno=0x%x,%d\n",errno,len);close(fd);return(ERROR);}close(fd);return(OK);}注明:使用这个代码,必须配置INCLUDE_HRFS_FORMAT,INCLUDE_XBD_RAMDRV和INCLUDE_XBD_PART_LIB内核组件。->hrfsSetupInstantiating/myramasrawFsFormatting/myramforHRFSInstantiating/myramasrawFsFormatting...OK./myramnowreadyforuse.value=0=0x0->hrfsFileExampleWriting12bytestofile.Reading12bytesfromfile.value=0=0x0->ll"/myram"ListingDirectory/myram:drwxrwxrwx1002048Jan100:00./drwxrwxrwx1002048Jan100:00../-rwxrwxrwx10012Jan100:00myfilevalue=0=0x0->12.4.5优化HRFS性能HRFS性能可以通过如下VxWorks配置和HRFS格式化步骤优化:·配置INCLUDE_HRFS_HISPEED_WRITE_MODE组件取代INCLUDE_HRFS_DEFAULT_WRITE_MODE组件。·设置HRFS_DEFAULT_MAX_BUFFER组件参数为1024。高速写模式组件提交目前的事务:缓存超过40%,5s延时。默认写模式,另一方面,没写一次,提交一次。·hrfsFormat()格式化时,使用大块——16或32KB12.4.6事务操作和提交策略HRFS是一个事务型文件系统。设置事务或提交点使得磁盘永久改变。Commitpolicies定义了设置那种提交点的具体条件。HRFS可以配置为写一次,自动提交(默认提交策略)。或者基于基于缓存用法和流逝时间执行提交(高速提交策略)。一些磁盘操作不管配置提交,在特定环境下,HRFS回滚,磁盘不发生变化,自从上一次提交,为了保护文件系统的一致性。AutomaticCommitPolicy默认,HRFS自动每写一次,执行一次提交操作。这个功能由INCLUDE_HRFS_DEFAULT_WRITE_MODE组件提供。任何改变磁盘上数据的操作导致一个事务点设置。这是为了防止数据丢失的最安全的策略。也是性能最低的策略,每写一次磁盘,导致一次提交。如下程序,使用自动提交策略时,如会引起磁盘修改,导致提交。write()■remove()VxWorksKernelProgrammer'sGuide,6.8298■delete()■mkdir()■rmdir()■link()■unlink()■truncate()■ftruncate()■ioctl()High-SpeedCommitPolicy高速提交策略当如下条件满足时,针对目前事务,执行提交:·当缓存使用超过40%。·若目前事务依然激活,5s流逝后。INCLUDE_HRFS_HISPEED_WRITE_MODE组件提供的这个功能。提高性能,这个组件配置相关信息,参考12.4.5OptimizingHRFSPerformance。强制提交不管提交策略,有些情况,总是执行提交。强制提交,如满足如下情况:·创建一个文件或目录。·删除一个文件或目录。·重命名、移动一个文件或目录。·节点日志空间耗尽。注明强制提交是一组自动提交——write()和truncate()不会。回滚一个回滚指取消上次提交后的任何更改。当系统未知掉电或复位时发生回滚。当文件系统遇到错误时,发生回滚(如write(),操作没有足够的磁盘空间时,潜在设备驱动产生一个错误时)。回滚的本质是发生在一个操作使得媒介修改时。读操作出现的错误不会造成回滚。一个回滚涉及HRFS返回到磁盘上一次事务提交点的状态,因此保持文件系统的完整度,避免数据丢失。若指明人为或周期性提交策略,有丢失数据的可能性——保留了文件系统的完整性。编程初始化提交若commit()函数要求,应用可以编码初始化提交操作。应用可以确定当写关键数据时,需要提交时。这可以提高性能,但可能丢失数据,INCLUDE_DISK_UTILS组件提供commit()函数。12.4.7文件访问时间戳访问时间戳可以通过配置INCLUDE_HRFS_ACCESS_TIMESTAMP组件使能。这个组件包括在BUNDLE_RTP_POSIX_PSE52组件包中。对于保存到磁盘的访问时间戳,卷必须用HRFSon-diskformat1.2或更高版本格式化。HRFSon-diskformat1.2是VxWorks6.3默认版本。参考hrfsAdvFormat()和hrfsAdvFormatFd()。当包括了访问时间戳组件后,使用正确的磁盘格式版本,从一个文件或目录读数据会引起本身的访问时间戳更新。这个可以导致重要的性能消耗,写操作、读操作、设置事务点都会记录。若应用要求POSIX兼容的情况下,才使用访问时间戳。12.4.8文件和目录最大数HRFS文件和目录都以inodes结构方式存储在磁盘上。在格式化最大inodes数,已经做为一个参数指明为函数hrfsFormat()。所有的文件和目录不能超过inodes数。试图创建一个文件或目录,当所有的inodes大于设置的inode时,会产生一个错误。删除一个文件或目录会释放对应的节点。12.4.9WorkingwithDirectories这一部分讨论了创建和删除目录,读目录入口。CreatingSubdirectories你可以创建和inodes数量一样多的子目录。子目录可用如下方式创建:·用open()。为了创建一个目录,O_CREAT选项必须设置在flags参数中和S_IFDIR或FSTAT_DIR选择必须设置在mode参数中。open()调用返回一个描述新目录的文件描述符。文件描述符智能用于读,不需要的时候关闭它。·用来之usrFsLib中的mkdir()函数。当用上述任何一种方法创建一个目录时,必须指明新的目录名。这个名字可以是一个全路径名或一个和目前工作目录相关的路径名。RemovingSubdirectories要删除的目录必须为空(除了“.”和“..”入口。)根目录不能删除。子目录可用如下方式删除:·使用带FIORMDIR功能的ioctl()函数,指明目录的名字。使用的描述符可以指卷上的任何文件或目录,或整个卷。·使用remove(),,指明目录的名称。·使用来自usrFsLib中的rmdir()函数。ReadingDirectoryEntries你可以用opendir(),readdir(),rewinddir(),和closedir()编程方式使用函数查找HRFS卷上的目录。为了获取关于一个具体文件的更详细的信息,使用fstat()或stat()函数。伴随标准文件信息,这些函数使用的结构也提供来之一个目录入口的文件属性字节。更多信息,参考APIreferencefordirLib。12.4.10WorkingwithFiles这一部分讨论了文件I/O和文件属性。FileI/ORoutinesHRFS文件系统设备上的文件用标准VxWorksI/O函数:creat(),remove(),write(),和read(),创建,删除,写,读。更多信息,参考10.5BasicI/O,和ioLibAPI参考。注明对于HRFS,remove()函数等同于unlink()。FileLinkingandUnlinking12.4.11HRFS支持的I/O控制功能12.4.12崩溃恢复和卷一致性12.4.13文件管理和全设备12.5MS-DOS兼容文件系统:dosFSDosFs文件系统是一个MS-DOS兼容的文件系统,提供相当大的灵活性来满足多个实时应用要求。主要功能有:·分层的文件和目录,允许高效组织和在一个卷上创建任意多文件。·在文件基础上,连接和非连接文件选择。·大范围兼容现有存储设备和检索媒介(录音带,硬盘等)。·从一个dosFs文件系统启动VxWorks的功能。·支持VFAT(MicrosoftVFAT长文件名)。·支持FAT12,FAT16,FAT32文件分配表类型。关于dosFs库的更多信息,参考VxWorksAPIreferencesfordosFsLib和dosFsFmtLib。关于MS-DOS文件系统更多信息,参考微软文档。dosFsandFlashMemory关于dosFs用于flash内存的信息,参考13.FlashFileSystemSupport:TrueFFS。dosFsandtheTransaction-BasedReliableFileSystemFacilitydosFs文件系统可以用于基于事务的可依赖文件系统工具(TRFS);参考12.6Transaction-BasedReliableFileSystemSupportfordosFs:TRFS。12.5.1配置VxWorksfordosFs为了使得VxWorks支持dosFs,配置内核必须的组件和可选的组件。必须的组件要求如下组件:INCLUDE_DOSFS_MAINdosFsLibINCLUDE_DOSFS_FATdosFsFAT12/16/32FAThandlerINCLUDE_XBDXBDcomponent需要如下一个或两个组件:INCLUDE_DOSFS_DIR_VFATMicrosoftVFATdirecthandlerINCLUDE_DOSFS_DIR_FIXEDStrict8.3&VxLongNamesdirectoryhandler另外,你必须为你的块设备增加合适的组件;如INCLUDE_ATA。若你使用一个不设计用于XBD工具的设备驱动,你必须使用INCLUDE_XBD_BLK_DEV封装组件和INCLUDE_XBD组件。参考11.8.2XBDBlockDeviceWrapper。注明:你使用INCLUDE_DOSFS组件,自动包括如下组件:■INCLUDE_DOSFS_MAIN■INCLUDE_DOSFS_DIR_VFAT■INCLUDE_DOSFS_DIR_FIXED■INCLUDE_DOSFS_FAT■INCLUDE_DOSFS_CHKDSK■INCLUDE_DOSFS_FMTOptionaldosFsComponents可选的dosFs组件如下:INCLUDE_DOSFS_PRTMSG_LEVELMessageoutputlevel.Tosuppressmessages,settheDOSFS_PRINTMSG_LEVELconfigurationparameterto0(zero).Thedefault(withorwithoutthiscomponent)istooutputmessages.INCLUDE_DOSFS_CACHEDiskcachefacility.INCLUDE_DOSFS_FMTdosFsfilesystemformattingmodule.INCLUDE_DOSFS_CHKDSKFilesystemintegritychecking.INCLUDE_DISK_UTILStandardfilesystemoperations,suchasls,cd,mkdir,xcopy,andsoon.INCLUDE_TARThetarutility.OptionalXBDComponentsINCLUDE_XBD_PART_LIBDiskpartitioningfacilities.INCLUDE_XBD_TRANSTRFSsupportfacility.INCLUDE_XBD_RAMDRVRAMdiskfacility.关于XBD工具的更多信息,参考11.8ExtendedBlockDeviceFacility:XBD。12.5.2配置dosFs几个dosFs组件配置参数用于当一个dosFs卷挂载时,定义文件系统的行为。参数如下:DOSFS_CHK_ONLY当一个dosFs卷挂载时,媒介进行错误分析,但不修复。DOSFS_CHK_REPAIR和DOSFS_CHK_ONLY相似,但是若发现错误,尝试修复。DOSFS_CHK_NONE媒介在挂载时不进行错误检测。DOSFS_CHK_FORCE和DOSFS_CHK_ONLY和DOSFS_CHK_REPAIR综合使用,即使磁盘标志清除要强制一致性检查。DOS_CHK_VERB_SILENT或DOS_CHK_VERB_0当dosFs挂载时,终端上不会有任何输出。DOS_CHK_VERB_1当dosFs挂载时,终端上产生最少的输出。DOS_CHK_VERB_2当dosFs挂载时,终端上产生最多的输出。其它参数用于配置文件系统的物理属性。DOSFS_DEFAULT_CREATE_OPTIONS针对dosFsLib组件的默认参数。指明了当一个dosFs文件系统实例化时,采取的行为。默认是DOSFS_CHK_NONE。DOSFS_DEFAULT_MAX_FILES最大文件数。默认是20。DOSFS_DEFAULT_FAT_CACHE_SIZEFAT组缓存大小(针对每一个dosFs卷)。默认是16KB。DOSFS_DEFAULT_DATA_DIR_CACHE_SIZE磁盘数据和组目录缓存大小(针对每一个dosFs卷)。DOSFS_CACHE_BACKGROUND_FLUSH_TASK_ENABLEcache-flushing模式。默认设置为FALSE。cache-flushing发生在用户的读写任务上下文中。设置为TRUE,一个分离的高优先级任务发起执行cacheflushing。使得这个单任务加速了写操作(尤其是当dosFS配置大缓存时),但是使用一个分离的高优先级任务可能对于实时性要求比较高的系统不合适。使用这些组件,优化dosFs性能的更多信息,参考12.5.5OptimizingdosFsPerformance。使用dosFsCacheInfo()和dosFsCacheTune()函数的文件系统实例可以动态调整缓存。函数dosFsCacheDelete()和dosFsCacheCreate()用于删除和改变缓存大小。为了改变这个大小,首先删除,其次创建。12.5.3创建一个dosFs文件系统这一部分描述了创建一个dosFs文件系统的过程。首先提供了一个概要,后详细介绍每一步。参考12.5.4dosFs,ATADisk,andRAMDiskExamples。OverviewofdosFsFileSystemCreation操作系统配置相关信息,参考12.5.1ConfiguringVxWorksfordosFs。注明文件系统在启动时自动初始化。创建一个dosFs文件系统的涉及的步骤如下:1若你使用一个定制驱动,创建合适的块设备。参考Step1:CreateaBlockDevice。若你使用一个针对设备的标准的VxWorks组件,自动创建。2若你使用一个不兼容XBD的设备驱动,创建一个XDB设备封装。参考Step2:CreateanXBDDeviceWrapper。(也参考11.8.2XBDBlockDeviceWrapper)3可选,创建和挂载分区。参考Step3:CreatePartitions。4若你没有使用预格式化磁盘,格式化卷,参考Step4:FormattingtheVolume。5可选,改变磁盘缓存大小。参考Step5:ChangetheDiskCacheSize。6可选,检测磁盘卷完整性。参考Step6:CheckDiskVolumeIntegrity。dosFsFileSystemCreationSteps在任何操作执行之前,必须初始化dosFs文件系统库,dosFsLib。这个在启动时自动发生,由包含下系统中的必须的dosFs组件触发。初始化文件系统,触发iosDrvInstall(),这个函数增加驱动到I/O系统设备表。指派给dosFs文件系统的驱动号记录在一个全局变量中,dosFsDrvNum。这个表指明了dosFs文件系统操作的入口点,设备使用dosFs访问。Step1:CreateaBlockDevice若你的设备正在使用一个标准VxWorks组件,自动创建。若你使用一个定制驱动,通过调用设备驱动的创建函数创建合适的块设备。这个函数的格式是xxxDevCreate(),xxx代表设备驱动类型;如scsiBlkDevCreate()或ataDevCreate()。驱动函数返回一个指向块设备描述符结构BLK_DEV的指针。这个结构描述了设备的物理属性和设备驱动提供的函数。返回的指针用于创建一个XBD块设备封装。块设备更多信息,参考TRFSCodeExamples。Step2:CreateanXBDDeviceWrapper若你使用一个不兼容XBD的设备驱动,要求一个XBD设备封装。若你已经在内核中配置了INCLUDE_XBD_BLK_DEVwrapper组件,封装会自动创建。否则使用xbdBlkDevCreate()函数为每一个块设备创建封装。XBD设备封装创建之后,物理设备自动探索文件系统和分区。若一个磁盘已经格式化,则磁盘挂载。若找到一个文件系统,则文件系统挂载。若文件系统不是dosFs,则必须格式化。Step3:CreatePartitions若你在你的系统中包含了INCLUDE_XBD_PART_LIB组件。你可以在一个磁盘上创建分区,挂载卷到分区上。使用xbdCreatePartition()函数创建分区。这一步只能执行一次,当磁盘第一次初始化时。否则会数据丢失。Step4:FormattingtheVolume若你使用一个未格式化磁盘或向取代目前磁盘上的文件系统,通过调用dosFsVolFormat()函数格式化磁盘。更多信息参考这个函数的API参考。MS-DOS和dosFs文件系统提供了文件分配表(FAT)的格式化选项和格式化目录。这些选项,在下面描述,是完全独立的。提醒:重新格式化磁盘,会造成数据丢失,破坏。FileAllocationTable(FAT)Formats一个卷FAT格式化在磁盘格式化过程中设置,更加卷大小或传递给dosFsVolFormat().的每个用户自定义设置。FAT选项概要如表Table12-2:。DirectoryFormats目录格式化选项:■MSFTLongNames(VFAT)使用不区分大小写的长文件名,达到254个字符。这个格式接收创建的短名的磁盘。MSFT长名是默认的目录格式。■ShortNames(8.3)不区分大小写的MS-DOS格式文件名,名字本身带8个大写字母字符和3个扩展字符。Step5:ChangetheDiskCacheSize若你已经包含了INCLUDE_DOSFS_CACHE组件,磁盘缓存被自动创建。两个组件参数定义了数据大小,目录入口和FAT缓存:DOSFS_DEFAULT_DATA_DIR_CACHE_SIZE和DOSFS_DEFAULT_FAT_CACHE_SIZE。另外,缓存可以使用dosFsCacheInfo()和dosFsCacheTune()函数动态改变。你可以针对文件系统的一个特殊实例改变缓存大小,先通过dosFsCacheDelete()函数销毁缓存,后通过dosFsCacheCreate()函数创建缓存。一个磁盘缓存目的是减少访问媒介的次数。不用于RAM磁盘或TrueFFS。若缓存组件包含,应该通过调用函数dosFsCacheDelete()删除缓存。Step6:CheckDiskVolumeIntegrity可选,使用dosFsChkDsk()函数检测卷磁盘完整性。磁盘检测大的磁盘比较耗时。参数DOSFS_DEFAULT_CREATE_OPTIONS(INCLUDE_DOSFS_MAIN)提供检测磁盘的选项,自动取代挂载的dosFs,这个可能是个耗时的过程,直到检测完毕,才能访问文件系统。同样的,可以通过调用dosFsChkDsk()函数执行检测。12.5.4dosFs,ATA磁盘,和RAM磁盘例子本部分提供了先前讨论的例子。这些例子使用不同的配置和设备类型。这意味着方法可以通用于大多数块设备。例子强调:用shell命令创建和在一个ATA磁盘上使用一个dosFs文件系统。创建和格式化分区的编码。创建和格式化一个RAM磁盘卷的代码。本部分例子要求vxWorks配置INCLUDE_DOSFS_FMT组件。其中一个例子依赖于INCLUDE_DOSFS_CACHE组件。提醒:因为设备名通过I/O系统使用简单字符串匹配识别,文件系统不仅使用一个斜线(/)为名字;否则,会发生不可预知的错误。Example12-4CreatedosFsforanATADisk这个例子演示了用shell名字如何初始化一个ATA磁盘上的dosFs文件系统。当这些步骤使用一个XBD兼容的ATA块设备时,他们适用于任何兼容XBD块设备。1若你使用一个定制驱动,创建一个ATA块设备,控制主ATA控制器(控制器0)主ATA硬盘(驱动盘0)。这个设备使用整个磁盘。->xbd=ataXbdDevCreate(0,0,0,0,"/ata")Newsymbol"xbd"addedtokernelsymboltable.Instantiating/ata:0asrawFsxbd=0xca4fe0:value=262145=0x40001xbd变量是device_t类型。0值标示函数ataXbdDevCreate()执行错误。这样一个错误表示一个BSP配置或硬件配置错误。若使用标准的INCLUDE_ATA设备组件,块设备自动创建。注明这种情况下默认设备名是/ata0a。2显示设备信息->devsdrvname0/null1/tyCo/01/tyCo/18yow-grand:9/vio4/ata:0value=25=0x19新ata驱动/ata:0被列出。名称中的0表示没有检测到分区。注明若设备上没有检测到文件系统,rawFs文件系统默认创建,并出现在设备列表中。3在磁盘上创建两个分区,指明50%为第二个分区的磁盘空间,剩下50%是第一个磁盘分区的空间。这一步只执行一次,当磁盘初始化时。->xbdCreatePartition("/ata:0",2,50,0,0)value=0=0x04显示新分区信息。->devsdrvname0/null1/tyCo/01/tyCo/18yow-grand:9/vio3/ata:13/ata:2注明/ata:0没有显示在列表中,/ata:1、/ata:2两个新设备已经增加。每个卷已经实例化rarFs,新尚未格式化。5格式化为dosFs卷。这一步只执行一次,当卷第一次格式化时。若卷已经格式化,则忽略这一步。本例用默认参数格式化文件系统。->dosFsVolFormat("/ata:1",0,0)Formatting/ata:1forDOSFSInstantiating/ata:1asrawFsFormatting...Retrievedoldvolumeparamswith%100confidence:VolumeParameters:FATtype:FAT32,sectorspercluster82FATcopies,0clusters,38425sectorsperFATSectorsreserved32,hidden0,FATsectors76850Rootdirentries0,sysId(null),serialnumber3a80000Label:""...Diskwith40149184sectorsof512byteswillbeformattedwith:VolumeParameters:FATtype:FAT32,sectorspercluster82FATcopies,5008841clusters,39209sectorsperFATSectorsreserved32,hidden0,FATsectors78418Rootdirentries0,sysIdVX5DOS32,serialnumber3a80000Label:""...OK.value=0=0x0->dosFsVolFormat("/ata:2",0,0)Formatting/ata:2forDOSFSInstantiating/ata:2asrawFsFormatting...Retrievedoldvolumeparamswith%100confidence:VolumeParameters:FATtype:FAT32,sectorspercluster82FATcopies,0clusters,19602sectorsperFATSectorsreserved32,hidden0,FATsectors39204Rootdirentries0,sysId(null),serialnumberc78ff000Label:""...Diskwith40144000sectorsof512byteswillbeformattedwith:VolumeParameters:FATtype:FAT32,sectorspercluster82FATcopies,5008195clusters,39204sectorsperFATSectorsreserved32,hidden0,FATsectors78408Rootdirentries0,sysIdVX5DOS32,serialnumberc78ff000Label:""...OK.value=0=0x0更多信息,参考dosFsFmtLib相关的API。6若内核中包含了INCLUDE_DOSFS_CACHE组件,128KB数据,16KB目录和一个64KBFAT缓存默认被创建。缓存大小可以通过先删除,再创建改变。如下例子,删除了默认缓存,创建了个2倍大小的缓存。->dosFsCacheDelete"/ata:1"value=0=0x0->dosFsCacheCreate"/ata:1",0,256*1024,0,32*1024,0,128*1024;value=0=0x07显示dosFs卷信息。->ll"/ata:1"ListingDirectory/ata:1:value=0=0x0->ll"/ata:2"ListingDirectory/ata:2:value=0=0x0->dosFsShow"/ata:2"volumedescriptorptr(pVolDesc):0xc7c358XBDdeviceblockI/Ohandle:0x60001autodiskcheckonmount:NOTENABLEDvolumewritemode:copyback(DOS_WRITE)max#ofsimultaneouslyopenfiles:22filedescriptorsinuse:0#ofdifferentfilesinuse:0#ofdescriptorsfordeletedfiles:0#ofobsoletedescriptors:0currentvolumeconfiguration:-volumelabel:NOLABEL;(inbootsector:)-volumeId:0xc78ff000-totalnumberofsectors:40,144,000-bytespersector:512-#ofsectorspercluster:8-#ofreservedsectors:32-FATentrysize:FAT32-#ofsectorsperFATcopy:39,204-#ofFATtablecopies:2-#ofhiddensectors:0-firstclusterisinsector#78,440-Updatelastaccessdateforopen-read-close=FALSE-directorystructure:VFAT-filenameformat:8-bit(extended-ASCII)-rootdirstartcluster:2FAThandlerinformation:-------------------------allocationgroupsize:501clusters-freespaceonvolume:20,513,562,620bytesvalue=0=0x0如上,我们可以看到针对/ata:2卷的Volume参数。文件系统卷已经挂载,并准备好使用。若你使用一个ATA硬盘或一个来自ATAPICD-ROM驱动的CD-ROM文件系统,你可以可选择的使用usrAtaConfig()函数。这个函数立即处理这几步。参考API相关信息。Example12-5CreatingandPartitioningaDiskandCreatingVolumes这部分代码传递一个指向块设备的指针,创建三个分区,创建这些分区的分区句柄,和创建他们的dosFs设备句柄。STATUSusrPartDiskFsInit(char*xbdName/*devicenameusedduringcreationofXBD*/){constchar*devNames[]={"/sd0a","/sd0b","/sd0c"};devname_txbdPartName;intnewDataCacheSize=0x40000/*256KBdatacache*/intnewFatCacheSize=0x20000/*128KBFATcache*/intnewDirCacheSize=0x8000/*32KBDircache*/DOSFS_CACHE_INFOcacheParams;inti;/*Mappartitionnames*/for(i=1;i<=3;i++){sprintf(xbdPartName,"%s:d",devNames[i-1],i);fsmNameInstall(devNames[i],xbdPartName);}/*createpartitions*/if((xbdCreatePartition(xbdName,3,50,45))==ERROR)returnERROR;/*Formattingthefirstpartition*/if(dosFsVolFormat(devNames[0],2,0)==ERROR)returnERROR;/*Re-configurethecacheforthefirstpartition*/if(dosFsCacheCreate(devNames[0],NULL,newDataCacheSize,NULL,newDirCacheSize,NULL,newFatCacheSize)==ERROR)returnERROR;/*Retrievethecurrentdatacachetuningparametersanddoublethem*/if(dosFsCacheInfoGet(devNames[0],DOS_DATA_CACHE,&cacheParams)==ERROR)returnERROR;cacheParams.bypass=cacheParams.bypass*2;cacheParams.readAhead=cacheParams.readAhead*2;if(dosFsCacheTune(devNames[0],DOS_DATA_CACHE,&cacheParams)==ERROR)returnERROR;/*Formattingthesecondpartition*/if(dosFsVolFormat(devNames[1],2,0)==ERROR)returnERROR;/*Formattingthethirdpartition*/if(dosFsVolFormat(devNames[2],2,0)==ERROR)returnERROR;returnOK;}注明:大多数情况下,可以针对不同的文件系统格式化不同的分区。Example12-6CreatingandFormattingaRAMDiskVolume如下代码创建一个RAM磁盘,用dosFs文件系统,并格式化。STATUSusrRamDiskInit(void/*noargument*/){intramDiskSize=512*1024;/*512KB,512bytespersector*/char*ramDiskDevName="/ram0";device_txbd;/*512byte/sec,nopartitionsupport*/xbd=xbdRamDiskDevCreate(512,ramDiskSize,0,ramDiskDevName);if(xbd==NULL)returnERROR;/*formattheRAMdisk,ignorememorycontents*/dosFsVolFormat(ramDiskDevName,DOS_OPT_BLANK|DOS_OPT_QUIET,NULL);returnOK;}12.5.5优化dosFs性能dosFs文件系统性能可以用如下组件参数设置,缓存改变,write()用法优化。■SetDOSFS_DEFAULT_DATA_DIR_GROUP_CACHE_SIZEto0x2000000.■SetDOSFS_CACHE_BACKGROUND_FLUSH_TASK_ENABLEtoTRUE.■SetDOSFS_DEFAULT_DATA_DIR_CACHE_SIZEto32MB.■SetDOSFS_DEFAULT_FAT_CACHE_SIZEto1MB.■AftercallingdosFsVolFormat(),calldosFsCacheTune()using16fortheclustersargumentand32fortheblocksarguments.■Whencallingwrite(),use512KBforthebytesargument.更多信息,参考12.5.2ConfiguringdosFs。12.5.6WorkingwithVolumesandDisks本部分讨论了访问卷配置信息和同步卷。关于ioctl函数的更多信息,参考12.5.11I/OControlFunctionsSupportedbydosFsLib。AccessingVolumeConfigurationInformationdosFsShow()函数用于shell中显示卷信息。dosFsVolDescGet()函数可以用于编程得到或验证一个指向DOS_VOLUME_DESC结构的指针。更多信息,参考相关API。SynchronizingVolumes当一个磁盘被synchronized时,所有修改的缓存被写入到磁盘,所以磁盘更新。这包括写入文件的数据,更新目录信息,和FAT。为了避免数据丢失,一个磁盘应该在删除之前同步。更多信息,参考close()和dosFsVolUnmount()。12.5.7WorkingwithDirectories12.5.8WorkingwithFiles12.5.9磁盘空间分配选项12.5.10CrashRecoveryandVolumeConsistency12.5.11dosFsLib支持的I/O控制函数12.5.12使用SCSI从一个本地dosFs文件系统启动12.6Transaction-Based支持dosFS的可依赖文件系统:TRFS基于事务的可依赖文件系统(TRFS)工具为dosFs文件系统提供了错误容忍文件系统I/O层。用INCLUDE_XBD_TRANS组件提供。TFFS针对dosFs文件系统——非可依赖的和非基于事务的DOS兼容文件系统提供文件系统一致性和快速恢复。设计操作于XBD兼容设备驱动,针对硬盘,软件,compactflashmedia,TrueFFSflash设备等。可以针对不兼容XBD设备用于XBD封装组件。TRFS针对抵抗突如其来的功率损耗提供可靠性:已经写到媒介上的文件和数据不能受到硬件,不能被删除或破坏,因为数据已经写入到实体上。TRFS在事务功能上提供了额外的保障:数据总是根据当下提交的事务保持完整性。用户应用在文件系统上设置事务点。若系统中有一个未知的失败,文件系统返回到上一次事务点的状态。也就是说,若一个事务提交后,媒介上数据发生变化,但是优于一个功耗损失,文件系统自动恢复到上一次提交事务时的状态,以确保数据的完整性。一旦挂载文件系统,TRFS只要检测到失败,就会回滚到上一次的安全事务。不像一些工具,基于基本的文件,提供数据的完整性,TRFS把媒介作为一个整体保护。针对一个文件系统的事务,意味着设置事务点会提交所有的文件,不仅仅用于一个文件。注:当TRFSI/O层增加到dosFs中,使用一个修改的媒介上个数,不兼容域其它基于FAT的文件系统,包括微软Windows和没有TRFS层的VxworksdosFs文件系统。因此可能当兼容其它系统时,不能使用是一个需求。12.6.1ConfiguringVxWorksWithTRFS增加INCLUDE_XBD_TRANS组件,为dosFS文件系统增加TRFS功能。12.6.2AutomaticInstantiationofTRFS若媒介已经被格式化为TRFS,TRFS自动检测和实例化,和dosFs或HRFS文件系统类似。主要区别是当文件监控检查到TRFS时,调用TRFS创建函数,创建函数创建另外一个XBD实例,并产生一个插入事件。监控检查到新的XBD,并开始探索。这种情况下,监控不直接检测媒介——所有的命令通过TRFS路由,执行合适的转换。若检测到一个文件系统,如dosFs,监控调用dosFs创建函数和实例化dosFs。否则,rawFs被实例化。关于文件系统自动实例化的信息,参考12.2FileSystemMonitor。12.6.3FormattingaDeviceforTRFSTRFS低水平格式化通过如下调用完成:usrFormatTrans(device,overHead,type);参数是:device格式化的设备名,如"/ata"。overHead一个整数,表示用于事务工作空间的磁盘空间(千分之几)。type整数值,FORMAT_REGULAR(0),不从磁盘上保留任何块,或FORMAT_TFFS(1),保留磁盘上第一块。一旦TRFS格式化完成,一个dosFs文件系统可以通过在相同的卷上调用dosFs格式化。当通过dosFsVolFormat()函数,创建一个FAT文件系统,一个事务点在格式化之后自动插入。unformat回滚到事务点。Example12-9FormattingaDeviceforTRFS/*CreateaRAMdiskwith512bytesizedsectorsand1024sectors.*/if(xbdRamDiskDevCreate(512,1024*512,0,"/trfs")==NULL){printf("CouldnotcreateRAMdisk\n");return;}/*PutTRFSontheRAMdisk*/if(usrFormatTrans("/trfs",100,0)!=OK){printf("Couldnotformat\n");return;}/*NowputdosFsonTRFS*/if(dosFsVolFormat("/trfs",DOS_OPT_BLANK,0)!=OK){printf("Couldnotformatfordos\n");return;}/*CreateafileontheTRFS/DosFSvolume*/fd=open("/trfs/myfile",O_CREAT|O_RDWR,0666);if(fd<0){printf("Couldn'tcreatefile\n");return;}/*Committhefilecreationtomedia*/ioctl(fd,CBIO_TRANS_COMMIT,0);12.6.4UsingTRFSinApplications一旦TRFS和dosFs被创建,dosFs文件系统用于正常的文件操作和操作命令。文件系统不会发生任何变化,直到TRFS用于提交。很重要注明整个文件系统——不是单独一个文件——被提交。整个磁盘状态在执行一个提交之前,必须保持一致;也就是说,当文件系统在提交时,将不会发生任何文件系统操作(如,被其它任务)。若多个任务更新文件系统,必须注意确保在设置一个事务点前,文件数据已经处于已知状态。有两种方法来提交文件系统:·使用针对TRFS格式化的设备卷名。·使用打开在TRFS上的文件描述符。usrTransCommit()函数使用TRFS设备卷名,导致执行提交操作。usrTransCommitFd()函数使用打开在TRFS上的一个文件描述符,导致整个文件系统的一次提交。TRFSCodeExamples如下demo强调了创建一个带TRFS的文件系统,并设置一个事务点。第一个程序创建一个新的TRFS层和dosFs文件系统;第二个设置一个事务点。12.7Raw文件系统:rawFsVxWorks提供一个arawfilesystem(rawFs)用于仅要求基本磁盘I/O功能的系统。rawFs文件系统,使用rawFsLib,对待整个磁盘卷如一个单独大文件。尽管dosFs文件系统提供了不同程度的功能,若系统不要求复杂功能的时候,体积和性能是rowFs的优点。rawFs文件系统不强加给磁盘上的数据任何组织。不维持任何目录信息;因此没有磁盘空间划分的概念。rawFs设备上的任何open()操作仅指明设备名;没有其他额外的文件名。整个磁盘域被对待为一个单独的文件,对于任何打开的设备描述符一样。所有的磁盘读写操作都使用一个相对于磁盘开始块的字节偏移。一个rawFs文件系统当插入的媒介不包含一个可识别的文件系统时,自动创建。12.7.1配置VxWorksforrawFs使用rawFs文件系统,配置VxWorks,增加INCLUDE_RAWFS和INCLUDE_XBD组件。若你使用一个不设计用于XBD工具的设备驱动,你必须使用INCLUDE_XBD_BLK_DEV封装组件和INCLUDE_XBD。参考11.8.2XBDBlockDeviceWrapper。设置INCLUDE_RAWFS组件的NUM_RAWFS_FILES参数为期望的打开的最多文件描述符数。使用多个和单个大的文件相关的文件描述符的信息,参考12.7.4rawFsFileI/O,。12.7.2创建一个rawFs文件系统rawFs文件系统是默认的文件系统。当VxWors不能实例化一个已知的文件系统时,如dosFs,HRFS,或cdromFs,自动创建。如下dosFs和HRFS,rawFs没有格式化工具。媒介上没有特别的数据结构,用于标示磁盘为raw。为了人为创建rawFs文件系统,目前的文件系统必须未实例化,并取代为rawFs。在相同的媒介有两个或多个文件系统会在vxWorks系统中产生多个实例。因此当实例化一个新的文件系统时,先前的文件系统必须被删除。参考Example12-10CreatingarawFsFileSystem,说明了这个过程。(参考12.2FileSystemMonitor)rawFs库rawFsLib在启动时自动初始化。rawFsInit()函数在启动vxWorks系统后被usrRoot()任务调用。函数使用一个单独参数,一次最多可以打开的文件描述符数,这个计数用于分配一组描述符;一个描述符用于每次打开的设备。可以用参数NUM_RAWFS_FILES设置INCLUDE_RAWFS组件。rawFsInit()函数也在I/O系统驱动表中(iosDrvInstall())针对rarFs文件系统创建一个入口。针对所有使用rawFs文件系统的设备。这个入口为rawFs文件操作,使用rawFs文件系统的设备指明入口。指派为rawFs文件系统的驱动号存放在一个全局变量中rawFsDrvNum。rawFs文件系统初始化后,一个或更多设备被创建。使用设备驱动的设备创建函数xxDevCreate()创建设备。驱动返回一个指向(BLK_DEV结构描述设备的物理方面和指明一个文件系统可以调用的设备驱动函数。创建后,块设备没有名字,也没有文件系统和它关联。为了初始化一个用于rawFs块设备,已经创建的块设备必须和rawFs关联,必须指派一个名字。这个通过rawFsDevInit()函数完成。参数是用于识别设备的名字和一个指向块设备描述结构(BLK_DEV)的指针。RAW_VOL_DESC*pVolDesc;BLK_DEV*pBlkDev;pVolDesc=rawFsDevInit("DEV1:",pBlkDev);调用rawFsDevInit()函数指派一个具体的设备名和增加到I/O系统设备表(用函数iosDevAdd())。也为设备分配和初始化文件系统卷描述符。返回一个指向卷描述符的指针给调用者;这个指针用于在既定的文件系统调用过程中识别卷。注明初始化rarFs文件系统,不需要格式化磁盘。使用带有FIODISKFORMAT参数的函数ioctl()格式化。注明:不需要磁盘初始化(FIODISKINIT),因为磁盘上没有文件系统结构。注,然后rawRs接收和其它文件系统兼容的ioctl()函数,执行空操作,返回OK。Example12-10CreatingarawFsFileSystem这个例子说明了创建一个rawFs文件系统。intfd;device_txbd;/*MapsomeXBDnames.Use:0and:1sincethediskmayormaynothavepartitions*/fsmNameMap("/ata:0","/rawfs");fsmNameMap("/ata:1","/rawfs");xbd=ataXbdDevCreate(0,0,0,0,"/ata");/*Getanfiledescriptortothecurrentfilesystem*/fd=open("/rawfs",0,0);/*Registeronthepathinstantiatorevent*//*Theejectionofthecurrentfilesystemisasynchronousandishandledbyanothertask.Dependingonrelativeprioritiesthismaynothappenimmediatelysothepathwaitevenfacilityisused.Eachfilesystemwilltripthiseventwhentheyinstatiatetoletwaitingtaskthatitisready.*/fsPathAddedEventSetup(&waitData,"/rawfs");fd=open("/rawfs",0,0);/*Ejectthecurrentfilesystemandputrawfsinitsplace*/ioctl(fd,XBD_SOFT_EJECT,(int)XBD_TOP);/*OurFDisnowinvalid*//*Waitforthepathtoinstantiate*/fsWaitForPath(&waitData);一旦fsWaitForPath()调用返回后,返回一个准备好的rawFs文件系统。12.7.3挂载rawFs卷12.7.4rawFs文件I/O12.7.5rawFslib支持的I/O控制功能12.8CD-ROM文件系统:cdromFsVxWorksCD-ROM文件系统,cdromFs允许应用从根据ISO9660标准文件系统或没有扩展的标准格式化的CDs上读数据。这一部分描述了cdronFs是如何组织的,配置和使用的。cdromFs库,让应用从任意CD-ROMs,CD-Rs,或CD-RWs上读数据。12.9只读内存文件系统:ROMFS12.10目标服务器文件系统13Flash文件系统支持:TrueFFS14网络文件系统:NFS15内存管理15.1介绍VxWorks提供了针对所有在内核中执行代码的内存管理工具,和针对实时进程执行的应用程序的内存管理工具。本章主要处理内核空间内存管理,尽管也提供了关于内存映射相关的信息。这一章讨论了如下主题:·VxWorks组件要求支持的内存管理类型。·针对不同Vxworks配置的内存布局。·ExcludingVxWorks使用的内存。·使用运行时内存autosizing。·内核中存在的内核堆和内存分区管理工具。·内存错误检测工具,包括VxWorks组件和风河编译器提供的工具。·虚拟内存管理,自动化和程序化的。·使用没有MMU的实时进程环境。关于基于进程的应用程序的内存管理工具,参考VxWorksApplicationProgrammer’sGuide:MemoryManagement。关于调试软件故障的附加的错误检测工具,参考19.ErrorDetectionandReporting。注:这一章提供了VxWorks内核中存在的工具信息。关于实时进程存在的工具信息,参考VxWorksApplicationProgrammer’sGuide中相关章节。15.2配置Vxworks内存管理工具关于VxWorks不同内存管理工具配置的信息,在具体工具章节讨论。参考。■15.4ShellCommands,p.379■15.5SystemRAMAutosizing,p.379■15.6ReservedMemory,p.380■15.7KernelHeapandMemoryPartitionManagement,p.380■15.8MemoryErrorDetection,p.382■15.9VirtualMemoryManagement,p.39015.3系统内存映射这一部分针对不同的配置和运行时行为描述了VxWorks内存映射:一个没有进程支持的系统。一个有进程支持的系统,但是没有进程运行。一个有进程支持且有两个进程运行的系统,有一个共享库和一个共享数据域。另外,在一个单一系统中描述了不同内存视角。15.3.1没有进程支持的系统内存映射在VxWorks系统中,RAM通过如下传递:LOCAL_MEM_LOCAL_ADRS配置参数,定义了系统RAM的开始地址。函数返回的地址是系统RAM的顶。若RAM使能autosizing,这个地址在运行时定义(参考15.5SystemRAMAutosizing)。若autosizing无效,sysPhysMemTop()使用BSP配置参数LOCAL_MEM_SIZE计算;sysPhysMemTop()返回LOCAL_MEM_LOCAL_ADRS+LOCAL_MEM_SIZE。(LOCAL_MEM_LOCAL_ADRS和LOCAL_MEM_SIZE通过INCLUDE_MEMORY_CONFIG组件配置)系统RAM必须是连续的。对于没有MMU或MMU禁用的系统而言,这意味着系统RAM必须位于连续的物理内存中。对于一个MMU使能的系统而言,系统RAM必须连续映射在虚拟内存中。后者,某些体系架构物理内存可能是不连续的,这些体系结构不需要一个统一的映射内核。具体参考VxWorksArchitectureSupplement。提醒:VxWorksSMP配置不支持小MMU配置。更多信息参考24.VxWorksSMP。系统内RAM,一个VxWorks系统的元素通过如下安排:RAM_LOW_ADRS下面有一个具体体系结构内存块布局,用于保存启动参数,系统异常信息域,异常或中断向量表等。更具体的信息,参考VxWorksArchitectureSupplement。内核代码(text,data,和bss)开始于地址RAM_LOW_ADRS。驻留ROM镜像例外,text段位于系统RAM外部(参考2.7VxWorksImageTypes)。WDB目标代理内存池位于内核代码上面,若WDB被配置到系统中(参考D.WDBTargetAgent)。内核堆在WDB内存池后面。一个可选的永久内存域。一个可选的用户预留内存域,位于内核堆上面。Figure15-1说明了没有进程支持的系统的通常内存映射。通过和带进程支持的内存映射对比——意味着针对没有创建的进程,有一段没有映射的内存参考Figure15-2.。注明针对WDB目标代理的内存池仅当WDB配置在内核时。没有WDB,内核堆起始于内饰BSSELF段结束。sysMemTop()函数返回内核堆域结束地址。若用户保留域内存大小(USER_RESERVED_MEM)和永久内存大小(PM_RESERVED_MEM)是0,sysMemTop()返回sysPhysMemTop(),相同的值,内核堆扩展了系统RAM域的结束地址。更多信息参考用户保留内存和永久内存。参考15.6ReservedMemory和15.3.2SystemMemoryMapwithProcessSupport。15.3.2带进程支持的系统内存映射无论是否支持进程,内核应用用同样的方式访问相同的内存管理工具。两种配置仅有的区别是内核堆大小的区别。没有进程支持的话,内核堆扩展到sysMemTop()。带进程支持的话,不能扩展到sysMemTop(),使用参数KERNEL_HEAP_SIZE代替(设置在INCLUDE_RTP组件中)。若不支持进程,这个参数忽略。默认,大小设置为和内核代码结束或当WDB组件配置到内核后WDB内存池结束间的三分之二。Figure15-2说明了这个配置。RAM位于sysMemTop()和未映射内核堆结束之间。当进程,共享库,或共享数据域空间被映射时,RAM页从没映射RAM域分配。可和Figure15-1比较。KERNEL_HEAP_SIZE的默认设置应该根据系统要求调整。15.3.3带进程运行的系统内存映射一个配置为实时进程的VxWorks系统可能有一个或多个进程执行。也可能有共享库和共享数据域。内核,每个进程,共享库,共享数据域在虚拟内存上占据分散空间。默认,每个Vxworks进程有自己的虚拟内存域;进程在虚拟内存中不能重叠。这种虚拟内存映射提供了速度优势,在一个可以适应有无MMU情况下的编程模型中,或调试应用中。VxWorks也可以配置成重叠虚拟内存模型;相关信息,参考VxWorksApplicationProgrammer’sGuide:Real-TimeProcesses。指派给一个进程的虚拟空间没有必要有一大块连续的虚拟内存空间组成。某些情况下,由几小块连接的虚拟内存空间组成。Figure15-3说明了带内核域(RAM和I/O),两个不同进程(RTPA和RTPB)的系统的内存映射,还有一个共享库,一个共享数据域。每个进程有自己的虚拟内存,有本身的MMU转换表定义用于映射虚拟和物理内存,和每个内存页的其它信息。这个内存上下文描述了进程中所有任务可以访问的虚拟空间。换句话说,定义了一个进程的内存视角。每个进程的内存上下文中内核空间被映射为高级访问(不用于用户模式)。因此,任务在执行过程中只能在系统调用时访问内核空间,或切换到高级模式下。更多信息,参考系统调用,VxWorksApplicationProgrammer’sGuide。一个共享库或共享数据域映射到一个进程的虚拟上下文中,仅当进程的应用代码打开或创建时,且在应用关闭或共享库或共享数据域删除时,从进程内存视角有效消失。Figure15-4说明了带两个进程(RTPA和RTPB),RTPA和RTPB都打开的一个共享库,和内核应用和RTPB打开的一个共享数据域的系统不同内存视角。第一个内存视图对应内核任务访问的内存空间。第二个和第三个分别对应进程A和进程B。注明灰色域仅在系统调用时访问。注明没有MMU,或MMU禁用系统上仅有一个内核和所有进程任务共享的内存视图。这个内存视图对应Figure15-3。系统中的任何任务,是否在内核中或进程中,可以访问所有的内存:内核空间,I/O域,任何进程内存,共享库和共享数据域。换句话说,这样的配置不提供任何内存保护。更多信息参考VxWorksApplicationProgrammer’sGuide:Real-TimeProcesses。15.4Shell命令Shell的adrSpaceShow()展示函数(对应C解析器)或adrspinfo命令(对于命令行解析器)可用于展示调用过程中地址空间的概要。分别有INCLUDE_ADR_SPACE_SHOW和INCLUDE_ADR_SPACE_SHELL_CMD组件提供。rtpMemShow()展示函数或rtpmeminfo命令可以用于展示进程的私有映射。这些功能由INCLUDE_RTP_SHOW和INCLUDE_VM_SHOW_SHELL_CMD组件提供。内核映射可以通过vmContextShow()展示函数或vmcontext命令展示。这些分别由INCLUDE_VM_SHOW和INCLUDE_VM_SHOW_SHELL_CMD组件提供。15.5系统RAMAutosizing当BSP支持RAMautosizing时,当使能运行时内存大小时,定义配置参数LOCAL_MEM_AUTOSIZE。针对这个参数和实现本身的默认定义状态取决于BSP。检查BSP引用查看是否支持这个功能。当BSP支持autosizing和定义了LOCAL_MEM_AUTOSIZE,系统RAM的顶部被报告为在运行时sysPhysMemTop()函数返回的值。若没有定义LOCAL_MEM_AUTOSIZE,系统RAM顶部被sysPhysMemTop()返回,通过如下计算:(LOCAL_MEM_LOCAL_ADRS+LOCAL_MEM_SIZE)若BSP不能执行运行时内存自动大小,则一个编译器错误会产生,通知受限的用户。LOCAL_MEM_AUTOSIZE,LOCAL_MEM_LOCAL_ADRS和LOCAL_MEM_SIZE是INCLUDE_MEMORY_CONFIG组件的的参数。15.6预留内存VxWorks中系统RAM的预留内存配置有两种方式:用户预留内存和永久内存。预留内存在系统启动或系统操作过程中不能清除。Bootloader不能也可能清除这个域,参考BootLoadersandReservedMemory。用户预留内存,用BSP参数USER_RESERVED_MEM配置,是系统RAM的一部分,独立于内核堆,由内核应用管理。永久内存,用参数PM_RESERVED_MEM配置,是系统RAM的一部分,用于错误检测和报告工具。(参考19.ErrorDetectionandReporting)用户预留内存永久内存的布局,参考Figure15-1和Figure15-2.。Bootloaders和预留内存Bootloader不确定清除预留内存,取决于用于创建它们的配置。若bootloader创建它时,USER_RESERVED_MEM和PM_RESERVED_MEM都设置为0,系统RAM通过如下地址计算清除:(LOCAL_MEM_LOCAL_ADRS+LOCAL_MEM_SIZE)为了确保预留内存不被清除,bootloader应该用设置期望值的USER_RESERVED_MEM和PM_RESERVED_MEM来创建;也就是说,相同的值要用于创建下载型VxWorks镜像。更多信息,参考3.BootLoader。注:若系统RAMautosizing使能,运行时系统RAM顶端可能和LOCAL_MEM_LOCAL_ADRS+LOCAL_MEM_SIZE计算的地址不同,导致不同的内存范围被清除。更多信息,参考15.5SystemRAMAutosizing。15.7内核堆和内存分区管理VxWorks提供了堆访问和内存分区管理工具。memLib和memPartLib库提供了访问内核堆的函数,包括标准ANSI-兼容函数,和内核内存分区管理的函数。内核堆被所有运行在内核中的代码使用,包括内核库和组件,内核应用,和执行系统调用的进程。内存分区包括应用和内核组件用于动态内存分配的内存区域。内存分区用于为具体的应用预留内存空间,或隔离应用上的动态内存用法。内核堆是一个典型的内存分区,也指系统内存分区。15.7.1配置内核堆和内存分区管理有两个组件用于配置内核堆和内存分区管理。内核堆和内存分区的核心功能由INCLUDE_MEM_MGR_BASIC组件提供(参考memPartLib中的API)。INCLUDE_MEM_MGR_FULL是一个扩展组件,是一个扩展功能,针对完全功能堆和内存分区管理。(参考memLib的API)内核堆在这些组件中的任何一个被配置进内核时被自动创建。内核堆大小如15.3SystemMemoryMaps描述设置,参考Figure15-1和Figure15-2。用INCLUDE_MEM_SHOW组件提供的展示函数获取的内核堆和内核内存分区的分配统计信息。更多信息,参考memShow.相关API。15.7.2基本堆和内存分区管理memPartLib库(INCLUDE_MEM_MGR_BASIC)提供内存分区支持的核心功能。包括标准的ANSI-兼容函数,如malloc()和free()。memPartLib提供的核心功能,包括如下API:·用memPartCreate()和memPartDelete()创建和删除内存分区。·增加内存到具体的内存分区,用memPartAddToPool()或memAddToPool()。·分配和是否一个具体内存分区的内存块,用memPartAlloc()、memPartAlignedAlloc()和memPartFree();malloc()和free()。15.7.3完整堆和内存分区管理memLib库(INCLUDE_MEM_MGR_FULL)为了提供完全功能内存分区和堆分配提供了一些函数。库提供功能如下:·分配对齐与具体边界的内存,使用memalign(),和页对齐,使用valloc()。·用在具体分区中重新分配内存块,使用memPartRealloc(),或堆中使用realloc()。·ANSI兼容函数calloc()和cfree()。·用memPartInfoGet()和memPartFindMax()获取内存分区统计,或在堆中,用memFindMax()和memInfoGet()。·内置错误检测。这个功能由堆和分区选项控制。使能两类错误。第一类,块错误,在free(),realloc(),memPartFree()和memPartRealloc()块验证过程中,检测第一种类型错误。第二种错误类型是分配错误,由任何一个分配函数或重新分配函数检测。可以使能记录错误信息选项和遇到错误时,挂起任务。设置和获取一个具体内存分区的错误处理选项用memPartOptionsSet()和memPartOptionsGet()完成。堆的调试选项由memOptionsSet()和memOptionGet()控制。附近的堆和内存分区错误检测由堆和分区内存工具提供(参考15.8.1HeapandPartitionMemoryInstrumentation)。更多信息,参考,memPartLib和memLib相关API。15.8内存错误检测内存错误检测由如下可选工具库提供:·memEdrLib库,针对进程中的用户堆和内存分区执行错误检测。这个库可以和风河编译器或GUN编译器一块可执行编译。·Run-TimeErrorChecking(RTEC)工具,检测额外的错误,如缓存上溢和下溢,动态或自动变量引用检测。(注明这个功能仅有风河编译器提供,TREC是VxWorks支持的Run-TimeAnalysisToolSuite—RTA—编译器的仅有功能)。检测到的错误通过错误检测工具或报告工具报告,因此这些错误报告相关的组件必须在内核中配置,参考19.ErrorDetectionandReporting。15.8.1堆和分区内存工具为了补充错误检测功能到memLib和memPartLib,必须增加相关组件,在memLib和memPartLib操作中执行自动化,程序化,交互式错误检测。这些组件帮助检测正常编程错误如重复释放一个已分配的块,释放或重新分配一个无效的指针,内存泄露。另外,使用编译器辅助工具,帮助检测边界检测错误,缓存上溢下溢,指针引用一个释放的内存块,指针引用变量外自动变化变量等。注明,辅助工具必须用于跟踪缓存上溢下溢。编译器工具的更多信息,参考15.8.2CompilerInstrumentation。自动化错误检查通过错误检查和报告工具记录错误。配置VxWorks内存分区和堆工具为了使能内存分区和堆工具的基本级别,必须配置如下控件到内核:■INCLUDE_MEM_EDR,包括基本的内存分区调试功能和工具。■INCLUDE_EDR_ERRLOG,INCLUDE_EDR_POLICIES和INCLUDE_EDR_SHOW包括错误检查,报告,和永久内存。更多信息参考19.ErrorDetectionandReporting。如下组件也应该包括:■INCLUDE_MEM_EDR_SHOW,使能显示函数。另外,INCLUDE_MEM_EDR组件如下参数需要修改:MEDR_EXTENDED_ENABLE设置为TRUE为每个分配的块使能日志跟踪信息,但是增加了内存开销,用于在分配数据库中存储入口。默认是FALSE。MEDR_FILL_FREE_ENABLE设置TRUE来使能pattern-filling队列释放块。这个帮助检测写入释放的缓存。默认设置为FALSE。MEDR_FREE_QUEUE_LEN释放队列的最大长度。当释放一个内存块时,立即返回到分区内存池,保持在队列中。这个对于引用已经释放的内存块比较有用。当队列达到最大允许的长度时,块以FIFO顺序返回到各个的内存池中。当参数为0时,队列无效。默认是64。MEDR_BLOCK_GUARD_ENABLE在每一个分配块前面和后面使能哨兵位。使能这个功能帮助检测缓冲上溢下溢和一些内存泄露。默认设置为FALSE。MEDR_POOL_SIZE设置内存池大小用于维护内存块数据库。默认设置为1MB。数据库使用每个内存块32字节,无须使能扩展信息(调用栈跟踪)。这个池从内核堆分配。错误类型在执行过程中,当分配、释放、重新分配函数调用时,错误被自动记录。如下错误类型被自动识别和记录:·分配块地址在同一个分区中一个已经分配块中。这表示分区内数据结构破坏。·分配返回任务栈空间块地址。这个表示分区内数据结构破坏。·分配返回内核静态数据区块地址。这表示分区数据结构破坏。·释放一个任务内栈空间指针。·释放一个已经释放的内存和仍在释放队列中的内存。·释放内核静态数据区域内存。·在分配内存的分区内释放其它分区的内存。·释放部分内存块。·当MEDR_BLOCK_GUARD_ENABLE环境变量为TRUE,释放一个带保护区破坏的内存块。·当MEDR_FILL_FREE_ENABLE环境变量为TRUE,在释放队列中的类型在释放队列中的被破坏的数据块。Shell命令Table15-1列出了显示函数和命令,用于shellC和命令行解析器来显示信息。代码例子如下内核应用代码用于演示用堆和分区内存工具处理不同检测到的错误(行号和引用目的)。用法在ShellSessionExample说明。#include<vxWorks.h>#include<stdlib.h>voidheapErrors(void){char*pChar;pChar=malloc(24);free(pChar+2);/*freepartialblock*/free(pChar);free(pChar);/*double-freeblock*/pChar=malloc(32);/*leakedmemory*/}Shellsession例子如下shellsession通过C解析器执行。样本代码被编译和链接到内核。内核必须包含INCLUDE_MEM_EDR和INCLUDE_MEM_EDR_SHOW组件。为了使能保存调用栈信息,参数MEDR_EXTENDED_ENABLE被设置为TRUE。还有,内核应该配置错误检测和报告工具,包括展示函数,参考19.2ConfiguringErrorDetectionandReportingFacilities。首先:标志索引分配的块:->memEdrBlockMarkvalue=6390=0x18f6下一步,清除错误日志。这一步可选,用于清除日志。->edrClearvalue=0=0x0内核应用可以通过sp()工具以一个新任务启动。->taskId=sp(heapErrors)Newsymbol"taskId"addedtokernelsymboltable.Taskspawned:id=0x246d010,name=t1taskId=0x2469ed0:value=38195216=0x246d010此时任务完成执行。如下命令列出了分配的内存块,但是没有被应用释放。注明列表显示了分配时间的调用栈。执行时,检测到的错误被记录到永久内存域。使用显示日志。第一个错误对应demo中第9行;第二个错误对应第12行。->edrShowERRORLOG=========LogSize:524288bytes(128pages)RecordSize:4096bytesMaxRecords:123CPUType:0x5aErrorsMissed:0(old)+0(recent)Errorcount:2Bootcount:20Generationcount:94==[1/2]==============================================================Severity/Facility:NON-FATAL/KERNELBootCycle:20OSVersion:6.0.0Time:THUJAN0100:00:311970(ticks=1880)Task:"t1"(0x0246d010)freeingpartofallocatedmemoryblockPARTITION:0x269888PTR=0x246bea2BLOCK:allocatedat0x0246bea0,24bytes<<<<<Traceback>>>>>0x0011d240vxTaskEntry+0x54:heapErrors()0x00111364heapErrors+0x24:free()0x001c26f8memPartFree+0xa4:0x001bdbb4()0x001bdc6cmemEdrItemGet+0x588:0x001bd71c()==[2/2]==============================================================Severity/Facility:NON-FATAL/KERNELBootCycle:20OSVersion:6.0.0Time:THUJAN0100:00:311970(ticks=1880)Task:"t1"(0x0246d010)freeingmemoryinfreelistPARTITION:0x269888PTR=0x246bea0BLOCK:freeblockat0x0246bea0,24bytes<<<<<Traceback>>>>>0x0011d240vxTaskEntry+0x54:heapErrors()0x00111374heapErrors+0x34:free()0x001c26f8memPartFree+0xa4:0x001bdbb4()0x001bdc6cmemEdrItemGet+0x588:0x001bd71c()value=0=0x015.8.2编译器工具若用户使用WindRiverCompiler(Diab)的Run-TimeErrorChecking(RTEC)功能编译应用可以检测到其它的错误。应该包含如下标志:-Xrtc=option注:Run-TimeErrorChecking(RTEC)功能在GNU编译器中没有。带-Xrtc标志的代码编译时针对运行时检测的工具,如指针引用检测和指针运算验证,标准库参数验证等。这些工具通过内存分区运行错误检测库支持。Table15-2列出了支持的-Xrtc选项。使用-Xrtc标志,不需要指明任何选项实现。一个独立的选项(或按位选项的组合)可用如下语法使能:-Xrtc=option通过TREC编译工具检测出来的错误和警告通过检测和报告设备记录(参考19.ErrorDetectionandReporting)。如下是识别的错误类型:·针对已分配内存块的边界检测。·静态(全局)变量的边界检测。·自动变化变量的边界检测。·引用释放队列中的块。·引用释放的任务栈栈部分。·重复引用一个NULL指针。其它信息,参考WindRiverCompilerUser’sGuide:Run-TimeErrorChecker。配置VxWorks支持RTEC增加INCLUDE_MEM_EDR_RTC组件。参考ConfiguringVxWorkswithMemoryPartitionandHeapInstrumentation。Shell命令编译器提供使用错误检测和报告工具自动检测应用中的日志错误工具。针对错误日志的一系列shell命令,参考19.4DisplayingandClearingErrorRecords。代码例子用RTEC工具编译的应用代码有编译器产生的构造函数。为了保证当一个模块被动态加载,构造函数被调用,模块必须和C++应用一样处理。如下规则可以使用:TGT_DIR=$(WIND_BASE)/target%.out:%.c@$(RM)$@$(CC)$(CFLAGS)-Xrtc=0xfb$(OPTION_OBJECT_ONLY)$<@$(RM)ctdt_$(BUILD_EXT).c$(NM)$(basename$@).o|$(MUNCH)>ctdt_$(BUILD_EXT).c$(MAKE)CC_COMPILER=$(OPTION_DOLLAR_SYMBOLS)ctdt_$(BUILD_EXT).o$(LD_PARTIAL)$(LD_PARTIAL_LAST_FLAGS)$(OPTION_OBJECT_NAME)$@$(basename$@).octdt_$(BUILD_EXT).oinclude$(TGT_DIR)/h/make/rules.library如下程序代码产生了不同的错误,包括行号。用法在ShellSessionExample说明。#include<vxWorks.h>#include<stdlib.h>#include<string.h>voidrefErrors(){charname[]="very_long_name";char*pChar;intstate[]={0,1,2,3};intix=0;pChar=malloc(13);memcpy(pChar,name,strlen(name));/*boundscheckviolation*//*ofallocatedblock*/for(ix=0;ix<4;ix++)state[ix]=state[ix+1];/*boundscheckviolation*/free(pChar);memcpy(pChar,"another_name",12);/*referenceafreeblock*/}ShellSession例子如下shellsession日志用C解析器执行。样本代码在VxWorks内核中编译和链接。内核必须包含INCLUDE_MEM_EDR和INCLUDE_MEM_EDR_RTC组件。内核也必须配置错误检测和报文工具,包括展示函数,如19.2ConfiguringErrorDetectionandReportingFacilities描述。首先,清除日志:->edrClearvalue=0=0x0用工具启动内核应用:->sprefErrorsTaskspawned:id=0x246d7d0,name=t2value=38197200=0x246d7d0此时,应用完成执行。执行时,已经完成错误检测,并记录错误到永久内存。使用edrShow()显示错误信息。->edrShowERRORLOG=========LogSize:524288bytes(128pages)RecordSize:4096bytesMaxRecords:123CPUType:0x5aErrorsMissed:0(old)+0(recent)Errorcount:3Bootcount:21Generationcount:97第一个错误对应测试程序的13行。长度为14的字符串被拷贝到缓存长度为13的缓存中。==[1/3]==============================================================Severity/Facility:NON-FATAL/KERNELBootCycle:21OSVersion:6.0.0Time:THUJAN0100:14:221970(ticks=51738)Task:"t2"(0x0246d7d0)InjectionPoint:refErr.c:13memoryblockbounds-checkviolationPTR=0x246be60OFFSET=0SIZE=14BLOCK:allocatedat0x0246be60,13bytes<<<<<Traceback>>>>>0x0011d240vxTaskEntry+0x54:0x00111390()0x00111470refErrors+0xe4:__rtc_chk_at()0x001bd02cmemEdrErrorLog+0x13c:_sigCtxSave()第二个错误指18行:局部state数组引用索引4.因为数组只有4个元素,有效索引是0到3。==[2/3]==============================================================Severity/Facility:NON-FATAL/KERNELBootCycle:21OSVersion:6.0.0Time:THUJAN0100:14:221970(ticks=51738)Task:"t2"(0x0246d7d0)InjectionPoint:refErr.c:18memoryblockbounds-checkviolationPTR=0x278ba94OFFSET=16SIZE=4BLOCK:automaticat0x0278ba94,16bytes<<<<<Traceback>>>>>0x0011d240vxTaskEntry+0x54:0x00111390()0x001114a0refErrors+0x114:__rtc_chk_at()0x001bd02cmemEdrErrorLog+0x13c:_sigCtxSave()最后一个错误是22行。修改已经释放的内存块:==[3/3]==============================================================Severity/Facility:NON-FATAL/KERNELBootCycle:21OSVersion:6.0.0Time:THUJAN0100:14:221970(ticks=51739)Task:"t2"(0x0246d7d0)InjectionPoint:refErr.c:22pointertofreememoryblockPTR=0x246be60OFFSET=0SIZE=12BLOCK:freeblockat0x0246be60,13bytes<<<<<Traceback>>>>>0x0011d240vxTaskEntry+0x54:0x00111390()0x00111518refErrors+0x18c:__rtc_chk_at()0x001bd02cmemEdrErrorLog+0x13c:_sigCtxSave()15.9虚拟内存管理VxWorks可以用一个独立于体系结构的接口为CPU的内存管理单元(MMU)提供虚拟内存支持。包括如下功能:·在启动时间设置内核内存上下文。·虚拟空间到物理内存的映射页。·设置每一个基本页的缓存熟悉。·设置每一个基本页的保护属性。·设置一个页映射有效或无效。·针对内存页锁定或解锁TLB入口。·使能页优化。虚拟内存(VM)的可编程元素由如下vmBaseLib库提供。注明:对称多处理器(SMP)和单处理器(UP)的VxWorks配置中提供的vmBaseLib库,和用于优化SmP的信息是不同的。vmBaseLib和SMP,的更多信息参考vmBaseLibRestrictions和UsingvmBaseLib。VxWorksSMP和移植的更多信息,参考24.VxWorksSMP和24.17MigratingCodetoVxWorksSMP。当VxWorks中配置了INCLUDE_RTP组件时,支持进程(RTP),虚拟内存工具针对多个虚拟内存上下文的管理提供支持,如进程内存上下文的创建和删除。关于附加的基于MMU的内存保护功能,参考15.10AdditionalMemoryProtectionFeatures。虚拟内存在使用过程中产生的错误的检测和报告,参考19.ErrorDetectionandReporting。15.9.1配置虚拟内存管理列出的组件提供了基本的虚拟内存管理,也提供了用于shell的展示函数。更多信息,参考15.10AdditionalMemoryProtectionFeatures。配置内核虚拟内存上下文内核虚拟内存上下文根据BSP提供的配置数据在启动时自动创建。sysPhysMemDesc[]表中,主要数据通常在BSP中的sysLib.c文件中定义。这个表定义了初始化内核映射和初始化属性。这个表的入口是PHYS_MEM_DESC结构类型,在vmLib.h中定义。通常不需要改变默认sysPhysMemDesc[]配置。然而,有时需要修改,如:支持新的驱动或设备(如flash内存)被增加到系统中。保护或既定入口的缓存属性必须改变。如flash内存入口为只读,如flash内存从来不被vxWorks写。然而,若一个flash驱动,如TrueFFS使用,必须设置受保护属性。表中有未使用的入口。通常,最好仅保存实际描述系统的入口,因为每个入口可能需要额外的系统RAM用于页表(取决于入口大小,相对于其他入口的位置,具体体系结构MMU参数等)。映射的比较大的内存块,更多的内存用于页表。sysPhysMemDesc[]表可以在运行时修改。这对于如PCI驱动可以被自动配置,意味着在运行时检测内存需求,这是很有用的。这种情况下,域大小和域地址可以根据对应的sysPhysMemDesc[]入口编程更新。确信在VM子系统用usrMmuInit()初始化前更新是非常重要的,如在sysHwInit()中执行。提醒:sysPhysMemDesc[]中定义的内存域必须页对齐,必须跨越完整页。换句话说,PHYS_MEM_DESC结构的前三个域必须有偶数个MMU页大小。指明若sysPhysMemDesc[]中元素没有页对齐,会在启动过程中重启。参考系统结构支持相关章节。配置例子这个例子基于多个CPU,使用共享内存网络。一个单个的内存卡用于共享内存池。因为这个内存不是默认映射的,必须为网络上的所有网卡在sysPhysMemDesc[]中增加映射。内存从开始,必须配置为非缓存,如下所示:/*sharedmemory*/{(VIRT_ADDR)0x4000000,/*virtualaddress*/(PHYS_ADDR)0x4000000,/*physicaladdress*/0x20000,/*length*//*initialstatemask*/MMU_ATTR_VALID_MSK|MMU_ATTR_PROT_MSK|MMU_ATTR_CACHE_MSK,/*initialstate*/MMU_ATTR_VALID|MMU_ATTR_PROT_SUP_READ|MMU_ATTR_PROT_SUP_WRITE|MMU_ATTR_CACHE_OFF}针对一些提醒结构,系统RAM(用于VIP、内核堆等)必须统一映射。这意味着sysPhysMemDesc[]表中对应入口,虚拟地址和物理地址必须一样。更多信息,参考15.3SystemMemoryMaps和VxWorksArchitectureSupplement。15.9.2编程管理虚拟内存这一部分描述了用于编程操作MMU的工具,使用vmBaseLib中的函数。你可以使得内存部分非缓存,写保护内存部分,无效页,TLB入口加锁或优化内存页大小。关于虚拟内存函数更多信息,参考VxWorksAPIreferenceforvmBaseLib。注:对称多处理器(SMP)和单处理器(UP)的VxWorks配置,和优化SmP应用的具体规定提供在不同的vmBaseLib库中。vmBaseLib和SMP更多信息参考vmBaseLibRestrictions和UsingvmBaseLib,关于SMP和移植的更多信息,参考24.VxWorksSMP和24.17MigratingCodetoVxWorksSMP。修改页状态每个虚拟内存页(通常4KB)关联一个状态。一个页可能是无效/有效,可读,可选,可执行,或缓存/非缓存。一个页的状态可以通过vmStateSet()函数改变。参考Table15-4和Table15-5列出的vmStateSet()函数使用的页状态常量和也状态标签。并不是所有的CPU支持所有的混合保护设置。如很多CPU不支持执行或非执行设置。这些CPU,可读也就意味着可执行。关于具体体系结构页状态和他们的组合更多信息,参考VxWorksArchitectureSupplement。使得内存非可写内存部分可以使用vmStateSet()写保护来防止无意的访问。这个用于限制一个特定函数修改一个数据对象。若一个数据对象全局只读,任务可以读这个对象,但不可以修改。任何必须修改这个对象的任务必须调用关联的函数。函数内部,数据可写,退出函数,内存设置为MMU_ATTR_PROT_SUP_READ。非可写内存例子这段代码中,一个任务调用dataModify()修改pData指向的数据结构。这个函数使得内存可写,修改数据,设置内存为不可写。若一个任务随后不使用dataModify(),尝试修改数据,一个数据访问异常产生。/*privateCode.h-headerfiletomakedatawritablefromroutineonly*/#defineMAX1024typedefstructmyData{charstuff[MAX];intmoreStuff;}MY_DATA;/*privateCode.c-usesVMcontextstomakedataprivatetoacodesegment*/#include<vxWorks.h>#include<string.h>#include<vmLib.h>#include<semLib.h>#include"privateCode.h"MY_DATA*pData;SEM_IDdataSemId;intpageSize;/*************************************************************************initData-allocatememoryandmakeitnonwritable**Thisroutineinitializesdataandshouldbecalledonlyonce.**/STATUSinitData(void){pageSize=vmPageSizeGet();/*createsemaphoretoprotectdata*/dataSemId=semBCreate(SEM_Q_PRIORITY,SEM_EMPTY);/*allocatememory=toapage*/pData=(MY_DATA*)valloc(pageSize);/*initializedataandmakeitread-only*/bzero((char*)pData,pageSize);if(vmStateSet(NULL,(VIRT_ADDR)pData,pageSize,MMU_ATTR_PROT_MSK,MMU_ATTR_PROT_SUP_READ)==ERROR){semGive(dataSemId);return(ERROR);}/*releasesemaphore*/semGive(dataSemId);return(OK);}/**********************************************************************dataModify-modifydata**Tomodifydata,tasksmustcallthisroutine,passingapointerto*thenewdata.*Totestfromtheshelluse:*->initData*->spdataModify*->dpData*->bfill(pdata,1024,‘X’)*/STATUSdataModify(MY_DATA*pNewData){/*takesemaphoreforexclusiveaccesstodata*/semTake(dataSemId,WAIT_FOREVER);/*makememorywritable*/if(vmStateSet(NULL,(VIRT_ADDR)pData,pageSize,MMU_ATTR_PROT_MSK,MMU_ATTR_PROT_SUP_READ|MMU_ATTR_PROT_SUP_WRITE)==ERROR){semGive(dataSemId);return(ERROR);}/*updatedata*/bcopy((char*)pNewData,(char*)pData,sizeof(MY_DATA));/*makememorynotwritable*/if(vmStateSet(NULL,(VIRT_ADDR)pData,pageSize,MMU_ATTR_PROT_MSK,MMU_ATTR_PROT_SUP_READ)==ERROR){semGive(dataSemId);return(ERROR);}semGive(dataSemId);return(OK);}无效内存页无效一个内存页,使用vmStateSet()如下:vmStateSet(NULL,address,len,MMU_ATTR_VALID_MSK,MMU_ATTR_VALID_NOT);任何对一个无效内存页的访问都会产生一个异常。重新有效内存页,使用vmStateSet()如下:vmStateSet(NULL,address,len,MMU_ATTR_VALID_MSK,MMU_ATTR_VALID);锁定TLB入口针对一些处理器,可能强制TranslationLook-asideBuffer(TLB)在TLB中永久保存。当对应MMU库支持这个功能时,函数vmPageLock()用于锁定页入口,和vmPageUnlock()函数解锁页入口。INCLUDE_LOCK_TEXT_SECTION组件提供TLB锁定功能。当这个组件包含到内核中时,内核镜像text段在启动过程中自动加锁。这个功能可以用于性能优化,和缓存锁定相似。当常用页锁定在TLB中,TLB数量丢失会减少。注明TLB入口数量通常被处理器类型限制,所以锁定太多的入口会导致现有入口在动态使用时竞争。更多信息,参考VxWorksArchitectureSupplement。页大小优化对于一些处理器,可能要使能大页,取代默认页大小VM_PAGE_SIZE,连续的有同样内存属性的内存块。优化有如下优点:·减少需要映射到内存的页表入口(PTE)数目,导致使用更少的内存。·最有效的TLB入口用法,导致最少的TLB丢失,因此会有更好的性能。启动时,整个内核内存空间优化(包括I/O块)可以通过配置组件INCLUDE_PAGE_SIZE_OPTIMIZATION完成。具体内存块的页大小优化可以通过调用vmPageOptimize()函数完成。De-optimization在必须的时候自动执行。如已经优化的内存块的一部分有不同的属性,大页被自动划分为多个小页,新的属性仅被设置要求的页。设置ISRs中页状态对于更多的处理器类型,vmStateSet()是一个非阻塞函数,可以在中断中安全调用。然而,某些情况下可能会阻塞,如支持页大小优化的处理器(参考PageSizeOptimization)。为了确定在vmStateSet()中断中安全调用,页必须首先设置MMU_ATTR_NO_BLOCK属性,如下例子展示操作过程:#include<vxWorks.h>#include<vmLib.h>#defineDATA_SIZE0x10000char*pData;voidsomeInitFunction(){/*allocatebuffer*/pData=(char*)valloc(DATA_SIZE);/*setno-blockattributeforthebuffer*/vmStateSet(NULL,(VIRT_ADDR)pData,DATA_SIZE,MMU_ATTR_SPL_MSK,MMU_ATTR_NO_BLOCK);}voidsomeISR(){.../*nowit'ssafetosetanyattributeforthebufferinanISR*/vmStateSet(NULL,(VIRT_ADDR)pData,DATA_SIZE,MMU_ATTR_PROT_MSK,MMU_ATTR_SUP_RWX);...}15.9.3问题解决Table15-6中描述的展示函数和命令可以帮助解决虚拟内存相关问题。这些函数和命令由NCLUDE_VM_SHOW、INCLUDE_VM_SHOW_SHELL_CMD、INCLUDE_RTP_SHOW、和INCLUDE_RTP_SHOW_SHELL_CMD组件提供。更多信息,和用法参考VxWorksshellreferences。15.10附加的内存保护功能VxWorks为了提供一个更可靠的运行环境提供了基于MMU的基本虚拟内存支持的补充功能。附加内存保护功能如下:·任务栈上溢和下溢检测。·中断栈上溢和下溢检测。·非可行性任务栈。·Text段写保护。·异常向量表写保护。关于基本虚拟内存支持的信息,参考15.9VirtualMemoryManagement。VxWorks附加工具使用这些功能进行错误检测和管理。参考19.ErrorDetectionandReporting。15.10.1配置Vxworks附加内存保护Table15-7表列出了支持附加内存保护功能的组件。可以以单元方式增加INCLUDE_KERNEL_HARDENING组件增加这些功能。独立的和合成的组件包括默认的INCLUDE_MMU_BASIC基本内存组件。注:内核text段保护——内核模块的text段动态加载到内存空间——不是默认提供。另一方面,进程和共享库的text段通常是写保护的,无论VxWorks是否配置INCLUDE_PROTECT_TEXT组件。通用一个进程任务的执行栈不会受到INCLUDE_PROTECT_TASK_STACK和INCLUDE_TASK_STACK_NO_EXEC组件影响——通常受保护除非taskSpawn()发起任务时,使用了VX_NO_STACK_PROTECT选项。15.10.2栈上溢或下溢检测VxWorks可以配置岗哨区插入到任务执行栈的开始和结束。更多信息,参考TaskStackGuardZones。操作系统可以配置插入岗哨区到中断栈的开始和结束。参考InterruptStackProtection。15.10.3不可执行任务栈VxWorks可以配置任务栈为非可执行的。更多信息,参考Non-ExecutableTaskStacks。15.10.4Text段写保护当VxWorks配置了INCLUDE_PROTECT_TEXT组件时,所有的text段是写保护的。当VxWorks加载时,所有的text是写保护的。任何附加对象模块的text段加载到内核空间,使用ld()显示只读。当对象模块加载后,写保护的内存以页大小方式分配。写保护内核应用代码没有额外的步骤要求。15.10.5异常向量表写保护当VxWorks配置了INCLUDE_PROTECT_VEC_TABLE组件时,在系统初始化时异常向量表是写保护的。提供了具体体系结构的API来自动修改向量表使能调用过程中的异常向量写使能。更多信息,参考VxWorksArchitectureSupplement。16内核Shell和Loader16.1介绍风河主机开发环境提供了主机驻留和执行的工具。这个方法保存了目标内存和资源。然而,很多情况下需要使用目标驻留工具:一个目标驻留shell,内核对象模块加载,调试工具,系统特征表。这些目标驻留工具用法如下:·通过串口连接调试一个部署系统;·开发和调试网络协议,对于查看一个网络的目标视图比较有用;·从一个目标磁盘上加载内核模块,从ROMFS,或通过网络,交互运行(可编程)。基于目标板的工具是相互独立的。如,内存应该在没有内核对象模块加载的情况下使用,反之亦然。然而,任意独立工具要完成相应功能都需要系统特征表。某种情况下,同时使用主机驻留开发工具和目标驻留工具比较有用。这种情况下,附加工具要求,所以维持一致性系统视图的环境要求。更多信息,参考16.4.5SynchronizingHostandKernelModulesListandSymbolTable。大多数情况下,目标驻留工具和主机开发环境工作机制类同。更多信息,参考WindRiverWorkbenchbyExample和VxWorksCommand-LineToolsUser’sGuide。这一章描述了目标驻留内核shell,内核对象模块加载,调试工具和系统特征表。也提供VxWorks使用的通常视图,从shell执行。16.2内核Shell大多数情况下,目标驻留内核shell和主机shell工作原理一样(WindSh—forWindShell)。内核shell,仅支持C解析器和命令行解析器。(参考16.2.3KernelandHostShellDifferences)关于主机shell和shell解析器,参考WindRiverWorkbenchHostShellUser’sGuide和WindRiverHostShellAPIReference。多内核shell链接可能同时运行,允许从主机控制台和远程连接访问,如telnet或rlogin。注:VxWorks是一个单用户操作系统。不支持多个用户同时访问。而多个shell链接可能可以并存,必须用相同的用户启动。注:内核shell操作数据类型为:int,long,short,char,double,或float。提醒:不要同时使用内核shell使用硬件断点,和片上调试工具(OCD)。同时使用会导致不可预知的行为,当两个寄存器都访问硬件断点寄存器时。16.2.1C解析器和命令行解析器内核shell包括一个C解析器和一个命令行解析器。区别如下:·命令行解析器主要设计用于启动,监控,调试实时进程应用程序(RTP)。也可以和内核模块loader一块加载和注销内核对象模块。提供一个如UNIX一样的shell环境。·C解析器设计用于监控和调试基于内核的代码。可以和内核模块loader一块加载,运行和注销内核对象模块。另外,为监控和启动RTP应用提供一下API。运行在C语言环境中。关于解析器的更多信息,参考WindRiverWorkbenchHostShellUser’sGuide。关于每个解析器支持的命令信息,参考InterpreterCommandsandReferences。关于增加新的命令到命令行解析器和针对内核shell创建解析器的相关信息,参考16.2.19AddingCustomCommandstotheCommandInterpreter和16.2.20CreatingaCustomInterpreter。切换解析器切换shellC和命令行解析器,使用cmd命令,当shellC解析器激活时或命令行解析器激活时。如下例子演示了从C解析器切换到命令行解析器,并随后切换到C解析器。->cmd[vxWorks*]#C->注明每一种解析器的shell提示区别(命令行解析器的提示表示内存上下文)。用非激活的解析器执行命令你也可以从一个目前没有激活的解析器上执行一个命令;也就是说,目前这个解析器没有运行的情况下。如你可以在C解析器上使用如下命令:调用命令行解析器emacs命令来设置行编辑模式到Emacs。->cmdemacs并在命令行解析器上工作,如下使用C解析器命令moduleShow()演示已加载的内核模块信息:[vxWorks*]#CmoduleShow解析器命令和引用关于个性化的C解析器函数信息,参考VxWorksKernelAPIReference中usrLib,dbgLib,和usrShellHistLib相关章节,还有不同展示程序库入口。dbgLib函数尤其有用,提供了管理任务断点,任务单步,反汇编,和任务栈跟踪等命令。注明内核shell也可以调用任何C函数,返回一个shell支持的数据类型(int,long,short,char,double,或float)。如可以使用VxWorks信号量API创建信号量,并操作。关于命令行解析器命令更多信息,参考VxWorksKernelShellCommandReference。关于内核shell本身更多帮助信息,参考16.2.7UsingKernelShellHelp。16.2.2风河CLIShell访问16.2.3内核和主机shell的区别目标机和主机shell的区别如下:·主机和内核shell提供的命令不同。内核shell,如提供和网络,共享数据,环境变量,也一些主机shell不提供的工具的相关命令。然而,主机和内核shell提供针对它们命令和C编译器的一组非常相似的命令。·每一种shell有自身独立的配置参数,有些通用。·两种shell都包括一个命令行和一个C解析器。主机shell也提供一个Tcl解析器和一个gdb解析器。gdb解析器有大约40个命令和目的是调试RTPs;引用主机文件系统路径。·针对主机shell,VxWorks必须配置WDBtargetagentc组件,(seeD.WDBTargetAgent)。针对内核shell,Vxworks必须配置内核shell组件,还有目标驻留特征表组件。·主机shell可以在主机上执行很多控制和信息函数,无须消耗目标机资源。·内核shell不需要任何风河主机工具支持。·主机shell的大多数函数使用主机系统资源,所以和目标机分离。这意味着主机shell可以在目标机上运行,而内核shell是Vxworks内核的一部分。如,主机shell的w()版本在目标机上分配内存。当使用内核shell时,可能会产生任务优先级相关错误。警告:shell命令必须使用和函数原型兼容的命令,否则会导致系统挂起。·内核shell自身有一组终端控制字符,不像主机shell,继承了主机窗口的一些控制字符。(16.2.8UsingKernelShellControlCharacters)·内核shell可以正确的解析针对UNIX和Linux主机系统路径中的波浪线操作符(或UNIX上的远程文件系统上的或通过ftp,rsh,NFS等的Linux主机访问),而主机shell不可以。如如下命令可以在内核shell上执行(用C解析器),通过用户panloki将会正确定位柱系统上的内核模块/home/panloki/foo.o并加载到内核:->ld<~/foo.o·当内核shell遇到一个表达式中的省略号字符串(“...”),针对字符串分配空间,包括null字节终止符,增加额外的开销。文字值是新分配空间中的字符串地址。如下表达式从目标机内存池中分配了12+字节,输入字符串到内存(包括空终止符),指派字符串地址给x:->x="hellothere"如下表达式用于返回内存到目标机内存池(参考内存管理memLib相关的信息)。->free(x)还有,若一个字符串文字没有指派一个一个符号,内存仍然永久分配。如下表达式使用从来不会释放的内存:->printf("hellothere")这是因为若字符串是临时分配的,一个字符串文字被传递给一个发起的任务函数,当任务执行和尝试访问这个字符串时,内核shell将会释放保存的临时数据。若使用于字符串的内存的累积对性能造成影响的话,你可以使用strFree()函数或相关的stringfree命令是否相关内存。主机shell也可以在目标机上分配内存,若需要在主机上使用字符串。然而,若命令在主机上执行(lkup(),ld()),则不需要在目标机上分配内存。16.2.4配置内核shell内核shell的功能由一组组件提供,一些是必须的,一些是可选的。必须的组件为了使用内核shell,你必须配置INCLUDE_SHELL组件。这个组件的配置参数描述在Table16-1.。你必须配置INCLUDE_STANDALONE_SYM_TBL或INCLUDE_NET_SYM_TBL组件,来支持特征表。关于配置特征表更多信息,参考16.4.1ConfiguringVxWorkswithSymbolTables。可选的组件Table16-3表描述了提供附加命令行解析器功能的组件,必须增加INCLUDE_SHELL_INTERP_CMD组件。附加的有用组件如下:INCLUDE_DISK_UTIL提供文件工具,如ls和cd(必须增加INCLUDE_DISK_UTIL_SHELL_CMD组件)。INCLUDE_SYM_TBL_SHOW提供特征表展示函数,如lkup。增加内核对象模块loader和unloader组件也是非常有用的。这些组件usrLib命名需要,加载模块到内核,或从内核中删除组件(参考16.2.11LoadingandUnloadingKernelObjectModules)。16.2.5针对内核shell连接配置变量内核shellconfigurationvariables用于控制shell连接变量设置,包括识别目前的shell解析器(命令行或C),行编辑模式(Vi或Emacs)等。考虑这些变量非常有用,如sessionvariables。注明shell的配置变量不是Vxworks环境变量,更多信息,参考envLib入口。Shell连接变量的默认设置是VxWorks配置和内核shell在编译时刻(静态)的一部分。随后可以在运行时修改(动态)。内核shell配置变量内核shell提供了几种配置,和其它工具提供的附加变量。一些shell的配置变量如下:INTERPRETER识别解析器,C或Cmd。默认是第一个解析器(C)。LINE_EDIT_MODE设置行编辑模式,Emacs或vi。默认是第一个行编辑模式(vi)。LINE_LENGTH设置shell行长度(不会动态改变),默认是256个字符。存在的配置变量组取决于VxWorks系统本身的配置。如VxWorks配置支持经常和命令行解析器组件(INCLUDE_RTP和INCLUDE_SHELL_INTERP_CMD),仅存在RTP_CREATE_STOP。内核shell变量列在shellLibAPI参考中。其它列在功能关联的API参考中(如taskspawn,提供RTP_TASK_SPAWN_PRIO、RTP_TASK_SPAWN_STACK_SIZE和RTP_TASK_SPAWN_OPTS)。静态配置内核shell配置变量内核shell配置变量的默认值是静态设置的——当VxWorks配置和编译时——配置如下组件:SHELL_DEFAULT_CONFIG针对所有shell链接的默认配置变量设置,除了配置了SHELL_FIRST_CONFIG或SHELL_REMOTE_CONFIG变量外。SHELL_FIRST_CONFIG在启动时初始化shell连接。覆盖了SHELL_DEFAULT_CONFIG的设置。默认设置为NULL。SHELL_REMOTE_CONFIG远程连接配置。覆盖了SHELL_DEFAULT_CONFIG的配置。默认设置为NULL。这些参数的任一个可以设置为一个配置变量字符串或用于改变个性化值。如,设置路径到RTP可执行应用使用VXE_PATH配置参数,如下:vxprjparametersetstringSHELL_DEFAULT_CONFIG"VXE_PATH=/home/xeno:/romfs"这个例子演示了一次配置多个配置参数:vxprjparametersetstringSHELL_DEFAULT_CONFIG"LINE_EDIT_MODE=emacs,LINE_LENGTH=256,STRING_FREE=manual,INTERPRETER=Cmd,VXE_PATH=.;/romfs"注明:逗号用于分割参数。动态配置内核shell配置参数内核shell配置参考可以在运行时基于当下shell连接动态改变。配置参数可以通过函数shConfig()使用C解析器改变或在命令行工具上使用命令setconfig。也可以shellConfigDefaultSet()编程为所有连接设置默认。或针对一个特别的连接shellConfigSet()设置变量。如,如下命令行改变行编辑模式到Emacs:[vxWorks]#setconfigLINE_EDIT_MODE="emacs"16.2.6启动内核shellVxWorks启动后,默认内核shell自动启动。若一个串口连接的控制台窗口打开,shell提示shell标志。关于启动VxWorks的更多信息,和启动一个控制台窗口,参考WindRiverWorkbenchbyExample。WindRiverWorkbenchbyExample参数控制是否初始化shell连接启动。默认是TRUE,此时shell连接自动启动。若WindRiverWorkbenchbyExample设置为FALSE,一个shell连接不会自动启动。这种情况下,一个shell连接可以编程启动,从主机shell,从一个telnet或rloginshell连接,或从wtxConsole(一个主机工具)。shellGenericInit()函数用于启动一个shell连接。16.2.7使用内核shell帮助16.2.8使用内核shell控制字符16.2.9内核shell历史16.2.10定义内核shell命令别名16.2.11加载和注销内核对象模块内核对象模块可以动态加载到一个带有目标驻留loader的运行的Vxworks内核中。关于使用loader配置VxWorks的更多信息,参考16.3KernelObject-ModuleLoader。注:关于shell中和实时进程一块工作的信息,参考WindRiverWorkbenchHostShellUser’sGuide、VxWorksApplicationProgrammer’sGuide和WindRiverHostShellAPIReference。如下是shell中典型的加载命令,使用C编译器用户下载app.o:->ld</home/panloki/appl.old()命令从一个文件中加载一个对象模块,或从标准输入到内核。模块中的扩展引用在加载时被解决。一旦一个应用模块被加载到目标内存,模块中子程序可以从shell直接触发,以任务发起,连接一个中断等。具体怎么完成通过加载对象模块使用的标志(全局特征可见或所有特征可见)。关于ld()的更多信息,参考VxWorksAPIreferenceforusrLib。关于reld()和unld()的更多信息,参考VxWorksAPIreferenceforunldLib。注明这种函数只能在shell中使用;不能编码。未定义的特征通过以正确的顺序加载模块避免。在下载之前链接依赖的文件可用于避免未解决的引用若它们之间有循环引用或模块数太多。静态链接器ldarch可以用于链接依赖的文件,所有它们可以合成一个单独的对象,加载到目标机中。关于静态链接内核模块的信息,参考VxWorksCommand-LineToolsGuide。关于C++模块的特别问题,参考5.5DownloadableKernelModulesinC++。卸载一个代码模块释放所有当加载模块时使用的资源,是可能的。这包括从目标特征表中删除特征,从内核加载模块列表中删除模块,从内核内存中删除text,data,bss段,且释放内存。不包括释放内存和其它资源(如信号量)。16.2.12使用内核shell调试VxWorks内核shell提供执行在内核中的调试代码工具,和在用户空间调试RTP应用的工具。使用动态printf动态printf工具提供了在运行时安装printf语句,不需要重新编译和加载应用程序(无论内核模块或RTP应用)。这个技术针对内核shell支持打印全局变量和指定内存区域的内容实现。(主机工具提供附加功能若应用已经debug编译)动态printf工具提供从未中断任务中测试代码,也可能设置软件断点。另外,动态printfs可以使用硬件断点设置。在C解析器中,这个通过hdprintf()函数完成。命令行工具用dprintf-h命令完成。在不清楚断点位置的情况下,动态printf工具非常有用,若出现竞争——当应用停止隐藏了这种情况。注明若在中断中调用,动态printf函数跳过,printf语句不会显示。关于使用动态printfs的更多信息,参考dprintf()和dprintf参考入口(C和命令行解析器)。注明命令行解析器允许使用动态printf在RTP应用中,而C解析器不可以。配置VxWorks动态printf组件提供C解析器dprintf()函数,和组件提供命令行解析器dprintf命令。动态Printf例子如下代码,编译为一个DKM模块:#include<vxWorks.h>#include<stdio.h>#include<sysLib.h>#include<taskLib.h>intmyIntVar=0;longlongintmyLongLongVar=0;char*myString;voidmyLoop(void){myIntVar++;myLongLongVar+=10;sprintf(myString,"%d%lld",myIntVar,myLongLongVar);}voidmyTest(void){myString=malloc(100);myString[0]=EOS;while(1){myLoop();taskDelay(sysClkRateGet());}}内核shell连接说明了如下,内核模块被加载,dprintf()用于动态打印变量的值。->ld<myTest.ovalue=1634729040=0x616ffc50='P'->dprintfmyLoop,0,0,"myIntVar=%d\n",&myIntVarvalue=0=0x0->dprintfmyLoop,0,0,"myLongLongVar=%lld\n",&myLongLongVarvalue=0=0x0->dprintfmyLoop,0,0,"string=%s\n",&myStringvalue=0=0x0->dprintfmyLoop,0,0,"int=%d,llong=%lld,string=%s\n",&myIntVar,&myLongLongVar,&myStringvalue=0=0x0->spmyTestTaskspawned:id=0x604c1a38,name=t1value=1615600184=0x604c1a38='8'=myString+0x6c8->0x604c1a38(t1):myIntVar=00x604c1a38(t1):myLongLongVar=00x604c1a38(t1):string=0x604c1a38(t1):int=0,llong=0,string=0x604c1a38(t1):myIntVar=10x604c1a38(t1):myLongLongVar=100x604c1a38(t1):string=1100x604c1a38(t1):int=1,llong=10,string=1100x604c1a38(t1):myIntVar=20x604c1a38(t1):myLongLongVar=200x604c1a38(t1):string=2200x604c1a38(t1):int=2,llong=20,string=2200x604c1a38(t1):myIntVar=30x604c1a38(t1):myLongLongVar=300x604c1a38(t1):string=3300x604c1a38(t1):int=3,llong=30,string=3300x604c1a38(t1):myIntVar=40x604c1a38(t1):myLongLongVar=400x604c1a38(t1):string=4400x604c1a38(t1):int=4,llong=40,string=4400x604c1a38(t1):myIntVar=50x604c1a38(t1):myLongLongVar=500x604c1a38(t1):string=5500x604c1a38(t1):int=5,llong=50,string=550调试内核任务内核shell包括针对内核空间的任务级别调试工具,若Vxworks配置了INCLUDE_DEBUG组件。这个组件提供的命令包括窗体任务端口工作机制,任务单步,反汇编和任务堆栈跟踪等功能。另外,这个组件提供动态printf工具。关于存在的所有调试命令信息,参考dgbLibentryintheVxWorksKernelAPIReferencefortheCinterpreter,VxWorksKernelShellCommandReferenceforthecommandinterpreter。调试RTP应用增加动态printf到RTP应用启动任务,调用实时进程中函数RTP应用编译需求运行时使用监控系统调用系统调用监控例子使用内核shell调试SMP系统16.2.13从内核shell中执行退出程序有时期望退出shell的一个语句评估。如,一个触发的函数可以过度迭代,挂起,或等待一个信号量。这个可能因为指定错误参数,函数实现错误,或监控调用函数的结果产生。这种情况下,常常需要退出或重启内核shell任务。这个通过默认点击CTRL+C完成。这个导致内核shell任务重启启动到最初入口点。注退出键可以通过调用tyAbortSet()函数改变为一个字符,而不是CTRL+C。当重启时,内核shell自动重新指派系统标准输入和输出流到最初内核shel首次发起的最初指派。因此任务内核shell重定向被取消,任何执行的shell脚本退出。仅当如下条件成立,退出工具工作:·excTask()正在运行。·指定的键盘设备驱动支持(所有的Vxworks提供的驱动支持)。还有,有些情况下你可能输入一个表达式导致内核shell产生一个重要错误,如一个总线/地址错误或一个特权违反。这些错误会导致任务挂起,要考虑进一步的调试。然而,当内核shell任务产生这样一个错误时,Vxworks自动重启内核shell,因为进一步的调试不可能没有它。注明这种情况,允许使用断点和单板跟踪,当用任务发起一个函数驱动从内核shell直接调用是非常有用的。当内核shell因为某些原因退出,因为一个重要错误,终端中止,任务跟踪。这个跟踪显示了当挂死时,内核shell执行的地方。注明一个异常函数可以当内核shell中止时,保存系统状态,不会清除。如,内核shell可能使用了一个信号量,程序执行尚没有释放。16.2.14内核shell安全VxWorks可以为内核shell配置登录(本地和远程)和超时安全机制。登录安全内核shell的控制台登录安全通过增加INCLUDE_SECURITY组件到Vxworks,并设置shell的SHELL_SECURE参数为TRUE。(默认是FALSE)使用这个配置,shell任务在启动时不能发起。取而代之的是,一个登录任务运行在控制台上,等待用户输入一个有效的登录ID和密码。登录验证通过之后,shell任务针对控制台发起。当用户从控制台退出,shell连接中止,一个新的登录任务发起。超时安全基本的控制台登录安全可以用超时安全提供。AUTOLOGOUT参数——默认不设置——可以设置为其它的非激活延迟(分钟)。若在延迟周期过程中没有字符输入到shell终端,用户自动退出登录,shell返回到授权提示。远程登录安全VxWorks可以配置一个远程登录安全功能,使用用户ID和密码限制访问系统。INCLUDE_SECURITY组件提供了这个功能。注明loginEncryptInstall()函数使能了加密函数(如SHA512)的使用,而不是简单默认。当Vxworks配置了远程登录安全时,系统提示一个登录用户名和密码当系统被远程访问时。默认用户名和密码是VxWorks发布版本提供target和password。改变默认用户名和密码默认用户名和密码可以通过函数提供,这个函数要求一个加密密码。为了创建一个加密密码,使用主机系统上的vxencrypt工具。输入工具一个密码,后显示加密版本。使用C解析器,使用loginUserAdd()函数语法如下:loginUserAdd"userName","encryptedPassword"如,若用户名fred有密码mysecret,被加密为bee9QdRzs,如下命令用于改变默认设置。->loginUserAdd"fred","bee9QdRzs"自动增加其他用户为了定义一组登录名,你可以在一个启动脚本中调用一系列loginUserAdd()函数。脚本包括一组入口如下:name="johan"password="SdbdccRRy"loginUserAdd(name,password)这个脚本通过startupscript启动参数标示。更多信息,参考3.5.2DescriptionofBootParameters。另外,你可以通过在usrAppInit()函数中调用loginUserAdd()函数增加用户名。如:voidusrAppInit(void){#ifdefUSER_APPL_INITUSER_APPL_INIT;/*forbackwardscompatibility*/#endifloginUserAdd("johan","SdbdccRRy");loginUserAdd("julie","dydQbQcdS");loginUserAdd("xeno","Szz9S9SSyz");}使用的更多信息usrAppInit(),参考4.12ConfiguringVxWorkstoRunApplicationsAutomatically。注:用户名和密码仅用于远程登录VxWorks系统。不会影响VxWorks到一个远程系统的网络访问;参考WindRiverNetworkStackProgrammer’sGuide。禁用远程登录安全远程登录安全工在启动时通过指明标志位启动参数flags的bit0x20(SYSFLAG_NO_SECURITY)禁用。16.2.15使用远程登录到内核shell用户可以通过telnet和rlogin登录到VxWorks系统,使用内核shell,由配置相关组件的VxWorks提供。VxWorks可也可以配置远程登录安全功能,使用输入用户名和密码登录系统。注明VxWorks不支持rlogin从Vxworks系统到主机的访问。关于远程登录安全信息,参考RemoteLoginSecurity。用telnet和rlogin远程登录当VxWorks第一次启动时,shell的终端是正常的系统控制台。你可以使用telnet从主机访问内核shell通过网络,若VxWorks配置了INCLUDE_IPTELNETS组件。这个组件创建了ipcom_telnetd任务当系统启动时。可能通过不同的网络启动几个shell。(可以用wtxConsole工具进行远程登录)为了通过网络远程访问内核shell,使用telnet命令:%telnetmyVxBox关于telnet的更多信息,参考WindRiverNetworkStackProgrammer'sGuide。UNIX主机系统也可以使用rlogin访问内核shell。VxWorks必须配置INCLUDE_RLOGIN组件创建tRlogind任务。完成终结rlogin连接,操作如下:·使用混合键。·用shell的C解析器使用命令logout()或命令行工具的命令logout。·在shell提示中输入波浪线和周期字符:->~.16.2.16发起可编程内核脚本通过一个解析器执行一个脚本的简单方法如下:fdScript=open("myScript",O_RDONLY);shellGenericInit("INTERPRETER=Cmd",0,NULL,&shellTaskName,FALSE,FALSE,fdScript,STD_OUT,STD_ERR);dotaskDelay(sysClkRateGet());while(taskNameToId(shellTaskName)!=ERROR);close(fdScript);do/while循环一直在等待中止shell脚本命令。16.2.17可编程执行Shell命令针对一个UNIX操作系统没有system()API。为了从一个应用中执行shell命令,上述描述的技术被使用。传递给shellGenericInit()API函数的不是一个文件句柄,而是一个伪设备从文件描述符(参考ptyDrv库中关于pseudo-devices信息)。应用编写想要在伪设备主端执行的命令。伪代码如下所示:fdSlave=open("system.S",O_RDWR);fdMaster=open("system.M",O_RDWR);shellGenericInit("INTERPRETER=Cmd",0,NULL,&shellTaskName,FALSE,FALSE,fdSlave,STD_OUT,STD_ERR);taskDelay(sysClkRateGet());write(fdMaster,"pwd\n",4);close(fdMaster);close(fdSlave);16.2.18可编程访问内核shell数据Shell数据在库中。这使得用户用一个shell连接关联数据值,在任何时间都可以访问它们。这个对于维护默认值非常有用,如内存dump宽度,反汇编长度等。这些数据值不能通过shell交互,仅编程。16.2.19增加定制命令到命令解析器内核shell的命令行解析器包括一个行解析器和一组命令。可以通过C编写的定制命令扩展(主机shell的命令行解析器也可以扩展,但是必须用Tcl编写)。一个命令语句的语法是标准shell命令行语法,和UNIXshshell或Windows的cmdshell的语法类似。语法是:command[options][arguments]空字符(如一个空格或tab)在一个语句中是一个有效的字分隔符。一个空字符可以用于参数字符若用反斜杠为前缀字符或使用双引号字符的参数。分号字符用于命令行分隔符,用于在单一输入行上输入多个命令。为了用于参数字符串一部分,分号必须escaped或引用。命令解析器划分语句字符串为命令名字符串和包括选项和参数字符串。这些选项和参数之后传递给命令函数。命令名可能是一个简单的名字(一个字,如reboot)或一个合成名(如taskinfo)。合成名对于创建命令类是非常有用的(任务命令,进程命令等)。选项不需要遵守任何严格的格式,但是推荐使用标准的UNIX选项格式,因为可以被命令行解析器自动处理。若选项没有遵守标准的UNIX格式,命令行函数必须解析命令字符串提取选项和参数。参考DefiningaCommand。标准的UNIX选项格式字符串是:-character[extraoptionargument]专有双—选项(--)和UNIX用于同样方式。也就是说,所有的元素都遵循这个标准被对待为一个参数字符串,不是选项。如,若命令test接受选项-a,-b和-f(后面带extra选项参数),之后如下命令设置三个选项,解析arg字符串为一个参数:test-a-b-farg然而,下一个命令仅设置-a选项。因为后面的双杠,-b,-和命令的arg元素是传递给test命令C函数的字符串:test-a---b-ffilearg命令解析器仅处理字符串。所以,命令函数的参数也是字符串。若需要命令行函数转换字符串为数字值。关于特征访问语法的更多信息,参考VxWorksCommand-LineToolsUser’sGuide。定义一个命令解析器通过一个C结构定义一个命令,这个C结构由不同的字符串和函数指针组成,如下:nameStruct={"cmdFullname",func,"opt","shortDesc","fullDesc","%s[synopsis]"};16.2.20创建一个定制解析器16.3内核对象模块加载目标驻留VxWorks内核对象模块loader让你在运行时动态增加对象模块到内核。这个操作,调用loading,或downloading,允许你安装内核空间应用或扩展操作系统本身。(为了简化,内核对象模块loader也被简称为内核loader,或loader,在本部分)。下载代码可能是一组程序,意味着可以被其它代码使用(相当于其它操作系统的一个库),或是一个应用,意味着被一个任务或一组任务调用。被下载的代码单元是对象模块。加载个性化对象模块的功能使用不同的方式给开发过程带来了极大的灵活性。这个工具在开发过程中的主要应用时卸载,重新编译,在开发中重新加载模块。可选择性是链接开发代码到VxWorks镜像,重新编译这个镜像,重新启动目标机,每一次开发代码必须重新编译。内核loader使得你动态扩展操作系统,因为一旦代码被加载,代码和被编译到镜像的启动代码没有区别。最后,你可以配置内核loader每次加载可以针对下载的模块有选择的处理内存分配,这使得可以灵活使用目标机内存。Loader可以针对下载代码分配内存,也可以在卸载模块时,释放内存;或调用者可以指定已经分配的内存地址。这使得用户可以更好的控制内存中的代码布局。更多信息,参考16.3.6SpecifyingMemoryLocationsforLoadingObjects。内核loader的功能由两个组件提供:loaderproper,在目标系统内存中安装对象模块的内容;unloader,卸载对象模块。另外系统特征表提供loader依赖的信息。提醒:当一个对象模块的任务正在运行时,不要卸载这个对象模块。否则会导致不可预知的行为。提醒:VxWorks内核应用程序必须针对运行的系统类型具体编译。针对单处理器(UP)编译的应用,针对对称多处理器(SMP)编译的系统,基于欧诺个一个VSP工程创建的不同版本库的VxWorks系统,都是二进制兼容的。注明loader拒绝一个内核模块若和加载到系统中的对象模块不兼容时,打印一个错误信息到控制台,设置errno为S_loadLib_INCOMPATIBLE_MODULE。提醒:风河编译器C++和GNUC++二进制文件不兼容。可下载的用C++写的内核模块必须用Vxworks使用的编译器编译。注明:目标驻留内核对象模块loader有时候会和bootloader混淆,bootloader用于加载内核镜像到内存。尽管这两个工具执行相似的功能,共享一些支持代码,但是它们是不同的实体。Bootloader仅加载完整的系统镜像,不执行重新分配。参考3.BootLoader。16.3.1内核和主机loader的区别风河为在运行时加载内核对象模块到Vxworks内存提供了两种方法:内核对象模块loader和主机loader。Loader基于对象文件本身提供的信息处理模块(部分和段描述符,和特征表)。两个loader仅支持ELF对象文件格式。两个loader都使用一个文件描述符,这个文件描述符上的流必须可以双方向查找,因为loader充分利用前向或后向查找功能,当读ELF文件内容时。当ELF文件解析时,它的段被拷贝到内存,根据ELF文件中找到的重定向信息重定向。根据加载标志,模块特征被增加到系统特征表中。两个loader都依赖内存分配器返回的地址来设置内存中模块位置,除非具体段地址在加载时已经被用户使用。关于内核loader功能和配置的更多信息,参考16.3KernelObject-ModuleLoader和16.3.2ConfiguringVxWorkswiththeKernelObject-ModuleLoader。关于主机shell的更多信息,参考WindRiverHostShellUser’sGuide。和VxWorks目标机的关系内核loader必须配置到VxWorks镜像中。内核loader的主要优点是可以独立使用Workbench开发工具和VxWorks产品套装(一个终端窗口是仅有主机需求)。非常有利于部署系统和开发环境。主机loader驻留在目标服务器主机系统上和使用WDB连接目标机和主机,来访问目标机。Workbench工具使用主机loader下载对象模块。主机loader的主要优势是当目标系统没有配置一个系统特征表或内核loader(减小目标机配置)可以使用,使用Workbench工具集成。(关于WDB信息,参考D.WDBTargetAgent。)模块和特征同步关于模块和特征同步的信息,参考16.4.5SynchronizingHostandKernelModulesListandSymbolTable。内存管理目标机loader只在目标内存中工作。主机loader使用主机内存(作为目标服务器的一部分)和目标内存。因为性能原因,主机loader首先处理和加载自身内存中的模块。一旦模块完全加载到主机内存中(也就是说,安装和重定位的段,特征解决方案已经替换)是拷贝到目标机内存的段。模块的text段拷贝保存在主机目标服务器内存用于一旦拷贝完成后的缓存目的。目标服务器从自身缓存中获取信息(如特征),当特征查询来之主机或一个反汇编代码被执行时(或来之主机)。默认,目标机服务器缓存内存允许动态增长。大小局限于一个特定大小,小心使用,确保足够大小(更多信息,参考tgtsvr参考入口)。主机loader用如下方式使用目标内存:若主机和内核模块列表和特征表被同步后,内存分配在系统内存分区中目标机上被完成(也就是说,相同的分区,程序调用malloc()和memalign())。若主机和内核模块列表和特征表不同步(有意的或因为Vxworks没有配置内核loader),WDB内存池中内存在目标机上分配(WDB内存池目标服务器用于管理目标分配)。(关于同步的信息,参考16.4.5SynchronizingHostandKernelModulesListandSymbolTable。)系统调试模式WDB目标机agent可以配置为系统模式调试,任务模式调试,或同时配置两种调试模式(通过主机工具控制两种模式的切换)。在任务模式,WDB运行为一个内核任务。在系统模式,WDB独立于内核操作,内核在WDB控制下。内核loader独立于WDBagent操作,不受它的模式影响。随着目标机运行(当一个continue命令在系统模式发送给目标机),使用目标机loader加载模块。然而,主机loader当系统调试模式有效时不能使用。这是因为只有WDBagent运行(没有调度),挂起函数调用是不可能的(WDBagent将会阻塞在调用中,冻结整个系统)。因为主机loader充分使用这些调用(如分配内存),在系统模式不能使用。关于系统调试模式的更多信息,参考D.WDBTargetAgent,D.3.3DebuggingModeOptions,和WorkbenchHostShellUser'sGuide:UsingtheCInterpreterwithVxWorks6.x.。16.3.2配置内核对象模块loader默认,内核对象模块loader不包含在VxWorks中。为了使用loader,你必须配置INCLUDE_LOADER组件。增加INCLUDE_LOADER组件会自动包含其他需要的组件。这些组件是:INCLUDE_UNLOADER提供卸载对象模块的工具。INCLUDE_MODULE_MANAGER提供管理已加载模块和或许关于他们信息的工具,更多信息,参考VxWorksAPIreferenceformoduleLib。INCLUDE_SYM_TBL提供存储和获取特征的工具。更多信息,参考16.4KernelSymbolTables和theVxWorksAPIreferenceforsymLib。INCLUDE_SYM_TBL_INIT指明初始化系统特征表的方法。提醒:若你想使用目标驻留特征表和内核对象模块loader附加到主机工具,你必须配置INCLUDE_WDB_MDL_SYM_SYNC组件提供主机目标特征表和模块同步。这个组件默认当内核loader和WDb代码包含在VxWorks中时默认包含。更多信息,参考16.4.5SynchronizingHostandKernelModulesListandSymbolTable。内核loader和unloader在随后章节讨论,参考theVxWorksAPIreferencesforloadLibandunldLib。编译器特性支持为了支持动态加载内核模块,VxWorks必须配置正确的编译器特性库。默认配置INCLUDE_DIAB_INTRINSICS组件支持风河编译器编译加载模块。使用GNU编译,使用INCLUDE_GNU_INTRINSICS组件支持加载模块。INCLUDE_DIAB_INTRINSICS组件提供libdcc.a和libdiabcplus.a。INCLUDE_GNU_INTRINSICS组件提供libgcc.a和libcplus.a。若你不打算下载模块,你可以简化配置INCLUDE_NO_INTRINSICS组件,仅提供编译内核自身需要的功能。16.3.3内核对象模块loaderAPI表Table16-5和表Table16-6针对加载和卸载内核模块提供了API函数,shellC解析器命令,shell命令行解析器命令。注明内核loader函数可以在C解析器中或代码中直接调用。Shell命令仅可以在shell中和不在程序中调用。通常,shell命令处理附加操作,如打开或关闭一个文件;也打印它们的结果和任何错误信息到控制台。上述函数和命令的使用方法在如下部分讨论。更详细的信息,参考关于loadLib,unldLib,和usrLibAPI引用更多信息,参考16.3.4SummaryListofKernelObject-ModuleLoaderOptions。16.3.4内核对象模块加载选项概要列表内核loader的行为可以使用loadLib和unldLib函数传递的加载标志控制(参考相关API获取更多信息)。多个标志可以同时使用,是OR操作符;一些是互斥的。本节列出了这些选项。…16.3.5加载C++模块到内核16.3.6针对加载对象指明内存位置16.3.7内核对象模块loader使用规则和使用警告16.4内核特征表一个特征表时一个数据结构,存储了描述模块中函数,变量和常量的信息和任何来之shell创建的变量信息。有一个特征表库,用于操作两种不同类型的特征表:一个用户创建的特征表和一个最通用的系统特征表。注明这两种类型的特征表都在内核中使用,完全独立于在RTPs中运行应用程序使用的特征表。内核对象模块loader需要一个系统特征表。使用LOAD_FULLY_LINKED选项,当完全链接对象模块被加载时,是仅有的异常。关于表Table16-14和loadModuleAt()intheVxWorksKernelAPIReference。特征入口表中每个特征包含这些项:namename是一个衍生之源码中name的字符串。valuevalue通常指特征指向的元素的地址:一个函数的地址,或一个变量的地址(也就是说,变量内容的地址)。Value用一个指针表示。group特征源模块组成员。symRef通常是特征源模块的模块ID。typetype提供特征的附近信息。对于系统特征表中特征,type在installDir/vxworks-6.x/target/h/symbol.h中定义。如SYM_UNDF,SYM_TEXT等。对于用户特征表,这个域由用户定义。特征更新无论模块从目标机加载或卸载,特征表都会更新。你可以使用表列出的内核对象模块loader选项控制存储在特征表中的精确信息。查找特征库你可以针对具体的特征查找所有的特征表。使用C解析器的Shell中使用lkup()。也可以使用symShow()显示通用特征信息。详细,参考这些命令的API。编程查找,使用特征库API,可用于通过地址、名称、类型查找,用于应用一个用户供给函数给特征表中所有特征的函数。详细,参考symLib参考入口。16.4.1配置VxWorks特征表VxWorks可以配置为支持用户特征表或同时支持用户特征表和系统特征表。关于用户特征表信息,参考16.4.6CreatingandUsingUserSymbolTables。关于系统特征表信息,参考16.4.4UsingtheVxWorksSystemSymbolTable。配置用户特征表配置INCLUDE_SYM_TBL组件到VxWorks,此组件提供基本的特征表库,symLib,支持创建用户特征表。一个用户特征表在运行时用函数symTblCreate()创建,带有特征表哈希表宽度和名称冲突策略和使用的内存分区。更多信息,参考symTblCreate()。配置一个系统特征表为了包含内核中存在的特征信息——因此使能shell,内核对象模块loader,调试工具——必须创建和初始化一个系统特征表。为了创建一个系统特征表,VxWorks必须包含INCLUDE_SYM_TBL_INIT组件和INCLUDE_SYM_TBL组件(也提供用户特征表)。INCLUDE_SYM_TBL_INIT组件包含了配置参数SYM_TBL_HASH_SIZE_LOG2,允许你修改特征表宽度。这个参数定义了系统特征表哈希表宽度。是一个2的指数幂的正值。默认值是8;所以系统特征表默认宽度是256。数值越小,需要的内存越小,但是降低了查询性能,所以查询平均花费更多的时间。系统特征表,sysSymTbl,配置运行名字冲突。若出现冲突情况,最近增加到特征表中的特征为要查找的特征。为了初始化系统特征表(增加VxWorks内核特征),VxWorks必须一个特征表组件,这个组件是内核镜像的一部分,或一个可以独立从主机下载的特征表:INCLUDE_STANDALONE_SYM_TBL创建一个内置系统特征表,系统特征表和VxWorks镜像包含在同样的模块中。更详细描述,参考16.4.2CreatingaBuilt-InSystemSymbolTable。INCLUDE_NET_SYM_TBL创建以独立的系统特征表,如一个.sym文件,可以下载到VxWorks系统。更详细描述,参考16.4.3CreatingaLoadableSystemSymbolTable。当系统特征表在系统初始化时第一次创建时,没有包含特征。特征必须在运行时增加到表中。这两个组件处理增加特征的方式不同。16.4.2创建一个内置系统特征表一个内置系统特征表拷贝信息到封装代码,之后当编译系统时,编译链接到内核。尽管使用一个内置特征表可能会产生一个比较大的VxWorks镜像文件,但是有很多优势,特别是对应生产系统:和使用一个可加载特征表相比需要更少的内存——因为使用内核对象模块loader和关联的一个可加载特征表需要的组件都需要占用内存。不需要目标机访问主机(不像可下载特征表)加载单独的镜像文件更快。对于部署没有网络连接的基于Rom系统比较有用,但是需要shell作为用户接口。产生特征信息一个内置系统特征表依赖makeSymTbl工具获取特征信息。这个工具使用GNU工具nmarch来产生关于包含在镜像中特征的信息。后处理这些信息到symTbl.c文件,这个文件包含一个数组,standTbl。在SymbolEntries描述的SYMBOL类型。数组中的每个入口有特征name和type域设置。地址(value)域不用makeSymTbl填充。编译和链接特征文件symTbl.c文件被对待为一个正常的c文件,被编译和链接到VxWorks内核镜像中。左右正常连接过程的一部分,工具链链接器为每一个数组中的全局特征填充正确的地址。当编译完成后,镜像中的特征信息作为VxWorks特征的一个全局数组。在系统初始化阶段内核镜像被加载到目标内存,来之SYMBOL全局数组中的信息用于构造系统特征表。在VxWorks内核镜像编译后,standTbl数组的定义在如下文件中查找:■installDir/vxworks-6.x/target/config/bspName/symTbl.cforimagesbuiltdirectlyfromaBSPdirectory.■installDir/vxworks-6.x/target/proj/projDir/buildDir/symTbl.cforimagesusingtheprojectfacility.16.4.3创建一个加载系统特征表一个可加载特征表被编译为一个独立的对象模块文件(vxWorks.sym文件)。这个文件可以独立下载到系统镜像,同时信息被拷贝到特征表。创建.sym文件可加载系统特征表使用一个名为vxWorks.sym的ELF文件,而不是symTbl.c文件。这个文件通过objcopy工具创建。加载一个.sym文件在启动和初始化过程中,vxWorks.sym文件使用内核对象模块loader加载,直接调用loadModuleAt().。为了下载文件vxWorks.sym,loader使用目前的默认设备,在FilenamesandtheDefaultDevice描述。为了下载Vxworks镜像,loader也使用默认设备,为目前正在下载的设备。因此默认设备用于下载vxWorks.sym文件,或不使用相同的设备。这是因为默认设备可以通过其它运行的初始化代码设置、复位。这个修改在VxWorks镜像下载之后,在特征表加载之前发生。不管怎样,标准的VxWorks配置,不包含定制系统初始化代码,下载vxWorks.sym文件的默认设备通常设置为其中一个网络设备,使用rsh或ftp协议。16.4.4使用Vxworks特征表一旦初始化完成,VxWorks系统特征表包含已编译启动镜像中的所有全局变量的名称和地址。这个信息在使能目标工具库功能时需要。目标机工具维护静态编译到系统或动态下载到系统所有代码的名称和地址信息。(LOAD_NO_SYMBOLS选项可以用于隐藏加载模块,所有它们的特征不会出现在特征表中,参考Table16-10。)。系统特征表中动态增加的和卸载的时刻:模块加载和卸载时。从shell中动态创建变量时。WDB代码同步特征信息到主机时。(参考16.4.5SynchronizingHostandKernelModulesListandSymbolTable)系统特征表和其它目标工具的具体依赖如下:·KernelObject-ModuleLoader:内核loader需要系统特征表。系统特征表不需要loader的存在。·DebuggingFacilities:基于目标的特征调试工具和用户名如i和tt,依赖系统特征表提供任务点入口信息,调用堆栈内容等。·KernelShell:内核shell不严格需要系统特征表,当时若没有系统特征表,它的功能会极大受限。内核shell需要系统特征表提供使用特征名运行函数的功能。内核shell使用系统特征表执行shell命令,调用系统函数,编辑全局变量。内核shell也包含库usrLib,包含命令i,ti,sp,period,和bootChange。·WDBTargetAgent:WDB目标代理增加特征到系统特征表做完主机同步特征的一部分。若用户代码需要特征表库提供的工具,应该创建另一个特征表,通过特征库操作。注:若你选择同时使用主机驻留和目标机驻留工具,使用同步方法保证主机和目标机驻留工具共享相同的特征列表。16.4.5同步主机和内核模块列表和特征表若主机工具和目标机工具将要使用一个目标机系统,维护在主机系统上的模块列表和特征表必须和目标机上的同步。这保证主机和目标机工具共享相同的特征列表。主机工具维护它们自身的模块列表和特征表——目标服务器模块列表和特征表——主机上。本章指主机模块列表和特征表。模块列表和特征表同步当配置WDBtargetagent和thekernelobject-moduleloader(INCLUDE_WDBandINCLUDE_LOADER)时,被自动提供。为了删除这个功能,你仅需要删除INCLUDE_WDB_MDL_SYM_SYNC组件。注明模块和特征同步仅在WDB代理处于任务模式时运行。若WDB代码处于系统模式,增加到主机或木板机的模块和特征都不会同步。关于WDB更多信息,参考D.WDBTargetAgent。16.4.6创建和使用用户特征表尽管可能内核中的用户代码操作系统特征表中的特征,这是不推荐的。增加和删除一个特征由操作系统库执行。任何其它使用系统特征表要和操作系统交互;任何简单的增加特征操作会造成不可预知的结果。因此,用户定义特征不应该编程到系统特征表。取而代之的是,当内核中用户代码需要一个特征时,一个用户特征表应该被创建。更多信息,参考VxWorksAPIreferenceforsymLib。16.5显示函数VxWorks包含在shell的C解析器上运行系统信息函数。不能用于编程。展示函数打印关于具体目标或服务的相关系统状态;然而,仅显示调用时刻的系统状态,不会对当前的系统状态产生影响。为了使用这些函数,你必须配置正确的组件到VxWorks内核。当触发这些函数时,输出被发送到标准输出设备。表Table16-15列出了公共系统展示函数。16.6公共问题我在一个函数中设置了一个断点,后从shell中调用这个函数,但是断点没有生效,为什么?解释运行的shell任务,选择VX_UNBREAKABLE选项。直接在内核shell命令行提示调用,执行在内核shell任务上下文。因此,直接设置断点,将不会生效。解决方案取代直接运行函数,使用taskSpawn()函数作为入口点,或shell的C解析器sp()命令。内存不够内核对象模块loader在加载一个模块时,提示内存不够;然而,检测存在的内存表示内存足够。为什么呢?怎么解决这个问题呢?解释内核loader通过一个针对文件管理的VxWorks的透传机制调用设备驱动,进而调用open,close,和ioctl。若你使用内核loader通过网络加载一个模块(而不是通过一个目标系统盘),加载一个对象模块要求的内存取决于通过网络的远程文件系统的访问方式。这是因为,不同的设备使用的加载函数操作不同。对于一些设备,I/O库操作目标内存的一个文件拷贝。加载一个文件,需要一个设备,这个设备驱动需要足够的内存来同时保持两份文件的拷贝。第一,当打开时,整个文件被拷贝到本地内存缓存中。第二,当链接到VxWorks时,文件驻留在内存中。这个拷贝之后用于执行不同的seek和read操作。因此,使用这些驱动要求足够的内存来保存两个要加载的文件拷贝,所以若比较小的内存不能满足加载操作的需求。考虑加载一个模块有时需要额外的空间,如必须用于内存对齐的空间(而工具链可能压缩对象文件来节省空间)。参考16.3.6SpecifyingMemoryLocationsforLoadingObjects。解决方案使用不同的设备下载文件。从一个挂载的主机文件系统通过NFS加载一个对象模块仅需要一份文件拷贝需要的内存空间(附加小部分预留空间)。"RelocationDoesNotFit"错误消息当下载,出现如下错误消息类型时:Relocationvaluedoesnotfitin26bits(offset:0x10,type:1)意味着什么,我们应该怎么做?解释一些体系结构使用小于32位指令引用内存中附近的位置。使用这些指令引用内存中附近位置比使用32位指令高效。这个问题当编译器产生这样一个引用一个内存中较远的位置产生。如,若调用printf()解码后,若对象代码加载在离内核近的位置,则成功,否则失败。更多信息,参考FunctionCalls,RelativeBranches,andLoadFailures。解决方案根据Loader打印的部分错误信息的重定位类型和偏移诊断。偏移和类型由readelf-r命令获取。重定向类型在ELF体系结构补充中描述。针对风河编译器使用-Xcode-absolute-far选项重新编译文件,针对GNU编译器,正确的longcall选项,-mlongcall(针对PPC体系结构)。参考VxWorksArchitectureSupplement相关章节。内核对象模块loader使用过多内存包括内核loadr导致包括系统特征表。这个系统特征表包括镜像中每一个全局特征信息。使用内核loader会占用额外的内存空间——内核loader存储目标驻留特征表的空间。解决方案使用主机工具,尽量不要使用目标机工具,从内核中删除所有的目标机工具。SymbolTableUnavailable加载目标机特征表失败。我们怎么使用内核shell调试问题,因为我们不能通过名字调用函数?解析这个问题常常是网络配置有关的问题,或驱动问题,和内核shell和特征表无关。解决方案使用函数和数据的地址,而不使用特征名。这个地址可以从主机上VxWorks内核获取,使用nmarch工具。如下例子是来自UNIX主机的一个例子:>nmarchvxWorks|grepmemShow0018b1e8TmemShow0018b1acTmemShowInit使用这个信息从内核shell中通过地址调用函数(当通过地址调用时,括号是强制性的)。对于重定向的模块,使用nm来获取函数地址(是模块的text段的偏移)之后,加模块text段起始地址值,加载到内存。17内核CoreDumps17.1介绍VxWorkskernelcoredump工具允许产生和存储kernelcoredump,排除核心转储选定的内存区域,从目标系统获取核心转储。核心转储文件可以用Workbench调试工具解析,确定失败原因,解析执行特别时刻的系统状态。注:VxWorks可以同时配置内核核心转储和RTP核心转储工具,VxWorksRTPcoredump更多信息,参考VxWorksApplicationProgrammer’sGuide。17.2关于内核CoreDumps一个VxWorks内核核心转储包括指定时刻系统内存内容拷贝。拷贝的内存域是一个核心转储,若有MMU,则是虚拟内存,若没有MMU,则是物理内存空间。产生默认,当重要错误发生时(也就是说导致系统重启的事件),核心转储工具产生一个核心转储。这个工具也可以配置为针对非重要错误产生核心转储。另外,一个核心转储函数用于在shell中交互产生一个核心转储,或编程。如下重要系统事件导致核心转储:·系统初始化,中断级别,或VxWorks调度中产生的重要系统异常。·内核本身检测到不可恢复的事件,导致内核报警情景。非重要事件涉及不是系统致命的和没有导致重启的没有处理的任务级事件。如,访问一个无效的地址导致的非致命事件。关于配置系统针对非致命事件产生一个核心转储的信息参考17.9GeneratingKernelCoreDumps。当核心转储产生开始,核心转储工具操作如下:·锁中断。·跳转到自身的受保护执行栈,开始转储VxWorks内存。——仅核心转储产生时刻的映射内存被转储。——当一个内核核心转储任务产生异常,仅内核内存被转储(不是RTPs使用的内存)。——当一个RTP系统调用产生异常,目前的RTP和内核内存都要转储。数据一个内核核心转储包括一个系统内存拷贝——从基内存地址LOCAL_MEM_LOCAL_ADRS到sysPhysMemTop()——用于核心转储本身的永久内存域。默认包含永久内存剩余部分,排除内核text部分。注明如下包含在核心转储中,若存在在当前系统中:·任何动态加载到内存的内核模块。·WDB目标代理内存池,若系统配置WDB。关于改变默认内核核心转储内容的信息,参考17.6ChangingtheAreasofMemoryCapturedbyCoreDumps。关于内核内存布局信息,参考15.MemoryManagement。Size核心转储的大小取决于VxWorks内存的大小,也就是说,LOCAL_MEM_LOCAL_ADRS到sysMemTop()。若有32MB,核心转储和这个大小差不多。若MMU使能,可能会更小,然而,仅映射的虚拟内存页被拷贝到核心转储。若压缩使能,大小很难估计,内存内容决定压缩效率。Filtering内核核心转储工具提供过滤具体内存域工具,使得核心转储减小自身大小,删除无关数据,更多信息,参考17.6.3FilteringSelectedMemoryAreasFromCoreDumps。额外的内存区域内核核心转储工具提供增加额外内存区域到核心转储的工具,更多信息,参考17.6.4AddingAdditionalMemoryAreastoCoreDumps。钩子函数提供用于安装钩子函数的函数当核心转储事件产生时调用,无论核心转储工具已经转储了具体数量的核心数据,或核心转储创建结束。钩子函数也用于安装内存过滤。更多信息,参考17.7UsingtheCoreDumpHookRoutineManager。文件定位核心转储文件写的位置由内核核心转储工具配置。默认写到Vxworks永久内存区域。也可以写到目标机上另外生设备上;但是必须增加一个定制组件才能这样做。拷贝核心转储从目标机到主机的函数,可以用shell交互,或编程交互。文件名内核核心转储文件要和如下语法规则一致(无论是存储在永久内存中或一个定制生设备上):vxcoreindex[.z]语法使用如下:索引为1表示是产生的第一个核心转储,产生一次,索引递增一次。索引一直存在,直到核心转储设备被格式化。若核心转储压缩使能(zlib,RLE),.z扩展增加。关于格式化更多信息,参考17.8InitializingtheKernelCoreDumpFacility。关于压缩更多信息,参考17.5EnablingCoreDumpFileCompression。文件压缩内核核心转储工具提供了文件操作的选择(zlib,RLE,或无)。更多信息,参考17.5EnablingCoreDumpFileCompression。17.3配置Vxworks基本内核CoreDump支持VxWorks内核核心转储工具针对创建和管理核心转储提供组件——包括来自核心转储和钩子函数管理的过滤指定内存区域。也提供针对文件压缩的可选组件。默认核心转储存储在永久内存中(热启动不会清除的标准内存区域,但是冷启动后清除),你必须创建自己的组件来支持生设备的支持(如flash设备和ATA设备)。你可以配置VxWorks6.8使用Workbench或vxprj支持核心转储。选择INCLUDE_CORE_DUMP组件,默认拉进需要的依赖。默认存储介质是永久内存。注明永久内存必须重新配置为尽量小来包含一个核心转储;参考17.5EnablingCoreDumpFileCompression和17.10ManagingKernelCoreDumpFiles。基本的核心转储组件和参数核心转储通常要求如下核心转储组件。INCLUDE_CORE_DUMP这个组件提供了核心内核转储支持。INCLUDE_CORE_DUMP组件有如下参数:CORE_DUMP_STACK_SIZE这个参数用于设置核心转储工具使用的栈大小。内核核心转储工具使用自身的栈来避免当系统崩溃或栈溢出导致的使用默认系统栈导致的问题。若这个参数设置为0,核心转储工具不使用自身的栈,但是使用目前的VxWorks栈。CORE_DUMP_CKSUM_ENABLE设置这个参数为TRUE来使能核心转储校验工具(默认是FALSE)。这用于检查在核心转储产生和设备重启之间是否核心转储已经崩溃。checksum状态可以使用coreDumpShow()或coreDumpInfoGet()验证。更多信息,参考VxWorksAPIreferenceentries。FATAL_SYSTEM_CORE_DUMP_ENABLE设置这个参数为TRUE来为任何致命系统错误产生一个核心转储。(默认是TRUE)KERNEL_APPL_CORE_DUMP_ENABLE设置这个参数为TRUE来为任何任务错误产生一个核心转储。(默认是FALSE)CORE_DUMP_MAX_HOOKS这个参数用于指明核心转储创建钩子和安装内核核心转储进程钩子的最大数量。当核心转储功能包含并提供允许当系统创建一个核心转储时增加触发调用的功能时,核心转储钩子库被配置在VxWorks中。CORE_DUMP_SKIP_USER_RESERVED_MEM设置这个参数为TRUE,从核心转储中排除用户预留内存。(默认是FALSE)。更多信息,参考17.6.2ExcludingUserReservedMemoryFromCoreDumps。CORE_DUMP_SKIP_TEXT_SECTION设置这个参数为TRUE,,从核心转储中,排除VxWorkstext段。(默认是TRUE)警告:删除VxWorkstext段会对可能给调试带来问题,因为text段的崩溃可能是导致系统产生核心转储的原因。正常text段的一个checksum,被作为核心转储一部分计算。IDE工具测试这个checksum。Text段崩溃导致checksum错误。INCLUDE_CORE_DUMP_MEM_FILTER这个组件允许你使用coreDumpMemFilterAdd()和coreDumpMemFilterDelete()从核心转储中增加和删除内存区域。若你配置了INCLUDE_CORE_DUMP组件,默认已经包含了必须的组件。CORE_DUMP_MEM_FILTER_MAX这个参数指明了可以从给一个核心存储中过滤的最大内存区域数。关于过滤的更多信息,参考17.4ConfiguringPersistentMemory-RegionStorage。INCLUDE_CORE_DUMP_MEM当配置INCLUDE_CORE_DUMP组件后,INCLUDE_CORE_DUMP_MEM组件默认被配置。写核心转储文件到永久内存区域是默认的存储方法。INCLUDE_CORE_DUMP_MEM组件有如下参数。CORE_DUMP_REGION_SIZE这个参数指明了为核心转储存储预留的内存空间大小,默认是PM_RESERVED_MEM-EDR_ERRLOG_SIZE。设置时必须考虑其他系统内存参数;更多信息,参考ConfiguringPersistentMemoryforCoreDumpUse。警告:当改变永久内存域本身的大小时,必须对应改变bootloader永久内存域。更多信息,参考ConfiguringPersistentMemoryforCoreDumpUse和17.4.3ConfiguringtheBootLoader。也要确定考虑用户预留内存配置。可选的组件INCLUDE_CORE_DUMP_UTILINCLUDE_CORE_DUMP_UTIL组件提供可选工具,包括如下:coreDumpCopy()coreDumpIsAvailable()coreDumpOpen()coreDumpNextGet()coreDumpRead()coreDumpInfoGet()coreDumpClose()这个组件用于在永久内存中存储核心转储。更多信息,参考CopyingCoreDumpstotheHost。INCLUDE_CORE_DUMP_COMPRESS这个组件提供核心转储压缩支持(基于zlib),压缩核心转储镜像。INCLUDE_CORE_DUMP_COMPRESS组件如如下参数:CORE_DUMP_COMPRESSION_LEVEL这个参数指明了压缩数量。默认是6,范围是1到9。数越大意味着,压缩度越高——花费更多的压缩时间。更多信息,参考17.5EnablingCoreDumpFileCompression。提醒:关于某些目标压缩可能会使用很长的时间,这可能导致你意味目标已经挂起。如发生这样的情况,风河推荐你设置压缩级别为1,或删除压缩组件。INCLUDE_CORE_DUMP_COMPRESS_RLE这个组件提供了核心转储压缩支持(基于运行长度编码),压缩核心转储镜像。更多信息,参考17.5EnablingCoreDumpFileCompression。提醒:关于某些目标压缩可能会使用很长的时间,这可能导致你意味目标已经挂起。如发生这样的情况,风河推荐你删除压缩组件。INCLUDE_CORE_DUMP_SHOWINCLUDE_CORE_DUMP_SHOW组件支持核心转储展示函数。关于核心转储展示函数更多信息,参考seeDisplayingInformationAboutCoreDumps。17.4配置永久内存区域存储这一部分提供了配置和使用永久内存作为VxWorks核心转储介质的详细流程。针对VxWorks核心转储功能默认配置写核心转储文件到永久内存区域,也用于存储错误检测和报告工具创建的记录。永久内存域是系统内存RAM的一部分——也就是说,在sysMemTop()上面。注明如下是关于永久内存域的信息:·若你使用一个VxWorks6.xbootloader,假如bootloader和VxWorks镜像的内存布局正确对齐,永久内存域的内容坚持通过一个热启动。(更多信息,参考17.4.3ConfiguringtheBootLoader。)·一个冷启动通常会删除永久内存域内容。你也可以使用pmInvalidate()函数明确删除域内容直到下一次重启创建它们。·永久内存的默认设置必须为核心转储重新配置。默认配置很小,很多核心转储不适合。作为用永久内存域为核心转储的一个选项,你也可以创建一个定制raw设备来提供核心转储工具(如flash内存或一个ATA磁盘)。更多信息,参考18.CoreDumpRawDeviceInterface。为了使用永久内存域核心转储功能,你必须执行如下:·配置VxWorks合适的组件(参考17.4.1ConfiguringtheVxWorksComponents)。这导致VxWorks产生核心转储,并存储它们在永久内存区域。·针对你期望的核心转储配置永久内存为合适大小(参考ConfiguringPersistentMemoryforCoreDumpUse。)这个设置驻留永久内存为核心转储介质。·配置bootloader使用相同大小的永久内存域(17.4.3ConfiguringtheBootLoader)。这通过重启,保持内存。17.4.1配置VxWorks组件默认,当增加INCLUDE_CORE_DUMP组件,即拉进永久内存存储要求的默认存储组件INCLUDE_CORE_DUMP_MEM。组件有如下配置参数:CORE_DUMP_REGION_SIZE这个参数指明了为核心转储存储预留的内存空间大小,默认是PM_RESERVED_MEM-EDR_ERRLOG_SIZE。设置时必须考虑其他系统内存参数;更多信息,参考ConfiguringPersistentMemoryforCoreDumpUse。警告:当改变永久内存域本身的大小时,必须对应改变bootloader永久内存域。更多信息,参考ConfiguringPersistentMemoryforCoreDumpUse和17.4.3ConfiguringtheBootLoader。也要确定考虑用户预留内存配置。关于永久内存用法VxWorks永久内存区域可以用于核心转储,错误检测和报告记录和其它目的。若错误检测和报告工具从VxWorks配置中排除,核心转储工具使用所有的永久内存空间,除非配置参数CORE_DUMP_REGION_SIZE设置为更小的值。若两种工具都包含在VxWorks中,永久内存的大小和分配由如下确定:永久内存域大小由INCLUDE_EDR_PM组件的PM_RESERVED_MEM参数确定。默认是6个内存页。错误检测和报告工具默认使用大约一半内存区域,如INCLUDE_EDR_ERRLOG组件的参数EDR_ERRLOG_SIZE定义。核心转储工具默认使用剩余的永久内存部分。通常比较小。(小于三个内存页)。关于错误检测和报告工具的更多信息,参考19.ErrorDetectionandReporting。配置核心转储永久内存用法若VxWorks配置了核心转储,没有没有配置错误检测和报告工具,你可以通过设置参数CORE_DUMP_REGION_SIZE来限制核心转储使用的永久内存大小。若VxWorks两个工具都配置,使用如下规则为核心转储永久内存配置正确的参数:设置EDR_ERRLOG_SIZE——默认是永久内存区域一半——指明一个具体的值。确定核心转储的大概大小,增加配置参数PM_RESERVED_MEM的大小(NCLUDE_EDR_PM组件)。若你想为其它目的预留一些永久内存空间,不是核心转储或错误检测和报告日志,设置参数CORE_DUMP_REGION_SIZE(组件INCLUDE_CORE_DUMP_MEM)为更小的值提醒:PM_RESERVED_MEM配置参数必须增加到足够大来容纳一个核心转储,但是必须保持足够小来为指向VxWorks预留足够的空间(镜像+系统内存)。17.4.2验证永久内存配置为了验证永久内存配置,在shell中使用pmShow()命令。若你配置了错误检测和报告记录工具,pmShow()用通用的方式展示两个永久内存域分区:->pmShowArenabaseaddress:0x62000000Arenarawsize:0x06000000Arenadatasize:0x05ffe000Arenafreespace:0x00000000Region0[edrErrorLog]offset:0x00002000size:0x00080000address:0x62002000Region1[CoreDumpStorage]offset:0x00082000size:0x05f7e000address:0x62082000value=0=0x0->关键数据元素如下所示:Arenarawsize是永久内存域总大小,包括永久内存私有数据或永久内存头(PM_RESERVED_MEM)。Arenadatasize是错误检测和报告日志和核心转储存储空间大小。Arenafreespace是0,若核心转储使用了所有的剩余永久内存空间。Region0是用于错误日志的永久内存区域,大小小于错误记录日志大小(EDR_ERRLOG_SIZE)。Region1是用于核心转储的永久内存区域,大小小于核心转储存储大小(CORE_DUMP_REGION_SIZE)。17.4.3配置bootloader永久内存区域是RAM系统内存上面的一块区域——在sysMemTop()上面——为错误记录和核心转储预留。INCLUDE_EDR_PM组件为永久内存提供支持。为了防止热启动时,永久内存区域被重写,bootloader必须设置和PM_RESERVED_MEM一样的值。否则,bootloader(镜像+堆)会重叠VxWorks永久内存域,破坏永久内存域中的数据。如,参考Figure17-1。因此,你改变默认VxWorks永久内存区域大小时,你必须创建和安装一个新的预留PM_RESERVED_MEM相同值的bootloader。注明当你改变bootloader的PM_RESERVED_MEM值时,你需要改变RAM_HIGH_ADRS的值,若RAM_HIGH_ADRS和sysMemTop()之间没有为bootloader本身预留足够的空间。若你这样做,也要确信RAM_LOW_ADRS和RAM_HIGH_ADRS之间有足够的空间容纳VxWorks镜像。另外,确信考虑用户预留内存配置。关于bootloader的更多信息,参考3.BootLoader。警告:不合适的bootloader配置,当系统启动时,会破坏永久内存区域。17.5使能CoreDumpFile压缩17.6通过捕获的CoreDumps改变内存区域17.7使用CoreDumpHookRoutine管理器17.8初始化内核CoreDump工具17.9产生内核CoreDumps17.10管理内核CoreDump文件17.11分析内核CoreDumps18CoreDumpRaw设备接口18.1介绍18.2创建定制Raw设备存储支持18.3编码一个Raw设备驱动接口18.4针对Raw设备驱动接口使用一个定制组件18.5增加Raw设备驱动接口到一个BSP19错误检测和报告19.1介绍VxWorks提供了一个错误检测和报告工具来用于帮助调试软件故障。通过记录软件异常到内存指定位置,且热启动时指定内存内容不会被清除。这个工具也允许为重要错误选择回复,针对开发和部署系统提供了可选择策略。错误检测和报告工具主要功能如下:·RAM中一个永久内存区域用于保存热启动不会清除的错误记录。·记录不同错误类型的机制。·错误记录为相关的运行错误提供详细的信息和发生错误的条件。·在shell中显示错误记录和清零错误日志的功能。·对于致命错误,可选择的系统回复错误处理选项。·用户模式使用错误报告的宏。edrLibAPI引用中描述的钩子函数作为基础,用于实现针对非RAM存储错误记录的定制功能。关于错误检测和报告函数的更多信息,参考edrLib,edrShow,edrErrLogLib,和edrSysDbgLib。关于相关工具的更多信息,参考15.8MemoryErrorDetection。注:本章提供了Vxworks内核中有的工具信息。关于实时进程有的相关工具信息,参考VxWorksApplicationProgrammer’sGuide。19.2配置错误检测和报告工具为了使用错误检测和报告工具:·VxWorks必须配置合适的组件。·一个永久RAM内存区域必须配置,且要足够大容纳错误日志。·可选,用户可以改变系统对致命错误的回复。19.2.1配置VxWorks为了使用错误检测和报告工具,内核必须配置如下组件:■INCLUDE_EDR_PM■INCLUDE_EDR_ERRLOG■INCLUDE_EDR_SHOW■INCLUDE_EDR_SYSDBG_FLAG为了方便增加上述所需组件,增加BUNDLE_EDR组件包即可。19.2.2配置永久内存区域永久内存域是系统内存上面的一个RAM域,指明为错误记录和核心转储预留。被MMU和VxWorksvmLib工具保护。热启动不会清除这块内存,提供一个使用的VxWorks6.xbootloader。注:永久内存域并不是所有的体系结构都支持(参考VxWorksArchitectureSupplement),并不针对对称多处理器(SMP)Vxworks配置写保护。关于VxWorksSMP和移植的更多信息,参考24.VxWorksSMP和24.17MigratingCodetoVxWorksSMP。冷启动会清除永久内存域。pmInvalidate()函数也显式用于销毁永久内存域,所以在下一次热启动时要重建。当目标系统包含一个MMU和VxWorks配置支持MMU时,永久内存域被写保护。永久内存域大小通过配置PM_RESERVED_MEM参数定义(组件INCLUDE_EDR_PM)。默认是6个内存页大小。默认,错误检测和报告工具使用一半的永久内存域。若没有其它应用需要永久内存域,可以配置组件用尽可能多永久内存。这个通过定义EDR_ERRLOG_SIZE为PM_RESERVED_MEM的大小,小于一页内存大小(用于维护内部永久内存数据结构)。若你增加改变默认永久内存域大小,你必须用相同的PM_RESERVED_MEM值创建一个新的bootloader。更多信息,参考ReconfiguringMemoryLayoutforaPersistentMemoryRegion。警告:若bootloader没有合适配置,会导致系统启动时永久内存域数据被破坏。EDR_RECORD_SIZE参数用于改变默认错误记录大小。注明由于性能原因,所有的记录的大小一样。pmShow()Shell命令(C解析器)用于显示分配和剩余的永久内存大小。关于永久内存的更多信息,参考15.6ReservedMemory和thepmLibAPIreference。警告:一个VxWorks6.xbootloader必须保证每次热启动永久内存域不会被清除。Bootloader的早期版本会清除这个域。19.2.3配置对应致命错误的回复错误检测和报告工具提供了两组针对指明错误的回复。参考关于回复的信息19.5FatalErrorHandlingOptions,针对实时系统选择不同的方式。19.3错误记录当系统产生错误时,错误记录自动产生。记录存储在永久内存上的一个环形缓存中。当永久内存满时,新增加的错误覆盖旧的错误记录。记录根据连个基本标准分类:·事件类型·严重级别事件类型区分了错误产生的背景(在系统初始化阶段,或在一个进程中等)。严重级别表示错误的严重性。如是重要错误,严重级别关联一个可选择的系统回复(19.5FatalErrorHandlingOptions)。根据产生的事件类型收集错误信息。通常,一个记录的完整的错误被记录。对于一些事件,记录部分被排除在清晰度外。如启动和重启记录排除记录的注册部分。错误记录保存系统产生错误事件时刻的详细信息。每个记录包括如下通用信息:·记录产生的日期时间·类型和严重级别·操作系统版本·任务ID·进程ID,若在进程中任务中调用失败。·记录产生的源文件,行号。·一个自由格式的文件信息。也可选包括如下体系结构相关信息:·内存映射·异常信息·处理器寄存器·反汇编列表(包括故障地址)·栈跟踪。19.4显示和清理错误记录edrShow库提供了一组针对shellC解析器的命令用于展示自从永久内存上一次清除后的错误记录。参考Table19-3。Shell的命令解析器提供可比较的命令。参考shell的API引用,或使用helpedr命令。附加显示错误记录,每个展示命令也展示错误日志的通用信息:·日志的总大小·每条记录大小·日志中记录最大条数·Cpu类型·丢失的记录数·日志中激活的记录数·日志创建后重启次数19.5重要错误处理选项除了生成错误记录,错误检测和报告工具针对每一种致命错误的事件类型提供两种系统回复模式:·调试模式,实验系统(开发)·部署模式,生产系统(成熟)在进程中这些模式对于致命错误的回复方式不同。在调试模式,一个进程中的致命错误会导致进程停止。在部署系统,一个进程中致命错误会导致进程中止。错误处理方式由系统调试标志确定(19.5.2SettingtheSystemDebugFlag)。默认是部署模式。Table19-4描述了每一种错误类型不同模式下的回复。也列出了当致命记录创建时调用的函数。调用的错误处理函数回复特定的致命错误。仅致命错误——非其他事件类型——有异常回调管理。这些回调调用在文件installDir/vxworks-6.x/target/config/comps/src/edrStub.c中。开发者可以修改文件中的函数来应用于不同的系统回复方式。函数名不能改变。注明:当调试器关联到目标机时,在错误处理选项激活之前获取系统控制权,因此当重启时调用错误处理选项时也可以调试。19.5.1配置错误处理选项为了为重要错误提供调试模式错误处理选项,VxWorks必须配置INCLUDE_EDR_SYSDBG_FLAG组件,默认被配置。此组件允许一个系统调试标志用于选择调试默认,也适用于复位部署模式(参考19.5.2SettingtheSystemDebugFlag)。若删除INCLUDE_EDR_SYSDBG_FLAG组件,系统默认为部署模式(参考Table19-4)。19.5.2设置系统调试标志错误检测和报告工具针对指明错误的回复,仅记录错误的方式取决于系统调试标志的设置。当系统配置了INCLUDE_EDR_SYSDBG_FLAG组件,标志可以用于在调试模式或部署模式(默认)设置致命错误处理。对于正在开发的系统,很明显希望系统处理易调试状态,而在部署系统中,目标是尽可能快从致命错误中恢复并开始新的工作。调试标志可以按照如下方式设置:静态,用bootloader配置。交互,启动时刻。编程,在应用代码中使用相关API。当一个系统启动时,控制台上显示的标志信息由系统调试标志定义。如:ED&RPolicyMode:Deployed使用的标示有Debug,Deployed或PermanentlyDeployed。后者标示NCLUDE_EDR_SYSDBG_FLAG组件没有包含在系统中,意味着只能使用部署模式,不能使用调试模式。更多信息,参考如下部分和edrSysDbgLib相关API(尤其是函数edrSystemDebugModeSet())。静态设置调试标志当一个bootloader配置和编译时系统可以用fbootloader参数设置系统为部署模式或调试模式。值0x000用于选择部署模式。值0x400用于选择调试模式。默认,设置为部署模式。关于配置和编译bootloader的更多信息,参考3.7ConfiguringandBuildingBootLoaders。交互设置调试模式为了交互改变系统启动标志,当引导时停止系统。后在bootloader命令行提示符中使用c命令。改变f参数的值:使用0x000为部署模式,0x400为调试模式。编程设置调试模式系统调试标志的状态也可以在用户代码中用edrSysDbgLibAPI改变。19.6在应用代码中使用错误报告APIsedrLib.h文件提供了一组方便的宏,开发者在开发者选择的条件下在源码中可以使用这些宏来产生错误信息(系统对致命信息的回复)。若VxWorks没有正确配置错误检测和报告工具相关组件,这些宏没有任何意思。因此代码必须无条件的编译来充分使用这些工具。edrLib.h在installDir/vxworks-6.x/target/h文件中。如下宏被提供:EDR_USER_INFO_INJECT(trace,msg)产生一个事件类型为USER和严重级别为INFO的记录到记录日志中。EDR_USER_WARNING_INJECT(trace,msg)产生一个事件类型为USER和严重级别为WARNING的记录到记录日志中。EDR_USER_FATAL_INJECT(trace,msg)产生一个事件类型为USER和严重级别为FATAL的记录到记录日志中。所有的宏使用相同的参数。trace参数是一个布尔型值标示是否一个跟踪回馈应该为记录产生。msg参数是一个增加到记录的字符串。19.7样本错误记录如下是一个失败内核任务产生的记录样本:==[1/1]==============================================================Severity/Facility:FATAL/KERNELBootCycle:1OSVersion:6.0.0Time:THUJAN0105:15:071970(ticks=1134446)Task:"kernelTask"(0x0068c6c8)InjectionPoint:excArchLib.c:2523fatalkerneltask-levelexception!<<<<<MemoryMap>>>>>0x00100000->0x002a48dc:kernel<<<<<ExceptionInformation>>>>>dataaccessExceptioncurrentinstructionaddress:0x002110ccMachineStatusRegister:0x0000b032DataAccessRegister:0x50000000ConditionRegister:0x20000080DatastorageinterruptRegister:0x40000000<<<<<Registers>>>>>r0=0x00210ff8sp=0x006e0f50r2=0x00000000r3=0x00213a10r4=0x00003032r5=0x00000001r6=0x0068c6c8r7=0x0000003ar8=0x00000000r9=0x00000000r10=0x00000002r11=0x00000002r12=0x0000007fr13=0x00000000r14=0x00000000r15=0x00000000r16=0x00000000r17=0x00000000r18=0x00000000r19=0x00000000r20=0x00000000r21=0x00000000r22=0x00000000r23=0x00000000r24=0x00000000r25=0x00000000r26=0x00000000r27=0x00000000r28=0x00000000r29=0x006e0f74r30=0x00000000r31=0x50000000msr=0x0000b032lr=0x00210ff8ctr=0x0024046cpc=0x002110cccr=0x20000080xer=0x20000000pgTblPtr=0x00481000scSrTblPtr=0x0047fe4csrTblPtr=0x0047fe4c<<<<<Disassembly>>>>>0x2110ac2c0b0004cmpicrf0,0,r11,0x4#40x2110b041820024bc0xc,2,0x2110d4#0x002110d40x2110b42c0b0008cmpicrf0,0,r11,0x8#80x2110b841820030bc0xc,2,0x2110e8#0x002110e80x2110bc4800004cb0x211108#0x002111080x2110c03c600021lisr3,0x21#330x2110c483e1001clwzr31,28(r1)0x2110c838633a10addir3,r3,0x3a10#14864*0x2110cca09f0000lhzr4,0(r31)0x2110d048000048b0x211118#0x002111180x2110d483e1001clwzr31,28(r1)0x2110d83c600021lisr3,0x21#330x2110dc38633a15addir3,r3,0x3a15#148690x2110e0809f0000lwzr4,0(r31)0x2110e448000034b0x211118#0x002111180x2110e883e1001clwzr31,28(r1)<<<<<Traceback>>>>>0x0011047cvxTaskEntry+0x54:0x00211244()0x00211258d+0x18:memoryDump()第二部分:VxWorks定制20定制组件和CDFs20.1介绍一个VxWorks组件是一个可以用Workbench或vxprj命令行工具配置的基本功能单元。VxWorks组件使用VxWorks组件描述语言以组件描述文件(CDFs)定义。这个语言提供定义组件对象和它们的属性的语法(如代码模块和初始化函数)。也为定义对象提供配置参数,初始化组,组件包和组件原型,还有Workbench文件夹和选择。实际上,CDFs用Workbench和vxprj提供外部接口,允许用户选择和配置组件,使用潜在配置和编译工具的内部接口,定义用于产生一个VxWorks镜像的代码。通过为定制操作系统工具(如文件系统和网络协议)创建CDFs,并和关联的代码安装,定制工具可以用WorkBench和vxprj管理,像标准的VxWorks功能一样。除了用于操作系统工具,组件描述语言和CDFs用于驱动和BSP。关于这些主题的更多信息,参考VxWorksDeviceDriverDeveloper’sGuide和VxWorksBSPDeveloper’sGuide。注:风河推荐使用CDFs来开发扩展VxWorks操作系统。功能可以以内核模块方式增加到VxWorks系统,不一定要定义为VxWorks组件(参考4.2AboutKernelApplications)。然而,为了充分利用Workbench和vxprj命令行配置工具,扩展操作系统应用使用CDFs开发。若你想创建API用于RTP应用的组件(运行在用户模式),也参考21.CustomSystemCalls。20.2组件的代码开发操作系统工具的代码通常在归档文件中提供。模块的初始化代码可能以二进制或源码格式提供。关于定义组件初始化CDF属性的信息,参考ComponentInitializationProperties和20.4.5InitializationGroupObjectandProperties。充分使用参数的用户代码通过工程工具方式应该以源码方式提供。关于定义组件参数和关联代码的CDF属性信息,参考ConfigurationParameterProperty和20.4.4ParameterObjectandProperties。关于代码安装位置信息,参考20.3CodeInstallationDirectories。20.3代码安装目录风河提供了VxWorks组件自身的不同元素在如下位置:源代码模块通常在installDir/vxworks-6.x/target/src或target/config目录中。头文件在installDir/vxworks-6.x/target/h。相关组件属性,参考HDR_FILES。提供在归档文件中的对象模块在installDir/vxworks-6.x/target/lib/objARCH。相关组件属性参考LINK_SYMS和MODULES。组件描述文件在installDir/vxworks-6.x/target/config/comps/vxWorks和其它目录,取决于它们的范围和功能,关于CDF内容相关信息,参考20.4CDFObjectsandProperties。Componentconfiglettes(sourcefragments)在installDir/vxworks-6.x/target/config/comps/src。相关组件属性,参考seeCONFIGLETTES。使用默认安装目录的优势是可以自动被工程工具读。若没有使用这些路径,元素的位置应该在CDF对象属性中指明。20.4CDF对象和属性这一部分提供了关于CDF对象和属性语法和关于操作系统工具组件的元素用法,BSP和驱动的相关参考信息。CDFs可能使用几种对象类,每一种有不同的属性。在一个CDF文件中没有强制的顺序信息——不同对象顺序,或对象内属性顺序。对象类如下:操作系统对象如下部分描述了用于针对一个操作系统工具或设备驱动创建一个组件的对象:■20.4.3ComponentObjectandProperties,p.495■20.4.4ParameterObjectandProperties,p.504■20.4.5InitializationGroupObjectandProperties,p.506组件组对象如下描述了分组组件相关信息:■20.4.6BundleObjectandProperties,p.509■20.4.7ProfileObjectandProperties,p.511Workbench对象如下描述了Workbench内核配置接口中用于控制显示和组件选择对象信息:■20.4.8WorkbenchFolderObjectandProperties,p.512■20.4.9WorkbenchSelectionObjectandProperties,p.515BSP对象如下描述了用于定义一个BSP的对象信息:■20.4.10BSPObjectandProperties,p.51620.4.1预留对象名前缀如下前缀针对具体对象类型命名:■INCLUDE_forcomponents.■BUNDLE_forcomponentbundles.■PROFILE_forprofiles.■FOLDER_forWorkbenchfolders.■SELECT_forWorkbenchselections.推荐使用前缀(避免命名冲突),但是不必须。不要混淆使用这些前缀。20.4.2强调操作符和对象的关系强调操作符可以用于定义对象属性,定义一种关系来扭转一种关系的定义——通常伴随list-of-members到member-of的改变行。如,若FOREST是一个景观对象用于标示树林的一个属性,后_FOREST用于标示属于一个具体的景观,没有修改景观对象本身。强调操作符最常用用法是指派一个新的组件对象到一个存在的初始化组中或Workbench文件夹中——分别使用_INIT_ORDER和_CHILDREN——无须修改已经存在的对象。如,假设针对文件夹FOLDER_EXISTINGCDF文件包括如下:FolderFOLDER_EXISTING{NAMETheexistingfolderSYNOPSISThefolderthat’sbeenaroundawhileCHILDRENINCLUDE_FOO\...}后一个新的组件INCLUDE_BAR可以增加到Workbench文件夹,无须修改文件夹CDF,在组件中使用_CHILDREN,如下:ComponentINCLUDE_BAR..._CHILDRENFOLDER_EXISTING...}注明一个关系可以使用前向或反向声明,但是有区别。前向声明定义一个关系。任何后来的前向声明会覆盖所有先前的关系(前向后后向)。后向声明通常用于增加到已经存在的关系,所有的反向声明都是累积的。20.4.3组件对象和属性组件对象定义操作系统功能单元或设备驱动,所以它们可以使用Workbench和vxprj命令行工程工具配置系统。最简单的组件要求一个组件名和与模块关联的身份。其它要求初始化函数和它们的初始化组,参考,组件依赖和Workbench组件配置接口中的属性定义显示标示等。组件对象的属性用于完成这些需求。新的组件应该独立于存在的CDF对象,两种情况例外:·新组件对象初始化函数必须和一个存在的初始化组对象关联。·新组件对象应该和一个存在的文件夹或针对Workbench显示和选择目的的选择组件绑定。一个单独的CDF文件可以定义多个组件。当一组组件形成一个逻辑组时非常有用。如,参考VxWorks内核shell的CDFs(包括很多组件)和loader(包括系统特征表相关组件):01shell.cdf和01loader.cdf在文件installDir/vxworks-6.x/target/config/comps/vxWorks中。关于Workbench内核中显示组件信息相关信息,参考20.4.8WorkbenchFolderObjectandProperties和20.4.9WorkbenchSelectionObjectandProperties。关于如何分配组件到组件包和原型,参考20.4.6BundleObjectandProperties和20.4.7ProfileObjectandProperties。关于文件命名规范,参考20.5CDFFileNamesandPrecedence。关键针对设备驱动创建组件和CDF文件的信息,参考VxWorksDeviceDriverDeveloper’sGuide。组件模板ComponentINCLUDE_MY_COMP{NAMEuser-friendlynameforGUISYNOPSISdescriptionforGUICONFIGLETTESfile1.cfile2.c...MODULESaLib.obLib.o...INIT_RTNinitMyComp(MAX_THISMAX_THAT);HDR_FILESheader1.hheader2.h...LINK_SYMSaFuncNamebFuncName...CFG_PARAMSMAX_THIS\MAX_THAT...REQUIRESINCLUDE_FOO1\INCLUDE_FOO2\INCLUDE_FOO3\...ARCHIVE/somePrivateDir/myArchiveDir/myArchive.aINCLUDE_WHENINCLUDE_BAR1\INCLUDE_BAR2\INCLUDE_BAR3\..._INIT_ORDERmyInitGroupINIT_BEFOREINCLUDE_FOOBAR_CHILDRENFOLDER_MY_FOLDER_DEFAULTSFOLDER_MY_FOLDER}组件名称一个组件必须有一个唯一的名字。组件名可以使用Workbench或vxprj来标示组件。对应操作系统工具,名字应该包括INCLUDE_prefix前缀(避免冲突),如INCLUDE_HRFS。对于设备驱动组件(尤其是VxBus兼容驱动),名字应该使用如下语法:DRV_driverType_driverName如,DRV_SIO_NS16550。名称作为一个C宏名称有效,用于VxWorks配置工具。组件命令指派语法如下,使用Component关键字和大括号内包括组件对象属性:ComponentINCLUDE_MYCOMP{//componentpropertyassignmentsgohere}参考ComponentTemplate和ComponentExample。组件名在组件Workbench展示Name列出现,参考Figure20-1。注明组件命名和组件描述不一样(参考WorkbenchDisplayProperties)。组件属性组件属性指派的值用于定义组件属性,如下:·组件提供的对象代码(模块)和源码。·工程编译的源码(有时指configlettes,因为通过CONFIGLETTES属性识别)·头文件。·系统启动时调用的初始化函数。·其它组件依赖。·WorkbenchGUI显示信息模块链接属性LINK_SYMS当组件增加到VIP工程时,提供一个链接一个组件对象模块到系统镜像的机制——即使没有那些模块的特征信息引用。这个通过指派其中一个对象的公共特征为LINK_SYMS;一个模块仅需要一个。假如,foo()和bar()分别由fooLib.o和barLib.o提供。后如下指派要保证当组件增加到工程后,fooLib.o和barLib.o连接到VxWorks内核。LINK_SYMSfoobar通常,所有的组件模块(除了用INIT_RTN标示)应该用LINK_SYMS标示。如是一个初始化函数,用INIT_RTN标示。会自动链接到系统镜像,当组件包含时;也不必须用LINK_SYMS标示,参考INIT_RTN。注明LINK_SYMS没有定义组件内部关系或依赖。所有定义LINK_SYMS为的特征必须有组件本身提供。关于显式定义组件依赖的信息,参考ComponentDependencyProperties。MODULES标识组件的对象文件来确保一个或多个特征被工程工具引用时对待为一个单元(被另外一个对象模块或应用代码),组件用工程注册。通常,所有的组件模块应该用MODULES标识。如:MODULESfooLib.obarLib.o注明用MODULES标识模块,当组件包含在工程后,不保证模块已经被链接。默认,模块不需要完成链接的对象(也就是说那些不会被特征引用的对象)不会链接到VxWorks内核。为了强制模块链接(当没有特征引用时),必须用INIT_RTN(初始化函数)或LINK_SYMS识别。参考INIT_RTN和LINK_SYMS。MODULES的作用是确保当其中一个模块基于一个来自另外一个组件或应用的特征引用时,所有标识的模块被链接到系统镜像。另外,初始化函数(若定义)和任何组件需求(REQUIRES)被增加到系统,增加被增加到组件的工程列表中。因此组件被对待为一个一致单元。若一个组件的模块不用MODULES标识,它们基于特征引用链接,但是组件不会增加到组件的VIP列表中和初始化函数不会链接。提醒:一个对象文件必须仅和一个组件管理,且名字是全局唯一的(整个系统中)。改变归档文件位置属性ARCHIVE标识对象模块存放的位置。这个在归档文件若没有安装在标识位置时,是必须的。如:ARCHIVE/home/someDir/fooLib.a注明一个归档文件用这种方式标识对归档文件中的内容是否链接到系统镜像中没有影响。仅提供可用的内容;特征引用和其它CDF对象属性定义确定链接。归档文件的标准位置是installDir/vxworks-6.x/target/lib/objARCH。构建系统自动引用这些目录。注:不要修改风河归档文件。为定制组件创建新的归档文件。组件初始化属性若一个组件有一个初始化函数,必须用在组件对象中标识(使用组件的INIT_RTN属性),也必须注册为一个初始化组。有两种可选择的方法为注册一个带初始化组的组件,目的是控制组内初始化顺序:从组件对象内部,使用_INIT_ORDER属性来注册一个初始化组组件,(可选)INIT_BEFORE来控制顺序。使用这个方法来增加一个组件到一个存在的初始化组,无须修改组对象。更多信息,参考_INIT_ORDER和INIT_BEFORE。从一个初始化组对象内,使用INIT_ORDER属性来注册组组件和控制初始化顺序。若你想为一个定制组件组创建一个新的初始化组,使用这个方法。更多信息参考20.4.5InitializationGroupObjectandProperties和INIT_ORDER。初始化组自身顺序通过初始化组对象属性处理(更多信息,参考InitializationGroupObjects)。INIT_RTN若组件有一个初始化函数,INIT_RTN必须用于标识它。指派初始化函数为INIT_RTN使得连接提供初始化函数的模块被链接到内核(若组件包含在工程中)。为了在系统启动阶段调用初始化函数,必须增加组件到初始化组。关于存在选项的更多信息,参考ComponentInitializationProperties。初始化函数指派方法如下:INIT_RTNfooInit(arg1,arg2);若刚好在一行上,则整个初始化函数本身会指派INIT_RTN;否则,将会指派签名,代码可能以组件归档文件中一个二进制模块提供,或源码格式提供(用CONFIGLETTES标识,参考CONFIGLETTES。)。关于组件初始化顺序的定义相关信息,参考INIT_BEFORE和_INIT_ORDER。注明初始化函数标识一个组件对象和一个初始化组对象的初始化函数不一样;组件INIT_RTN属性和初始化组INIT_RTN属性不一样。若一个组件没有初始化函数,任何组件的模块应该包含在系统镜像中,不管是否有外部引用,而LINK_SYMS必须用于识别这些模块。参考LINK_SYMS。_INIT_ORDER指派组件为指定的初始化组。这个属性用于增加组件到存在的初始化组。也可以增加组件到初始化组对象本身(参考ComponentInitializationProperties和20.4.5InitializationGroupObjectandProperties;参考20.4.2UnderscoreOperatorandObjectRelationships)。大多数情况下,定制组件应该在VxWorks组件后初始化,指明如下:_INIT_ORDERusrRoot一个指派给一个存在初始化组的组件(或初始化组)默认使用_INIT_ORDER属性在那个组中的最后一个初始化。INIT_BEFORE可以用于改变这个顺序(参考INIT_BEFORE)。INIT_BEFORE在一个指明为INIT_BEFORE属性前调用这个组件的初始化函数。这个属性用于用_INIT_ORDER指定的初始化组中的初始化顺序(参考_INIT_ORDER)。INIT_BEFORE有效的引起组件被插入到一个存在的初始化组组件列表中(参考INIT_ORDER)。大多数情况下,定制组件应该在VxWorks组件后初始化;然而,你可以使用这个INIT_BEFORE组件覆盖这个行为。如:..._INIT_ORDERusrRootINIT_BEFOREINCLUDE_USER_APPL...在这个例子中,组件声明为初始化组usrRoot的一个成员(基本的VxWorks初始化组),将会在VxWorks组件INCLUDE_USER_APPL前初始化。相同的机制可以用于控制定制初始化组内初始化顺序。INIT_BEFORE属性仅作用于初始化组内组件顺序。不要引用不在初始化组中的组件顺序,是无效的。另外,此属性和_INIT_ORDER属性配合使用。源文件属性CONFIGLETTES标识C源文件应该作为组件的一部分编译。Configlette定义将会包括宏的使用。如:CONFIGLETTES$(MY_VAR)\myFile.cMY_VAR是一个编译宏或一个环境变量。通常,属性用于标识组件参数使用的代码(用于指明一个数,一个设置为真假的二进制值等),或当前组件的存在条件。为了让用户可访问使得用Workbench或vxprj配置,参数本身必须用组件CFG_PARAMS属性和一个参数对象指定。如,若代码用CONFIGLETTES使用MAX_FOO宏标识,,则对象CFG_PARAMS属性必须设置为MAX_FOO,必须有一个MAX_FOO参数定义了参数的属性。参考CFG_PARAMS和20.4.4ParameterObjectandProperties。对于每一个用CONFIGLETTES标识的源文件,工程工具在会在工程的prjConfig.c文件中生成一个#include目录(和用HDR_FILES标识的头文件一样;参考HDR_FILES)。没有指定包含顺序,所有的代码和其它工程产生的代码一块编译。关于prjConfig.c和其它工程文件的更多信息,参考20.8AboutCDFs,VxWorksConfiguration,andBuild。当所有标识为CONFIGLETTES的源文件(和包含的组件关联)在一块编译时,小心修改源文件中的任何配置宏值,因为修改可能会在其它源文件中可见,会导致不可预知的结果。用CONFIGLETTES标识的源文件的默认存放位置是installDir/vxworks-6.x/target/config/comps/src。这些源码的头文件用HDR_FILES属性标识。(参考HDR_FILES)提醒:不要修改风河提供的源文件installDir/vxworks-6.x/target/config/comps/src。HDR_FILES标识组件初始化函数和其它源代码需要的头文件。如:HDR_FILESfoo.hbar.h对于每一个用HDR_FILES标识的头文件,工程工具会在工程的prjConfig.c文件中产生一个#include目录(和用CONFIGLETTES标识的源文件一样,参考CONFIGLETTES)。所有的文件和工程产生的代码一块编译。关于prjConfig.c和其它工程文件更多信息,参考20.8AboutCDFs,VxWorksConfiguration,andBuild。参考INIT_RTN对应标识组件的初始化函数;参考CONFIGLETTES对应标识组件的源码元素。配置参数属性CFG_PARAMS标识组件关联的配置参数,通常是一个预处理宏列表,暴漏给用户用于通过Workbench或vxprj改变。每个配置参数必须在自身的参数对象中独立描述;参考20.4.4ParameterObjectandProperties。如,若组件中的一个参数用如下方式描述:ComponentINCLUDE_TRBL{...CFG_PARAMSMAX_TRBL...}之后,必须有一个参数对象定义,如下:ParameterMAX_TRBL{NAMEmaximumtrblSYNOPSISThemaximumtrblpermitted.Highernumberformore.TYPEintDEFAULT0x29A}组件参数必须用用CONFIGLETTES或INIT_RTN标识的代码中实现;否则无效。参考CONFIGLETTES和INIT_RTN。组件依赖属性当组件基于特征引用时,组件间的依赖被工程工具自动解决。(当MODULES属性被正确使用,这样所有组件的模块被链接到镜像,且组件被增加到工程;参考MODULES)当引用基于函数指针或源码提供的函数时,需要的组件必须显示标识。如,若INCLUDE_FOO提供foo.o,且在foo.o中调用logMsg(),,则编译系统会自动确定需要增加INCLUDE_LOGGING组件到VxWorks内核,当INCLUDE_FOO组件增加时。若INCLUDE_FOO包含调用barCreate(),由INCLUDE_BAR提供,这些调用通过函数指针(或若代码由源码格式的组件提供),则INCLUDE_FOO组件必须使用REQUIRES属性来标识这个依赖,所以当INCLUDE_FOO组件增加时,INCLUDE_BAR组件增加到配置中。如下属性定义了组件间的依赖关系,这些组件的组件间依赖关系不是由特征引用解决的。REQUIRES一个组件需要其它一系列组件才能工作,这些组件不是基于特征引用包含到系统中。尤其是,当要求的组件以源码格式提供时或函数通过函数指针引用时必须使用REQUIRES属性。如,若组件A用REQUIRES,识别组件B,但是B没有一个相应的指派(非链接),则如下行为要保存正确:·若你增加A到一个工程,B自动增加。·若你随后删除B,则A也会被自动删除因为A离了B不能正常工作。通用,若你删除了A,则B自动删除,因为B根据REQUIRES,依赖关系增加,而不是由用户显示删除。·若你增加B到工程A,A不会自动增加,因为B不需要A。·若你随后增加A,对B的依赖已经完成。·若你之后删除A,B依然是工程的一部分,因为被用户显式增加。INCLUDE_WHEN若包含指派为INCLUDE_WHEN的组件被包含时,设置一个依赖关系让自动包含组件。这个属性为用户提供了极大的便利性,不表示一个组件对另外一个组件的硬依赖。若INCLUDE_FOO和INCLUDE_BAR组件包含在配置中,如下意味着组件会被自动增加。INCLUDE_WHENINCLUDE_FOOINCLUDE_BAR删除此组件时,不会对INCLUDE_FOO或INCLUDE_BAR组件产生影响;但是若INCLUDE_FOO或INCLUDE_BAR组件删除时,依赖的组件也会被删除。Workbench显示属性如下属性控制Workbench中组件的信息显示。NAME一个描述性字符串,出现在Workbench中组件显示信息Description列(参考Figure20-1)。这个字符串的目的是提供一个比Name列中提供的用户更加友好的描述,是用于vxprj命令行配置工具的标识符。这个字符串提供一个简要组件描述(制约于显示空间),而SYNOPSIS属性可以用于提供一个更全面的描述。注明组件名和用于NAME属性的描述性字符串不一样。组件名(如INCLUDE_FOO组件)出现在Workbench组件显示信息Name列(参考Figure20-1)。关于组件名更多信息,参考ComponentName。SYNOPSIS出现在Workbench组件显示中Synopsis选项卡中的描述性字符串。这个字符串用于提供一个更完整的描述。如NAME和SYNOPSIS,,如下属性也会影响Workbench中的用户显示方式:_CHILDREN表示这个组件是指定文件夹或选择成员。这个属性允许增加组件到文件夹或选择,无须修改文件夹或选择对象本身。若引用的文件夹或选择不存在,组件将不会显示在Workbench中。参考20.4.8WorkbenchFolderObjectandProperties和20.4.9WorkbenchSelectionObjectandProperties,也参考20.4.2UnderscoreOperatorandObjectRelationships。_DEFAULTS表示这个组件是指定文件夹或选择的默认组件。这个属性允许增加组件到一个文件夹或选择,无须修改引用的文件夹或选择本身。必须和联合使用_CHILDREN。参考20.4.2UnderscoreOperatorandObjectRelationships和DEFAULTS,20.4.9WorkbenchSelectionObjectandProperties。组件例子作为一个例子,一个信息登录组件的CDF文件格式如下:ComponentINCLUDE_LOGGING{NAMEmessageloggingSYNOPSISProvideslogMsgsupportMODULESlogLib.oINIT_RTNlogInit(consoleFd,MAX_LOG_MSGS);CFG_PARAMSMAX_LOG_MSGSHDR_FILESlogLib.h}强调下,在另外一个配置系统中,使用语句,日志组件的定义如下所示;#ifdefINCLUDE_LOGGINGlogInit(consoleFd,MAX_LOG_MSGS);#endif/*INCLUDE_LOGGING*/20.4.4参数对象和属性如下属性描述了一个参数:NAME一个描述性字符串,出现在Workbench组件显示Description列(Figure20-1)。这个字符串提供一个更友好的组件描述,和Name列提供的信息相比,是用于vxprj命令行配置工具的标识符。TYPE定义参数的数据类型,可能是int,uint,bool,string,exists或untyped。类型exists应该用于当参数名应该被定义或未定义为C宏(默认可以设置为TRUE或FALSE)。如,配置参数CLEAR_BSS的数据类型是exists。若它的值设置为TRUE(默认),如下出现在工程文件prjParams.h中:#undefCLEAR_BSS#defineCLEAR_BSS关于prjParams.h和其它工程文件的信息,参考20.8AboutCDFs,VxWorksConfiguration,andBuild。DEFAULT参数的默认值。如没有设置,用户必须定义一个值来使用这个组件。参数例子tip组件对象和相关的参数对象实现了TIP_CONFIG_STRING和TIP_ESCAPE_CHARACTER配置参数——用于初始化函数的参数呢——如下:ComponentINCLUDE_TIP{NAMEtipseriallineconnectionutilitySYNOPSISinteractiveutilitytoconnecttoandmanage\multipleseriallinesCONFIGLETTESusrTip.cMODULEStipLib.oINIT_RTNusrTipInit(TIP_CONFIG_STRING,TIP_ESCAPE_CHARACTER);HDR_FILESprivate/tipLibP.hLINK_SYMStipStartINCLUDE_WHENINCLUDE_SHELLCFG_PARAMSTIP_CONFIG_STRING\TIP_ESCAPE_CHARACTERHELPtip}ParameterTIP_CONFIG_STRING{NAMETipglobalconfigurationstringSYNOPSISUsedtodefineconnectionlinesattributesTYPEstringDEFAULT""}ParameterTIP_ESCAPE_CHARACTER{NAMETipescapecharacterSYNOPSISCharactertogglingcommandmodeTYPEstringDEFAULT"~"}注明参数用于组件初始化函数usrTipInit(),用CFG_PARAMS属性标识为组件的参数,每个参数独立定义。usrTipInit()函数的原型——在usrTip.c文件中提供,用CONFIGLETTES属性标识——如下:voidusrTipInit(char*configString,char*escapeCharStr)编译工程后,prjConfig.c文件包含如下:usrTipInit(TIP_CONFIG_STRING,TIP_ESCAPE_CHARACTER);/*interactiveutilitytoconnecttoandmanagemultipleseriallines*/prjParams.h文件包含如下定义(若参数保存默认)#undefTIP_CONFIG_STRING#defineTIP_CONFIG_STRING""#undefTIP_ESCAPE_CHARACTER#defineTIP_ESCAPE_CHARACTER"~"关于prjConfig.c,prjParams.h的和其它工程的更多信息,参考20.8AboutCDFs,VxWorksConfiguration,andBuild。20.4.5初始化组对象和属性若一个组件有一个初始化函数,则必须用INIT_RTN属性标识,且需要注册在一个初始化组中。注册到一个初始化组有两个方式来控制组内初始化顺序:来自组件对象,使用_INIT_ORDE属性注册组件到一个初始化组,和INIT_BEFORE可选来控制顺序。使用这个方法来增加组件到存在的初始化组,无须修改组对象本身。更多信息参考ComponentInitializationProperties,_INIT_ORDER和INIT_BEFORE。来自初始化组对象,使用属性注册组组件并控制初始化顺序。使用这个方法,如你想创建一个新的初始化组。更多信息,参考InitializationGroupObjects和INIT_ORDER。组本身初始化顺序由初始化组对象属性确定(参考InitializationGroupObjects)。初始化组对象初始化组为相关对象初始化提供逻辑分组的办法,在系统启动时,控制系统设备的初始化顺序。初始化组必须有自身的初始化函数。可能在另外一个初始化组中,或直接注册到VxWorksroot初始化组中,usrRoot,是预内核初始化入口点。若一个初始化组没有直接注册到usrRoot,它必须注册到一个初始化组序列中,它的顶级初始化组是usrRoot.。如,内核shell针对每个组件有初始化函数;这些函数被注册到一个shell初始化组中,这个组用usrRoot注册。初始化组内代码被合成在工程文件prjConfig.c中,每个工程产生一个。关于prjConfig.c的更多信息,参考20.8AboutCDFs,VxWorksConfiguration,andBuild。提醒:不要修改风河初始化组对象。初始化组模板InitGroupinitMyGroup{SYNOPSISdescriptionofinitializationgroupINIT_RTNinitMyGrpRtn()_INIT_ORDER[usrRoot|customInitGroup]INIT_ORDERINCLUDE_FOO1\INCLUDE_FOO2\INCLUDE_FOO3\...INIT_BEFOREsomeOtherInitGroup}初始化组名所有的初始化组名用一个init前缀来避免命名冲突。(风河初始化组用usr前缀的原因是为了前向兼容)名称必须唯一。初始化组名指派语法如下,在大括号前使用InitGroup关键字和名称,括号内是初始化组对象属性:InitGroupinitMygroup{//initializationgrouppropertyassignmentsgohere}参考InitializationGroupTemplate和InitializationGroupExample。初始化组属性如下属性描述了初始化组:NAMENAME属性用于提供初始化组名;然而,不会出现在WorkbenchGUI中。SYNOPSISSYNOPSIS属性用于提供一个简要、可读初始化组定义。INIT_RTN初始化组自身的初始化函数。当这个函数在系统启动时调用时,初始化组中所有组件函数随后被调用。(可以通过初始化组INIT_ORDER属性或组件属性_INIT_ORDER指派初始化组组件;参考_INIT_ORDER)注明初始化函数针对一个组件对象的标识和初始化函数针对一个初始化组对象的标识不同;组件INIT_RTN属性和初始化组INIT_RTN属性不同。_INIT_ORDER指派初始化组到指定的初始化组。这个属性用于增加初始化组到一个存在的初始化组。也可以用INIT_ORDER直接增加初始化组到其它初始化组的对象中(参考INIT_ORDER和20.4.2UnderscoreOperatorandObjectRelationships)。大多数情况下,定制初始化组应该在VxWorks初始化组后初始化,具体如下:_INIT_ORDERusrRoot定制初始化组的任何顺序必须最注册到usrRoot。也就是说,若一个初始化组没有直接注册到usrRoot,那么它必须注册在另外一个初始化组中,而这个初始化组的顶级初始化组应该注册到usrRoot。一个初始化组可以用_INIT_ORDER属性绑定到一个存在的初始化组,默认在那个组的最后一个初始化。INIT_BEFORE属性用于改变初始化顺序(参考INIT_BEFORE)。INIT_ORDER属于初始化组的组件应该按顺序初始化。每个组件有自身的用INIT_RTN属性标识的初始化函数。参考INIT_RTN。其它初始化组可以使用INIT_ORDER属性来控制定制初始化组的初始化顺序。参考INIT_BEFORE。INIT_BEFORE在指明这个属性的初始化组前调用这个初始化组。这个属性用于精确定义初始化组的调用顺序。初始化组例子如组件包含如下初始化函数定义:ComponentINCLUDE_FOO{...INIT_RTNfooInit(arg1,arg2);_INIT_ORDERinitFooBarINIT_BEFOREINCLUDE_BAR...}初始化组定义如下:InitGroupinitFooBar{INIT_RTNfooBarInit();SYNOPSISinitializationgroupforfoobar_INIT_ORDERusrRootINIT_ORDERINCLUDE_ABC\INCLUDE_BAR\INCLUDE_BAR2\}在这个例子中,INCLUDE_FOO组件注册在初始化组initFooBar中。组内组件在VxWorks初始化组usrRoot之后初始化,如下顺序:1.INCLUDE_ABC2.INCLUDE_FOO3.INCLUDE_BAR4.INCLUDE_BAR220.4.6包对象和属性包对象可以用于关联组件,使得这些组件在一块使用,便于操作系统的设备配置。如,BUNDLE_POSIX提供内核中支持POSIX的所有组件。一个包可以在VIP中增加或删除。工程工具不会跟踪删除或增加一个包,甚至设置组件标识。当一个包用于增加组件时,设备为工程记录组件,但是删除组件,系统不做其他要求。包模块BundleBUNDLE_MY_BUNDLE{NAMEuser-friendlynameforGUISYNOPSISdescriptionforGUICOMPONENTSINCLUDE_FOO1\INCLUDE_FOO2\INCLUDE_FOO3\...\}包名包名用于Workbench和vxprj来识别包。名称必须唯一,推荐使用BUNDLE_前缀,避免名称冲突。包名分配的语法如下,在括号前使用关键字和名字,括号内包含包对象属性:BundleBUNDLE_MY_BUNDLE{//bundlepropertyassignmentsgohere}参考BundleTemplate和BundleExample。注明报名和用于NAME属性的用户用户描述性字符串不一样。包属性NAME一个描述性字符串。这个字符串目的是提供用户友好的包描述字符串,在vxprj命令行配置工具中作为标识。SYNOPSIS一个描述性字符串。这个字符串用于提供一个完整描述。COMPONENTS当包增加到内核时,要增加到内核配置中的组件列表。包例子如,网络内核shell((BUNDLE_NET_SHELL)包含一组组件功能,包含内核shell,loader,网络特征表等。BundleBUNDLE_NET_SHELL{NAMEnetworkkernelshellSYNOPSISKernelshelltoolwithnetworkingsymboltableCOMPONENTSINCLUDE_SHELL\INCLUDE_LOADER\INCLUDE_DISK_UTIL\INCLUDE_SHOW_ROUTINES\INCLUDE_STAT_SYM_TBL\INCLUDE_DEBUG\INCLUDE_UNLOADER\INCLUDE_MEM_SHOW\INCLUDE_SYM_TBL_SHOW\INCLUDE_CPLUS\INCLUDE_NET_SYM_TBL_CHILDRENFOLDER_BUNDLES}关于组件的相关信息,参考20.4.3ComponentObjectandProperties。20.4.7原型对象和属性一个原型对象定义一组对象,这组对象用于创建一个工程的起始点。如,提供了一个包含开发和调试组件的和支持实时进程的一个VxWorks系统。原型仅用于创建VIP工程时。当使用原型取代默认Vxworks配置时。原型定义的组件被增加到工程中。因此,创建工程之后,工程中没有原型的跟踪。一个原型不能删除。原型可以根据其它原型编译。原型模板ProfilePROFILE_NAME{NAMEuser-friendlynameforGUISYNOPSISdescriptionforGUIPROFILESPROFILE_A...COMPONENTSINCLUDE_FOO1\INCLUDE_FOO2\INCLUDE_FOO3\...}原型名原型名用于Workbench或vxprj来识别原型。名字必须唯一,推荐使用PROFILE_前缀来避免名字冲突。原型名的指派语法如下,在括号前使用Profile关键字和名称,括号内核包含原型对象的属性:ProfilePROFILE_NAME{//profilepropertyassignmentsgohere}参考ProfileTemplate。原型名出现在WorkbenchVIP配置原型对话框中。注明原型名和NAME属性描述性字符串不一样。原型属性NAMESYNOPSISPROFILES一系列基本原型COMPONENTS包含的组件原型例子这个例子被截断,因为包含很多组件。参考installDir/vxworks-6.x/target/config/comps/vxWork目录下的01profile_development.cdf文件。ProfilePROFILE_DEVELOPMENT{NAMEVxWorksKernelDevelopmentConfigurationProfileSYNOPSISVxWorkskernelincludingdevelopmentanddebuggingcomponentsPROFILESBSP_DEFAULT#ifdef_WRS_VX_SMPCOMPONENTSINCLUDE_ANSI_ASSERT\INCLUDE_ANSI_CTYPE\INCLUDE_ANSI_LOCALE\INCLUDE_ANSI_MATH\INCLUDE_ANSI_STDIO\INCLUDE_ANSI_STDIO_EXTRA\INCLUDE_ANSI_STDLIB\INCLUDE_ANSI_STRING\INCLUDE_ANSI_TIME\INCLUDE_APPL_LOG_UTIL\INCLUDE_BASE_KERNEL\INCLUDE_BOOT_LINE_INIT\...}20.4.8Workbench文件夹对象和属性文件夹对象控制Workbench内核配置工具的组件显示。它们针对组件逻辑关系分组,创建一个目录类型层次,对于展示每一个组件的信息非常有用,如:·关联其它组件。·配置参数和默认设置。·是否包含在目前的工程。·母猪的安装中是否存在。文件夹可以包含组件,选择和其它文件夹。文件夹本身作为一个配置元素——它们的内容可以作为一个单元选择,增加或删除(或一个子组内容,依据默认文件夹对象中的标识)。文件夹不像包和原型,文件夹仅可用于包含一组组件为一个单元。文件夹也存在配置参数修改。FOLDER_ROOT文件夹是文件夹层次的基础。任何文件夹都必须最终注册到FOLDER_ROOT。注:当创建一个新的组件组时(新的或存在的),使用文件夹对象。不要修改已经存在的文件夹,除了使用_CHILDREN属性。文件夹模板FolderFOLDER_NAME{NAMEuser-friendlynameforGUISYNOPSISdescriptionforGUICHILDRENINCLUDE_FOO1\INCLUDE_FOO2\INCLUDE_FOO3\...DEFAULTSINCLUDE_FOO3\...}文件夹名文件夹名必须唯一,应该有一个FOLDER_前缀避免冲突。文件夹命名指派如下:括号前使用Folder关键字和名称,括号内包含文件夹对象属性:FolderFOLDER_MY_FOLDER{//folderpropertyassignmentsgohere}参考FolderTemplate和FolderExample。文件夹名出线在Workbench组件显示Name列(Figure20-1)。注明文件夹名和NAME属性不一样。文件夹属性如下属性描述了一个文件夹。NAMESYNOPSISCHILDREN标识属于文件夹的组件,文件夹和选择。_CHILDREN这个文件夹是一个指定文件夹的子文件夹——指定文件夹必须存在。这个属性用于增加一个文件夹到另外一个文件夹,无须修改另外的文件夹(参考20.4.2UnderscoreOperatorandObjectRelationships)。FOLDER_ROOT文件夹是文件夹层次基础。所有的文件夹必须直接或间接注册到FOLDER_ROOT文件夹,为了在Workbench中显示。DEFAULTS文件夹中的默认组件。当增加一个文件夹时,会影响文件夹分组,因为这个文件夹的默认组件会立刻增加。对于复杂子系统,DEFAULTS属性非常有用(如WDB目标代理),因为它提供一个快捷方式来便于系统配置,预确定选择组。文件夹例子文件夹可以包含多个组件。如,ANSI功能包含在如下文件夹:FolderFOLDER_ANSI{NAMEANSICcomponents(libc)SYNOPSISANSIlibrariesCHILDRENINCLUDE_ANSI_ASSERT\INCLUDE_ANSI_CTYPE\INCLUDE_ANSI_LOCALE\INCLUDE_ANSI_MATH\INCLUDE_ANSI_STDIO\INCLUDE_ANSI_STDLIB\INCLUDE_ANSI_STRING\INCLUDE_ANSI_TIME\INCLUDE_ANSI_STDIO_EXTRADEFAULTSINCLUDE_ANSI_ASSERTINCLUDE_ANSI_CTYPE\INCLUDE_ANSI_MATHINCLUDE_ANSI_STDIO\INCLUDE_ANSI_STDLIBINCLUDE_ANSI_STRING\INCLUDE_ANSI_TIME}20.4.9Workbench选择对象和属性一个选择对象用于在Workbench组件配置接口中显示提供相关功能组件的可选选项(如时间戳驱动)。选择对象提供这种选项识别,提供指明是否只有一个组件的count属性,一些具体数量大于一个组件,或任何大于一个组件的树,可以被选择。选择仅可用于组件操作;不要被其它CDF对象使用(参数,文件夹,BSP,等)。选择模板SelectionSELECT_MY_SELECTION{NAMEuser-friendlynameforGUISYNOPSISdescriptionforGUICOUNTmin-maxCHILDRENINCLUDE_FOO1\INCLUDE_FOO2\INCLUDE_FOO3\...DEFAULTSINCLUDE_FOO3\...}选择名称选择名称必须唯一,应该有SELECT_前缀避免命名冲突。命名格式如下,使用Selection关键字和名称:SelectionSELECT_MY_SELECTION{//folderpropertyassignmentsgohere}参考SelectionTemplate和SelectionExample。选择名出现在Workbench组件显示Name列(参考Figure20-1)。选择属性如下属性描述一个选择:NAMESYNOPSISCOUNT为可能被选择的用CHILDREN标识的组件设置一个最小计数和最大计数。使用1-1表示只有一个组件;使用1到1或大于1。CHILDREN要选择的组件。DEFAULTS默认组件。默认的DEFAULT组件数应该和COUNT值保持一致。选择例子如下例子提供了一组时间戳驱动的选择例子:SelectionSELECT_TIMESTAMP{NAMEselecttimestampingCOUNT1-1CHILDRENINCLUDE_SYS_TIMESTAMP\INCLUDE_USER_TIMESTAMP\INCLUDE_SEQ_TIMESTAMPDEFAULTSINCLUDE_SEQ_TIMESTAMP}有三个时间戳驱动,这三个值用CHILDREN属性标识。COUNT允许选择一个,也就是说最小和最大都是1。20.4.10BSP对象和属性BSP对象定义BSPs和他们的属性,所以可以使用Workbench或vxprj选择到VIP。关于BSPcdf文件命名要求更多信息,参考20.5CDFFileNamesandPrecedence。关于BSP开发更多信息,参考VxWorksBSPDeveloper’sGuide。BSP模板BspbspName{NAMEdescriptivestringCPUcpuNameREQUIRESINCLUDE_FOO1\INCLUDE_FOO2\INCLUDE_FOO3\...FP[hard][soft][...]ENDIAN[big|little]MP_OPTIONSSMPGUEST_OS_OPTIONSUPPORTED}BSP命名一个BSP必须有一个唯一的名字。BSP名字用于Workbench和vxprj来识别BSP。名字必须唯一且和安装目录名匹配。(参考20.6CDFInstallationDirectoriesandPrecedence)。BSP命名指派语法虚线,括号前使用关键字和名称,括号内是BSP对象属性:BspmyBspName{//BSPpropertyassignmentsgohere}参考BSPTemplate和BSPExample。BSP名字出现在WorbenchBSPs显示列(如ProjectSetup对话框)。关于参数更多新下,要包含在BSPCDFs中,参考20.4.4ParameterObjectandProperties。BSP属性NAMECPU支持的CPU名REQUIRESBSP依赖的组件列表,当一个VxWorks配置基于这个BSP时,所有的组件要增加。并不是所有BSP要求组件。FP浮点数操作模式列表。当支持CPU提供的一个subset模式时,BSP仅需要定义浮点数模式。第一个列出的被做为一个VSB默认操作模式。关于当下CPU支持的模式,参考VxWorksArchitectureSupplement。ENDIAN定义大小端模式。值为big和little.。BSP仅定义CPU支持的大小端模式。MP_OPTIONS定义多处理器支持。目前SMP是可以指派的仅有值。当BSP支持对称多处理器(SMP)时,使用。GUEST_OS_OPTION识别对于运行的VxWorksBSP支持作为一个使用风河管理程序的客户端操作系统(一个可选产品)。这个属性可能用SUPPORTED或REQUIRED指派。REQUIRED值表示BSP支持在硬件和左右一个管理程序客户端操心系统运行VxWorks(如pcPentium4BSP)。pcPentium4BSP值表示BSP仅支持作为一个管理程序客户端操作系统运行VxWorks,不能在硬件上运行(如rvb8572BSP)。一个基于一个GUEST_OS_OPTION设置为REQUIRED的BSP创建的VIP可以和一个VSB工程关联,这个VSB工程用WRHV_GUEST选项编译。一个基于一个GUEST_OS_OPTION设置为SUPPORTED的BSP创建的VIP,可以和任何支持BSP的VSB关联。关于使用VxWorks为一个管理员客户OS的更多信息,参考VxWorksGuestOSforHypervisorx.xProgrammer’sGuide。BSP例子BsppcPentium4{NAMEboardsupportpackageCPUPENTIUM4REQUIRESINCLUDE_KERNEL\INCLUDE_PCI\INCLUDE_PENTIUM_PCI\INCLUDE_PCI_OLD_CONFIG_ROUTINES\INCLUDE_PCPENTIUM4_PARAMS\DRV_TIMER_I8253\DRV_NVRAM_FILE\INCLUDE_MMU_P6_32BIT\INCLUDE_CPU_LIGHT_PWR_MGRMP_OPTIONSSMPGUEST_OS_OPTIONSUPPORTED}20.5CDF文件名和优先级如下语法用于命名CDFs:nnSomeName.cdfnn是两个数字。如00comp_foo.cdf。.cdf后缀是必需的。推荐使用十进制的前缀,方便用于控制工程工具控制读CDF文件的顺序。工程工具读CDF文件的顺序是按照字母数字排序的。当相同的CDF对象属性定制在多个文件中,使用最后一个定义读。如,若INCLUDE_FOO组件定义为100foo.cdf和20foo.cdf,用不同的初始化函数识别,使用最后一个初始化函数。提醒:风河预留了前50个数字,00someName.cdf到49someName.cdf。剩余的数字,50到99,用于第三方组件。文件命名的通用规范服从BSP,组件,文件夹,包和原型CDFs:■nnbsp.cdf■nncomp_compName.cdf■nnfolder_folderName.cdf■nnbundle_bundleName.cdf■nnprofile_profileName.cdfnn推荐使用两个数字前缀。一个单独的组件CDF可以定义多个组件。20.6CDF安装目录和优先级多个CDF可能定义一个当下组件和本身属性。多个定义的优先级由一个数字机制确定,这个机制用于CDF命名规范和工程工具读目录下文件的顺序。注明和有最初的优先级在当一个VIP创建时的任何CDF文件。当配置原型用于工程时,这个规则异常,这种情况下configAll.h和config.h忽略。configAll.h和config.h中CDF函数优先级和文件中的配置文件中的组件定义当工程第一次创建时,传递任何CDF定义——除了一个configurationprofile用于创建VIP时。(方法使用配置VxWorks和来构建它,由于其他原因已经弃用。更多信息,参考VxWorksCommand-LineToolsUser’sGuide:ConfiguringandBuildingVxWorksUsingconfig.h.))关于CDF优先级CDF读顺序是非常重要的。若多个文件描述相同组件的相同属性,最后一个读的覆盖先前的。后者读文件由更高的优先级。用两种互补方法确定优先级:以指定的顺序读CDF文件目录下文件。每个目录下,按数字字母顺序读。CDF目录和优先级一个CDF必须存放在合适的目录下,使得Workbench和vxprj可以找到这个CDF文件,且应用与系统的合适级别(所有的体系结构和BSP,一个单一的体系结构,或一个单一的BSP)。工程工具读所有如下目录下的CDFs文件,安装如下顺序:1.installDir/vxworks-6.x/target/config/comps/vxWorks对于通用VxWorks组件。2.installDir/vxworks-6.x/target/config/comps/vxWorks/arch/arch对于具体体系结构的VxWorks组件。3.installDir/vxworks-6.x/target/config/comps/vxWorks/tool/tool.4.installDir/vxworks-6.x/target/config/bspName具体BSP组件。注明BSP对象名必须和CDFs安装的文件名一致。5.User-defineddirectories.标准VxWorksCDF安装目录之外的目录。必须用WIND_USER_CDF_PATH环境变量标识,否则不会读。这个变量可以设置一系列路径,使用标准路径分隔符分开(Windows下为分号,UNIX下为冒号)。这个设置优先级的方法允许工程,BSP,CPU具体体系几个覆盖通用组件参数。注明有最初的优先级(参考PrecedenceofconfigAll.handconfig.hOverCDFFiles),目录中CDF文件安装数字字母顺序读取(参考20.5CDFFileNamesandPrecedence)。覆盖风河组件属性当你没有直接修改任何风河CDF文件时,你可以重新指定组件属性在优先级高的CDF文件中或CDF目录位置。更多信息,参考20.6CDFInstallationDirectoriesandPrecedence。提醒:不要改变风河提供的CDFs目录。使用命名规范或安装位置来创建一个文件,这个文件的优先级比较高,可以覆盖默认风河提供的组件属性。20.7CDF测试验证组件已经正确编写有几种方法:·检测语法,语义和存在性Vxprj命令提供了最基本的测试:vxprjcomponentcheck[projectFile][component...]如:%vxprjcomponentcheckMyProject.wpj若没有指定工程文件,vxprj在当前目录查找一个.wpj文件。检查是否组件有效。若没有指名组件,会检测工程中的每一个组件。也要检查组件是否存在(若一个模块丢失)或任何其它需求的组件不存在。注明不存在的组件以灰色标注。基于测试结果,做必要的修改。直到没有错误。·检查组件依赖最简单的检查依赖方法是从一个测试工程中增加和删除组件(使用Workbench或vxprj),检查增加和删除了那个组件。Vxprj组件依赖命令可用于深入分析,若需要,使用如下语法:vxprjcomponentdependencies[projectFile]component[component...]如,如下命令展示了一系列组件需要INCLUDE_OBJ_LIB组件:%vxprjcomponentdependenciesINCLUDE_OBJ_LIB若没有指明工程,vxprj会在当下目录下查找一个.wpj文件。·Workbench中检查组件层次通过操作Workbench组件层次可视化检查来验证选择,文件夹和你增加的新的组件被正确包含。查看你的新的元素如何出现在文件树中。检查组件关联的参数和默认值。若你增加一个包含组件的文件夹,你的配置中已经包含了文件夹,Workbench组件层次会以粗体显示文件夹中所有默认的组件(也就是说,DEFAULTS属性对应的值)。·检查prjConfig.c检查工程文件prjConfig.c来确信正确产生的初始化函数和正确顺序。关于文件和其它工程工程的更多信息,参考20.8AboutCDFs,VxWorksConfiguration,andBuild。·编译和启动系统验证结果镜像的编译和启动。注:Workbench内核配置工具在一个VIP创建后,不会自动改变CDFs。VIP必须关闭,重新打开后才能接收变化。Vxprj工具当触发时每次读CDFs文件。20.8关于CDFs,Vxworks配置,和编译CDF文件提供的信息用于配置工具(Workbench和vxprj接口)来动态在工程目录下产生C文件,这些C文件用于编译系统。这些文件不能修改:■linkSyms.c■prjComps.h■prjConfig.c■prjParams.h■Makefile.mk21定制系统调用21.1介绍VxWorks系统调用接口为应用提供内核服务,在用户控件如进程一样操作。开发者可以很简单的扩展这些接口,增加定制系统调用到操作系统,使得满足应用的特别要求。(参考VxWorksApplicationProgrammer’sGuide)最初,开发者的主要任务是扩展系统调用接口用于定制系统调用和命名,计数,和参数规范,保持一致,和编写系统调用回调来支持这个设计初衷。参考21.3SystemCallRequirements和21.4SystemCallHandlerRequirements。系统调用接口可以静态或动态扩展。静态扩展涉及使用配置文件和编译系统工具创建一个包含新的系统调用功能的VxWorks系统镜像。动态扩展涉及使用主机或主机shell,和内核对象模块loader,下载一个系统调用开发版本到内核。参考21.5AddingSystemCalls和21.6MonitoringAndDebuggingSystemCalls。21.2系统是如何调用系统调用时C调用函数。用短小的汇编语言实现调用stubs。Stubs执行一个陷阱指令,切换执行模式从用户模式到内核模式。所有的stubs是相同的,处理唯一系统调用数,传递给内核来识别系统调用。在内核模式,一个陷阱处理从用户栈拷贝任何系统调用参数到内核栈,之后调用系统调回回调。每个系统回调基于一个参数——参数数组的地址。回调函数解析参数域为成员是参数的一个结构。系统调用回调可能在内核中调用其他函数来服务系统调用请求。它们必须验证系统调用的参数,若需要,返回错误。系统调用调度体系结构允许在编译或运行时安装系统调用回调。21.3系统调用需求为了可以自动产生系统调用,并保证正确的运行时操作,系统调用必须严格遵守命名,计数,参数和返回值规则。21.3.1系统调用命名规则一个系统调用关联的不同元素必须衍生之来自系统调用本身的名字。遵守这个规范非常重要,是为了避免针对增加系统调用使用自动机制造成的编译错误。参考Table21-1。开发者在系统中使用系统调用名称调用定义文件。系统调用stub从定义文件信息中自动产生。开发者必须编写系统调用回调函数,包括系统调用参数结构。如,系统调用的名字是,则foo(),:系统调用stub命名为SYSCALL_STUB_foo.s。Stub在用户模式实现函数foo()。系统调用foo()的系统调用回调函数必须命名为fooSc()。当一个应用在用户空间调用foo()时,函数fooSc().被调用。内核开发者编写fooSc()函数。除非fooSc()存在,当内核重新编译时会产生错误。若foo系统调用传递至少一个参数,foo的参数结构必须在系统调用回调中声明为structfooScArgs。关于系统调回回调需求更多信息,参考21.4SystemCallHandlerRequirements。关于增加系统调用到操作系统信息——静态或动态,参考21.5AddingSystemCalls。21.3.2系统调用计数规则每一个系统调用必须有一个唯一的系统调用号。系统调用号通过系统调用stub传递给内核,之后用于识别和执行正确的系统调用回调。一个系统调用号是两个数字的连接:·系统调用组号·系统调用组内函数号组号实现为一个10位域,函数号为一个6位域。这允许达到1024个系统调用组,每一个组有64个函数。总得系统调用数空间因此可以累积为65536给调用。6个系统调用组——2到7——为定制应用预留。(客户可能从风河中请求一个格式化系统调用组分配)所有的系统调用组位风河应用预留。警告:不要使用任何系统调用组号除了针对定制应用使用。若使用会和风河或风河合作伙伴系统调用的实现冲突。风河调用组数和系统调用函数数在syscallNum.def文件中定义。客户不能修改。客户系统调用组数和系统调用函数数在syscallUsrNum.def文件中定义。风河系统调用数定义文件,和客户系统调用定义文件模板在installDir/vxworks-6.x/target/share/h目录下。当下系统调用组简化为提供补充功能相关系统调用集合。如,VxworksSCG_STANDARD组包括通常发现的如UNIX一样的系统调用(POSIX),和SCG_VXWORKS组,包括针对VxWorks或和UNIX系统调用不一样的系统调用。关于使用系统调用定义文件来产生系统调用,参考21.5.1AddingSystemCallsStatically。21.3.3系统调用参数规则系统调用可能仅占用8个参数。在32位系统上要特别考虑64位参数。浮点型和向量型参数不允许。风河系统调用定义在syscallApi.def文件中。用户不允许修改。用户系统调用定义在syscallUsrApi.def文件中。关于编辑这个文件的信息,参考21.5.1AddingSystemCallsStatically。参数数系统调用可能占用最大8个参数(回调可以容纳的最大参数数)。每个参数期望是一个native-word大小。一个native-word的大小是一个针对32位体系结构的32位数,针对64位体系结构的64位数。对于大多数系统调用(使用32位),因此参数列表中字数等于函数使用的参数数。若一个系统调用回调需要的参数多于8个,则需要打包为一个结构体,结构体的地址作为参数传递为系统调用。64位参数问题21.3.4系统调用返回值规则系统调用可能会仅返回一个nativeword作为一个返回值(也就是说整数值或指针等)。64位返回值不允许直接返回,尽管它们需要通过使用私有函数模仿。这样做,一个系统调用必须有一个强调命名前缀,必须有一个指向返回值的指针,指向其中一个参数。如:longlongget64BitValue(void)必须有一个陪同函数:void_get64BitValue(longlong*pReturnValue)_get64BitValue()函数是实际的系统调用,在syscallUsrNum.def和syscallUsrApi.def文件中定义。get64BitValue()函数可以编写如下:longlongget64BitValue(void){longlongvalue;_get64BitValue(&value);returnvalue;}(get64BitValue()函数将会由用户编写,后放置在用户模式库中,_get64BitValue()函数将会自动产生;参考21.5AddingSystemCalls)值-1(ERROR)是系统调用唯一允许的错误返回值。没有系统调用会把-1看成一个有效值。当返回值为-1时,操作系统通过陷阱边界正确传递error值,所以用户模式必须访问它。若NULL是返回的错误,则系统调用本身必须用另外一个函数实现,这个函数返回-1。系统调用的-1值之后在用户模式被另外一个函数转换为NULL。21.4系统调用回调需求系统调用回调必须遵守命名规范,和系统调用参数结构的组织需求。它们应该验证参数。若错误产生,它们设置error,并返回ERROR。一个系统调用回调通常调用一个或多个内核函数,内核函数提供需要的功能。某些情况下,代码将会直接调用公共内核API;某些情况下,可能会跳过内核级别验证,直接调用潜在的功能。提醒:为了强制分离内核和用户空间,并不是所有的内核API来之一个系统调用回调调用。尤其是,若它们的操作涉及传递一个用户端任务ID或一个RTPID,则API不能调用。APIs也不能调用用于创建内核对象,若那些APIs已经在用户空间通过标准的系统调回接口直接访问。如包括taskSpawn(),taskCreate(),msgQCreate(),pthread_create(),和各种信号量创建函数,POSIX线程函数等。21.4.1系统调用回调命名规则系统调用回调必须根据系统调用命名规范命名,这意味着必须使用和系统调用一样的名字,但是增加一个Sc。如,系统调用foo()必须使用系统调用fooSc()回调。所有的系统调用回调使用一个单一参数,指向参数结构体。参数结构命名必须和系统调用命名规范保持一致,这意味着必须使用和系统调用回调一样的名字,增加Args。如fooSc的参数结构必须声明为fooScArgs。如,write()系统调用声明为:intwrite(intfd,char*buf,intnbytes)write系统调用函数的系统调用回调因此命名为writeSc(),声明如下:intwriteSc(structwriteScArgs*pArgs)参数结构是writeScArgs,声明为:structwriteScArgs{intfd;char*buf;intnbytes;};参考21.3.1SystemCallNamingRules。21.4.2系统调用回调参数验证一个系统调用回调应该验证所有的参数。尤其是应该:边界检查数值。验证任何内存地址确保在目前的内存上下文内可访问(进程中的内存,不是内核中的内存)。参考API应用入口scMemValidate()通过系统调用关于指针验证的信息。21.4.3系统调用回调错误报告系统调用的最后,若失败,系统调用回调应该正确设置errno,之后返回-1(ERROR)。若返回值是-1(ERROR),内核errno值之后被拷贝到调用进程的error。若没有错误,简单返回一个值,这个值会被拷贝到用户模式。若回调在返回ERROR之前设置error,用户模式代码查看相同的error值。21.5增加系统调用系统调用可以静态或动态添加。这意味着它们可以配置并编译到VxWorks操作系统镜像,或交互添加到操作系统中,当运行在一个目标机上时。动态添加对应快速原型打印和系统调用调试非常有用。静态配置对于稳定的开发环境,生产系统比较有用。21.5.1静态增加系统调用静态增加系统调用基于syscallUsrNum.def和syscallUsrApi.def系统调用定义文件的使用。这两个文件定义了系统调用名字和号,它们的原型,属于哪个系统调用组,和关联的组件。scgen工具程序使用这些文件——随着这些文件和那些用于标准VxWorks系统调用文件比较——产生要求的系统调用设备和开发者编写的系统调用回调协调工作。scgen程序被集成到编译系统,当编译系统检测到syscallUsrNum.def和syscallUsrApi.def文件变化时,自动运行。注:默认syscallUsrNum.def.template和syscallUsrApi.def.template在installDir/vxworks-6.x/target/share/h目录中。使用相同目录下和文件拷贝,去掉.template扩展,并创建合适的入口,如描述21.5.1AddingSystemCallsStatically。scgen工作原理1.使用风河和用户系统调用定义文件中的系统调用定义,scgen产生如下:产生installDir/vxworks-6.x/target/h/syscall.h和installDir/vxworks-6.x/target/usr/h/syscall.h文件。两个文件的内容是一样的。它们定义了所有的系统中的系统调用数和组数。这些文件提供了内核和用户空间代码的共享信息。2.每一个系统调用由一个系统调用汇编stub文件。Stub文件被存放在installDir/vxworks-6.x/target/usr/src/arch目录下合适体系结构目录,编译为libvx.a或libc.so。3.一个文件包含系统中系统调用的参数结构。这个文件是具体体系结构/ABI的,用于位于内核中的系统调用回调。这个文件命名为syscallArgsArchAbi.h在installDir/vxworks-6.x/target/h/arch/archName目录下(如installDir/vxworks-6.x/target/h/arch/ppc/syscallArgsppc.h)。4.一个针对编译时所用的系统调用组识别的包含一个预初始化系统调用组表的文件。这个文件是installDir/vxworks-6.x/target/h/syscallTbl.h。所有的输出被编译系统自动使用;构建合适系统调用设施到系统不需要用户干预。scgen工具也可以在命令行工作下运行,用于调试。静态增加系统调用的过程增加一个新的系统调用到VxWorks的基本步骤如下:1.若你要创建一个新的系统调用组,为这个组增加一个入口到syscallUsrNum.def。参考Step1:DefineaNewSystemCallGroup。2.通过增加合适的入口到syscallUsrNum.def和syscallUsrApi.def来定义一个新的系统调用。参考Step2:DefineaNewSystemCall。3.在内核模式源码VxWorks安装端创建系统调用回调函数。参考Step3:CreatetheSystemCallHandlerRoutine。4.为用户模式VxWorks安装测为系统调用回调本身编辑和创建makefiles。参考Step4:ModifyMakefilesforSystemCallHandler。5.在用户模式VxWorks安装测为系统调用本身创建一个头文件。参考Step5:CreateHeaderFileforSystemCall。6.重新编译VxWorks内核模式和用户模式库。参考Step6:RebuildtheVxWorksLibraries。7.重新编译VxWorks。参考Step7:RebuildVxWorks。每个步骤的详细描述如下。Step1:DefineaNewSystemCallGroup若你需要定义一个新的系统调用组,使用如下语法增加到installDir/vxworks-6.x/target/share/h/syscallUsrNum.def:SYSCALL_GROUPSCG_sgcGroupNamegroupNumcomponentNames6个系统调用组——2到7——为用户使用预留;所有其它系统调用组为风河使用预留。(参考21.3.2SystemCallNumberingRules)若你需要正式增加一个组到VxWorks,联系风河。系统调用组名必须唯一。若你第一次创建一个系统调用组,通过拷贝installDir/vxworks-6.x/target/share/h/syscallUsrNum.def.tempate文件创建syscallUsrNum.def。警告:不要使用任何系统调用组号除了为客户使用预留的组号。否则会和风河或风河合作伙伴实现的系统调用冲突。关联系统调用和组件识别组件名称是可选的,提供了关联一个系统调用组(所有的调用)和具体操作系统组件的方法来包含在VxWorks配置中。工作如下:·若没有定义一个组件名,系统调用组总是包含在系统中。·若定义一个组件,系统调用组会包含到系统或不包含到系统——取决于组件是否存在。也就是说若用户把组件包含在一个VxWorks配置中,之后系统调用组自动包含。但是若组件没有包含在系统中,组同样不会包含。域必须用一个多个空格分隔。如一个新的组称为,应该用如下入口定义(N是选择用于的组号):SYSCALL_GROUPSCG_MY_NEW_GROUPNINCLUDE_FOO系统调用是系统调用组的一部分用如下SYSCALL_GROUP定义行标识。每个组可以识别64个系统调用。Step2:DefineaNewSystemCall定义一个新的系统调用,你必须在不同文件中创建入口:·一个入口在syscallUsrNum.def文件中,指派入口到一个系统调用组和关联系统调用名和号。·一个入口在syscallUsrApi.def文件中,定义系统调用名和参数。若你第一次创建一个系统调用,拷贝installDir/vxworks-6.x/target/share/h/syscallUsrApi.def.tempate文件创建syscallUsrApi.def。系统调用定义语法为了增加一个系统调用到一个调用用,在合适的系统调用组名下增加一个入口到installDir/vxworks-6.x/target/share/h/syscallUsrApi.def,语法如下:sysCallNumsysCallName注明增加系统调用到一个系统调用组的最后非常重要,使用已经指派的数。重新使用存在的数会破坏存在二进制文件的兼容性;所有存在的应用必须重新编译。系统调用数不需要严格排序(也就是说可以为未来使用预留一个间隔)。定义一个系统调用,增加一个入口到syscallUsrApi.def,使用如下语法:sysCallNamenumArgs[argTypearg1;argTypearg2;argTypeangN;]\CompNameINCLUDEheaderFileName.h系统调用定义行可以使用斜线分割给多行。用于syscallUsrApi.def文件的系统调用的名字必须和syscallUsrNum.def文件使用的名字匹配。当定义参数个数时,考虑任何64位参数并依据调整这个数(和64位参数相关问题,参考21.3.3SystemCallArgumentRules)。系统调用的参数在一个大括号列表中描述。大括号的开始和结束位置都有一个空格。参数之间用分号隔开,之后至少一个空格。若系统调用不使用任何参数,不需要大括号和参数列表。可能有多个组件名被列出。若对于任何包括在操作系统配置中的组件,当系统编译时,系统调用也会包含。(关于定制组件更多信息,参考20.CustomComponentsandCDFs)当编辑和文件时,通常会遇到如下问题,且混淆scgen工具:·大括号开始和结束没有空格。·换行时没有斜线。·带有空的大括号。这会导致产生临时C文件有一个编译错误。牢记,任何一个系统调用组不能超过64个系统调用。若系统调用头文件中包括一个新类型定义,头文件必须用INCLUDE语句标识。scgen工具必须在产生参数结构之前解决所有的类型,这是定制定义的通知机制。如这个语法如何使用,参考SystemCallDefinitionExample。也参考风河系统调用定义文件(syscallNum.def和syscallApi.def文件),但是不要修改这些文件。系统调用定义例子假设我们想增加定制系统调用myNewSyscall()到一个新的系统调用组SCG_USGR0(在syscallNum.def中定义)。首先,通过拷贝syscallUsrNum.def.template创建syscallUsrNum.def文件。之后编辑,为合适的组增加一个系统调用组入口,和系统调回号和名。系统调用组2到7为用户定制预留;不要使用任何其它组号。如:SYSCALL_GROUPSCG_USER021myNewSyscall之后必须编辑editsyscallUsrApi.def文件来定义系统调用本身。系统调用myNewSyscall()原型如下:intmyNewSyscall(MY_NEW_TYPEa,intb,char*c);这个调用有三个参数,和一个定义在定制头文件中的一个类型。假如我们想有条件的实现系统调用,取决于是否INCLUDE_FOO组件配置到操作系统。syscallUsrApi.def文件中入口如下:INCLUDE<myNewType.h>myNewSyscall3[MY_NEW_TYPEa;intb;char*c;]INCLUDE_FOOStep3:CreatetheSystemCallHandlerRoutine写系统调用回调函数(代码要求,参考21.4SystemCallHandlerRequirements)。之后添加到内核模式VxWorks安装侧installDir/vxworks-6.x/target/src。你也可以增加定制子目录来存放定制系统调用。Step4:ModifyMakefilesforSystemCallHandler为了编译系统调用回调,你必须编辑installDir/vxworks-6.x/target/src目录下的makefile文件,并在系统调用回调子目录下创建一个makefile文件。增加存放系统调用回调源码的子目录名字,用COMMON_SUBDIRS宏在列表中指明在installDir/vxworks-6.x/target/src/Makefile文件中。myFoo为子目录,修改如下:COMMON_SUBDIRS=archaimbootdebugdrvdsieventfshwiflibcmath\offsetosostoolpfwposixrpctipc\tffsusbusb2usrutilvxmpwindwrnwvssiipnetcci-light\myFoo之后在目录下创建makefile文件。如,参考installDir/vxworks-6.x/target/src/os/rtp。你可以拷贝这个makefile,删除DOC_FILES宏,保留TGT_DIR和OBJS宏,和include语句,并编辑LIB_BASE_NAME和OBJS_COMMON宏。fooSc.c作为myFoo子目录中系统回调源码。如#Makefile-makefileforfooScTGT_DIR=$(WIND_BASE)/targetLIB_BASE_NAME=myFooOBJS_COMMON=fooSc.oOBJS=$(OBJS_COMMON)include$(TGT_DIR)/h/make/rules.libraryStep5:CreateHeaderFileforSystemCall为系统调用编写头文件,增加到vxWorks安装目录installDir/vxworks-6.x/target/usr/h内核模式侧。Step6:RebuildtheVxWorksLibraries重新编译installDir/vxworks-6.x/target/src和installDir/vxworks-6.x/target/usr/src目录中VxWorks内核模式和用户模式库源码。在每个目录使用如下命令:makeCPU=cpuTypeTOOL=toolType这个命令自动检测syscallUsrNum.def和syscallUsrApi.def文件中的变化,触发scgen工具,之后重新编译源码树。Step7:RebuildVxWorks使用Workbench或vxprj重新编译VxWorks。Step8:TesttheSystemCall创建和运行一个RTP应用,充分使用新的系统调用。21.5.2动态增加系统调用你可以动态的在一个目标机上扩展系统调用接口,通过下载一个为安装系统调用回调代码,如系统调用回调函数本身的内核对象模块。你不需要修改系统调用定义文件,运行scgen,,或重新编译内核。这个方法针对快速打印原型比较有用。在一个部署系统中使用作用不大,不推荐使用。系统调用安装代码安装在内核中的系统调用回调代码包括:·一张针对系统调用回调函数的初始化表。·一个系统回调注册函数的调用。这些代码应该包含在和系统调用回调相同的模块中。你必须为系统调用标识一个系统调用组,可能是一个目标系统没有使用的组。程序表系统调回回调函数表用于注册系统调用回调函数到系统调用设施中,当模块加载时。如,如系统调回回调函数是testFunc0(),testFunc1(),testFunc2(),和testFunc3(),,这个表声明如下:_WRS_DATA_ALIGN_BYTES(16)SYSCALL_RTN_TBL_ENTRYtestScRtnTbl[]={{(FUNCPTR)testFunc0,1,"testFunc0",0},/*routine0*/{(FUNCPTR)testFunc1,2,"testFunc0",1},/*routine1*/{(FUNCPTR)testFunc2,3,"testFunc0",2},/*routine2*/{(FUNCPTR)testFunc3,4,"testFunc0",3}/*routine3*/}_WRS_DATA_ALIGN_BYTES(16)指令指示编译器/链接器表中是16字节对齐。这个指示是可选的,但是为了提高性能,增加这个选项。编译对象模块编译包含系统调用回调和注册代码的对象模块。参考4.9BuildingKernelApplicationModules。下载模块和注册系统调用编译模块之后,下载,注册,检查注册是否成功:1.用调试器,主机shell,或内核shell下载到目标主机。在shelll(使用C解析器)下载模块foo.o,如下:->ld<foo.o2.在任何系统调用路由到新的回调之前注销新的回调到系统调用体系结构中。通过调用syscallGroupRegister()函数完成。如:->syscallGroupRegister(2,"testGroup",4,&testScRtnTbl,0)第一个参数是一个存放组号的变量(一个整数);第二个变量是组名;第三个变量是系统调用回调函数数目,如表中定义;第四个变量是表名;最后一个变量是注册是否强制覆盖一个已经存在的入口。(注明当从shell中执行调用时,第三个参数使用&地址操作符——在程序中执行时,无须这样)检查syscallGroupRegister()返回值非常重要,若返回一个错误,打印错误信息。参考syscallGroupRegister()。3.通过运行syscallShow()验证组注册。系统调用体系结构已经准备好路由系统调用新安装的回调。使得在进程中调用系统回调测试一个新的系统调用快速方法是创建和运行一个简化的RTP应用。首先,针对新的系统调用统计系统调用数量。为了完成这个,使用SYSCALL_NUMBER()工具宏(在syscall.h中定义)。如,若你使用组数2(如上描述),之后这个函数的系统调用数是如下调用返回的值:SYSCALL_NUMBER(2,0)testFunc1()的系统调用数是这个调用返回的值。SYSCALL_NUMBER(2,1)等。为了操作实际的系统调用,应该要调用syscall()函数。前8个参数是传递个系统调用的参数,第9个参数是系统调用数。如,在用户模式应用进程中调用testFunc0(),如:inttestFunc0(intarg1,intarg2,intarg3,intarg4,intarg5){returnsyscall(arg1,arg2,arg3,arg4,arg5,0,0,0,SYSCALL_NUMBER(2,0));}注明你必须在syscall()函数中使用第9个参数。最后一个参数是系统调用数,前8个是系统调用参数。若你的函数使用小于8个参数,是0代替。21.6监控和调试系统调用这一部分讨论了show函数,syscallmonitor(),得到调试信息,和调试,系统调用。若展示函数包含在VxWorks配置中(组件INCLUDE_SHOW_ROUTINES),即有了系统调用组,可以在shell中用syscallShow()命令显示。syscallMonitor()函数在内核模式监控多个系统调用,或基于每个进程。列出了每个系统调用模式,参数。函数简化为:syscallMonitor(level,RTP_ID)若level参数设置为1,系统调用监控打开;若设置为0,关闭。若RTP_ID设置为一个RTP_ID,仅监控对应的进程;若设置为0,则监控所有的进程。关于syscallShow()和syscallMonitor()的更多信息,参考VxWorksKernelAPIReference。syscallHookLib库使用钩子函数为增加扩展到VxWorks系统调用库提供函数。钩子函数可以在不修改内核代码的前提下增加。当系统调用组注册后,内核提供调用,和退出系统调用。每个钩子类型有一个函数指针数组表示。对于每个钩子类型,钩子函数按照增加顺序调用。更多信息,参考VxWorksKernelAPIReference中syscallHookLib入口。21.7文档定制系统调用因为系统调用不是用C编写的函数,apigen文档产生工具不能用于从源码注释中生成API参考。你可以创建一个C文件函数头,用于apigen工具。系统调用的函数头和其它C函数不同。这儿是针对getpid():函数的一个函数头:/*************************************************************************getpid-Gettheprocessidentifierforthecallingprocess.**SYNOPSIS*\cs*intgetpid*(*void*)*\ce**DESCRIPTION**Thisroutinegetstheprocessidentifierforthecallingprocess.*TheIDisguaranteedtobeuniqueandisusefulforconstructing*uniquelynamedentitiessuchastemporaryfilesetc.**RETURNS:Processidentifierforthecallingprocess.**ERRNO:N/A.**SEEALSO:*.pG"Multitasking"**/没有代码或C声明没有遵守这个头。编译器把这个头当做一个注释块,但是apigen使用这个头产生API文档。上述头文件中所有域必须存在在代码中。你有两个选择定位注释位置:你将增加系统调用函数头到一个存在的c源文件中(C源文件中也可能有其他函数的代码)。确信源文件是目录下makefile中DOC_FILES列表的一部分。apigen工具不会处理这个。你将创建一个仅包含头,没有C代码的C文件。这个文件时makefileDOC_FILES列表的一部分;不是OBJS列表的一部分(因为没有代码编译)。关于API文件生成要求的编码规范,和工具更多信息,参考VxWorksBSPDeveloper’sGuide和theapigenentryintheWindRiverHostUtilitiesAPIreference。22定制调度22.1介绍22.2一个定制调度的需求22.3传统的Vxworks调度第三部分:多处理技术23多处理技术概述24VxWorksSMP25VxWorksAMP概述26针对AMP配置VxWorks27针对AMP启动Vxworks28VxWorks技巧实用程序29MIPC串行设备(MSD)30MND:节点间仿真一个以太网连接31共享内存对象:VxMP32分布式共享内存:DSHM33消息通道第四部分:附录AVT-100颜色管理技巧B针对AMP的原子操作C针对AMP的spinlocksDWDBTargetAgent
/
本文档为【VxWorks-kernal-programmer's-guide】,请使用软件OFFICE或WPS软件打开。作品中的文字与图均可以修改和编辑, 图片更改请在作品中右键图片并更换,文字修改请直接点击文字进行修改,也可以新增和删除文档中的内容。
[版权声明] 本站所有资料为用户分享产生,若发现您的权利被侵害,请联系客服邮件isharekefu@iask.cn,我们尽快处理。 本作品所展示的图片、画像、字体、音乐的版权可能需版权方额外授权,请谨慎使用。 网站提供的党政主题相关内容(国旗、国徽、党徽..)目的在于配合国家政策宣传,仅限个人学习分享使用,禁止用于任何广告和商用目的。

历史搜索

    清空历史搜索