侯佩儒,曹炳堯,宋英雄
(上海大學 特種光纖與光接入網重點實驗室,上海 200444)
隨著現有的工程系統越發規模大型化、需求復雜化、模塊細分化[1],如:自動駕駛汽車的車載嵌入式系統平均具有1億行代碼,并分布在近100個嵌入式計算機中[2]。傳統的設計模式使用文檔,難以進行有效的模塊劃分與跨領域溝通,從而導致研制周期存在較高風險。因此,出現了基于模型的系統工程(MBSE,model-based systems engineering)。其采用標準化建模的方式來支持系統的完整設計,改變了復雜系統的開發模式,尤其是對智慧物聯網技術的高速發展催生出的復雜嵌入式系統開發具備重要主導意義。目前,MBSE已廣泛應用在航空航天[3-5]、國防軍工[6-7]、智慧城市[8]、軌道交通[9]等領域。
基于MBSE的復雜嵌入式系統設計包括需求分析、系統設計、測試驗證和需求確認等多個環節[10-11]。其中,虛擬驗證在測試驗證中尤為重要,但卻一定程度上缺乏研究和探索[12]。由于嵌入式系統對硬件的依賴性強、場景專業性高、系統復雜性大,對嵌入式軟件開發進行虛擬驗證需要在實物集成前提前模擬異構硬件系統,并考慮復雜嵌入式系統中各子系統之間的關聯。但是,目前仍缺乏高效通用、考慮整體的嵌入式模擬驗證工具或平臺。
虛擬化技術的快速發展與廣泛應用,解決了單個嵌入式開發板的模擬問題。在本文中,每個利用虛擬化方式模擬的異構子系統稱為虛擬設備。虛擬設備是指在MBSE模擬系統中,對應嵌入式系統中可獨立分離的物理單元,用于驗證開發的軟件模擬組件。例如,在汽車部分自動化嵌入式系統中,有Arduino 和樹莓派兩個主從開發板模塊,前者連接控制電機的驅動器,后者連接多種傳感器進行數據處理與計算。這兩個模塊使用串行端口進行通信[13-14],是互為可分離的物理單元,使用虛擬化技術模擬的這兩塊模塊單元即為虛擬設備。常用的虛擬化技術有操作系統層面虛擬化的Docker[15]、硬件開發板層面虛擬化的QEMU(Quick Emulator)[16-17]等。QEMU具有跨平臺、高速度、可移植等優點,因此,本文選取QEMU來創建虛擬設備。
嵌入式系統中,各組件間最普遍的通信方式是基于網絡和串行端口的通信。關于虛擬設備間的通信調試,已有一些相關研究。例如,文獻[18]利用將QEMU中虛擬網卡與宿主機的tap虛擬網卡綁定的方式進行虛擬設備網絡通信;文獻[19]利用源定位技術、基于頻率發包與跟蹤技術等方法開發了可模擬CAN總線的網絡仿真開發平臺,可進行物理或虛擬控制設備的總線通信測試等。然而,關于串行端口通信調試的研究相對較少,主要是采用物理和虛擬兩種方式。物理方式是使用計算機上的真實物理串行端口進行測試,需要使用物理連接線完成端口間的連通,并將真實串行端口配置綁定到虛擬設備內使用。這種方式測試簡單、可靠,但是僅支持小規模驗證,在大規模場景下時會面臨物理接線復雜、計算機的端口資源受限等問題。虛擬方式是采用模擬手段,在宿主機內產生模擬串行端口供虛擬設備測試使用。相較于物理方式,該方式的優點在于不依賴硬件、驗證成本低。在Windows系統中,已有成熟可用的虛擬串行端口工具VSPD(virtual serial port driver)。在Linux系統中,雖然沒有類似的工具,但可以借助pty(pseudo terminal)偽終端設備來實現虛擬設備間的通信。但是,不論是成熟工具VSPD還是系統自帶pty設備,都進行了嚴密的封裝,難以靈活接入統計或埋點接口,自定義調試難度較高,導致虛擬設備間的串行端口通信驗證較為困難。由上述論述可知,針對虛擬設備互聯問題,需要研究靈活可用的串行通信模擬技術,以便實現多組件間的通信調試。
本文針對在MBSE虛擬驗證環節,嵌入式虛擬設備間的串行端口通信問題展開研究,以實現在Linux系統環境下完成虛擬設備間串行通信的目標,并支持復雜嵌入式系統的全面數字化模擬,對MBSE工程化的最后一步實施與落地、和我國“十四五”規劃深入推進數字化發展都具有重大意義。
為了解決復雜嵌入式系統中子系統之間的串行端口通信模擬驗證問題,需要首先構建虛擬設備,并創建串行端口,再進行多個虛擬設備之間的通信。如圖1所示,通信時主要涉及到兩類主體對象:虛擬設備、串行端口鏈路,多個虛擬設備通過串行端口鏈路實現串行數據傳輸。

