趙婉芳,趙 剛
(1.北京電子科技職業學院 北京 100016;2.中國電話號簿公司 北京 100032)
隨著通信業務的迅猛發展,通信設備使用單處理器處理所有任務的工作方式已經很難滿足業務需求。為了提高系統業務處理能力,大部分的通信設備引入了雙處理器工作方式,即使用兩個相同類型或者不同類型的處理器協作完成系統中的不同任務,這種方式不僅提高了設備性能,還大大增強了系統的實時性、可靠性和適用性。該類設備的兩個處理器由于需要協作共同完成任務,因此處理器間必須進行數據的交換,處理器間的通信變得非常重要,它不僅會影響數據交換的可靠性,還會影響整個系統的協作處理能力和設備的整體性能。
目前,處理器間通信主要有主從方式和對等方式。在主從方式下,一個處理器作為主端(Master),連接一個或多個處理器作為從端(Slave),所有通信都由主端發起,從端不能主動發送數據,如并行總線、SPI等;而在對等方式中,多個處理器通過網絡連接在一起,每個處理器都能主動向外發送數據,當發生沖突時能主動回避并重發,如以太網等。一般來說,對等方式的效率較高,設計較靈活,但是處理器間的網絡連接復雜度較高,需要一個外置數據交換芯片來進行數據的交換處理。如何找到一種可靠、低成本、容易實現的雙處理器間通信方式,降低嵌入式系統的復雜度和開發成本,對雙處理器嵌入式系統的開發和應用具有很好的現實意義。
本文針對通信設備中雙處理器間通信的需求和特點,綜合考慮開發成本、周期、穩定性等因素,提出了一種基于串口的對等通信方式。該通信方式將TCP/IP與串口結合起來,通過串口傳輸以太網數據,利用TCP/IP來保證數據傳輸的正確性,實現了兩個處理器間的全雙工通信。
處理器間通信一般可以通過數據交換、并行總線或者串行總線等方式實現。由于現代處理器都有豐富的串口可以利用,因此串口通信方式相對于數據交換方式和并行總線方式而言不用增加額外的器件,實現成本低,硬件連接簡單,而且傳輸速率能達到10 KB/s甚至更高,可以滿足大部分產品設計的需求,是應用比較廣泛的一種通信方式。
在目前采用雙處理器結構的通信設備中,兩個處理器有明確的分工:數據處理器專門進行數據處理;控制處理器主要完成控制工作。位于兩個處理器之間的通信通道則主要用于傳遞控制信息。由于控制信息的特點是數據量小,對數據傳輸的及時性和準確性要求高,因此數據傳輸的穩定、可靠是處理器間通信最重要的條件。另外還需考慮到實現方式的通用性(可以適用于多種操作系統和處理器)和易實現。綜上所述,通信通道需要滿足以下條件:
·數據傳輸穩定、可靠;
·數據傳輸速率在10 KB/s以上;
·移植性好,可用于多種處理器互聯;
·易于編寫應用軟件。
如果在系統中采用串口通信方式來實現處理器間信息的交換,則能夠保證實現的低成本和易實現,但是數據傳輸的正確性無法保證。因為串口傳輸數據只是利用奇偶位校驗數據,這種校驗方式只能檢測出信息傳輸過程中的部分誤碼,即只能檢出1位誤碼,不能檢出2位及2位以上誤碼。另外,奇偶校驗方式不能糾錯,在發現錯誤后,只能要求重發,誤碼率較高,嚴重影響數據傳輸的正確性。因此,如何保證數據傳輸穩定、可靠,提高處理器協作處理能力,是實現處理器間通信需要解決的首要問題。
本文在串口通信方式基礎上引入了TCP/IP協議棧,用于在串口上傳輸以太網數據,實現雙處理器間的通信,并利用TCP/IP來保證數據傳輸的正確性。由于TCP/IP是一種面向連接的協議,TCP在傳輸前必須先通過 “三重握手”在主機間建立TCP連接,它所傳輸的數據流采用了順序號和應答措施,可以發現數據的丟失、段的失序和對傳輸錯誤的排除,因此完全能實現數據流的可靠傳輸。其次,TCP/IP符合開放系統互連(OSI)模型,采用分層結構(傳輸層、網絡層、鏈路層、應用層),易于移植到串口上使用,保證了實現方式的易移植性。此外,TCP/IP是一種十分成熟的網絡協議,可以被移植到各種操作系統中,在主流嵌入式操作系統中都有TCP/IP協議棧,在產品開發中易于實現。可見,該方式在解決了數據傳輸的高可靠性的同時,保證了易實現、低成本和易移植。
具體的實現原理為:等待發送的數據先通過網絡協議棧處理,即給數據加上TCP/IP頭部和以太網頭部,再放到串口上傳輸;對方接收到后,將數據包傳給網絡協議棧處理,去掉TCP/IP頭部和以太網頭部,最終得到所需的數據。該方式的實現需要軟硬件配合,硬件上需要將串口連接起來,保證處理器之間的物理聯通,而實現的重點和難點在于軟件驅動的設計和實現。
在兩個處理器中驅動程序的結構是相同的,在系統中的位置如圖1所示。

