为什么我的长时间运行的 python 脚本在运行大约 3 天后会因“无效指针”而崩溃?

dan*_*mcb 5 python python-3.x raspberry-pi raspberry-pi3

我写了一个 python 3 脚本来测试到 FPGA 的 SPI 链接。它在 Raspberry Pi 3 上运行。测试的工作方式如下:将 FPGA 置于测试模式(一个按钮开关)后,发送第一个字节,该字节可以是任何值。然后无限期发送更多字节。每一个都增加发送的第一个值,截断为 8 位。因此,如果第一个值为 37,则 FPGA 需要以下序列:

37, 74, 111, 148, 185, 222, 4, 41 ...

一些额外的 IO 引脚用于在设备之间发送信号 - RUN(RPi 输出)开始测试(这是必要的,因为如果 FPGA 需要一个字节,它会在大约 15ms 内超时)并且 ERR(FPGA 输出)发出错误信号。因此可以在两端计算错误。

此外,RPi 脚本会在每百万字节中写入一行发送的字节数和错误数的摘要。

所有这些都很好。但是运行大约3天后,我在RPi上收到以下错误:

free(): 无效指针: 0x00405340

我在两个相同的测试设置上得到完全相同的错误,即使是相同的内存地址。最后一个报告说“发送了 4294M 字节,0 个错误”

我似乎已经证明了 SPI 链接,但我担心这个长时间运行的程序会无缘无故地崩溃。

这是我的测试代码的重要部分:

def _report(self, msg):
        now = datetime.datetime.now()
        os.system("echo \"{} : {}\" > spitest_last.log".format(now, msg))

    def spi_test(self):
        global end_loop
        input("Put the FPGA board into SPI test mode (SW1) and press any key")
        self._set_run(True)
        self.END_LOOP = False
        print("SPI test is running, CTRL-C to end.")
        # first byte is sent without LOAD, this is the seed
        self._send_byte(self._val)
        self._next_val()
        end_loop = False
        err_flag = False
        err_cnt = 0
        byte_count = 1
        while not end_loop:
            mb = byte_count % 1000000 
            if mb == 0:
                msg = "{}M bytes sent, {} errors".format(int(byte_count/1000000), err_cnt)
                print("\r" + msg, end="")
                self._report(msg)
                err_flag = True
            else:
                err_flag = False
            #print("sending: {}".format(self._val))
            self._set_load(True)
            if self._errors and err_flag:
                self._send_byte(self._val + 1)
            else:
                self._send_byte(self._val)
            if self.is_error():
                err_cnt += 1
                msg = "{}M bytes sent, {} errors".format(int(byte_count/1000000), err_cnt)
                print("\r{}".format(msg), end="")
                self._report(msg)
            self._set_load(False)
            # increase the value by the seed and truncate to 8 bits
            self._next_val()
            byte_count += 1

        # test is done
        input("\nSPI test ended ({} bytes sent, {} errors). Press ENTER to end.".format(byte_count, err_cnt))
        self._set_run(False)
Run Code Online (Sandbox Code Playgroud)

(澄清说明:有一个命令行选项可以每百万字节人为地创建一个错误。因此“err_flag”变量。)

我已经尝试在控制台模式下使用 python3,并且 byte_count 变量的大小似乎没有问题(根据我对 python 整数大小限制的了解,不应该存在)。

任何人都知道可能导致这种情况的原因?

Dmi*_* M. 4

此问题仅与 3.5 之前的 spidev 版本有关。下面的评论是假设我使用的是 spidev 的升级版本。

#################################################### ############################

我可以确认这个问题。它对于 RPi3B 和 RPi4B 都是持久的。在 RPi3 和 RPi4 上使用 python 3.7.3。我尝试过的spidev版本是3.3、3.4和最新的3.5。通过简单地循环这一行,我能够多次重现此错误。

spidevice2.xfer2([0x00, 0x00, 0x00, 0x00])
Run Code Online (Sandbox Code Playgroud)

