QNX Resource Managers

QNX Resource Managers

  给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完成的,仍然值得将一个线程作为资源管理器来编写,以响应上面所示的读写操作。

The types of resource managers

  对于客户端,根据你自己想做多少工作来展示一个合适的POSIX file system ,您可以将资源管理器分为两种类型:

  1. Device resource managers
  2. Filesystem resource managers

Device resource managers

  设备资源管理器只在文件系统中创建单个文件条目,每个条目都在进程管理器中注册。每个名称通常表示一个设备。这些资源管理器通常依赖于资源管理器库来完成向用户显示POSIX设备的大部分工作。
  例如,串行端口驱动程序注册诸如/dev/ser1和/dev/ser2这样的名称:当用户执行ls -l /dev时,库执行必要的处理,以使用适当的信息响应生成的_IO_STAT消息。编写串口驱动程序的人可以将精力集中在管理串口硬件的细节上。

Filesystem resource managers

  文件系统资源管理器向进程管理器注册挂载点。挂载点是向进程管理器注册的路径的一部分。路径的其余部分由文件系统资源管理器管理。
  例如,当文件系统资源管理器在/mount上附加一个挂载点,并且检查路径/mount/home/thomasf时:

  • /mount/ :标识由进程管理器管理的挂载点。
  • home/thomasf: 标识将由文件系统资源管理器管理的其余部分。

下面是一些使用文件系统资源管理器的例子:

  1. flash文件系统驱动程序(尽管flash驱动程序的源代码负责处理这些细节)。
  2. 一个tar文件系统进程,它将tar文件的内容表示为一个文件系统,用户可以cd 进入,在里面ls。
  3. 一个邮箱管理进程,它注册名称/mailboxes并管理看起来像目录的单个邮箱,以及包含实际消息的文件。

通过本地IPC进行通信

  一旦资源管理器建立了它的路径名前缀,它将在任何客户机程序试图对该路径名执行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消息传递完成的。这允许一些独特的功能:

  • 一个定义良好的应用程序接口。在开发环境中,这允许对客户端和资源管理器端的实现进行非常清晰的分工。
  • 一个到资源管理器的简单接口。因为所有交互与资源管理器通过本机IPC,也没有特殊的“后门”钩子或安排与操作系统,资源管理器的作者可以专注于手头的任务,而不是担心在其他操作系统所需的所有特殊考虑。
  • 免费网络透明性。由于底层的本机IPC消息传递机制本质上是网络分布式的,客户机或服务器(资源管理器)不需要任何额外的工作,因此程序可以无缝地访问网络中其他节点上的资源,甚至不需要知道它们正在通过网络。
      NOTE:所有QNX中微子设备驱动程序和文件系统都实现为资源管理器。这意味着“本机”QNX中微子设备驱动程序或文件系统所能做的一切,用户编写的资源管理器也可以做到。
      以FTP文件系统为例。在这里,资源管理器将接管部分路径名空间(例如/ftp),并允许用户将cd 到ftp站点以获取文件。例如,cd /ftp/rtfm.mit.edu/pub将连接到ftp站点rtfm.mit.edu并将目录更改为/pub。之后,用户可以打开、编辑或复制文件。
    特定于应用程序的文件系统是用户编写的资源管理器的另一个示例。对于广泛使用基于磁盘的文件的应用程序,可以编写与该应用程序一起工作的定制文件系统,并提供更好的性能。自定义资源管理器的可能性仅受到应用程序开发人员想象力的限制。

资源管理器体系结构

这是资源管理器的核心:

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

架构包括三个部分:

  1. 创建一个通道,以便客户端程序可以连接到资源管理器来发送消息。
  2. 将由资源管理器负责的路径名(或多个路径名)注册到进程管理器,以便它可以将针对特定路径名的打开请求解析到此资源管理器。
  3. 接收和处理消息。
    每个资源管理器都需要这个消息处理结构(上面的switch/case)。但是,我们提供了一组方便的库函数来处理此功能(以及其他关键功能)。

Message types

