在解决PCI-1710(研华公司的数据采集板卡)的BUG时,偶然碰到了一个问题,中断方式的AI客户例程运行第二次就会出现segment fault,并且在linux下无论再输入任何指令,都会segment fault,不管是ls,vi,甚至是reboot,全都无法工作了
现在我这就为大家把这个BUG揪出来
在examples/console/mad_int例程中,malloc了一个reading_ array,用于接收采集数据,在分配此段内存后,使用for循环的方式对其进行了初始化,但是初始化并没有很好的完成它的工作,因为循环边界错误的设置成了num_chan,代码片段如下:
- for (idx = 0; idx < num_chan; idx ++) {
- gain_array[idx] = gain_code;
- reading_array[idx] = 0; /* 只初始化了num_chan * sizeof(ushort)个字节 */
- voltage_array[idx] = 0;
- }
当初这个循环是希望初始化gain_array,因为gain_array只有num_chan个短整数。然而reading_array这个关键的接收缓冲区,其长度是convertion_number * sizeof(ushort)。
由于没有完整的初始化整个reading_array,因此这段内存并没有被完整的换入内存,这个问题也在之后的实验中得到了证实,其导致的结果是内存空间与交换空间的数据严重损坏。
当不对reading_array做任何初始化工作,并把它传递给一个会陷入内核的接口时,是非常危险的,在随后的实验中,我没有对reading_array做任何初始化,然后把它的句柄传递给DRV_FAIIntScanStart接口,第一次启动例程运行良好,收到正确的采集数据,但在本次安全退出例程后,严重的问题随之而来,shell中除cd命令外,输入任何指令都会是segment fault,从现象看,至少shell已经被严重损坏了,reboot命令无效,只能复位启动,然而即便是复位启动,也可能会在第二次、或第三次的时候无法进入系统,即在kernel初始化后会又一次重启计算机(这个现象已经复现很多次),看样子硬盘上的交换空间也被严重损坏了,因为系统在启动期间很可能是从交换空间获取许多必要信息的。
原因就是在于接收数据的reading_array并没有被换入内存,应该说它还没来得及被换入内存,它应该还在交换空间上睡觉呢,我们在ISR中向reading_array写入数据,而页交换守护进程swapd的优先级要远落后于ISR,它根本没机会将reading_array换入内存,而现在内存上的那段空间,很可能就是shell正在使用的空间,结果便是shell空间的数据被严重损坏,所有的指令都没用了。
更糟的是,ISR将一堆采集数据写入shell空间后不久,swapd又将这些数据回写到相应的swap空间(这时轮到它干活了,可它根本不知道它都干了些什么),这段被写的一塌糊涂的swap空间很可能就是之后重启计算机时,需要启动系统时用到的,所以很多次复位启动也会随之失败,最后只能冷启动或断电,我想一定是电源管理模块拯救了swap空间。
接下来要坐的就是要解决这个大BUG,我们需要在FAIIntStart(一切快速AI接口都需要)接口中的ioctl(陷入内核)之前加上一句mlock()调用,用它来把用户malloc的内存换入物理内存,并锁在那。
这样在ISR中就可以放心的对reading_array进行写操作了,那些数据会安全的写入真正的用户缓冲区,而不是什么其它鬼地方。
当然在FAITerminate接口中的ioctl(弹出内核)之后,再加上一句munlock()调用,用它来解除在物理内存中对用户缓冲区的锁定。
因此从这个实验得到的结论是,当你在用户态分配一段缓冲区,并准备将它的句柄交给一个第三方接口时,务必记得先对它进行初始化,这样内核就会帮你把这段缓冲区换入物理内存,这样做是非常必要的,因为那个第三方接口可能会陷入内核,然后不把你的缓冲区锁定在内存里,就对它进行疯狂的写入,而且还是在ISR中。