你的位置:首页 > 操作系统

[操作系统]最近项目的几个问题


  最近项目压力比较大,为了赶时间很多代码都得图简便,然而碰到的问题还是需要重新整理一下,即便当时不懂事后也得弄清楚。项目的主要任务是一个C6678的PCI板卡驱动,用于FFT计算,一个图形界面显示程序显示处理前后结果。设备操作上,需要实时从C6678的内存中读取两个数据,一个是64KB的unsigned short型原始数据,一个是128KB处理后的float型数据。

      项目里使用了一个不错的绘图类库:

  • 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的结果对比看是否一致。

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

      第四步,这一步是本项目里面难以理解的一个问题,甚至我自己都有点没弄清。将读取到的起始地址又回写到PCI设备的地址空间中,这点非常不解,一开始我并没有在驱动的初始化里加这两句,因为一般的PCI设备驱动都没见这么做的。但驱动程序加载后看到分配的地址是错误的,后来在别人的提示下加了这两句,果然能够正常工作了,理由是可能BIOS做的不够好,不能正确获取到板卡配置空间的信息,而Linux则可以。

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);

     后面就是自负设备驱动的一些流程了,多说无益。创建设备文件,注册设备。

 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就是根据直接读到的原始数据绘制的波形,对应下面的图就是经过傅里叶变换计算得到的频谱图。

      好了就写这么多,睡了。

      此时此刻,竟身披北京国安,与陌生人道晚安。