在圖1中可以看到,串口驅動在TCP/IP和以太網協議下方,與網卡驅動在同一層。該串口驅動的作用就是將串口設備模擬成網卡設備。對于上層協議棧,串口成為一個虛擬網卡設備。上層協議棧通過該驅動在串口上收發數據包。這種結構的好處是不破壞系統原有的結構,盡量使用系統已有的軟件模塊,在編寫應用程序時使用標準的socket接口編程,軟件移植性好,可在多種操作系統環境中運行。由于Linux系統支持x86、ARM、PowerPC等體系結構的處理器,內核高效、穩定,可根據系統需要進行剪裁,并且支持TCP/IP,在嵌入式系統中應用十分廣泛,因此本文的處理器采用Linux作為操作系統。
驅動程序是實現處理器間通信的關鍵,驅動程序部分的主要任務是將數據包通過串口發送出去,同時將串口接收到的數據包傳遞給上層協議棧處理,主要解決如何在上層協議棧和串口驅動之間進行數據交換以及在物理串口上傳輸以太網數據問題,因此驅動程序的設計分為上層協議棧接口和串口驅動兩部分。上層協議棧接口部分負責將協議棧與串口驅動連接起來。串口驅動部分則實現串口硬件模塊的控制,如發送數據、接收數據和中斷處理等功能。
從圖1可以看到,該驅動程序處于協議棧的底部,因此需要提供與上層協議棧的接口。由于在Linux 2.6內核源碼樹include/linux/netdevice.h頭文件中有net_device結構體的定義,net_device是網卡驅動向上層協議棧注冊時使用的一個專用結構體,該結構體中定義了很多網絡參數和供網絡協議接口層調用的設備方法。本文所述驅動屬于虛擬網卡驅動范疇,因此向上層協議棧注冊時也需要通過net_device結構體進行。在net_device結構體中需要定義的網絡參數主要有網絡設備名稱、I/O基地址、最大傳輸單元等,需要定義的接口函數有設備初始化函數、統計信息查詢函數等。當net_device結構被注冊到Linux內核中,就完成了上層協議棧與串口驅動的連接,上層協議棧就能通過net_device結構體來使用串口驅動。具體的內容將在下文實現部分中描述。
串口驅動的主要任務是控制串口硬件模塊發送數據和接收數據。其中串口中斷處理函數是串口驅動實現的核心,串口數據發送和接收過程主要由該函數來實現,中斷函數的具體流程如圖2所示。

