Writing a Resource Manager -- Chapter 1:What Is a Resource Manager?

Chapter 1:What Is a Resource Manager?

一般而言,资源管理器是向文件系统命名空间中注册名称的过程。其他进程使用该路径与资源管理器进行通信。

为了使QNX Neutrino RTOS具有很大的灵活性,最大限度地减少最终系统运行的内存要求,并应对自定义嵌入式系统中可能存在的各种设备,操作系统允许用户编写的流程充当可以动态启动和停止的资源管理器。

资源管理器通常负责向各种类型的设备提供接口。这可能涉及管理实际硬件设备(如串行\并行端口,网卡和磁盘驱动器)或虚拟设备(如/dev/null,network filesystem和pseudo-ttys)。

在其他操作系统中,此功能传统上与设备驱动程序相关联。但与设备驱动程序不同,资源管理器不需要与内核进行任何特殊安排。事实上,资源管理器作为一个独立于内核的进程运行,看起来就像任何其他用户级程序一样。

内核(procnto)本身就是一个资源管理器;它以与任何其他进程处理它们相同的方式处理/dev/null,/proc和其他几个资源。

资源管理器接受来自其他程序的消息,并且可选择性地与硬件通信。它在路径名空间中注册了一个路径名前缀(例如,/dev/ser1),其他进程可以使用标准C库open()函数打开该名称,然后通过文件描述符从中read()和write()。发生这种情况时,资源管理器会收到一个打开请求,然后是读取和写入请求。

资源管理器不仅限于处理open(),read()和write()调用;它还可以支持任何基于文件描述符或文件指针的函数,以及其他形式的IPC。

在QNX Neutrino中添加资源管理器不会影响操作系统的任何其他部分 - 驱动程序的开发和调试与任何其他应用程序一样。由于资源管理器位于自己的受保护地址空间中,因此设备驱动程序中的错误不会导致整个操作系统关闭。

如果您已经在大多数UNIX变体中编写了设备驱动程序,那么您习惯于限制在设备驱动程序中可以执行的操作;但由于QNX Neutrino中的设备驱动程序只是一个常规过程,因此您不能限制自己可以做的事情(除了ISR中存在的限制)。

要在路径名空间中注册前缀,资源管理器必须启用PROCMGR_AID_PATHSPACE功能。为了创建公共频道(即,没有设置_NTO_CHF_PRIVATE),您的进程必须启用PROCMGR_AID_PUBLIC_CHANNEL功能。有关更多信息,请参阅QNX Neutrino C库参考中的procmgr_ability()。

例如,串行端口可以由名为devc-ser8250的资源管理器管理,尽管实际资源可能在路径名空间中被称为/dev/ser1。当进程请求串行端口服务时,它会通过打开一个串行端口(在本例中为/dev/ser1)来实现。

fd = open("/dev/ser1", O_RDWR);
for (packet = 0; packet < npackets; packet++)
{
write(fd, packets[packet], PACKET_SIZE);
}
close(fd);

因为资源管理器作为进程执行,所以它们的使用不仅限于设备驱动程序;任何服务器都可以写为资源管理器。例如,给予要在GUI界面中显示的DVD文件的服务器不会被归类为驱动程序,但可以将其写为资源管理器。它可以注册名称/dev/dvd,因此客户端可以执行以下操作:

fd = open("/dev/dvd", O_WRONLY);
while (data = get_dvd_data(handle, &nbytes))
{
bytes_written = write(fd, data, nbytes);
if (bytes_written != nbytes)
{
perror ("Error writing the DVD data");
}
}
close(fd);

Why write a resource manager?

以下是您要编写资源管理器的几个原因:

  • 客户端API是POSIX。
        用于与资源管理器通信的API大部分是POSIX。所有C程序员都熟悉open(),read()和write()函数。从新学习的成本最小化考虑,因此需要记录服务器的接口。

  • 您可以减少接口类型的数量。

如果您有许多服务器进程,则将每个服务器编写为资源管理器会将客户端需要使用的不同接口的数量保持在最低限度。

例如,如果您有一个程序员团队来构建您的整个应用程序,并且每个程序员都在为该应用程序编写一个或多个服务器。这些程序员可能直接为您的公司工作,或者他们可能属于为您的模块化平台开发附加硬件的合作伙伴公司。

如果服务器是资源管理器,那么所有这些服务器的接口就是POSIX函数:open(),read(),write(),以及其他任何有意义的东西。对于不适合 read/write 模型的控件类型信息,有devctl()(尽管devctl()不是POSIX)。

  • 命令行实用程序可以与资源管理器通信。

由于用于与资源管理器通信的API是POSIX函数集,并且由于标准POSIX实用程序使用此API,因此可以使用这些实用程序与资源管理器进行通信。