圖1 基于串行端口的虛擬設備間通信方式
虛擬設備的主要作用是為嵌入式軟件提供接近硬件的運行環境,如:具備模擬的嵌入式外圍設備、能夠利用二進制翻譯等技術直接執行異構指令集代碼等。由于虛擬設備采用軟件邏輯構建模擬,在驗證各單元的通信過程中,缺少物理串口介質的鏈接。而在其中執行串行通信時,必須依賴串行端口,才能保證模擬系統中的程序可以和物理設備無縫移植。那就必須要求虛擬設備中具有串行端口,才能進一步對串行端口設備進行開啟關閉、發送數據、接收數據等操作。
串行端口鏈路位于多個虛擬設備之外,由多個外部串行端口及之間的端口連接線構成。其主要作用是接收虛擬設備中內部虛擬串行端口發送的數據,并將數據傳輸到另一端,發送給另一個虛擬設備,從而為兩端虛擬設備的內部虛擬串行端口之間建立可通信的串行鏈路通道,實現虛擬設備間的串行端口通信。為了使內部虛擬串行端口使用該鏈路,還需要將內外串行端口進行綁定或映射。
為實現總體方案設計,根據虛擬設備間進行串行端口通信的方式,提出了如圖2所示的功能需求。

圖2 基于串行端口的虛擬設備間通信功能需求
根據上述功能需求分析,本文提出基于模擬串行端口的虛擬設備間通信架構,如圖3所示。自下而上分為物理硬件層、虛擬設備層、串行通信層。

