假設(shè)您有兩個進(jìn)程:一個服務(wù)器和一個客戶端。服務(wù)器進(jìn)程從硬件接口讀取一些 I/O 并將數(shù)據(jù)傳遞給客戶端進(jìn)程。這些進(jìn)程可能在也可能不在單獨(dú)的處理器上運(yùn)行。特別是,它們沒有共同的共享內(nèi)存區(qū)域。

在這種情況下,服務(wù)器和客戶端必須通過它們之間的一些顯式管道進(jìn)行通信。該通信機(jī)制可以根據(jù)系統(tǒng)以不同方式實(shí)現(xiàn)。該系統(tǒng)的服務(wù)器部分可以使用類似于以下偽代碼的代碼運(yùn)行:
while (1) {
get_data_from_pins();
發(fā)送數(shù)據(jù)到客戶端();
}
并且客戶端部分可以使用以下代碼模式運(yùn)行:
while (1) {
wait_for_then_get_data_from_server();
處理數(shù)據(jù)();
}
在這種情況下,服務(wù)器初始化通信,客戶端等待并響應(yīng)通信。所以服務(wù)器是主機(jī),客戶端是從機(jī)。這也許是意料之中的,因為整個過程是由硬件接口上的數(shù)據(jù)到達(dá)驅(qū)動的。到目前為止,這很簡單。但是,如果考慮到時序要求,事情會變得有點(diǎn)復(fù)雜。根據(jù)系統(tǒng)的需要,兩個進(jìn)程之間的協(xié)議可能必須更加復(fù)雜。
阻塞行為和緩沖
假設(shè)通信管道本身只有有限的緩沖,服務(wù)器進(jìn)程中對send_data_to_client () 的調(diào)用將被阻塞– 它將等到客戶端準(zhǔn)備好。如果客戶端及時準(zhǔn)備好并且數(shù)據(jù)可以在需要從硬件讀取下一項數(shù)據(jù)之前進(jìn)行通信,這很好。
但是,如果不是這種情況并且客戶端太慢,那么來自硬件的下一個數(shù)據(jù)將會丟失。
有不同的方法可以解決阻塞問題。如果硬件具有某種流控制,則可能會推遲接口并在數(shù)據(jù)流中向上游推送延遲。但是,有時這是不可能的。
本文的其余部分著眼于沒有流量控制的情況,其中客戶端的拉動具有可變時間,而硬件的推流具有固定時間。在這種情況下,常見的解決方案是為數(shù)據(jù)使用緩沖區(qū)。
使用緩沖區(qū),服務(wù)器進(jìn)程從硬件接口讀取數(shù)據(jù)并將數(shù)據(jù)放入 FIFO??蛻舳诉M(jìn)程要求服務(wù)器從 FIFO 的另一端提供數(shù)據(jù)。緩沖區(qū)需要足夠大,以應(yīng)對在客戶端最長處理時間內(nèi)可以到達(dá)的數(shù)據(jù)量。

