記一次linux下串列埠資料丟包解決過程
專案中兩個晶片之間用串列埠進行通訊,由於傳輸格式中有校驗位,在資料量很大的時候總是校驗失敗。於是花了很長的時間最終解決了這個問題。
首先串列埠丟資料有兩種情況(明顯排除傳送端傳送的資料不對),第一種是通道也就是串列埠線或者連線口不行,無法承受很高的波特率(我使用的波特率是921600),第二種就是接收端由於某種原因丟資料。通過觀察我排除了第一種情況,因為如果是通道承受不了太高的波特率的話那平時的小段小段的資料也可能會丟包,而明顯我的情況是隻有在一次傳輸大量資料(大概512位元組以上)時才會丟資料。所以問題出在接收端的處理流程。
於是藉此機會也瞭解了一般linux中串列埠接收資料的處理流程。我們都知道應用層通過select和read來及時的讀取串列埠的資料,而讀取的資料其實是記憶體裡的FIFO緩衝區的資料。而串列埠模組的資料從RXD最終到記憶體裡的FIFO會經過幾個流程,大概如下圖所示(這裡我們僅分析串列埠接收端即RXD的處理流程):
圖1 串列埠接收端處理流程
如圖所示,串並轉換模組將來自於RXD傳輸線上的資料轉換成對應的一段資料,比如8位資料位一位停止位無校驗位的情況下,就是轉換成9bit的資料。然後將其中傳輸的資料部分寫入到模組自帶的硬體FIFO中(有了硬體FIFO,cpu就不用每接收到一個位元組觸發一次中斷了)。當硬體FIFO達到設定的閾值時觸發串列埠中斷,串列埠中斷處理程式通過配置DMA來搬運存在硬FIFO裡的資料到記憶體裡開闢的軟FIFO裡。應用層通過read()等介面讀取記憶體中FIFO的資料。
接收端發生了丟失串列埠資料的情況,由上圖可知有兩種情況。第一可能是記憶體中的軟FIFO由於是定長的,應用層讀取頻次太低導致該FIFO溢位從而導致資料丟失。第二種可能就是該串列埠模組自身的硬體FIFO(也是定長的)溢位導致資料丟失。
對於第一種情況我在應用層呼叫了ioctl介面設定軟FIFO大小為1M,如下程式碼所示(雖然還是沒解決,不過寫在這裡提供參考):
#include <asm-generic/ioctl.h> #include <termios.h> #include <linux/serial.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/ioctl.h> int UartBuffSizeSet(char *dev_path,int size) { int ret; int fd = open(dev_path, O_RDWR | O_NOCTTY | O_NONBLOCK); if(fd < 0){ return -1; } struct serial_struct serial; ret = ioctl(fd, TIOCGSERIAL, &serial); if (ret != 0) { close(fd); return -2; }+ serial.xmit_fifo_size = 1024*1024; //1M ret = ioctl(fd, TIOCSSERIAL, &serial); if(ret != 0) { close(fd); return -3; } close(fd); return 0; }
我設定完之後發現丟資料問題還是很明顯,大概可以確認是硬體FIFO溢位,後來發現不丟資料總是在傳送端一次傳送64位元組以下的時候,一旦一次傳輸超過太多比如我這裡的512位元組就丟資料,而我的裝置串列埠硬FIFO的大小正好是64位元組,所以可以確認是硬體FIFO溢位了。
由上面的圖1可知串列埠硬FIFO達到閾值之後會觸發中斷,然後DMA會把硬FIFO裡的資料搬運到記憶體中。如果FIFO溢位了那麼就是DMA沒有把資料搬運到記憶體中,也就是由於串列埠觸發中斷時CPU在處理其他中斷暫時遮蔽了所有中斷,導致沒有響應串列埠的中斷,這時串列埠硬FIFO依然持續增長直到溢位。
由於我的執行環境有攝像頭、影象處理、影象輸出等單元。CPU可能在這些的中斷裡停留時間較長。有一個解決的方法就是優化當前系統中的各個中斷處理流程。顯然這工作量不小而且可能提升不了多少,我的解決方法非常簡單,但需要各位和我一樣是多處理器的平臺。我將串列埠的中斷繫結到了CPU1上就ok了,CPU0預設處理了很多中斷。
首先通過指令: cat /proc/interrupts 檢視系統中各個裝置的中斷號:
#cat /proc/interrupts
CPU0 CPU1
29: 991442 4917 GIC 29 arch_timer
30: 0 0 GIC 30 arch_timer
36: 232252 0 GIC 36 uart-pl011
38: 0 453209 GIC 38
41: 0 0 GIC 41 pl022
42: 0 0 GIC 42 pl022
43: 0 0 GIC 43 pl022
44: 0 0 GIC 44 pl022
45: 0 0 GIC 45 himci
51: 0 0 GIC 51 ehci_hcd:usb3
52: 1 0 GIC 52 ohci_hcd:usb4
54: 0 0 GIC 54 xhci-hcd:usb1
55: 1689 0 GIC 55 himci
56: 52 0 GIC 56 himci
57: 253785 0 GIC 57 10050000.ethernet
59: 294156 0 GIC 59
60: 0 0 GIC 60 mipi0_int
61: 0 0 GIC 61 mipi1_int
62: 588957 0 GIC 62 ISP
63: 0 0 GIC 63 ISP
64: 294465 0 GIC 64
66: 19617 0 GIC 66 tde_osr_isr
67: 513076 0 GIC 67
68: 0 0 GIC 68 AIO Interrupt
69: 489697 0 GIC 69
70: 588293 0 GIC 70
71: 588277 0 GIC 71
96: 0 143945 GIC 96 timer
IPI0: 0 0 CPU wakeup interrupts
IPI1: 0 0 Timer broadcast interrupts
IPI2: 413363 612 Rescheduling interrupts
IPI3: 0 0 Function call interrupts
IPI4: 1 2 Single function call interrupts
IPI5: 0 0 CPU stop interrupts
IPI6: 0 0 IRQ work interrupts
IPI7: 0 0 completion interrupts
IPI8: 0 0 CPU backtrace
Err: 0
可以看到我的串列埠裝置中斷號是36。
使用如下指令將中斷號36唯一繫結到CPU1上:
echo "2" >> /proc/irq/38/smp_affinity
echo 輸入的數字的各個bit為1代表使用對應的CPU,比如bit0為1代表使用CPU0,可同時繫結多個CPU。
至此我遇到的串列埠資料丟失問題就得到了解決,雖然最後解決只是一句指令的事情,但是前面的分析查詢原因花了不少功夫。而且如果我的執行平臺是單核CPU那還得費腦筋解決,對於單核的CPU我大概想了一下在我這種原因(硬FIFO溢位)導致的串列埠丟資料的問題的解決方法:
1.儘量優化中斷處理流程,拆分一次傳輸的size。
2.降低串列埠波特率。
3.找出執行時間較長的中斷處理程式,視情況來決定在它遮蔽中斷時不遮蔽串列埠的中斷。
4.晶片外接帶較大FIFO的串列埠模組,降低串列埠中斷觸發週期。(我自己猜想的,不知道有沒有這樣的模組)。