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時使用共享內存並不是太困難 - 這是一種充分利用這兩個方面的方法。


 

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