圖3 整體方案架構
物理硬件層:為Linux系統的服務器或虛擬機,為上層實現提供必要的整體硬件環境;
虛擬設備層:為多個虛擬設備的模擬,包括內部虛擬串行端口的創建與綁定,以及其他硬件模擬;
串行通信層:為最上層,負責創建、配置、連接外部串行端口,連接后外部串行端口對之間可以構建出串行通信鏈路,通過內外部串行端口綁定和該串行通信鏈路,多個虛擬設備間即可實現串行端口通信。
總體方案設計與實現時,主要考慮以下4個功能模塊的設計:
1)外部串行端口創建模塊:
該模塊負責在虛擬設備外部創建具備串行端口相關操作功能的設備,以供虛擬設備使用。鑒于已有虛擬串行端口工具具有強封裝的特點,難以在其中接入調試、監控、埋點等功能,無法自定義配置,只能進行基本驗證。
本方案將自行編碼實現開放、可控的虛擬串行端口模擬。為了具備兼容性、可擴展性,以及支持測試代碼在無額外修改的情況下的可移植性,采用底層驅動開發的方式,分析Linux系統中的標準串行端口驅動,在此基礎上類比開發虛擬的串行端口驅動。該種方案下,用戶在應用層看到的虛擬串行端口與標準串行端口是一致的,操作也相同,區別僅在于執行底層操作時前者使用模擬方式,而后者使用物理方式。
2)外部串行端口配置:
為模擬創建的外部串行端口配置鏈路等相關參數。例如:配置該端口需要建立連接的另一個串行端口的索引信息,以便于后續鏈路構建。
3)外部串行端口連接:
對于多個配置了對端信息的虛擬串行端口,連接對端的作用主要在于構建接收、發送數據的通信鏈路。可以利用多種方式傳遞兩端發送的消息,例如:管道、共享內存、文件、消息隊列、網絡等。以文件交互方式為例,每個串行端口維護自己的屬性文件,并設置文件的寫入回調函數。在發送時將信息寫入對端串行端口對應的文件中,對端文件觸發寫入回調,通知對端端口讀取文件,從而實現完整的收發過程。
相較于文件方式,網絡傳遞信息可開發度更高,也更具有分布式擴展性,更適合大規模MBSE系統虛擬驗證環節的云服務化。因此,本方案采用網絡方式實現多個串行端口的連接與鏈路構建。
4)內部虛擬串行端口創建與綁定模塊:
該模塊借助成熟的虛擬化軟件QEMU來實現虛擬設備的整體硬件模擬。并將模擬時創建的內部虛擬串行端口與虛擬設備外、宿主機內的外部串行端口進行綁定,將后者作為前者的設備后端,即可在虛擬設備內使用外部串行端口實現具體功能。
根據上述設計可知,內外部串行端口綁定之后,在虛擬設備內對內部串行端口執行發送、接收等操作都是依靠外部串行端口及其通信鏈路來完成的。因此,本文接下來的研究重點是基于Linux系統底層驅動開發的虛擬外部串行端口模擬和其鏈路的構建與實現。
在進行模擬串行端口的底層驅動開發之前,需要先對Linux系統中標準串行端口的驅動實現進行分析,以標準驅動為參照類比設計與實現,確保系統對虛擬串行端口的兼容性和后續擴展性、可移植性。
串行端口是嵌入式系統中常用的硬件端口,有RS232、RS422、RS485等多種電氣標準[20]。在Linux系統中,串行端口是一種tty(Teletypes)設備,用于串行地輸入、輸出數據。其設備驅動位于操作系統的內核空間,提供了計算機硬件與應用程序的接口。在內核空間實現虛擬串行端口的模擬,能真正做到上層軟件無感知。
Linux內核中,使用cdev結構體描述字符設備,該結構體是所有字符設備的抽象[21]。串行端口的整體驅動框架如圖4所示。自右至左,驅動可分為3個層次,分別為字符設備層、tty核心層和串行端口硬件層。字符設備層將tty設備作為字符設備,提供系統調用的統一接口,即cdev結構體的操作函數集。操作函數會進一步調用tty核心層的實現函數。tty核心層對底層硬件的具體形式進行解耦與抽象,負責將上層操作經線路規程進行格式化協議轉換后,調用至下層。串行硬件核心層則真正地實現了對設備的管理和操作,直接與底層硬件交互,完成用戶請求。