在串口驅動中基于中斷函數的數據發送和接收過程如下。
發送數據過程:上層協議棧在組包過程中調用uart_header()來構造以太網包的頭部,然后將構造好的以太網包發送給uart_xmit()。在函數 uart_xmit()中,調用netif_stop_queue()暫停上層協議棧繼續向串口驅動發送數據,防止過多數據使串口驅動無法處理,然后調用uart_encode()給發送數據添加串口幀格式。處理后的數據放在發送緩存中等待發送。將前15 byte數據寫入串口TX_FIFO(發送FIFO),并使能發送中斷。接下來的發送工作由串口中斷處理程序完成。當TX_FIFO中的數據發送完時,串口產生中斷信號,中斷處理程序將未發送的數據填入FIFO中,如此循環,直到數據發送完成。完成后,調用netif_start_queue()打開數據發送隊列。
接收數據過程:串口的接收中斷在初始化時被使能,并設置RX_FIFO(接收FIFO)中斷門限為8個字符,即RX_FIFO中的數據超過8個時將觸發串口中斷。中斷處理程序從RX_FIFO中讀取數據,邊讀邊解碼,數據解碼后得到以太網包。在多個中斷后,當收到一個完整的以太網包后,調用netif_rx(),將整個數據包傳遞給上層協議棧處理。
在驅動設計中串口分幀也是一個關鍵點。由于串口設備是流設備,數據以 byte為單位傳輸,傳輸的數據沒有幀結構,而網絡數據是分幀傳輸的,因此無法使用串口設備來傳輸網絡數據。此外,在串口硬件模塊中也沒有分幀機制。為了實現使用串口傳輸網絡數據,可以通過在驅動中添加分幀機制的方法來實現數據分幀。考慮到處理器的開銷問題,采用了一種簡單的機制來實現分幀,即通過在發送方給數據包添加頭部和尾部標識的方法來實現,具體定義如下:在發送數據包的頭部添加多個“0x7e”,尾部添加一個“0x7e”,中間是數據包,如圖3所示。