根据 RPi 版本,最多需要 11 小时。在 1073014000 次调用(四舍五入到 1000)之后,脚本因“无效指针”而崩溃。发送的字节总数与danmcb的情况相同。看起来 2^32 字节代表了一个限制。

我尝试了不同的方法。例如,不时调用 close(),然后调用 open()。这没有帮助。

然后,我尝试在本地创建 spiDev 对象,以便为每批数据重新创建它。

def spiLoop():
    spidevice2 = spidev.SpiDev()
    spidevice2.open(0, 1)
    spidevice2.max_speed_hz = 15000000
    spidevice2.mode = 1 # Data is clocked in on falling edge
    
    for j in range(100000):
        spidevice2.xfer2([0x00, 0x00, 0x00, 0x00])
        
    spidevice2.close()
Run Code Online (Sandbox Code Playgroud)

大约之后它仍然崩溃。xfer2([0x00, 0x00, 0x00, 0x00]) 的 2^30 次调用相当于大约 2^32 字节。

编辑1

为了加快该过程,我使用下面的代码以 4096 字节的块进行发送。我在本地反复创建了 SpiDev 对象。花了 2 个小时才达到 2^32 字节数。

def spiLoop():
    spidevice2 = spidev.SpiDev()
    spidevice2.open(0, 1)
    spidevice2.max_speed_hz = 25000000
    spidevice2.mode = 1 # Data is clocked in on falling edge
    
    to_send = [0x00] * 2**12 # 4096 bytes
    for j in range(100):
        spidevice2.xfer2(to_send)
        
    spidevice2.close()
    del spidevice2

def runSPI():
    for i in range(2**31 - 1):
        spiLoop()            
        print((2**12 * 100 * (i + 1)) / 2**20, 'Mbytes')
Run Code Online (Sandbox Code Playgroud)

发送 2^32 字节后 spi 崩溃

编辑2

即时重新加载 spidev 也没有帮助。我在 RPi3 和 RPi4 上尝试了此代码,结果相同:

import importlib
def spiLoop():
    importlib.reload(spidev)
    spidevice2 = spidev.SpiDev()
    spidevice2.open(0, 1)
    spidevice2.max_speed_hz = 25000000
    spidevice2.mode = 1 # Data is clocked in on falling edge
    
    to_send = [0x00] * 2**12 # 4096 bytes
    for j in range(100):
        spidevice2.xfer2(to_send)
        
    spidevice2.close()
    del spidevice2

def runSPI():
    for i in range(2**31 - 1):
        spiLoop()            
        print((2**12 * 100 * (i + 1)) / 2**20, 'Mbytes')
Run Code Online (Sandbox Code Playgroud)

重新加载 spidev 包没有帮助

编辑3

执行代码片段也没有隔离问题。在发送第 4 个 1G 字节数据后,它崩溃了。

program = '''
import spidev
spidevice = None

def configSPI():
    global spidevice
    
    # We only have SPI bus 0 available to us on the Pi
    bus = 0
    #Device is the chip select pin. Set to 0 or 1, depending on the connections
    device = 1

    spidevice = spidev.SpiDev()
    spidevice.open(bus, device)
    spidevice.max_speed_hz = 250000000
    
    spidevice.mode = 1 # Data is clocked in on falling edge

def spiLoop():
    to_send = [0xAA] * 2**12
    loops = 1024
    for j in range(loops):
        spidevice.xfer2(to_send)
    
    return len(to_send) * loops    

configSPI()
bytes_total = 0

while True:
    bytes_sent = spiLoop()
    bytes_total += bytes_sent            
    print(int(bytes_total / 2**20), "Mbytes", int(1000 * (bytes_total / 2**30)) / 10, "% finished")
    if bytes_total > 2**30:
        break

'''
for i in range(100):
    exec(program)
    print("program executed", i + 1, "times, bytes sent > ", (i + 1) * 2**30)
Run Code Online (Sandbox Code Playgroud)

在此输入图像描述