The types of resource managers

有两种类型的资源管理器:

  • 设备资源管理器

  • 文件系统资源管理器

您使用的类型取决于您希望资源管理器执行的操作,以及您希望自己完成的工作量,以便向客户端提供正确的POSIX文件系统。

 

Device resource managers(设备资源管理器)

设备资源管理器仅在文件系统中创建单个文件条目,每个条目都向进程管理器注册。每个名称通常代表一个设备。这些资源管理器通常依赖资源管理器库来完成向用户呈现POSIX设备的大部分工作。

例如,串行端口驱动程序注册名称,例如/dev/ser1和/dev/ser2。当用户执行ls -l /dev时,库会执行必要的处理以使用适当的信息响应生成的_IO_STAT消息。编写串行端口驱动程序的人可以专注于管理串行端口硬件的细节。

Filesystem resource managers(文件系统资源管理器)

文件系统资源管理器使用进程管理器注册安装点。挂载点是向进程管理器注册的路径部分。路径的其余部分由文件系统资源管理器管理。例如,当文件系统资源管理器在/mount处附加挂载点时,将检查路径/mount/home/thomasf:

/mount/

标识由进程管理器管理的装入点。

home/ thomasf

标识要由文件系统资源管理器管理的剩余部分。

使用文件系统资源管理器的示例如下:

  • flash文件系统驱动程序(虽然flash驱动程序的源代码负责这些细节)

  • tar文件系统进程,它将tar文件的内容通过用户cd进入和ls输出显示为文件系统。

  • 邮箱管理进程,用于注册名称“/mailboxes”并管理看起来像目录的单个邮箱以及包含实际邮件的文件

Communication via native IPC

一旦资源管理器建立了其路径名前缀,只要任何客户端程序尝试对该路径名执行open(),read(),write()等,它就会收到消息。例如,在devc-ser*接管了路径名/dev/ser1之后,客户端程序执行:

fd = open ("/dev/ser1", O_RDONLY);

客户端的C库将构造一个_IO_CONNECT消息,然后通过IPC将其发送到devc-ser*资源管理器。
一段时间后,当客户端程序执行时:

read (fd, buf, BUFSIZ)

客户端的C库构造一条_IO_READ消息,然后将其发送到资源管理器。

关键是客户端程序和资源管理器之间的所有通信都是通过本机IPC消息传递完成的。这允许许多独特的功能:

  • 应用程序的定义良好的接口。在开发环境中,这允许实现客户端和资源管理器端的非常清晰的分工。

  • 资源管理器的简单接口。由于与资源管理器的所有交互都通过本机IPC,并且没有与操作系统的特殊“后门”挂钩或安排,资源管理器的编写者可以专注于手头的任务,而不是担心所有特殊注意事项在其他操作系统中需要。

  • Free Network透明度。由于底层本机IPC消息传递机制本质上是网络分布式的,无需客户端或服务器(资源管理器)所需的任何额外工作,因此程序可以无缝地访问网络中其他节点上的资源,甚至不会意识到它们正在通过网络。

所有QNX Neutrino RTOS设备驱动程序和文件系统都作为资源管理器实现。这意味着“本机”QNX Neutrino设备驱动程序或文件系统可以执行的所有操作,用户编写的资源管理器也可以执行。

例如,考虑FTP文件系统。这里资源管理器将接管路径名空间的一部分(例如,/ftp)并允许用户cd进入FTP站点以获取文件。例如,cd /ftp/rtfm.mit.edu/pub将连接到FTP站点rtfm.mit.edu并将目录更改为/pub。在此之后,用户可以打开,编辑或复制文件。

特定于应用程序的文件系统将是用户编写的资源管理器的另一个示例。鉴于广泛使用基于磁盘的文件的应用程序,可以编写可与该应用程序配合使用的定制文件系统,并提供卓越的性能。

自定义资源管理器的可能性仅受应用程序开发人员的想象力限制。

Examples of resource managers

在进入资源管理器的工作之前,让我们考虑一些实际和可能的用途。

Transparent Distributed Processing (Qnet) statistics

例如,透明分布式处理(Qnet) - io-pkt核心网络堆栈的一部分 - 包含注册名称/proc/qnetstats的资源管理器代码。如果您打开此名称并从中读取,则资源管理器代码将使用描述Qnet统计信息的文本正文进行响应。

cat实用程序获取文件的名称并打开文件,从中读取文件,并将其读取的内容显示到标准输出(通常是屏幕)。因此,您可以输入:cat /proc/qnetstats

Qnet资源管理器代码响应文本,例如:

kif net_server : 0,3
kif waiting : 1,2
kif net_client : 0,1
kif buffer : 0,1
kif outbound_msgs : 0,1
kif vtid : 0,1
kif server_msgs : 0,1
kif nd_down : 42
kif nd_up : 132
kif nd_changed : 3
kif send_acks : 0
kif client_kercalls : 14
kif server_msgs : 202898
kif server_unblock : 0
qos tx_begin_errors : 0
qos tx_done_errors : 0
qos tx_throttled : 0
qos tx_failed : 8
qos pkts_rxd_noL4 : 0
qos tx_conn_created : 43
qos tx_conn_deleted : 41
qos rx_conn_created : 35
qos rx_conn_deleted : 33
qos rx_seq_order : 0

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

GPS devices

通常,GPS设备每秒发送一个数据流。流由在命令组中组织的信息组成。以下是GPS输出的示例:

$GPGSA,A,3,17,16,22,31,03,18,25,,,,,,1.6,1.0,1.2*39
$GPGGA,185030.30,4532.8959,N,07344.2298,W,1,07,1.0,23.8,M,-32.0,M,,*69
$GPGLL,4532.8959,N,07344.2298,W,185030.30,A*12
$GPRMC,185030.00,A,4532.8959,N,07344.2298,W,0.9,116.9,160198,,*27
$GPVTG,116.9,T,,,0.9,N,1.7,K*2D
$GPZDA,185030.30,16,01,1998,,*65
$GPGSV,2,1,08,03,55,142,50,22,51,059,51,18,48,284,53,31,23,187,52*78

每行对应一个数据集。这是一些数据集的C结构:

typedef struct GPSRMC_s {
double UTC;
int Status;
Degree_t Latitude;
NORTHSOUTH Northing;
Degree_t Longitude;
EASTWEST Easting;
float Speed;
float Heading;
} GPSRMC_t;
 

typedef struct GPSVTG_s {
float Heading;

float SpeedInKnots;
float SpeedInKMH;
} GPSVTG_t;


typedef struct GPSUTM_s {
UTM_t X;
UTM_t Y;
} GPSUTM_t;

您可以为每个GPS格式命令提供一个API:gps_get_rmc(),gps_get_vtg(),get_get_utm()等。在内部,每个功能将发送具有不同命令的消息,并且回复是GPS最后接收的数据。

第一个障碍是read()和write()是半双工操作;你不能用它们来发送命令并获取数据。你可以这样做:

GPSUTM_t utm;
int cmd = GPS_GET_GSA;
fd = open( "/dev/gps1", O_RDWR );
write( fd, &cmd, sizeof( cmd) );
read( fd, &data, sizeof(data) );
close(fd);

但这段代码看起来不自然。没有人会期望以这种方式使用read()和write()。您可以使用devctl()发送命令并请求特定数据,但如果您将驱动程序实现为资源管理器,则可以为每个命令集设置不同的路径。驱动程序将创建以下路径名:

  • /dev/gps1/gsa.txt

  • /dev/gps1/gsa.bin

  • /dev/gps1/gga.bin

  • /dev/gps1/gga.txt

想要获得GSA信息的程序会这样做:

gps_gsa_t gsa;
int fd;
fd = open ( "/dev/gps1/gsa.bin", O_RDONLY );
read( fd, &gsa, sizeof( gsa ) );
close ( fd);

同时使用.txt和.bin扩展名的好处是,如果使用.txt文件,read()返回的数据将采用ASCII格式。如果使用.bin文件,则数据将以C结构返回,以便于使用。但是,如果大多数程序更喜欢使用二进制表示,为什么支持* .txt?原因很简单。从shell中你可以输入:
#cat /dev/gps1/rmc.txt
你会看到:
# GPRMC,185030.00,A,4532.8959,N,07344.2298,W,0.9,116.9,160198,,*27
您现在可以从shell访问GPS数据。但你可以做得更多:

  • 如果您想了解GPS支持的所有命令,只需键入:

#ls / dev / gps1

  • 要从C程序执行相同操作,请使用opendir()和readdir()。

  • 如果您希望在有新数据时通知您的程序,只需使用select()或ionotify()而不是轮询数据。

与您的想法相反,在资源管理器中支持这些功能非常简单。

Database example

这个特殊的设计要求一个集中文件访问的程序。开发人员决定让一个程序处理它,而不是让每个程序处理数据文件的特定位置(在硬盘上或在FLASH或RAM中)。此外,由一个程序完成的文件更新要求通知使用该文件的所有程序。因此,不是让每个程序相互通知,只有一个程序会处理通知。该程序巧妙地命名为“数据库”。

原始设计中的API包括db_read(),db_write(),db_update_notification()等,但资源管理器更适合。API已更改为熟悉的open(),read(),write(),select(),ionotify()等。