由于使用“0x7e”作為標識,因此數據包中的“0x7e”必須用其他方式表示。將數據包中的 “0x7e”替換為“0x7d 0x5e”、“0x7d”替換為“0x7d 0x5d”。接收到數據后,先識別幀的邊界,然后還原數據包。數據包中的數據需進行如下處理:
0x7d 0x5e→0x7e
0x7d 0x5d→0x7d
利用以上串口分幀機制就能實現在串口上傳輸以太網數據。
該驅動在Linux系統中以模塊的形式實現,可以動態地加載和卸載。驅動程序主要實現兩類函數:一類是以太網協議棧的接口函數;另一類是串口驅動模塊函數。在使用串口之前,必須對串口設備進行初始化。在初始化過程中,首先清空FIFO和中斷寄存器;然后設置串口的參數,如波特率、FIFO中斷觸發點;最后注冊中斷處理函數,使能中斷并啟動發送隊列。
經過初始化,串口進入工作狀態,開始收發數據,因此首先要申明Linux驅動模塊的初始化函數和卸載函數。初始化函數module_init(uart_init_module)的功能是告知Linux系統當初始化串口驅動模塊時調用模塊初始化函數uart_init_module()。而卸載函數module_exit(uart_cleanup_module)的功能則是告知系統當卸載該驅動模塊時調用函數uart_cleanup_module()來完成驅動模塊卸載操作,解注冊網絡設備并釋放該驅動占用的資源。
驅動模塊初始化函數uart_init_module()的主要實現代碼如下:
#define UART_NAME “etu”
static int__init uart_init_module(void)
{
struct net_device*net_dev=NULL;
/*分配內存空間,并初始化結構net_dev*/
net_dev=alloc_netdev(sizeof(struct uart_priv),UART_NAME,uart_init);
/*給網絡設備注冊一個名字 */
dev_alloc_name(net_dev,UART_NAME);
/*在系統中注冊新的網絡設備 */
register_netdev(net_dev);
return 0;
}
uart_init_module()函數首先給一個 net_device結構分配空間,然后定義網絡設備名為“etu”,最后向系統注冊一個新的網絡設備。
結構體net_device描述了該虛擬網絡設備的所有信息,包括各種操作函數和參數,其定義在頭文件netdevice.h中,主要定義的網絡參數和接口函數如下。
(1)網絡參數
name:網絡設備名稱,使用ifconfig命令可以查看。
base_addr,irq:網絡設備的I/O基地址、中斷號。
hard_header_len:硬件頭的長度,以太網的值為14。
mtu:最大傳輸單元,以太網中值為1 500 byte。
dev_addr[MAX_ADDR_LEN]:硬件 (MAC)地址長度及設備硬件地址,以太網地址長度是48 bit。
flags:網絡接口狀態標志,它的值可以是IFF_UP、IFF_BROADCAST等。
(2)接口函數
int(*init)(struct net_device*dev):設備初始化函數,僅驅動初始化時調用一次。
int(*open)(struct net_device*dev):設備打開接口函數,當設備被激活時調用,在該函數中注冊設備所需的系統資源,如地址空間、IRQ、DMA等,同時激活硬件。
int(*stop)(struct net_device*dev):設備關閉接口函數,這個函數關閉硬件,釋放系統資源。
int(*hard_start_xmit)(struct sk_buff*skb,struct net_device*dev):初始化數據包傳輸的函數。
struct net_device_stats* (*get_stats)(struct net_device*dev):統計信息查詢接口函數,調用該函數,可查詢設備的各種統計信息,如接收字節數、發送字節數等。
int(*do_ioctl)(struct net_device*dev,struct ifreq*ifr,int cmd):該接口函數提供設置網絡設備參數的接口,如修改硬件地址(MAC)。
定義好的net_device結構體在函數uart_init()中被初始化。該初始化函數將驅動中定義的操作函數賦值給結構體net_device中的函數指針,并初始化結構體net_device中的各個成員參數。經過初始化后,net_device結構被注冊到Linux內核中,這樣就完成了上層協議棧與串口驅動的連接。上層協議棧就能通過net_device結構體來使用串口驅動。
在串口驅動模塊中則主要實現以下函數。
static int uart_open(struct net_device*dev):設備使能函數,該函數的工作是配置串口模塊參數,注冊串口中斷處理函數,使能串口中斷,啟動發送隊列。
static int eos_release(struct net_device*dev):設備關閉函數,該函數的工作是停止發送隊列,關閉串口模塊,禁止串口中斷,清除串口的FIFO。
static int uart_xmit(struct sk_buff*skb,struct net_device*dev):發送函數,該函數完成數據的發送工作,對數據進行編碼,然后放入串口的FIFO中,使能串口的發送中斷。
static int uart_header(struct sk_buff*skb,struct net_device*dev,unsigned short type,void*daddr,void*saddr,unsigned len):填寫以太網數據包的頭部函數,該函數填寫發送數據包的頭部,如源MAC、目的MAC等。
在實際使用中要注意串口的波特率可設置為115 200,如果要提高串口的傳輸速率,除了提高串口波特率外,后面幾個因素都會影響串口的傳輸速率,如串口FIFO深度、FIFO中斷觸發位置、中斷延遲、CPU性能等。另外,在調試過程中要進行串口中斷處理程序優化,并盡量減少執行時間。
處理器間的通信一直是多處理器嵌入式系統的研究重點,本文通過在串口上引入TCP/IP協議棧的方式實現了雙處理器間的通信,在保證了實現方式的低成本、易移植和易實現的前提下,大大提高了通信的可靠性和穩定性。本文的實現方式具有很好的移植性,不僅適用于通信設備,還適用于任何雙處理器的嵌入式系統,對嵌入式系統的開發和應用具有很好的現實意義。
1 Rubin A著.Lisoleg譯.Linux設備驅動程序.北京:中國電力出版社,2000
2 Rubin A.Linux device drivers.Sebastopol:O’Reilly Press,2001
3 DanielP,CesatiB M.Understanding the Linux Kernel.Sebastopol:O’Reilly Press,2000
4 趙炯.Linux內核完全注釋.北京:機械工業出版社,2004