在架构上,资源管理器将接收两类消息:

  1. connect messages
  2. I/O messages

  connect消息由客户机发出,以执行基于路径名的操作(例如,io_open消息)。这可能涉及执行诸如权限检查(客户机是否具有打开此设备的正确权限?)和为该请求设置上下文等操作I/O消息依赖于此上下文(在客户端和资源管理器之间创建)来执行I/O消息的后续处理(例如,io_read)。
  这种设计是有充分理由的。例如,为每个read()请求传递完整的路径名是低效的。io_open处理程序还可以执行我们希望只执行一次的任务(例如,权限检查),而不是每次都执行I/O的消息。另外,当read()从磁盘文件中读取4096字节时,可能还有20兆字节等待读取。因此,read()函数需要一些上下文信息来告诉它从文件中读取的位置。

资源管理器共享库

  在自定义嵌入式系统中,部分设计工作可能会花费在编写资源管理器上,因为系统中可能没有现成的驱动程序可以用于自定义硬件组件。我们的资源管理器共享库使这个任务相对简单。

自动默认消息处理

  如果资源管理器由于某种原因不想处理某些函数(例如,一个数模转换器不支持像lseek()这样的函数,或者软件不需要它),共享库将方便地提供默认操作。有两个级别的默认操作:

  1. 第一级只是将ENOSYS返回给客户机应用程序,通知它不支持该特定函数。
  2. 第二层(即, iofunc_*()共享库)允许资源管理器自动处理各种功能。

open(), dup(), and close()

  资源管理器共享库提供的另一个方便的服务是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的一个显著特征是能够使用线程。通过使用多个线程,可以构造一个资源管理器,使多个线程等待消息,然后同时处理它们。这个线程管理是资源管理器共享库提供的另一个方便的功能。除了跟踪创建的线程数和等待的线程数之外,该库还负责维护最佳线程数。

dispatch functions

OS提供了一组dispatch_*函数:

  • 允许需要支持多种消息类型(例如,资源管理器可以处理自己的私有消息范围)的管理器和客户端使用公共阻塞点。
  • 为不绑定到资源管理器的消息类型提供灵活的接口(处理私人讯息及脉冲码)
  • 从线程中解耦阻塞和处理程序代码。您可以在主代码中实现资源管理器事件循环。这种解耦还有助于简化调试,因为您可以在阻塞函数和处理程序函数之间放置一个断点。

Combine messages

  为了节省网络带宽并提供对原子操作的支持,操作系统支持合并消息。合并消息由客户端的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语义,再次将工作从程序员那里卸下。
需要考虑三个主要结构:

  • context
  • attributes structure
  • mount structure

figure39

  第一个数据结构,上下文,已经讨论过了(请参阅关于“消息类型”)。它保存每次打开时使用的数据,比如文件中的当前位置(lseek()偏移量)。
  由于资源管理器可能负责多个设备(例如,devc-ser*可能负责/dev/ser1、/dev/ser2、/dev/ser3等),因此属性结构在每个设备的基础上保存数据。属性结构包含设备所有者的用户和组ID、最后修改时间等项。
  对于文件系统(块I/O设备)管理器,还要使用一个结构。这是挂载结构,其中包含对整个挂载设备全局的数据项。
  当许多客户端程序打开了特定资源上的各种设备时,数据结构可能是这样的:
Figure40

  iofunc_*()默认函数的操作是基于这样的假设:程序员已经使用了上下文块和属性结构的默认定义。这是一个安全的假设,原因有二:

  1. 对于大多数应用程序,默认的上下文和属性结构包含足够的信息。
  2. 如果默认结构没有包含足够的信息,则可以将其封装到您已定义的结构中。
      根据定义,默认结构必须是其各自超结构的第一个成员,允许通过iofunc_*()默认函数干净、简单地访问所需的基本成员:
    Figure41

这个库包含iofunc_*()默认的客户端函数处理程序:

  • chmod()
  • chown()
  • close()
  • devctl()
  • fpathconf()
  • fseek()
  • fstat()
  • lock()
  • lseek()
  • mmap()
  • open()
  • pathconf()
  • stat()
  • utime()

总结

  QNX Neutrino RTOS支持路径名空间映射,具有定义良好的资源管理器接口,并提供一组用于公共资源管理器功能的库,从而为开发新硬件的“驱动程序”提供了前所未有的灵活性和简单性——这是许多嵌入式系统的关键特性。

发布了1 篇原创文章 · 获赞 0 · 访问量 3325
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 大白 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览