最近項目的幾個問題

  最近項目壓力比較大,爲了趕時間不少代碼都得圖簡便,然而碰到的問題仍是須要從新整理一下,即使當時不懂過後也得弄清楚。項目的主要任務是一個C6678的PCI板卡驅動,用於FFT計算,一個圖形界面顯示程序顯示處理先後結果。設備操做上,須要實時從C6678的內存中讀取兩個數據,一個是64KB的unsigned short型原始數據,一個是128KB處理後的float型數據。node

      項目裏使用了一個不錯的繪圖類庫:linux

  • QCustomplot:包含了許多基本的繪圖操做,能夠在其基礎上做二次開發。

      官網:http://www.qcustomplot.com/架構

       先說說PCI設備驅動的一些重要接口和流程,Linux設備驅動框架不想談了,隨便找本驅動的書都有。首先是設備的探測,在linux系統下使用命令lspci -vv能夠看到已插入的板卡的一些信息,包括設備名稱及地址空間等。板卡上有4個C6678 DSP,因此可以發現4路設備。框架

      程序中初始化設備驅動,首先調用pci_get_device接口發現PCI設備,由於有4路DSP,因此循環調用了4次,並且這裏將板卡當作字符設備處理。性能

 1 for (index=0;index<MAX_NUM;index++) {    
 2     C6678DSP[index] = pci_get_device(VENDOR_ID , DEVICE_ID , prev_node);
 3     if (!C6678DSP[index]) {
 4         printk("<C6678DSP>: No C6678DSP%d Card Found!\n",index);
 5         return -ENXIO;
 6     }
 7     else {
 8         prev_node = C6678DSP[index];
 9         printk("<C6678DSP>: C6678DSP%d Card Found!\n",index);
10     } 

  接着第二步,使能設備:測試

1 if (pci_enable_device(C6678DSP[index]))
2     return -EIO;

  第三步,獲取設備的起始和結束地址,獲取的這個值能夠和前面lspci -vv的結果對比看是否一致。spa

1 base0start=pci_resource_start(C6678DSP[index],0);
2 endsrc0=pci_resource_end(C6678DSP[index],0);
3 base0len=endsrc0-base0start;

      第四步,這一步是本項目裏面難以理解的一個問題,甚至我本身都有點沒弄清。將讀取到的起始地址又回寫到PCI設備的地址空間中,這點很是不解,一開始我並無在驅動的初始化里加這兩句,由於通常的PCI設備驅動都沒見這麼作的。但驅動程序加載後看到分配的地址是錯誤的,後來在別人的提示下加了這兩句,果真可以正常工做了,理由是可能BIOS作的不夠好,不能正確獲取到板卡配置空間的信息,而Linux則能夠。.net

1 pci_write_config_dword(C6678DSP[index],0x10,base0start);
2 pci_write_config_dword(C6678DSP[index],0x14,base1start);

      接下來調用ioremap進行內存映射,中間的一些代碼我都省略了,不通用。指針

1 baseAddrDoThingA = ioremap(base0start,base0len);   
2 baseAddrDoThingB = ioremap(base1start,base1len);

     後面就是字符設備驅動的一些流程了,多說無益。建立設備文件,註冊設備。code

 1             //register major device
 2             dev = MKDEV(c6678_major, 0);
 3             if (c6678_major) {
 4                    ret = register_chrdev_region(dev, 1, "C6678DSP");
 5                    printk("<C6678DSP>:C6678DSP major is defined!\n");
 6             } else {
 7                 ret = alloc_chrdev_region(&dev, 0, 1, "C6678DSP");
 8                 c6678_major = MAJOR(dev);
 9                 printk("<C6678DSP>:C6678DSP major is not defined!\n");
10             }
11             if (ret <0) {
12                 printk("<C6678DSP>: alloc C6678DSP device number error\n");
13                 return ret;
14             }
15             
16             c6678_setup_cdev(&c6678_cdev, 0);
17             printk("<C6678DSP>: C6678DSP major is %d. \n", c6678_major);
18 
19             /* make fs node */
20             ret = mknod("/dev/C6678", S_IFCHR | S_IRWXU | S_IRWXG | S_IRWXO, dev);
21             if (ret) {
22                 printk("<C6678>Mknod error.\n"):
23                 return ret;
24             }
25 
26             printk("Node make finished.\n");
27             return 0;
28        
29 fail_req_irq:    
30 fail_alloc_sendq:            
31     cdev_del(&c6678_cdev.cdev);
32     unregister_chrdev_region(dev, 1);
33     return -1;
34 }

  這是驅動的初始化,完成之後,就能夠編寫針對你本身設備的read/write/ioctl等設備使用接口了。

       驅動編寫完畢後,編譯成一個.ko文件,insmod ./C6678.ko後即可以在lsmod及/proc/device/下看到本身的設備了。接下來使用以下命令建立對應文件節點,由於分配的主設備號爲248,次設備號位0,字符型設備因此這麼寫。

