原文:
1,應用背景分析
在很多的實際工程應用中,通訊方式通常是rs232、rs485、i2c和spi等等。這類介面都有乙個共同的特點:按照位元組流的方式來進行通訊,即每中斷一次,表明成功傳送或者接收乙個位元組。
而乙太網一次可以傳輸1k多個位元組。由於乙太網有專門的tcp/ip協議棧來處理,這裡我們不討論。
按位元組方式域小資料塊方式(can和usb)通訊的介面,不同之處在於:一次接收的資料長度不一樣。為了提高演算法的通用性,我們應該抽象這些硬體介面的不同,也就是說,我們應該遮蔽不同介面之間的差異。對於的資料處理程式來說,它可以認為這類的介面傳過來的是位元組流,也就是一串資料。這裡指的usb介面是指嵌入式裝置的usb做從口來實現和主機通訊的情況。
2 位元組流通訊協議的一般格式
位元組流通訊協議一般包含這樣幾個域:
前導碼 + 幀長度 + 幀號 + 資料域 + 校驗
實際的通訊協議需要定義各個域的長度和每乙個bit的確切的含義。有的通訊協議將幀長度放在幀號的後面。
3 位元組流資料處理演算法的實現
我們先看看什麼是迴圈fifo緩衝區。
fifo緩衝區是具有先進先出功能的緩衝區。可以使用如下結構體來定義:
typedef struct buffer_tbuffer;
這個結構體非常簡單,head記錄緩衝區的頭,tail記錄緩衝區的尾,data用來存放資料。在對head和tail修改時,需要對buffer_len取模,防止溢位。緩衝區的有效資料長度可以這樣計算:
(tail + buffer_len - 1 - head)%buffer_len。
在這個結構體的基礎上,按照fifo的方式實現緩衝區的初始化、寫入和讀出3個函式,也就實現了fifo緩衝區。迴圈緩衝是指當資料寫到最後乙個data中的元素後,緊接著再應該寫入到data的第0個元素,將線性的緩衝區變成乙個回環。當進行寫入和讀出時,資料在這個迴圈緩衝區中移動,遮蔽內部操作的細節。
實現演算法時,需要注意以下幾點:
1,緩衝區空和滿的判斷條件:當head和tail相等的時候,緩衝區空,而當緩衝區中已經寫入了buffer_len-1個資料時,緩衝區滿。為什麼不寫入buffer_len個資料呢?因為寫入buffer_len個資料後,head和tail相等,這就無法判斷緩衝區是空還是滿。
2,寫入和讀出的策略
當讀取或者寫入緩衝區時,需要檢查緩衝區中的資料或者空間是否足夠。
在讀取時,如果沒有足夠的資料,是讀取已有的資料還是不讀取任何資料,而在寫入時,如果空間不夠,是部分寫入還是不寫入任何資料,這取決於你的應用。一般情況下,在空間不夠時,可以不做任何操作。當出現上述情況,留給上層的程式去處理。在實際應用中,如果讀取和寫入的程式設計的合理,緩衝區的大小合適,一般是不會出現寫入失敗的情況的。
另外乙個程式設計的細節是,對於陣列的操作,一定要小心陣列越界的情況。千萬千萬要嚴格檢查,否則,在除錯的時候,他可能讓你鬱悶很長時間。
你可能早就想問,為什麼要使用迴圈緩衝區呢?不著急,咱們來慢慢分析。
了解了位元組流裝置的底層細節,也了解了位元組流裝置的一般協議格式,還知道了迴圈fifo緩衝區的原理和實現方法,我們不難看出,緩衝區具有如下的優點:
1,對於上層函式,抽象了對硬體介面的操作細節,提高了可移植性。簡單一點說,就是將上層函式和底層驅動(實際完成介面操作的函式)之間通過迴圈fifo緩衝連線起來。當底層的介面由rs232換成can或者usb等等介面時,對通訊協議處理的上層的演算法不用改動,只需要改動底層的驅動,將接收的資料正確的放入緩衝區或者將緩衝區中要傳送的資料傳送出去就可以了。
2,使用迴圈緩衝區,可以非常方便的實現:前導碼的搜尋,錯誤包的處理
前導碼的搜尋:前導碼通常位於乙個有效資料報的前端,因此,對迴圈緩衝區實現乙個scan函式,完成對接收緩衝區中前幾個資料的瀏覽功能,即只是檢視(複製出來),不從緩衝區中讀出。假設我們的前導碼是:0xa5,0x5a,0xa5,0x5a,處理前導碼的搜尋可以這樣:每次從緩衝區中scan出4個位元組,與前導碼比較,如果相同,則搜尋成功,如果不同,則從快取區中讀出乙個位元組丟棄之,再scan緩衝區,直到搜尋成功。
我們來看看協議的一般處理流程:
資料處理的起始條件是:緩衝區中的有效資料長度大於或者等於最小包長。
2,幀長度的檢查:scan出長度域,通過協議中可能出現的最大和最小包長檢查,如果正常,則轉到3,否則丟棄乙個位元組,轉到1。
3,幀號的檢查:scan出幀號,檢查幀號是否為有效的幀號,有效,則轉到4,否則,丟棄乙個位元組,轉到1。
4,校驗和的檢查:scan出長度後的資料域和校驗域,檢查校驗和是否正確,錯誤則丟棄乙個位元組,轉到1。如果正確,則讀取這一完整的幀,取出幀號和資料域。轉到5。
5,根據幀號,執行相應的操作。
利用迴圈fifo緩衝區的特性,提供對資料的快取和對位元組流資料靈活多樣的訪問方式,來對協議的各個域進行嚴格檢查,實現對部分域錯誤的包和不完整的包的完美過濾,以及對混亂資料中正確包準確無誤的抽取。所有的這些特性,正是位元組流裝置所需。
4 演算法的改進
4.1 buffer資料結構的改進
這個資料結構是我最早設計buffer資料結構設計的模型,它滿足了我當時工程的需要。但是,有其自身的缺陷,也許你已經想到了。
由於我先前的裝置只有乙個介面使用這個buffer,執行的非常好。當我在做新的專案時,有多個介面需要這樣的buffer,問題就出現了:我的buffer的長度是乙個固定值(buffer_len),如果多個介面需要的緩衝區不一樣,這個buffer就不能很好的滿足,也就是說,我的buffer是乙個固定的值,不能靈活的改變來適應不同介面對buffer長度的不同需要。因此,做如下改進:
typedef struct buffer_tbuffer;
對buffer初始化時,傳送乙個由外部宣告的全域性陣列指標和陣列長度,來實現對data和len的初始化。這樣,緩衝區的長度就可以自由的變化。在多工環境下,使用psem來實現互斥。
需要注意的是,data是個char型別的指標,無論什麼資料,都按照char型別操作。而對於多位元組的資料操作,要特別注意位元組序的問題。通訊協議應該準確的指明多位元組域的位元組序排列問題,避免位元組序問題對裝置間的通訊造成混亂。
4.2 面向介面的buffer
我們知道,通訊介面一般都有接收和傳送功能,那麼,針對乙個介面,我們需要兩個緩衝區,乙個用於接收,另乙個用於傳送。因此,可以定義如下結構:
typedef struct com_buffer_tcom_buffer;
在此結構體的基礎上,實現對裝置的讀、寫和瀏覽等功能即可。
4.3 使用堆提高記憶體的使用率
如果我們的系統中有多個位元組流裝置,每乙個位元組流裝置將使用兩個全域性陣列來作緩衝區。實際中,我們的這些介面多半不會一直工作,但它卻永遠的占用了記憶體。如果在裝置需要工作的時候,動態的獲取記憶體,使用完後,釋放記憶體,這將減少裝置對記憶體的占用時間,提高記憶體的使用率。
使用堆需要注意的是,如果直接使用庫中所實現的malloc函式,極有可能出現記憶體碎片。因此,應該使用專門的記憶體管理的函式。
4.4 關於前導碼的丟棄
我們先前假設前導碼是:0xa5,0x5a,0xa5,0x5a,實際上,當前導碼搜尋成功後,我們在丟棄的時候,可以一次丟掉2個位元組,因為前導碼前兩個位元組和後兩個位元組是一樣的。類似的,如果前導碼為「abca」,一次可以丟掉3個位元組,如果前導碼為「abcd」,一次可以丟掉4個位元組。顯然,這個可以加快搜尋的速度。而對於前導碼的搜尋,也可以採用相同的技巧。
4.5 其他有用的buffer操作函式
1,buffer的清空函式:用來將緩衝區清空,準備接收新的資料。最好將所有的資料清零。
2,buffer的資料丟棄函式:替換用從緩衝區讀來丟棄資料,提高程式的執行速度。
5 迴圈fifo緩衝區的缺點
老子說過:「禍兮福之所倚,福兮禍之所伏」。中國的先哲太偉大了。
世界上的任何事物,都具有兩面性。優點,正是其缺點的所在。
迴圈fifo緩衝區將位元組流按照先後順序排列成乙個有序資料塊來儲存,供上層函式來讀取,抽象了介面。但是,先進先出的特性也表明,先到的命令先執行,後到的命令後執行。對於緊急的命令,也只能等到前面的命令執行完了才能執行。如果在緊急命令之前有很多耗時的命令,那麼,緊急命令的實時性將很難得到保證。
在實際工程中,這種需求可能很少。但我們還是要考慮這些情況,防止意外發生。
其實,提高命令響應的實時性是有辦法解決的,參考的原型就是串列埠的軟體流控方式。
實現原理是這樣:設定乙個緊急命令位元組(urgent_char = 0xff,fill_char=0x00),當串列埠收到緊急命令位元組時,清空buffer,等待緊急命令的到來。為了實現資料的透明傳輸,顯然在實現緩衝區傳送時,需要做位元組填充,也就是當使用者的資料中存在緊急命令位元組時,在其後填充乙個fill_char。
到此為止,問題視乎完美解決了。
6 解決了舊的問題,新的問題又將出現
無論是在帶有os的系統中還是在沒有os的系統中,中斷中操作的函式必須可重入的,在操作的全域性變數時,也要特別小心。buffer的清空函式正是集這兩個問題於一身。
當任務在讀取緩衝區時,緊急命令位元組到達,執行buffer的清空函式,將損壞緩衝區的資料。中斷返回到應用程式,讀取的緩衝區資料是錯誤的,有可能導致執行錯誤的命令。
如果在命令校驗為正確之前,清空緩衝區不會出現任何錯誤。只有當命令校驗正確後,從緩衝區讀取資料時,發生中斷,清空快取區會導致讀出的資料不正確,可能發生異常的情況。
在os系統中,很好解決。可以殺死資料處理任務,建立緊急任務來處理緊急命令。
在沒有os的系統中,我們可以設定乙個緊急標誌,當讀取資料返回時,檢查這個標誌,如果有緊急命令,則返回讀取失敗。然而,這並不能完美的解決僅僅命令的實時性問題。
有兩種糟糕的情況:
1,當緊急標誌的判斷之後出現緊急命令,意味著緊急命令還將等待一條命令的執行完畢。
2,當緊急命令傳送之前,程式在處理乙個耗時較長的命令,意味著緊急命令還將等待這條條命令執行完畢。
7 總結
實際使用中,如果對實時性有要求的話,強烈建議使用實時作業系統。沒有實時性要求的系統,根據自己的實際情況,權衡利弊,對以上策略選擇性的實現。
海量資料處理演算法(top K問題)
有乙個1g大小的乙個檔案,裡面每一行是乙個詞,詞的大小不超過16位元組,記憶體限制大小是1m。返回頻數最高的100個詞。1.分治 順序讀檔案中,對於每個詞c,取hash c 2000,然後按照該值存到2000個小檔案中。這樣每個檔案大概是500k左右。如果其中的有的檔案超過了1m大小,還可以按照類似...
python資料處理與分析
使用stack將列轉換為行,使用unstack將行轉換為列 data dataframe np.arange 6 reshape 2,3 index pd.index ohio colorado name state columns pd.index one two three n ame numb...
串列埠通訊的CRC演算法與實現
crc的全稱為cyclic redundancy check,中文名稱為迴圈冗餘校驗。它是一類重要的線性分組碼,編碼和解碼方法簡單,檢錯和糾錯能力強,在通訊領域廣泛地用於實現差錯控制。實際上,除資料通訊外,crc在其它很多領域也是大有用武之地的。例如我們讀軟盤上的檔案,以及解壓乙個zip檔案時,偶爾...