圖4 串行端口驅動框架
自頂層向下,驅動主要包括uart_driver、tty_driver、uart_state、uart_port四大數據結構。uart_driver是全局的根數據結構,進行了高度的軟件邏輯抽象,負責保存和控制其他所有結構體信息。tty_driver是對tty層的具體實現;uart_state和uart_port是底層驅動的具體實現。整體而言,設備硬件和uart_state、uart_port一一對應,而一個uart_driver對應一個tty_driver和多個uart_state、uart_port,即多個同類型設備共用一種設備驅動。
完整的串行端口驅動主要包括驅動注冊、設備初始化、應用操作3個部分。本章以Linux系統中8250串行端口為例,依次從這3個部分進行分析。
在注冊前,8250串行端口的最上層驅動uart_driver通過dev_name成員指定該設備在Linux系統中對應的字符設備文件名稱前綴,major和minor成員分別指定設備的主設備號和最大支持設備數量等。
之后,按照標準串行端口的驅動注冊流程注冊。通過uart_register_driver()函數向內核注冊上述uart_driver定義,并進行一系列初始化操作。為每個uart_state申請空間,初始化state的tty_port成員,并為驅動分配與初始化tty_driver,設置其操作函數集。該函數集即為系統調用的接口,構建起Linux內核驅動和用戶空間交互的橋梁。最后進行tty_driver的注冊,將其掛載到全局tty驅動鏈表,并進行proc文件相關的注冊。此時,雖然在內核上注冊了驅動,但還沒有對接真正的硬件端口。
當硬件設備接入系統時,系統會根據圖4左上角所示的平臺驅動,調用設備獨有的dw8250_probe()硬件初始化函數。該部分與硬件端口緊密相關,但也遵守標準串行端口的設備初始化流程。
8250串行端口在uart_port之上,封裝了具體類型uart_8250_port。在初始化時,首先根據調用的硬件平臺設備,對其中uart_port結構體成員進行相關初始化。設置中斷處理函數、線路規程設置函數、設備類型、寄存器地址等,并分配私有數據等。其次,按照標準串行端口設備初始化流程進一步初始化,主要通過uart_add_one_port()函數實現。該函數根據串行端口編號將對應uart_state和該uart_port進行雙向綁定,并將設備對應的uart_port添加到uart_driver。由此,設備利用硬件初始化函數,通過串行端口硬件層,與tty核心層和字符設備層建立了聯系,用戶空間可以對真實的硬件設備進行操作。
在進行串行端口讀寫操作之前,需要先打開串行端口。打開流程如圖4所示,自右至左為系統調用后,內核由字符設備層的tty_open()函數開始逐層調用。直到串行端口硬件層,根據uart_port的操作集調用8250串行端口獨有的serial8250_startup()函數,進行串行端口的硬件設置,如波特率、請求發送信號和硬件寄存器設置等,并初始化與使能串行端口的中斷。
串行端口打開之后,以讀取數據為例分析,主要涉及到兩個線程,分別稱為前臺線程和后臺線程。應用程序在用戶空間使用read()函數讀取串行端口數據,經系統調用進入內核空間時由前臺線程執行并進行等待。后臺線程負責在中斷有數據時進行讀取,把讀取的數據填充至tty_buffer中,再調用flush_to_ldisc()函數,將數據存放進線路規程的數據接收緩存中,喚醒前臺線程,使前臺線程讀取緩存中的數據,并將數據從內核空間拷貝進用戶空間中,即可完成接收。向串行端口寫入數據也是類似的,但數據流相反,在此不做贅述。
根據對標準串行端口的驅動分析,能夠知道Linux系統為各種不同類型的串行端口提供了可復用的uart框架。例如,通過封裝的根數據結構uart_driver,不同的串行端口可以定義自己的驅動信息。如圖5所示,自定義串行端口驅動在復用已有框架的基礎上,需要修改和定義3個主要部分。第一部分是每個串行端口驅動注冊時都需要定義的,包含設備驅動的名稱、設備號等信息;第二和第三部分涉及底層硬件的特定實現,不同的串行端口需提供不同的操作函數以與硬件進行交互。這三部分恰好對應于驅動注冊、設備初始化和功能操作。