問題是如何設(shè)計兩個進(jìn)程之間的通信協(xié)議,使得服務(wù)器可以在需要時從硬件中讀取數(shù)據(jù),而客戶端可以在需要時獲取數(shù)據(jù)?
輪詢
該問題的一種解決方案是服務(wù)器在每次從硬件收集數(shù)據(jù)之間反復(fù)輪詢客戶端以確保其準(zhǔn)備就緒。代碼看起來像這樣:
while (1) {
get_data_from_pins();
add_data_to_fifo();
while (!time_to_get_data()) {
client_ready = poll_client();
if (!fifo_empty() && client_ready) {
get_data_from_fifo();
發(fā)送數(shù)據(jù)到客戶端();
}
}
}
對應(yīng)的客戶端代碼是:
while (1) {
signal_ready_to_server();
get_data_from_server();
處理數(shù)據(jù)();
服務(wù)器
可能會在硬件交互之間發(fā)送幾項數(shù)據(jù)或不發(fā)送數(shù)據(jù),在這種情況下,緩沖區(qū)將開始填滿以待稍后清空。
基于事件的編程:選擇
用一個在固定時間內(nèi)重復(fù)輪詢的循環(huán)編寫通信是編寫此類代碼的一種略顯笨拙的方式。更好的方法是使用一種編程風(fēng)格,指示進(jìn)程直接對系統(tǒng)中發(fā)生的事件做出反應(yīng)。
以這種風(fēng)格編碼的關(guān)鍵是使用選擇來等待事件在指定集合之外發(fā)生,然后在其中一個發(fā)生時做出反應(yīng)。XC 編程語言中的 select 結(jié)構(gòu)可以做到這一點(diǎn),其他領(lǐng)域的結(jié)構(gòu)(例如 Unix 中的 select 系統(tǒng)調(diào)用或 SystemC 中的 wait 調(diào)用)也是如此。
XC 風(fēng)格的 select 語句與 C 中的 switch 語句有類似的形式:
select {
case event1:
。..
break;
案例事件2:
。..
休息;
。..。
}
該語句等待 event1、event2 等之一發(fā)生,然后執(zhí)行相關(guān)案例主體中的代碼。鑒于此構(gòu)造,服務(wù)器代碼可以以非輪詢方式重寫:
而(1){
選擇{
案例pins_ready():
get_data_from_pins();
add_data_to_fifo();
休息;
case !fifo_empty() && client_ready():
get_data_from_fifo();
發(fā)送數(shù)據(jù)到客戶端();
休息;
}
}
使客戶端再次成為從屬
添加緩沖區(qū)的行為使客戶端進(jìn)程成為通信的主控。它標(biāo)志著服務(wù)器和客戶端之間數(shù)據(jù)事務(wù)的開始。如果客戶端想要對 select 語句中的其他事件以及傳入數(shù)據(jù)做出反應(yīng),這將是一個問題。
您可以通過引入一個從服務(wù)器拉取并推送到客戶端的中間進(jìn)程來使客戶端再次成為從屬:

在這種情況下,中間進(jìn)程的偽代碼是:
while (1) {
signal_ready_to_server();
get_data_from_server();
發(fā)送數(shù)據(jù)到客戶端();
現(xiàn)在
客戶端進(jìn)程可以對中間進(jìn)程推送數(shù)據(jù)的事件做出反應(yīng),服務(wù)器進(jìn)程可以對中間進(jìn)程拉取數(shù)據(jù)的事件做出反應(yīng)。
提高效率:利用通信緩沖區(qū)
中間過程的引入是低效的。有一個完整的過程,只關(guān)心鏟數(shù)據(jù),改變其他進(jìn)程的主從關(guān)系。如果過程很便宜,這不是問題,但在許多情況下它們很昂貴或有限。幸運(yùn)的是,如果管道中有少量緩沖,您可以不使用中間過程。
如果有足夠的緩沖在管道本身中存儲一個字節(jié),服務(wù)器可以發(fā)送一個通知字節(jié),然后繼續(xù)處理。當(dāng)服務(wù)器第一次將數(shù)據(jù)放入其緩沖區(qū)時,它會發(fā)送一個通知:

然后,客戶端可以對該通知到達(dá)的事件做出反應(yīng),并知道現(xiàn)在有可用的數(shù)據(jù)。然后它可以向服務(wù)器發(fā)出信號,表明它已準(zhǔn)備好接收數(shù)據(jù):

服務(wù)器可以對此做出響應(yīng)(前提是它不忙于處理硬件)并將數(shù)據(jù)發(fā)送給客戶端:

此事務(wù)完成后,管道將清除通知字節(jié)。因此,如果緩沖區(qū)中有更多數(shù)據(jù),服務(wù)器可以發(fā)送一個新的。

如果緩沖區(qū)為空,則管道保持暢通,直到服務(wù)器接收到更多數(shù)據(jù)。在任何時候,管道中只有一個通知字節(jié),因此管道緩沖區(qū)永遠(yuǎn)不會溢出并阻塞服務(wù)器進(jìn)程。
在這種情況下,服務(wù)器的代碼如下所示:
int notify = 0; // 此變量跟蹤通知是否在
// 位于管道中
while (1) {
select {
case pins_ready():
get_data_from_pins();
add_data_to_fifo();
if (!notified) {
send_notification_to_client();
通知 = 1;
}
打破;
案例 !fifo_empty() && client_ready():
get_data_from_fifo();
發(fā)送數(shù)據(jù)到客戶端();
if (fifo_empty()) {
通知 = 0;
}
其他 {
send_notification_to_client();
通知 = 1;
}
打破;
這段代碼需要注意的重要一點(diǎn)是send_notification_to_client
調(diào)用
不會阻塞,因此代碼會一直運(yùn)行。另一方面,對 send_data_to_client 的調(diào)用將阻塞,直到客戶端準(zhǔn)備好。但是,在這種情況下,客戶端將準(zhǔn)備就緒,因為它向服務(wù)器發(fā)出了準(zhǔn)備就緒的信號。
這種情況下的客戶端代碼是:
while (1) {
select {
案例 get_notification_from_server():
signal_ready_to_server();
get_data_from_server();
處理數(shù)據(jù)();
休息;
。..
}
}
此版本的通信協(xié)議允許客戶端成為從屬服務(wù)器,服務(wù)器緩沖區(qū)位于正確的位置,而無需中間進(jìn)程。
在 XC 中執(zhí)行 在
使用XC 的XMOS 平臺上,服務(wù)器和客戶端進(jìn)程將是 XC 線程,通信機(jī)制將是 XC 通道。
來自服務(wù)器的通知需要使用異步 outct 原語來發(fā)送控制令牌,而無需在通信中進(jìn)行正常的 XC 同步握手。
此控制令牌應(yīng)該是 XS1_CT_END 令牌,以確保在令牌傳遞到目標(biāo)通道結(jié)束緩沖區(qū)后線程之間的任何內(nèi)核間切換都是空閑的:
send_notification_to_client(chanend c) {
outct(c, XS1_CT_END);
}
客戶端可以在類似下面的代碼中選擇這個通知:
select {
。..
case inct_byref(c, tmp): // 接收通知
c 《: 0; // 發(fā)送就緒信號
c :》 len; // 接收數(shù)據(jù)長度
for (int i=0;i》len;i++ // 接收數(shù)據(jù)
c :》 data[i];
break;
}
評論