客户端程序只看到一个路径/dbase/filename,但在/dbase/中找到的文件实际上并不存在;他们可能散落在各处。

数据库资源管理器程序在open()期间查看文件名,并确定文件需要去或读取的位置。当然,这取决于文件名中的特定字段。例如,如果文件的扩展名为.tmp,则会转到RAM磁盘。

这种设计的真正美妙之处在于客户端程序的设计者可以在不运行数据库程序的情况下测试他们的应用程序。open()将由文件系统直接处理。

为了支持文件更改的通知,客户端将在文件的文件描述符上使用ionotify()或select()。遗憾的是,文件系统本身不支持该功能,因此需要运行数据库程序才能测试操作。

I2C (Inter-Integrated Circuit) driver

此示例是I2C总线控制器的驱动程序。I2C总线只是一个2线总线,硬件实现起来非常便宜。其在总线上最多支持127个设备,每个设备可以处理256个命令。当设备想要从另一个设备读取信息或从另一个设备写入信息时,必须首先将它们设置为主设备以拥有总线。然后主设备发出从设备地址和命令寄存器号。然后slave对所接收的命令起作用。

您认为资源管理器在这种情况下不适用。所有read()和write()操作都需要设备地址和命令寄存器。您可以使用devctl(),但资源管理器再次提供更简洁的接口。

使用资源管理器,每个设备都将位于其自己的目录下,并且每个寄存器都有一个文件名:/dev/i2c1//

需要注意的一点是,每个文件名(127个设备* 256个寄存器 = 32512个文件名)并不一定存在。每个设备都将根据需要实时创建。因此,每个open()实际上都是O_CREAT。

为了防止由于存在大量文件引起的任何问题,您可以使/dev/i2c1目录的ls命令不返回任何内容。或者,您可以使ls列出至少已打开一次的文件名。在这一点上,重要的是要澄清文件名的存在完全由资源管理器处理;操作系统本身并未在该过程中使用。因此,如果文件名响应open()请求,而不是ls,则不是问题。

剩下的一个要素是:I2C总线具有波特率的概念。已经有C函数来设置波特率,你可以通过shell中的stty命令使它工作。

使用驱动程序的人不必担心库或头文件的问题,因为没有。资源管理器免费允许通过shell访问每个命令寄存器,或者就此而言,通过来自Linux或Windows机器的SAMBA。通过shell访问使调试变得如此简单 - 不需要编写自定义测试工具,而且它具有令人难以置信的灵活性,更不用说支持每个设备的每个命令寄存器的单独权限。

还有一件事:这段代码可能更清晰:

fd = open ( "/dev/i2c1/34/45" );
read( fd, &variable, sizeof( variable ) );
close(fd);

这样做会好得多:

fd = open ( "/dev/i2c1/flash/page_number" );
read( fd, &page_number, sizeof( page_number ) );
close (fd );

当然,您可以使用#define指令来显示有意义的信息,但更好的解决方案是创建驱动程序可以读取以创建别名的配置文件。配置文件如下所示:

[4=temperature_sensor]
10=max
11=min
12=temperature
13=alarm
[5=flash]
211=page_number

方括号内的字段定义设备地址和名称。后面的数据指定该设备的每个寄存器。这种方法的主要优点是:

  • 配置文件的格式有助于记录程序。

  • 如果更改了硬件,并为设备分配了新地址,则只需更改配置文件;没有必要重新编译程序以重新编译。

这些预定义设备将始终通过ls命令显示。

When not to use a resource manager

最明显的情况是程序不需要接收消息。但是,如果您的程序是事件驱动的,您可能希望查看使用调度库来处理内部事件。将来,如果您的程序需要接收消息,您可以轻松将其转换为资源管理器。

另一种情况可能是您的程序仅与其子项接口。父进程可以访问与他们交互所需的所有孩子的信息。

不过,您几乎可以将所有内容都变成资源管理器。如果资源管理器的客户端仅使用POSIX API,则编写的文档较少,代码非常便携。当然,在许多情况下,通常非常需要在POSIX API之上提供API。您可以隐藏devctl()或自定义消息的详细信息。如果必须移植到不同的操作系统,则可以更改API的内部。

如果必须以非常高的速率传输大量数据,资源管理器可能会成为问题。由于资源管理器基本上通过IPC内核原语进行接口,因此它们比简单的memcpy()慢,但没有什么可以阻止您使用混合共享内存,POSIX API和自定义消息,所有这些都隐藏在您自己的API中。让API检测资源管理器是否是本地的并且在远程使用本地和IPC时使用共享内存并不是太困难 - 这是一种充分利用这两个方面的方法。


 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章