圖5 自定義串行端口驅動設計框架
基于標準串行端口的驅動框架,本章參照8250串行端口的定義和注冊方式,對總體方案中的外部虛擬串行端口創建、配置、連接模塊進行設計,并最終實現了一個完整可用的外部虛擬串行端口驅動。
創建虛擬串行端口需要進行驅動注冊和設備初始化。驅動注冊確保虛擬設備能在Linux系統中被識別,并提供內核空間的接口供用戶空間使用;設備初始化類比設備接入的動作,在系統上產生虛擬設備,并進行一系列初始化操作。
1)驅動注冊:
參考8250串行端口,虛擬串行端口首先需要定義自身的驅動信息。設置驅動名為“virtual-uart”、字符設備文件名稱前綴為“vttyU”、最大支持串行端口個數等。并且,在未顯式地指定設備號的情況下,Linux系統會在驅動加載時為其動態分配設備號,以避免人為指定發生沖突。驅動信息定義完成后,通過uart_register_driver()函數將其注冊進內核,并對uart_driver等數據結構進行初始化。后續過程與標準串行端口驅動注冊流程相同,進行復用。
2)設備初始化:
在設備創建與初始化之前,需要先定義虛擬串行端口整體的端口數據結構。類比8250串行端口在uart_port結構體之上,進一步封裝了與硬件相關的具體類型。在驅動設計時,也為虛擬串行端口封裝了具體數據結構virtual_uart_port,包含了uart_port。由于虛擬串行端口不具有硬件設備,所以無法提供物理中斷和寄存器。為了真實模擬串行端口接收與發送數據的流程,該數據結構內提供接收和發送使能標志位,用于控制虛擬串行端口能否收發,并使用自旋鎖保護這兩個使能信號。此外,該數據結構還提供了一個工作隊列,用于模擬中斷處理函數,完成數據中斷發送操作。
在具備整體的端口數據結構后,就可以進行具體設備的定義與注冊。由于不存在真實設備,無法通過系統自動識別與掃描設備的方式獲取設備。因此,需要自行定義多個平臺設備,并為每個設備指定不重復的串行端口索引,以便后續串行端口連接時互相識別。定義好設備后,利用platform_device_register()函數將其注冊進內核中。但此時設備并不能進行初始化操作,因為內核中沒有該設備對應的平臺驅動。
為了保證設備進一步初始化并與整體的端口數據結構建立聯系,還需要定義對應的平臺驅動。其結構體為platform_driver,具有probe、remove接口。其中,probe接口完成設備對應uart_port的注冊,remove接口完成uart_port的注銷。并且,需要為平臺驅動設置與設備相同的名稱來實現設備和驅動間的匹配檢測。此外,還需要實現設備注冊函數,該函數為uart_port申請內存,設置端口類型、序號、具體的操作函數集等,以及初始化整體數據結構中的自旋鎖和工作隊列,并指定模擬串行端口發送中斷功能的函數作為該工作隊列的回調函數。其中,操作函數集和模擬發送中斷功能的回調函數是后續實現虛擬串行端口連接的重點內容,將在后續章節具體設計。最后調用uart_add_one_port()函數將初始化后的uart_port添加到第一步注冊的驅動中。
配置模塊主要負責向串行端口配置構建鏈路相關信息,例如:與該串行端口相連接的另一個串行端口的設備索引,以便串行端口可以自由組合與連接。這也為驅動模塊加載進內核后,留下可交互的方式。
在用戶空間,可以使用ioctl(input/output control)函數對設備進行一些特殊控制,實現用戶空間和內核驅動之間的溝通。該函數指定設備的文件描述符、請求號和具體參數,其中請求號代表交互協議,設備驅動會根據請求號執行相應操作。用戶空間的ioctl請求經系統調用后,會調用字符設備層的tty_ioctl()函數,函數內部將進一步根據請求號調用相應控制函數。因此,需要為配置事件預設未被占用、唯一的請求號。
進入內核空間后,在設備初始化時,可以通過uart_port指定的操作函數集實現對應ioctl()函數。該函數根據預設的請求號執行期望的配置操作,例如:配置構建鏈路所需對端信息。首先,獲取攜帶的請求參數,即得到對端串行端口的索引,并將其保存在本設備端口信息中。之后在串行端口連接時,就可以知道連接的對象。如果需要更換連接對象,只需要重新配置即可,使驅動更加靈活。
真實的物理串行端口對是通過外部物理連接線來連通的,虛擬串行端口則只能使用軟件的方式進行實現。經過總體方案中對文件、共享內存、管道等實現手段的討論,為了具有更高可開發度和擴展性,決定采用網絡的連接方式。具體實現方式是在設備驅動中設置uart_port的操作函數集,使用網絡收發方式完成虛擬串行端口的發送和接收。
使用網絡來模擬虛擬串行端口間的通道連接有兩種方案,如圖6所示。

