任務(wù)信令和通信機(jī)制
在本章中,將簡(jiǎn)要介紹任務(wù)信號(hào)和任務(wù)間通信的核心機(jī)制。這些基元是事件驅(qū)動(dòng)的并行編程的基礎(chǔ),它是基于RTOS的應(yīng)用程序良好實(shí)現(xiàn)的基礎(chǔ)。
與其直接進(jìn)入FreeRTOS的API,不如將每個(gè)基元與一些圖形例子和一些關(guān)于每個(gè)機(jī)制可被使用的建議一起介紹。不要擔(dān)心:在后面的章節(jié)中,我們將進(jìn)入使用API的細(xì)枝末節(jié)?,F(xiàn)在,讓我們把注意力集中在基本原理上。
實(shí)時(shí)操作系統(tǒng)隊(duì)列
隊(duì)列的概念相當(dāng)簡(jiǎn)單,但它們也非常強(qiáng)大和靈活,特別是如果你傳統(tǒng)上用C語(yǔ)言在裸機(jī)上編程的話。 在其核心,隊(duì)列只是一個(gè)循環(huán)緩沖區(qū)。然而,這個(gè)緩沖區(qū)包含一些非常特殊的屬性,比如原生的多線程安全,每個(gè)隊(duì)列可以靈活地容納任何類(lèi)型的數(shù)據(jù),以及喚醒正在等待隊(duì)列中出現(xiàn)的項(xiàng)目的其他任務(wù)。默認(rèn)情況下,數(shù)據(jù)存儲(chǔ)在隊(duì)列中使用先進(jìn)先出(FIFO)排序--第一個(gè)被放入隊(duì)列的項(xiàng)目就是第一個(gè)被從隊(duì)列中移除的項(xiàng)目。
我們將首先看看當(dāng)隊(duì)列處于不同狀態(tài)和以不同方式使用時(shí)的一些簡(jiǎn)單行為(發(fā)送與接收),然后繼續(xù)討論如何用隊(duì)列在任務(wù)之間傳遞信息。
簡(jiǎn)單的隊(duì)列發(fā)送
第一個(gè)隊(duì)列例子是簡(jiǎn)單地將一個(gè)項(xiàng)目添加(也被稱為發(fā)送)到有空位的隊(duì)列中:
當(dāng)項(xiàng)目被添加到有可用空間的隊(duì)列中時(shí),添加立即發(fā)生。因?yàn)殛?duì)列中的空間是可用的,所以將項(xiàng)目發(fā)送到隊(duì)列的任務(wù)繼續(xù)運(yùn)行,除非有另優(yōu)先級(jí)更高的任務(wù)在等待隊(duì)列中出現(xiàn)的項(xiàng)目。
盡管與隊(duì)列的交互通常發(fā)生在任務(wù)內(nèi)部,但這并不總是這樣的。在一些特殊情況下,隊(duì)列也可以從ISR中訪問(wèn)(但這種行為有不同的規(guī)則)。在本章的例子中,我們將假設(shè)任務(wù)從隊(duì)列中發(fā)送和接收項(xiàng)目。
簡(jiǎn)單的隊(duì)列接收
在下圖中,任務(wù)被顯從隊(duì)列中接收一個(gè)項(xiàng)目:
當(dāng)任務(wù)準(zhǔn)備從隊(duì)列中接收項(xiàng)目時(shí),默認(rèn)情況下,它將獲得最老的項(xiàng)目。在這個(gè)例子中,由于隊(duì)列中至少有一個(gè)項(xiàng)目,所以接收被立即處理,任務(wù)繼續(xù)運(yùn)行。
滿隊(duì)列發(fā)送
當(dāng)隊(duì)列已滿時(shí),沒(méi)有信息被丟棄。相反,試圖將項(xiàng)目發(fā)送到隊(duì)列的任務(wù)將等待隊(duì)列中的可用空間,最長(zhǎng)時(shí)間為預(yù)先確定的數(shù)量:
當(dāng)隊(duì)列已滿時(shí),試圖向隊(duì)列發(fā)送項(xiàng)目的任務(wù)將等待,直到隊(duì)列中的空間變得可用,但只到指定的超時(shí)值。
在這個(gè)例子中,如果任務(wù)試圖向滿的隊(duì)列發(fā)送,并且它的超時(shí)值是10毫秒--它將只等待10毫秒的隊(duì)列中的空間變得可用。超時(shí)結(jié)束后,調(diào)用將返回并通知調(diào)用代碼發(fā)送失敗。如何處理這個(gè)失敗是由設(shè)置調(diào)用代碼的程序員決定的,并將根據(jù)使用情況而變化。極大的超時(shí)值可以用于真正的非關(guān)鍵性功能。只是要注意,這將導(dǎo)致發(fā)送任務(wù)有效地永遠(yuǎn)等待隊(duì)列中的空位(這顯然不再是實(shí)時(shí)的了)
你的代碼通常會(huì)被結(jié)構(gòu)化,以便嘗試向隊(duì)列中發(fā)送不會(huì)超時(shí)。作為程序員,你應(yīng)該根據(jù)具體情況來(lái)決定什么是可接受的時(shí)間量。你也有責(zé)任確定超時(shí)的嚴(yán)重性和糾正措施,如果真的發(fā)生超時(shí)。潛在的糾正措施可以從什么都不做(想想視頻通話中的丟幀)到緊急關(guān)機(jī)。
接收空隊(duì)列
訪問(wèn)隊(duì)列可能導(dǎo)致任務(wù)阻塞的另一種情況是接收空隊(duì)列:
與等待空間的發(fā)送類(lèi)似,從隊(duì)列中接收的任務(wù)也有可能被延遲。在空隊(duì)列的情況下,試圖從隊(duì)列中接收的任務(wù)將被阻塞,直到隊(duì)列中出現(xiàn)項(xiàng)目。如果在超時(shí)之前沒(méi)有項(xiàng)目出現(xiàn),調(diào)用代碼將被通知失敗。同樣,要采取的確切行動(dòng)方案也是不同的。
有時(shí),會(huì)使用無(wú)限期的等待。你經(jīng)常會(huì)遇到一些隊(duì)列的等待時(shí)間非常長(zhǎng),這些隊(duì)列正在接收來(lái)自外部接口的輸入,如串行端口,它們可能不會(huì)不斷地發(fā)送數(shù)據(jù)。如果串口另一端的人類(lèi)用戶在很長(zhǎng)一段時(shí)間內(nèi)沒(méi)有發(fā)送數(shù)據(jù),那就完全沒(méi)有問(wèn)題。
另一方面,接收超時(shí)也可以用來(lái)確保你有一個(gè)最低可接受的數(shù)據(jù)量來(lái)處理。讓我們使用一個(gè)旨在以10赫茲(每秒10個(gè)讀數(shù))提供新讀數(shù)的傳感器。如果你正在實(shí)現(xiàn)一個(gè)依賴于這個(gè)傳感器的新鮮讀數(shù)的算法,一個(gè)略大于100毫秒的超時(shí)可以用來(lái)觸發(fā)一個(gè)錯(cuò)誤。這個(gè)超時(shí)將保證該算法總是在新鮮的傳感器讀數(shù)上行動(dòng)。在這種情況下,擊中超時(shí)可用于觸發(fā)某種類(lèi)型的糾正措施或通知,說(shuō)明傳感器沒(méi)有按照預(yù)期執(zhí)行。
任務(wù)間通信的隊(duì)列
既然已經(jīng)介紹了隊(duì)列的簡(jiǎn)單行為,我們就來(lái)看看如何利用它們?cè)谌蝿?wù)之間移動(dòng)數(shù)據(jù)。隊(duì)列的非常常見(jiàn)的用例是讓一個(gè)任務(wù)填充隊(duì)列,而另一個(gè)任務(wù)則從同一隊(duì)列中讀取數(shù)據(jù)。這通常是直截了當(dāng)?shù)?,但可能有一些?xì)微的差別,這取決于系統(tǒng)是如何設(shè)置的:
在前面的例子中,任務(wù)1和任務(wù)2都在與同一個(gè)隊(duì)列進(jìn)行交互。任務(wù)1將向隊(duì)列發(fā)送一個(gè)項(xiàng)目。只要任務(wù)2的優(yōu)先級(jí)比任務(wù)1高,它就會(huì)立即收到該項(xiàng)目。
讓我們考慮另一個(gè)實(shí)例,在實(shí)踐中,當(dāng)多個(gè)任務(wù)與隊(duì)列進(jìn)行交互時(shí),經(jīng)常會(huì)出現(xiàn)這種情況。由于搶占式調(diào)度器總是運(yùn)行具有最高優(yōu)先級(jí)的任務(wù),如果該任務(wù)總是有數(shù)據(jù)要寫(xiě)入隊(duì)列,那么在另一個(gè)任務(wù)有機(jī)會(huì)從隊(duì)列中讀取數(shù)據(jù)之前,隊(duì)列就會(huì)充滿。下面是一個(gè)例子,說(shuō)明這可能會(huì)發(fā)生的情況:
下面的數(shù)字與時(shí)間軸上的索引相對(duì)應(yīng):
- 任務(wù)2試圖從空隊(duì)列中接收一個(gè)項(xiàng)目。沒(méi)有項(xiàng)目可用,所以任務(wù)2阻塞。
- 任務(wù)1向隊(duì)列添加項(xiàng)目。由于它是系統(tǒng)中優(yōu)先級(jí)最高的任務(wù),任務(wù)1向隊(duì)列中添加項(xiàng)目,直到它沒(méi)有更多的項(xiàng)目可以添加,或者直到隊(duì)列已滿。
- 隊(duì)列被填滿了,所以任務(wù)1被阻塞了。
- 任務(wù)2被調(diào)度器賦予上下文,因?yàn)樗F(xiàn)在是可能運(yùn)行的最高優(yōu)先級(jí)任務(wù)。
- 一旦有項(xiàng)目從隊(duì)列中移出,任務(wù)1就會(huì)再次被賦予上下文(這是系統(tǒng)中優(yōu)先級(jí)最高的任務(wù),它現(xiàn)在可以運(yùn)行了,因?yàn)樗诘却?duì)列中的空間時(shí)被阻塞了)。在添加一個(gè)項(xiàng)目后,隊(duì)列已經(jīng)滿了,任務(wù)1被阻塞了。
- 任務(wù)2被賦予上下文并從隊(duì)列中接收一個(gè)項(xiàng)目:
隊(duì)列的另一個(gè)極其常見(jiàn)的用例是讓隊(duì)列接受來(lái)自許多不同來(lái)源的輸入。這對(duì)于像調(diào)試串口或日志文件這樣的東西特別有用。許多不同的任務(wù)可以寫(xiě)入隊(duì)列,由一個(gè)任務(wù)負(fù)責(zé)從隊(duì)列中接收數(shù)據(jù)并將其推送到共享資源上。
實(shí)時(shí)操作系統(tǒng)的信號(hào)
Semaphores是另一種非常直接的,但卻很強(qiáng)大的結(jié)構(gòu)。semaphore這個(gè)詞起源于希臘語(yǔ)--近似的英語(yǔ)翻譯是sign-bearer,這是一種非常直觀的思考方式。信號(hào)燈被用來(lái)表示某些事情已經(jīng)發(fā)生;它們是事件的信號(hào)。一些信號(hào)燈的使用案例包括以下內(nèi)容:
-
ISR完成了對(duì)外圍設(shè)備的服務(wù)。它可以給出信號(hào),為任務(wù)提供信號(hào),表明數(shù)據(jù)已經(jīng)準(zhǔn)備好進(jìn)一步處理。
-
任務(wù)到達(dá)了關(guān)口,它需要等待系統(tǒng)中的其他任務(wù)跟上,然后再繼續(xù)前進(jìn)。在這種情況下,可以用semaphore來(lái)同步任務(wù)。
限制受限資源的同時(shí)使用者的數(shù)量。
-
使用RTOS的方便之處在于信號(hào)燈的預(yù)先存在。它們被包含在每RTOS的實(shí)現(xiàn)中,因?yàn)樗鼈兊墓δ苁侨绱说幕荆ê完P(guān)鍵)。有兩種不同類(lèi)型的信號(hào)燈可供選擇:計(jì)數(shù)信號(hào)燈和二進(jìn)制信號(hào)燈。
計(jì)數(shù)信號(hào)
Counting semaphores最常被用來(lái)管理對(duì)同時(shí)使用的用戶數(shù)量有限制的共享資源。在創(chuàng)建時(shí),它們可以被配置為持有最大值,稱為上限。通常給出的計(jì)算semaphores的例子是數(shù)據(jù)庫(kù)中的讀者...... 好吧,我們?cè)谶@里談?wù)摰氖腔?a target="_blank">MCU的嵌入式系統(tǒng),所以讓我們保持我們的例子的相關(guān)性。如果你對(duì)數(shù)據(jù)庫(kù)感興趣,你可能最好用通用的操作系統(tǒng)! 對(duì)于我們的例子,假設(shè)你正在實(shí)現(xiàn)基于套接字的通信驅(qū)動(dòng),而你的系統(tǒng)只有足夠的內(nèi)存來(lái)滿足有限數(shù)量的同時(shí)套接字連接。
在下圖中,我們有一個(gè)共享網(wǎng)絡(luò)資源,可以容納兩個(gè)同時(shí)進(jìn)行的套接字連接。然而,有三個(gè)任務(wù)需要訪問(wèn)。計(jì)數(shù)信號(hào)被用來(lái)限制同時(shí)進(jìn)行的套接字連接的數(shù)量。每當(dāng)任務(wù)使用完共享資源(即它的套接字關(guān)閉),它必須交出它的信號(hào),以便另一任務(wù)能夠獲得對(duì)網(wǎng)絡(luò)的訪問(wèn)。如果任務(wù)碰巧給了已經(jīng)達(dá)到最大計(jì)數(shù)的信號(hào)燈,這個(gè)計(jì)數(shù)將保持不變:
前面的圖演繹了一個(gè)共享資源只能同時(shí)為兩個(gè)任務(wù)服務(wù)的例子(盡管系統(tǒng)中有三個(gè)任務(wù)需要使用該資源)。如果任務(wù)要使用套接字,而這個(gè)套接字受到計(jì)數(shù)信號(hào)的保護(hù),它必須首先從池中獲取一個(gè)信號(hào)。如果沒(méi)有semaphore,那么該任務(wù)必須等待,直到有semaphore可用:
- 最初,semaphore被創(chuàng)建,最大(上限)為2,初始計(jì)數(shù)為0。
- 當(dāng)任務(wù)A和任務(wù)B試圖獲取semaphore時(shí),他們立即成功。這時(shí),他們可以各自打開(kāi)套接字,通過(guò)網(wǎng)絡(luò)進(jìn)行通信。
- TaskC稍后,所以它需要等待,直到semaphores的計(jì)數(shù)小于2,這時(shí)網(wǎng)絡(luò)套接字就可以自由使用了。
- 在TaskB完成了通過(guò)其套接字的通信后,它將返回semaphore。
- 現(xiàn)在有了semaphore,TaskC完成了它的取舍,并被允許訪問(wèn)網(wǎng)絡(luò)。
- 在TaskC獲得訪問(wèn)權(quán)后不久,TaskB有另一條消息要發(fā)送,所以它試圖獲取信號(hào)燈,但需要等待可用的信號(hào)燈,所以它被置于睡眠狀態(tài)。
- 當(dāng)TaskC在網(wǎng)絡(luò)上進(jìn)行通信時(shí),TaskA完成并返回它的semaphore。
- 任務(wù)B被喚醒并完成了它的任務(wù),這使得它能夠開(kāi)始通過(guò)網(wǎng)絡(luò)進(jìn)行通信。
- 在TaskB得到它的信號(hào)后,TaskC完成了它的事務(wù)并歸還了它的信號(hào)。
等待信號(hào)是RTOS與其他大多數(shù)信號(hào)實(shí)現(xiàn)不同的地方--任務(wù)在等待信號(hào)時(shí)可以超時(shí)。如果任務(wù)未能及時(shí)獲得信號(hào),它就不能訪問(wèn)共享資源。相反,它必須采取另一種行動(dòng)。這個(gè)替代行動(dòng)可以是任何數(shù)量的行動(dòng),從嚴(yán)重到觸發(fā)緊急關(guān)機(jī)程序的故障,到僅僅在日志文件中提及或推送到調(diào)試串口供以后分析的良性事件。作為一個(gè)程序員,應(yīng)該由你來(lái)決定什么是適當(dāng)?shù)男袆?dòng)方案,這有時(shí)會(huì)促使你與其他學(xué)科進(jìn)行一些困難的討論。
二進(jìn)制信號(hào)
二進(jìn)制信號(hào)燈實(shí)際上就是最大計(jì)數(shù)為1的計(jì)數(shù)信號(hào)燈,它們最常用于同步。當(dāng)任務(wù)需要在事件上進(jìn)行同步時(shí),它將嘗試使用信號(hào)鏈,阻塞直到信號(hào)鏈變得可用或直到指定的超時(shí)時(shí)間結(jié)束。系統(tǒng)的另異步部分(無(wú)論是任務(wù)還是ISR)將給出信號(hào)燈。二進(jìn)制semaphores可以被多次給出,那段代碼沒(méi)有必要返回它們。在下面的例子中,任務(wù)A只給出信號(hào),而任務(wù)B只接受信號(hào):
任務(wù)B被設(shè)置為在繼續(xù)履行其職責(zé)之前等待信號(hào)(信號(hào)):
-
最初,TaskB試圖接受信號(hào),但它并不存在,所以TaskB進(jìn)入了睡眠狀態(tài)。
-
過(guò)了一段時(shí)間,任務(wù)A發(fā)出了信號(hào)。
-
任務(wù)B被喚醒(由調(diào)度器喚醒;這發(fā)生在后臺(tái)),現(xiàn)在有了信號(hào)燈。它將進(jìn)行它所需要的工作,直到完成。然而,請(qǐng)注意,任務(wù)B不需要?dú)w還二進(jìn)制信號(hào)。相反,它只是再次等待它。
-
任務(wù)B再次被阻塞,因?yàn)樾盘?hào)燈不可用(就像第一次一樣),所以它進(jìn)入睡眠狀態(tài),直到有信號(hào)燈可用。
周而復(fù)始。
如果任務(wù)B "交還 "二進(jìn)制信號(hào),它將立即再次運(yùn)行,而不會(huì)收到來(lái)自任務(wù)A的指令。其結(jié)果只是全速運(yùn)行的循環(huán),而不是在任務(wù)A發(fā)出信號(hào)的條件下被提示。
實(shí)時(shí)操作系統(tǒng)的互斥
術(shù)語(yǔ)mutex是相互排斥的簡(jiǎn)寫(xiě)。在共享資源和任務(wù)的上下文中,互斥意味著,如果一個(gè)任務(wù)正在使用共享資源,那么該任務(wù)是唯一被允許使用該資源的任務(wù)--所有其他任務(wù)都需要等待。
如果這一切聽(tīng)起來(lái)很像二進(jìn)制信號(hào)燈,那是因?yàn)樗褪?。然而,它還有一個(gè)額外的功能,我們很快就會(huì)介紹。首先,讓我們來(lái)看看使用二進(jìn)制信號(hào)燈來(lái)提供相互排斥的問(wèn)題。
優(yōu)先級(jí)倒置
讓我們來(lái)看看在試圖使用二進(jìn)制信號(hào)提供互斥功能時(shí)發(fā)生的常見(jiàn)問(wèn)題。
考慮三個(gè)任務(wù),A、B和C,其中A的優(yōu)先級(jí)最高,B的優(yōu)先級(jí)居中,而C的優(yōu)先級(jí)最低。任務(wù)A和C依靠信號(hào)燈來(lái)訪問(wèn)它們之間共享的資源。由于任務(wù)A是系統(tǒng)中優(yōu)先級(jí)最高的任務(wù),它應(yīng)該總是在其他任務(wù)之前運(yùn)行。然而,由于任務(wù)A和任務(wù)C都依賴于它們之間共享的資源(由二進(jìn)制信號(hào)燈守護(hù)),這里有意外的依賴關(guān)系:
讓我們一步一步地通過(guò)這個(gè)例子來(lái)看看這種情況是如何發(fā)生的:
- 任務(wù)C(系統(tǒng)中優(yōu)先級(jí)最低的任務(wù))獲得了二進(jìn)制信號(hào),開(kāi)始做一些工作。
- 在任務(wù)C完成工作之前,任務(wù)A(最高優(yōu)先級(jí)的任務(wù))中斷并試圖獲得相同的信號(hào),但由于任務(wù)C已經(jīng)獲得了信號(hào)而被迫等待。
- 任務(wù)B也搶占了任務(wù)C,因?yàn)槿蝿?wù)B的優(yōu)先級(jí)比任務(wù)C高。
- 任務(wù)C用共享資源完成了剩余的工作,這時(shí)它把信號(hào)燈還給了任務(wù)C。
- 任務(wù)A終于可以運(yùn)行了。
任務(wù)A最終能夠運(yùn)行,但要等到兩個(gè)低優(yōu)先級(jí)的任務(wù)都運(yùn)行完了才行。任務(wù)C用共享資源完成它的工作是不可避免的(除非在設(shè)計(jì)上做出改變,以防止它與任務(wù)A訪問(wèn)相同的共享資源)。然而,任務(wù)B也有機(jī)會(huì)運(yùn)行到完成,盡管任務(wù)A在旁邊等待,并且有更高的優(yōu)先級(jí)!這就是優(yōu)先級(jí)倒置--更高的優(yōu)先級(jí)是指任務(wù)A在完成工作時(shí),他的任務(wù)也在完成!這就是優(yōu)先級(jí)倒置--系統(tǒng)中優(yōu)先級(jí)較高的任務(wù)正在等待運(yùn)行,但它被迫等待,而另一個(gè)優(yōu)先級(jí)較低的任務(wù)正在運(yùn)行--在這種情況下,這兩個(gè)任務(wù)的優(yōu)先級(jí)實(shí)際上是倒置的。
互斥器使優(yōu)先級(jí)倒置最小化
早些時(shí)候,我們?cè)f(shuō)過(guò),在FreeRTOS中,突變體是二進(jìn)制信號(hào),有一個(gè)重要的附加功能。這個(gè)重要的特性就是優(yōu)先級(jí)繼承--互斥器有能力暫時(shí)改變一個(gè)任務(wù)的優(yōu)先級(jí),以避免在系統(tǒng)中造成重大延誤。當(dāng)調(diào)度員發(fā)現(xiàn)一個(gè)高優(yōu)先級(jí)的任務(wù)試圖獲取一個(gè)已經(jīng)被低優(yōu)先級(jí)的任務(wù)所持有的突變時(shí),就會(huì)出現(xiàn)這種情況。在這種特定情況下,調(diào)度器將暫時(shí)提高低級(jí)任務(wù)的優(yōu)先級(jí),直到它釋放突變。在這一點(diǎn)上,低級(jí)任務(wù)的優(yōu)先級(jí)將被設(shè)置回它在優(yōu)先級(jí)繼承之前的狀態(tài)。讓我們來(lái)看看上圖中使用互斥(而不是二進(jìn)制信號(hào))實(shí)現(xiàn)的完全相同的例子:
讓我們一步一步地通過(guò)這個(gè)例子來(lái)看看這種情況是如何發(fā)生的:
- 任務(wù)A仍然在等待任務(wù)C返回互斥。
- 任務(wù)C的優(yōu)先級(jí)被提高到與更高優(yōu)先級(jí)的任務(wù)A相同。任務(wù)C運(yùn)行到完成,因?yàn)樗钟衜utex,是一個(gè)高優(yōu)先級(jí)的任務(wù)。
- 任務(wù)C返回突變,它的優(yōu)先級(jí)被降到了它持有突變之前的水平,因?yàn)橥蛔兊⒄`了高優(yōu)先級(jí)任務(wù)。
- 任務(wù)A拿著mutex并完成了它的工作。
- 任務(wù)B被允許運(yùn)行。
根據(jù)任務(wù)C在共享資源上花費(fèi)的時(shí)間,以及任務(wù)A的時(shí)間敏感性,這可能是一個(gè)主要的問(wèn)題,也可能不是什么大問(wèn)題??梢赃M(jìn)行時(shí)間分析,以確保任務(wù)A仍然符合最后期限,但跟蹤所有可能的優(yōu)先級(jí)倒置和其他高優(yōu)先級(jí)異步事件的原因可能被證明是具有挑戰(zhàn)性的。至少,用戶應(yīng)該利用為獲取突變提供的內(nèi)置超時(shí),并在突變未能及時(shí)獲取的情況下執(zhí)行適當(dāng)?shù)奶娲袆?dòng)。關(guān)于如何實(shí)現(xiàn)這一目標(biāo)的更多細(xì)節(jié)可以在第9章 "任務(wù)間通信 "中找到。
Mutexes和semaphores是任務(wù)間信號(hào)傳遞的相當(dāng)標(biāo)準(zhǔn)的機(jī)制。它們?cè)诓煌腞TOS之間是非常標(biāo)準(zhǔn)的,并且提供了很好的靈活性。
評(píng)論
查看更多