赞
踩
给QNX Neutrino RTOS很大程度的灵活性,最终系统的运行时内存需求降到最低,并应对各种各样的设备,这样的系统可能会被发现在一个定制的嵌入式系统中,这样的操作系统允许用户编写的进程作为动态资源管理器,还可以动态启动和停止。
资源管理器通常负责为各种类型的设备提供接口。这可能涉及管理实际的硬件设备(如串口、并行端口、网卡和磁盘驱动器)或虚拟设备(如/dev/null、网络文件系统和伪ttys)。
在其他操作系统中,此功能通常与设备驱动程序关联。但是,与设备驱动程序不同,资源管理器不需要与内核进行任何特殊的安排。事实上,资源管理器看起来就像任何其他用户级的程序。
由于QNX Neutrino RTOS是一个分布式的微内核操作系统,几乎所有的非内核功能都是由用户可安装的程序提供的,因此客户端程序和资源管理器之间需要一个干净的、定义良好的接口。所有资源管理器功能都有文档记录;在内核和资源管理器之间没有“魔法”或私有接口。
实际上,资源管理器基本上是一个用户级的服务器程序,它接受来自其他程序的消息,并可选地与硬件通信。同样,我们的本机IPC服务的强大功能和灵活性允许资源管理器与操作系统解耦。
资源管理器和使用关联资源的客户端程序之间的绑定是通过一种称为路径名空间映射(pathname space mapping)的灵活机制来完成的。
在路径名空间映射中,是在路径名和资源管理器之间建立关联。资源管理器通过通知进程管理器(它是负责处理请求的)来映射pathname。(或者下面,对于文件系统),某个挂载点。这允许进程管理器关联服务(即,资源管理器提供的功能)到路径名。
例如,一个串行端口可能由一个名为devc-ser*的资源管理器管理,但是实际的资源可能在路径名空间中被称为/dev/ser1。因此,当一个程序请求串行端口服务时,它通常通过打开一个串行端口来实现——在本例中是/dev/ser1。
这里有几个原因,为什么你可能想写一个资源管理器:
客户端API是POSIX。
与资源管理器通信的API大部分是POSIX。所有C程序员都熟悉open()、read()和write()函数。培训成本降到了最低,而且也不需要再次为服务器的接口编制文档。
您可以减少接口类型的数量。
如果您有许多服务器进程,那么将每个服务器编写为一个资源管理器可以将客户端需要使用的不同接口的数量降到最低。例如,假设有一个程序员团队构建整个应用程序,每个程序员都为该应用程序编写一个或多个服务器。
这些程序员可能直接为您的公司工作,或者他们可能属于为您的模块化平台开发附加硬件的合作公司。如果服务器是资源管理器,那么所有这些服务器的接口都是POSIX函数:open()、read()、write(),以及其他任何有意义的函数。对于不适合读/写模型的控制类型消息,可以使用devctl()(尽管devctl()不是POSIX)。
命令行实用程序可以与资源管理器通信。
由于用于与资源管理器通信的API是POSIX函数集,而且标准POSIX工具程序使用这个API,所以您可以使用这些工具程序与资源管理器通信。例如,假设一个资源管理器注册了名称/proc/my_stats.如果您打开这个名称并从中读取,资源管理器将使用描述其统计信息的文本正文进行响应。cat实用程序获取文件的名称并打开该文件,从该文件中读取数据,然后将其读取的内容显示到标准输出(通常是屏幕)。因此,你可以输出:cat /proc/my_stats。资源管理器将使用适当的统计数据进行响应。
您还可以使用命令行实用工具来实现robot-arm驱动程序。驱动程序可以注册名称/dev/robot/arm/angle,并将对该设备的任何写操作解释为要将机器人手臂设置为的角度。要从命令行测试驱动程序,您需要输入:echo 87 >/dev/robot/arm/angle echo实用程序打开/dev/robot/arm/angle并将字符串(“87”)写入其中。驱动程序通过将机器人手臂设置为87度来处理写操作。注意,这是在没有编写特别的测试程序的情况下完成的。
另一个例子是/dev/robot/registers/r1、r2、…从这些名称读取返回相应寄存器的内容;写入这些名称将相应的寄存器设置为给定的值。即使您的所有其他IPC都是通过一些非posix API完成的,仍然值得将一个线程作为资源管理器来编写,以响应上面所示的读写操作。
对于客户端,根据你自己想做多少工作来展示一个合适的POSIX file system ,您可以将资源管理器分为两种类型:
设备资源管理器只在文件系统中创建单个文件条目,每个条目都在进程管理器中注册。每个名称通常表示一个设备。这些资源管理器通常依赖于资源管理器库来完成向用户显示POSIX设备的大部分工作。
例如,串行端口驱动程序注册诸如/dev/ser1和/dev/ser2这样的名称:当用户执行ls -l /dev时,库执行必要的处理,以使用适当的信息响应生成的_IO_STAT消息。编写串口驱动程序的人可以将精力集中在管理串口硬件的细节上。
文件系统资源管理器向进程管理器注册挂载点。挂载点是向进程管理器注册的路径的一部分。路径的其余部分由文件系统资源管理器管理。
例如,当文件系统资源管理器在/mount上附加一个挂载点,并且检查路径/mount/home/thomasf时:
下面是一些使用文件系统资源管理器的例子:
一旦资源管理器建立了它的路径名前缀,它将在任何客户机程序试图对该路径名执行open()、read()、write()等操作时接收消息。
例如,在devc-ser接管了路径名/dev/ser1之后,客户端程序执行:fd = open ("/dev/ser1", O_RDONLY);客户机的C库将构造一个io_open消息,然后通过IPC将其发送给devc-ser资源管理器。一段时间后,当客户端程序执行:read (fd, buf, BUFSIZ); 客户机的C库构造一个io_read消息,然后将其发送给资源管理器。
关键的一点是,客户端程序和资源管理器之间的所有通信都是通过本地IPC消息传递完成的。这允许一些独特的功能:
这是资源管理器的核心:
initialize the dispatch interface register the pathname with the process manager DO forever receive a message SWITCH on the type of message CASE io_open: perform io_open processing ENDCASE CASE io_read: perform io_read processing ENDCASE CASE io_write: perform io_write processing ENDCASE . // etc. handle all other messages . // that may occur, performing . // processing as appropriate ENDSWITCH ENDDO
架构包括三个部分:
在架构上,资源管理器将接收两类消息:
connect消息由客户机发出,以执行基于路径名的操作(例如,io_open消息)。这可能涉及执行诸如权限检查(客户机是否具有打开此设备的正确权限?)和为该请求设置上下文等操作I/O消息依赖于此上下文(在客户端和资源管理器之间创建)来执行I/O消息的后续处理(例如,io_read)。
这种设计是有充分理由的。例如,为每个read()请求传递完整的路径名是低效的。io_open处理程序还可以执行我们希望只执行一次的任务(例如,权限检查),而不是每次都执行I/O的消息。另外,当read()从磁盘文件中读取4096字节时,可能还有20兆字节等待读取。因此,read()函数需要一些上下文信息来告诉它从文件中读取的位置。
在自定义嵌入式系统中,部分设计工作可能会花费在编写资源管理器上,因为系统中可能没有现成的驱动程序可以用于自定义硬件组件。我们的资源管理器共享库使这个任务相对简单。
如果资源管理器由于某种原因不想处理某些函数(例如,一个数模转换器不支持像lseek()这样的函数,或者软件不需要它),共享库将方便地提供默认操作。有两个级别的默认操作:
资源管理器共享库提供的另一个方便的服务是dup()消息的自动处理。假设客户端程序执行的代码最终执行:
fd = open ("/dev/device", O_RDONLY);
...
fd2 = dup (fd);
...
fd3 = dup (fd);
...
close (fd3);
...
close (fd2);
...
close (fd);
客户机将为第一个open()生成一个io_open消息,然后为两个dup()调用生成两个io_dup消息。然后,当客户机执行close()调用时,将生成三条io_close消息。
由于dup()函数会生成重复的文件描述符,所以不应该为每个描述符分配新的上下文信息。当io_close消息到达时,因为没有为每个dup()分配新的上下文,所以每个io_close消息也不应该释放内存!(如果是这样,第一个结尾就会抹去上下文。)。资源管理器共享库提供了默认的处理程序,这些处理程序跟踪open()、dup()和close()消息,仅对最后一次关闭(即关闭)执行工作。(上面示例中的第三个io_close消息)。
QNX中微子RTOS的一个显著特征是能够使用线程。通过使用多个线程,可以构造一个资源管理器,使多个线程等待消息,然后同时处理它们。这个线程管理是资源管理器共享库提供的另一个方便的功能。除了跟踪创建的线程数和等待的线程数之外,该库还负责维护最佳线程数。
OS提供了一组dispatch_*函数:
为了节省网络带宽并提供对原子操作的支持,操作系统支持合并消息。合并消息由客户端的C库,由许多I/O和/或连接消息一起构造。
例如,readblock()函数允许线程原子性地执行lseek()和read()操作。这是在客户端库中通过将io_lseek和io_read消息合并到一起完成的。当资源管理器共享库接收到消息时,它将同时处理io_lseek和io_read消息,从而有效地使readblock()函数具有原子性的行为。
组合消息对于stat()函数也很有用。stat()调用可以在客户端库中实现为open()、fstat()和close()。该库不是生成三个独立的消息(每个组件函数一个消息),而是将它们放在一起,形成一个连续的组合消息。这提高了性能,特别是在网络连接上,还简化了资源管理器,它不需要使用connect函数来处理stat()。
资源管理器共享库处理与分解组合消息的各个组件并将其传递给提供的各种处理函数相关的问题。同样,这将最小化与编写资源管理器相关的工作。
由于资源管理器接收的大量消息都处理一组公共属性,因此操作系统提供了另一种级别的缺省处理。
第二层称为iofunc_()共享库,它允许资源管理器自动处理stat()、chmod()、chown()、lseek()等函数,而程序员无需编写额外的代码。作为一个额外的好处,这些iofunc_()默认处理程序实现了消息的POSIX语义,再次将工作从程序员那里卸下。
需要考虑三个主要结构:
第一个数据结构,上下文,已经讨论过了(请参阅关于“消息类型”)。它保存每次打开时使用的数据,比如文件中的当前位置(lseek()偏移量)。
由于资源管理器可能负责多个设备(例如,devc-ser*可能负责/dev/ser1、/dev/ser2、/dev/ser3等),因此属性结构在每个设备的基础上保存数据。属性结构包含设备所有者的用户和组ID、最后修改时间等项。
对于文件系统(块I/O设备)管理器,还要使用一个结构。这是挂载结构,其中包含对整个挂载设备全局的数据项。
当许多客户端程序打开了特定资源上的各种设备时,数据结构可能是这样的:
iofunc_*()默认函数的操作是基于这样的假设:程序员已经使用了上下文块和属性结构的默认定义。这是一个安全的假设,原因有二:
这个库包含iofunc_*()默认的客户端函数处理程序:
QNX Neutrino RTOS支持路径名空间映射,具有定义良好的资源管理器接口,并提供一组用于公共资源管理器功能的库,从而为开发新硬件的“驱动程序”提供了前所未有的灵活性和简单性——这是许多嵌入式系统的关键特性。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。