圖6 網絡模擬串行端口間通信的方案示意圖
第一種方案需要在每個串行端口的整體數據結構中維護一個網絡Socket套接字成員,并且兩個要連接的串行端口要求一個具備服務端,另一個具備客戶端,根據配置信息來設置本設備的Socket句柄是服務端或是客戶端。在串行端口設備初始化時,通過對端索引信息獲取對端地址,連接對端Socket。連接后,將對端Socket句柄一并放入整體數據結構中維護,以便于收發數據時直接使用。通過這種方式,可以模擬出串行端口之間的收發傳輸通道。
第二種方案需要在每個串行端口的整體數據結構中維護兩個網絡Socket套接字成員,其中一個為服務端,用于模擬串行端口的接收數據引腳,另一個為客戶端,用于模擬串行端口的發送數據引腳。在這種方式下,兩個要連接的串行端口只需要將本端的客戶端連接到對端的服務端即可完成連接,并由客戶端發送數據,服務端接收數據。通過這種方式,可以模擬出真實的串行端口物理傳輸方式,即兩個串行端口對之間建立的兩條網絡通道對應了真實串行端口的發送與接收數據兩條物理線。
相較于第一種方案,第二種方案更為細化,每個串行端口都是相同且獨立的,串行端口之間耦合性更低,無需像第一種方案根據端口索引值設置本端與對端Socket套接字是客戶端還是服務端,再進行成對綁定。因此,本文選擇第二種方案來實現連接模塊。
在設備初始化時,根據設備索引值配置監聽端口號并開啟服務端,等待客戶端連接請求。同時,開啟一個線程循環判斷服務端是否接收到消息。若有消息,則模擬接收中斷,將收取到的數據發送到tty_buffer的接收緩存中,并調用tty_flip_buffer_push()函數,將接收緩存中的數據通過線路規程進行接收。在發送數據時,使用設備初始化時配置給工作隊列的回調函數來模擬發送中斷。在回調函數中,客戶端根據對端索引值獲取端口號,連接對端串行端口的服務端,并利用網絡連接發送數據。即可在沒有實際硬件的情況下,使用虛擬串行端口完成數據收發的功能。
本文使用12核CPU、32GB內存、200GB硬盤的虛擬機作為宿主機進行實驗,該虛擬機位于搭載Intel(R)Xeon(R)Silver 4214R CPU @ 2.40GHz處理器、型號為UniServer R4900 G3的服務器上,并使用QEMU模擬器來模擬虛擬設備,完成整體方案驗證,具體實驗系統環境如表1所示。

表1 實驗系統環境參數
為了驗證方案設計與實現的有效性和正確性,分別對軟件模擬的串行端口進行功能和性能驗證、對基于模擬串行端口的虛擬設備互聯通信進行可行性驗證。
在本方案中,虛擬串行端口驅動被分為設備注冊和串行端口整體驅動兩部分。前者負責定義硬件設備并將其注冊進內核中,以模擬硬件接入;后者負責實現串行端口相關結構的初始化、注冊、端口連接等功能,是串行端口的整體設備驅動。這兩個驅動模塊均具備頭文件、模塊加載與卸載函數、模塊許可聲明等規范結構。同時,為驅動模塊編寫編譯文件,內容包括讀取內核源碼中的編譯文件和指明模塊源碼中各文件的依賴關系等。
編譯執行后,會生成對應.ko模塊文件,可以使用insmod命令加載模塊。以兩個虛擬串行端口為例,驅動模塊被加載到系統內核后,將執行初始化程序,開辟內存、新建線程等。此時,如表2所示,在系統的/dev目錄下,將會出現兩個串行端口字符設備,分別為vttyU0和vttyU1,設備前綴由驅動定義的dev_name指定。

表2 驅動加載前后系統/dev目錄變化
用戶態通過執行ioctl()函數來指定每個串行端口的對端索引信息。內核態驅動完成相應的設置后,本設備的Socket客戶端就會連接至對端設備的Socket服務端,實現串行端口之間的鏈路連通。在本實驗中,即連接了vttyU0和vttyU1。對于用戶態的應用程序而言,這兩個字符設備與其他普通串行端口設備并無區別。此時,兩個串行端口的連接情況如圖7所示。編寫串行端口讀寫程序,對其進行功能和性能測試。

