【问题标题】:QSerialPort::readAll() leads to SIGSEGV / SIGABRT if called in a while-loop如果在 while 循环中调用 QSerialPort::readAll() 会导致 SIGSEGV / SIGABRT
【发布时间】:2015-10-21 19:01:35
【问题描述】:

我正在使用 QSerialPort 与硬件设备通信。新数据不会发出“readyRead”信号,所以我决定使用 QThread 编写一个读取线程。

这是代码:

void ReadThread::run()
{
    while(true){
        readData();
        if (buffer.size() > 0) parseData();
    }
}

void ReadThread::readData()
{
    buffer.append(device->readAll();
}

缓冲区是一个私有 QByteArray,设备是一个指向 QSerialPort 的指针。 ParseData 将解析数据并发出一些信号。当 parseData 离开时,缓冲区被清除。

这可行,但是在一段时间后(有时是 10 秒,有时是 1 小时),程序会因 SIGSEGV 而崩溃,并出现以下跟踪:

Program received signal SIGSEGV, Segmentation fault.
[Switching to Thread 0x7ffff3498700 (LWP 24870)]
malloc_consolidate (av=av@entry=0x7fffec000020) at malloc.c:4151
(gdb) bt
#0  malloc_consolidate (av=av@entry=0x7fffec000020) at malloc.c:4151
#1  0x00007ffff62c2ee8 in _int_malloc (av=av@entry=0x7fffec000020, bytes=bytes@entry=32769) at malloc.c:3423
#2  0x00007ffff62c4661 in _int_realloc (av=av@entry=0x7fffec000020, oldp=oldp@entry=0x7fffec0013b0, oldsize=oldsize@entry=64, nb=nb@entry=32784) at malloc.c:4286
#3  0x00007ffff62c57b9 in __GI___libc_realloc (oldmem=0x7fffec0013c0, bytes=32768) at malloc.c:3029
#4  0x00007ffff70d1cdd in QByteArray::reallocData(unsigned int, QFlags<QArrayData::AllocationOption>) () from /usr/lib/x86_64-linux-gnu/libQt5Core.so.5
#5  0x00007ffff70d1f07 in QByteArray::resize(int) () from /usr/lib/x86_64-linux-gnu/libQt5Core.so.5
#6  0x00007ffff799f9fc in free (bytes=<optimized out>, this=0x609458)
    at ../../include/QtSerialPort/5.3.2/QtSerialPort/private/../../../../../src/serialport/qt4support/include/private/qringbuffer_p.h:140
#7  read (maxLength=<optimized out>, data=<optimized out>, this=0x609458)
    at ../../include/QtSerialPort/5.3.2/QtSerialPort/private/../../../../../src/serialport/qt4support/include/private/qringbuffer_p.h:326
#8  QSerialPort::readData (this=<optimized out>, data=<optimized out>, maxSize=<optimized out>) at qserialport.cpp:1341
#9  0x00007ffff722bdf0 in QIODevice::read(char*, long long) () from /usr/lib/x86_64-linux-gnu/libQt5Core.so.5
#10 0x00007ffff722cbaf in QIODevice::readAll() () from /usr/lib/x86_64-linux-gnu/libQt5Core.so.5
#11 0x00007ffff7bd0741 in readThread::readData (this=0x6066c0) at ../reader.cpp:212
#12 0x00007ffff7bc80d0 in readThread::run (this=0x6066c0) at ../reader.cpp:16
#13 0x00007ffff70cdd2e in ?? () from /usr/lib/x86_64-linux-gnu/libQt5Core.so.5
#14 0x00007ffff6e1c0a4 in start_thread (arg=0x7ffff3498700) at pthread_create.c:309
#15 0x00007ffff632f04d in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:111

我不确定如何正确重现该问题,因为它是随机出现的。如果我在 while 循环中注释掉“readData()”,就不会再出现崩溃(当然也就无法解析数据了)。

有人知道这可能是什么吗?

【问题讨论】:

  • 新数据不会发出“readyRead”-信号 - 我认为你应该先研究一下。 QSerialport 是从 QIODevice 派生的,所以当数据可用时它当然应该发出 readyRead。
  • 我猜想你正在某个地方写内存超出分配块的范围,但不在你提供的代码中。您是否尝试过任何其他工具,例如 valgrind 或电动围栏?
  • @TheDarkKnight 是的,我当然会看看这个。
  • @DarkFalcon 我没有通过 QSerialPort 写入设备。实际上,我只需要 QSerialPort 的“readAll”功能。使用 valdrind(通过 QtCreator)运行程序有一个有趣的效果,即程序不会崩溃(运行时间超过 24 小时)。
  • @boltzmann138:如果它在 valgrind 下成功运行,那么这听起来像是一个竞态条件。在 valgrind 下运行的一个副作用是所有线程都被调度在一个虚拟 CPU 上,这意味着与真正的 CPU 不同,在任何给定时间只能执行一个线程。 Valentin Heinitz 可能走在正确的轨道上。

标签: c++ qt segmentation-fault


【解决方案1】:

什么是缓冲区?莫非,另一个线程正在从缓冲区中读取数据,然后将其清除?

尝试锁定它(以及线程之间共享的所有其他数据),例如使用互斥锁

QMutex mx; // could be also member of the ReadThread class

void ReadThread::readData()
{
    mx.lock();
    buffer.append(device->readAll();
    mx.unlock();
}

并且在从另一个线程读取和清除缓冲区的代码中执行相同的操作(我没有做假设,这是 parseData())

另一种可能性是,parseData() 调用了一些在 GUI-Thread 中运行的代码。这在 Qt4 中不起作用,可能在 Qt5 中也不起作用

【讨论】:

    【解决方案2】:

    您正在同时使用来自多个线程的QObject 的实例。如您所见,这通常会导致未定义的行为。 QSerialPort 可以在 GUI 线程上正常工作。只有在你让它在那里工作后,你才能将它移到工作线程中。

    请注意,如果事件循环(main()QThread::run() 中的app.exec() 调用)未执行,则不会发生信号。看起来好像您尝试编写伪同步代码并且(可以预见地)失败了。不要那样做。

    这样的事情应该可以工作:

    #include <QtCore>
    #include <QtSerialPort>
    
    int main(int argc, char ** argv) {
      QCoreApplication app(argc, argv);
      QSerialPort port;
      port.setPortName(...);
      port.setBaudRate(...);
      ... // etc
      if (! port.open(QIODevice::ReadWrite)) {
        qWarning() << "can't open the port";
        return 1;
      }
      ... // set the port
      connect(&port, &QIODevice::readyRead, [&]{
        qDebug() << "got" << port.readAll().size() << "bytes";
      });
      return app.exec(); // the signals will be emitted from here
    }
    

    【讨论】:

      【解决方案3】:

      确保所有与串口相关的对象都被初始化并且只在单独的线程中使用。使用信号/槽机制将接收到的数据或解析的事件发送到 UI 线程。

      还要注意,如果你继承了readThread中的QThread,构造函数可能会在UI线程中执行,而其他函数可能会在readThread中执行。在这种情况下,启动 readThread 并在其他函数之前运行单独的初始化函数(例如,通过从 UI 线程发送适当的信号)。

      【讨论】:

        猜你喜欢
        • 2023-03-21
        • 2014-05-18
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-02-14
        • 1970-01-01
        • 2020-08-01
        • 2012-10-13
        相关资源
        最近更新 更多