1 mknod /dev/C6678 c 248 0

      運行了一下讀寫接口的測試程序,正確無誤,接下來就開始編寫界面程序了。

      然而編寫界面程序並不順利,起初想將直接從硬件讀取到的數據放在buffer中,而後直接繪圖,但出了個棘手的問題。

      來看一段代碼,須要從指定文件讀64KB的數據到buffer中,進行後續處理。乍一看並無什麼問題,編譯一下依然沒有什麼問題。然而一運行就崩了,並且每次都蹦,試想一下是爲何?

 1 void MainWindow::addGraphRaw(int index)
 2 {
 3     int n = 32768; // number of FFT points in graph
 4     QVector<double> x(n), y(n);
 5     unsigned short *buffer = new unsigned short [1024*32] ;
 6 
 7     ifstream fin(datafiles[index].toStdString().c_str());
 8     if (!fin.is_open()) {
 9         std::cout<<"Cannot find the datafiles."<<std::endl;
10         return ;
11     }
12 
13 
14     fin.read((char *)buffer, 1024*32*2);
15 
16     for (int i=0; i<n; i++) {
17       x[i] = i;
18       y[i] =( (double)buffer[i])/64;
19     }

  最開始幾回並無報出什麼錯誤,但後來每次都會彈出一個對話框,報系統SIGBUS信號,致使程序崩潰。SIGBUS信號是好像是內存訪問相關的,因而在網上搜索了一下SIGBUS相關的問題。來看看通常是怎麼說的(基本都是這種相似的說法):

SIGBUS(Bus error)意味着指針所對應的地址是有效地址,但總線不能正常使用該指針。一般是未對齊的數據訪問所致。

SIGSEGV(Segment fault)意味着指針所對應的地址是無效地址,沒有物理內存對應該地址。

  以爲有點道理,但又不很明白。這個界面程序我在x86的機器上跑不會有任何問題,而在龍芯的機器上一跑就SIGBUS。以後又查到的一種說法:

CPU處於性能方面的考慮,要求對數據進行訪問時都必須是地址對齊的。若是發現進行的不是地址對齊的訪問,就會發送SIGBUS信號給進程,使進程產生 core dump。RISC包括SPARC(一種微處理器架構)都是這種類型的芯片。x86系列CPU都支持不對齊訪問,也提供了開關禁用這個機制。x86架構不要求對齊訪問的時候,一定會有性能代價。例如,對int的訪問應該是4字節對齊的,即地址應該是4的倍數,對short則是2字節對齊的,地址應該是2的倍數。

      摘自:http://blog.csdn.net/klarclm/article/details/8509552

      我的以爲這種說法就明白多了,龍芯是MIPS架構,與SPARC存在一樣的問題,必須地址對齊,而x86則支持不對齊訪問,這也就解釋了爲何程序在x86機器上可以正常運行,一到龍芯機器上就SIGBUS。這點也是項目中最詭異的一個點。並且最後爲了不這個問題,不得已將直接讀取出來的數據分別保存在一個Raw文件一個Proc文件中,供繪圖時再次讀取(而不是以前直接用讀取的數據繪圖,一用就SIGBUS)。固然這也下降了程序的性能,雖然很少。

      最後的效果圖以下,只開了一路A/D轉換做爲示例,原始輸入波形是一個100MHz,0.5Vpp的正弦波,圖1就是根據直接讀到的原始數據繪製的波形,對應下面的圖就是通過傅里葉變換計算獲得的頻譜圖。

      好了就寫這麼多,睡了。

      此時此刻,竟身披北京國安,與陌生人道晚安。

相關文章
相關標籤/搜索