圖7 串行端口測試連接圖
1)功能測試。將vttyU1作為發送端,vttyU0作為接收端,進行數據持續收發操作,并將數據內容及傳輸次數打印到終端。交換發送端與接收端后,結果仍然相同,結果如下所示:
(1)vttyU1發送數據
action@action-virtual-machine:~/uart-test sudo ./virtual_uart_test --dev=/dev/vttyU1 --type=write
Send Data: SEND TEST! Num = 1!
Send Data: SEND TEST! Num = 2!
Send Data: SEND TEST! Num = 3!
Send Data: SEND TEST! Num = 4!
Send Data: SEND TEST! Num = 5!
Send Data: SEND TEST! Num = 6!
Send Data: SEND TEST! Num = 7!
Send Data: SEND TEST! Num = 8!
Send Data: SEND TEST! Num = 9!
Send Data: SEND TEST! Num = 10!
(2)vttyU0接收數據
action@action-virtual-machine:~/uart-test sudo ./virtual_uart_test --dev=/dev/vttyU0 --type=read
Receive Data: SEND TEST! Num = 1!
Receive Data: SEND TEST! Num = 2!
Receive Data: SEND TEST! Num = 3!
Receive Data: SEND TEST! Num = 4!
Receive Data: SEND TEST! Num = 5!
Receive Data: SEND TEST! Num = 6!
Receive Data: SEND TEST! Num = 7!
Receive Data: SEND TEST! Num = 8!
Receive Data: SEND TEST! Num = 9!
Receive Data: SEND TEST! Num = 10!
實驗結果證明,串行端口模擬產生后,在用戶態的操作與物理串行端口的操作一致,接收到的數據和對端發送的數據內容相同,因此通信功能可用。
2)性能測試。對串行端口的最大傳輸速率進行測試,測試方式為:將vttyU1作為發送端,以最小的時間間隔,持續發送1 024字節的數據給vttyU0,使得兩個串行端口驅動一直處于最大速率的發送與接收狀態。在接收端vttyU0設置流量計數模塊和時間計算模塊,得到接收一定數據量時所花費的時間,以計算單位時間內傳輸的比特數,即數據傳輸速率。本實驗進行20組測試,每組測試發送和接收100 MB數據,結果如圖8所示。

圖8 串行端口傳輸速率測試結果
實驗結果表明,本方案設計的虛擬串行端口驅動的數據傳輸速率快且較為穩定,最大傳輸速率介于416.26 ~474.74 mbps之間,平均值為456.98 mbps,遠超過物理串行端口的傳輸速率。例如,RS232串行端口最大傳輸速率為20 kbps,RS422/RS485串行端口最大傳輸速率為10 mbps等。此外,串行端口也在不斷發展更高速的傳輸速率。該虛擬串行端口不僅能夠滿足基礎物理串行端口模擬的速率要求,也能夠應對速率更高的增強型串行端口模擬的場景,符合設計需求。
對虛擬設備間互聯通信進行驗證,首先需要創建兩個虛擬設備,使用QEMU在宿主機內模擬兩塊樹莓派3B開發板,包括CPU、內存、外圍設備等硬件資源模擬。其次,每個虛擬設備內都具有串行端口,將前述實驗中創建的兩個模擬串行端口分別綁定為虛擬設備內串行端口的設備后端,使得兩個虛擬設備可以借助外部宿主機內的虛擬串行端口對進行通信。模擬與綁定的具體命令如下所示:
指定模擬樹莓派3B 1024 MB內存
sudo qemu-system-aarch64 -M raspi3b -m 1024
-dtb img/bcm2710-rpi-3-b.dtb
指定內核
-kernel img/kernel8.img
指定加載鏡像與格式
-drive format=raw,file=img/2020-02-13-raspbian-buster.img
啟動附加命令
-append "rw earlycon=pl011,0x3f201000 console=ttyAMA0 loglevel=8 root=/dev/ mmcblk0p2 fsck.repair=yes net.ifnames=0 rootwait memtest=1 dwc_otg.fiq_fsm_enable=0 8250.nr_uarts=1"
指定標準輸入輸出
-serial stdio
為樹莓派啟用USB鍵盤鼠標等模擬
-usb -device usb-kbd -device usb-tablet
綁定外部串行端口/dev/vttyU0(另一個綁定/dev/vttyU1)
-serial /dev/vttyU0
綁定完成后,啟動虛擬設備,進入系統后可以看到在/dev目錄下都多出一個設備ttyS0。該設備是串行端口的設備前端,將消息在虛擬設備和設備后端之間進行轉發,真實地與虛擬設備外進行數據收發交互的仍為宿主機內創建的虛擬串行端口。由此,兩塊虛擬開發板借助外部虛擬串行端口對vttyU0和vttyU1建立了兩個內部ttyS0的通信鏈路,連接情況如圖9所示。

圖9 虛擬設備串行通信測試連接圖
對其進行收發測試,測試方式為:在一塊虛擬樹莓派開發板內部打開串行端口ttyS0,發送數據并打印發送次數,另一塊虛擬樹莓派開發板內部打開串行端口ttyS0,進行數據接收并輸出至終端。反過來通信也是相同的,測試結果如下所示:
(1)虛擬設備1發送數據
pi@raspberrypi:~/myTests/uart-test ./virtual-uart --dev=/dev/ttyS0 --type=write
Send Data: SEND TEST! Num = 1!
Send Data: SEND TEST! Num = 2!
Send Data: SEND TEST! Num = 3!
Send Data: SEND TEST! Num = 4!
Send Data: SEND TEST! Num = 5!
Send Data: SEND TEST! Num = 6!
Send Data: SEND TEST! Num = 7!
Send Data: SEND TEST! Num = 8!
Send Data: SEND TEST! Num = 9!
Send Data: SEND TEST! Num = 10!
(2)虛擬設備2接收數據
pi@raspberrypi:~/myTests/uart-test ./virtual-uart --dev=/dev/ttyS0 --type=read
Receive Data: SEND TEST! Num = 1!
Receive Data: SEND TEST! Num = 2!
Receive Data: SEND TEST! Num = 3!
Receive Data: SEND TEST! Num = 4!
Receive Data: SEND TEST! Num = 5!
Receive Data: SEND TEST! Num = 6!
Receive Data: SEND TEST! Num = 7!
Receive Data: SEND TEST! Num = 8!
Receive Data: SEND TEST! Num = 9!
Receive Data: SEND TEST! Num = 10!
測試結果表明,虛擬設備間可以利用本方案實現的虛擬串行端口,建立傳輸通道,進行雙向通信。該程序也可以直接移植到物理的嵌入式終端上,直接進行串行設備通信,可以確保模擬系統和程序具有較高的保真度和移植性。
本文針對在MBSE系統中的虛擬設備間進行串行互聯通信的需求,提出了一種針對Linux系統的軟件模擬串行端口方案,并基于模擬串行端口實現多個嵌入式虛擬設備之間的互聯通信。本文的串行端口模擬通過參照Linux系統的標準串行端口驅動,復用標準uart驅動框架,完成內核驅動開發,從內核態產生虛擬串行端口,并實現了參數配置、通道連接等功能。
通過對兩個已連接的虛擬串行端口進行通信測試,驗證了本文提出的模擬串行端口的可用性和具備平均456.98Mbps的最大傳輸速率,能夠滿足物理串行端口模擬的傳輸速率要求。
同時,通過為QEMU構建的虛擬設備綁定模擬串行端口,實現了虛擬設備之間的串行數據通信。基于虛擬設備開發的串行通信程序,可直接移植到物理硬件設備上,提高了嵌入式系統開發調試的效率,為復雜嵌入式系統的全面數字化模擬提供了支持。