我正在编写C ++类(SerialPort
),以便在2个线程中使用它(每个对象都有一个共享描述符)。我想在第一个线程中发送数据,然后在第二个线程中接收数据。它按预期工作。但是我想做对。而且我希望我的应用程序能够抵抗繁重的流量(在Linux输出缓冲区经常充满的情况下)。因此,我在SerialPort::receive()
中使用了pselect,它可以按预期工作。但是,当我在SerialPort::send()
中使用它时,当输出缓冲区已满时它不会阻塞!我可以使用ioctl
检测到这种情况,但是我无法等待此缓冲区中的一些可用空间。因此,发生了积极的等待循环,女巫只是挂了该应用程序!!!注意:我写的是Linux串行端口输出缓冲区,而不是我的应用程序串行输出缓冲区(我为其分配了1MB,并且按预期运行)。在下面,我粘贴了函数SerialPort::receive()
和SerialPort::send()
,请给我一些提示,以防串行otuput缓冲区已满时如何避免主动等待循环...
提前致以最诚挚的问候 雅西克
ps1:这是我的代码(由于以前的开发人员的习惯,错误消息被擦亮了-这不是我的决定,只是翻译工具要求它以保持翻译过程的一致性):
void SerialPort::receive()
{
// http://www.linuxprogrammingblog.com/code-examples/using-pselect-to-avoid-a-signal-race
// https://www.cmrr.umn.edu/~strupp/serial.html
if(mPortDescriptor < 0)
throw Exception(tr("Zły deskryptor pliku: %1").arg(mPortDescriptor).toUtf8().data());
struct sigaction lSigAction;
memset (&lSigAction, 0, sizeof(lSigAction));
lSigAction.sa_handler = gSignTermHandler;
// This server should shut down on SIGTERM.
if(sigaction(SIGTERM, &lSigAction, 0))
throw Exception(tr("Błąd sigaction! %1, %2").arg(errno).arg(strerror(errno)));
sigset_t lMask;
sigemptyset (&lMask);
sigaddset (&lMask, SIGTERM);
sigset_t orig_mask;
if(sigprocmask(SIG_BLOCK, &lMask, &orig_mask) < 0)
throw Exception(tr("Błąd sigprocmask! %1, %2").arg(errno).arg(strerror(errno)));
struct timespec timeout;
timeout.tv_sec = 1;
timeout.tv_nsec = 0;
fd_set lFileDescriptorSet;
/* BANG! we can get SIGTERM at this point, but it will be
* delivered while we are in pselect(), because now
* we block SIGTERM.
*/
FD_ZERO(&lFileDescriptorSet);
FD_SET(mPortDescriptor, &lFileDescriptorSet);
while(true)
{
int lRes = pselect(mPortDescriptor + 1, &lFileDescriptorSet, NULL, NULL, &timeout, &orig_mask);
if(mEnd)
return;
if((lRes < 0) && (errno != EINTR))
throw Exception(tr("Bład pselect! %1, %2").arg(errno).arg(strerror(errno)));
if(lRes == 0) // timeout
continue;
if(gExitRequest)
throw Exception(tr("Przechwycono sygnał SIGTERM! Koniec odbierania danych."));
if(!FD_ISSET(mPortDescriptor, &lFileDescriptorSet))
continue;
int lSize = 0;
ioctl(mPortDescriptor, FIONREAD, &lSize);
if(lSize <= 0)
continue;
QByteArray lResult;
lResult.resize(lSize);
int lReaded(0), lPartial(0);
while(lReaded < lSize)
{
if(mEnd)
return;
lPartial = ::read(mPortDescriptor, lResult.data(), lSize);
if(lPartial > 0)
lReaded += lPartial;
else if(lPartial < 0)
{
qCritical() << tr("Uwaga! Błąd czytania portu szeregowego! Kod błędu: %1, wiadomość: %2").arg(errno).arg(strerror(errno));
break;
}
}
emit read(lResult);
}
}
void SerialPort::send()
{
// http://www.linuxprogrammingblog.com/code-examples/using-pselect-to-avoid-a-signal-race
// https://www.cmrr.umn.edu/~strupp/serial.html
if(mPortDescriptor < 0)
throw Exception(tr("Zły deskryptor pliku: %1").arg(mPortDescriptor).toUtf8().data());
struct sigaction lSigAction;
memset (&lSigAction, 0, sizeof(lSigAction));
lSigAction.sa_handler = gSignTermHandler;
// This server should shut down on SIGTERM.
if(sigaction(SIGTERM, &lSigAction, 0))
throw Exception(tr("Błąd sigaction! %1, %2").arg(errno).arg(strerror(errno)));
sigset_t lMask;
sigemptyset(&lMask);
sigaddset(&lMask, SIGTERM);
sigset_t orig_mask;
if(sigprocmask(SIG_BLOCK, &lMask, &orig_mask) < 0)
throw Exception(tr("Błąd sigprocmask! %1, %2").arg(errno).arg(strerror(errno)));
struct timespec timeout;
timeout.tv_sec = 1;
timeout.tv_nsec = 0;
fd_set lFileDescriptorSet;
/* BANG! we can get SIGTERM at this point, but it will be
* delivered while we are in pselect(), because now
* we block SIGTERM.
*/
FD_ZERO(&lFileDescriptorSet);
FD_SET(mPortDescriptor, &lFileDescriptorSet);
int lNumberToAquire(0);
while(true)
{
lNumberToAquire = 0;
if(mBufferEnd > mBufferStart)
lNumberToAquire = mBufferEnd - mBufferStart;
else if(mBufferEnd < mBufferStart)
lNumberToAquire = mSendBuffer.size() - mBufferStart;
if(lNumberToAquire <= 0)
lNumberToAquire = 1;
int lFreeSpaceSize = 0;
ioctl(mPortDescriptor, TIOCOUTQ, &lFreeSpaceSize);
if(lFreeSpaceSize <= 0) // we must wait for serial port
{
int lRes = pselect(mPortDescriptor + 1, NULL, &lFileDescriptorSet, NULL, &timeout, &orig_mask);
if(mEnd)
return;
if((lRes < 0) && (errno != EINTR))
throw Exception(tr("Bład pselect! Kod błedu: %1, komunikat: %2").arg(errno).arg(strerror(errno)));
if(lRes == 0) // timeout
continue;
if(gExitRequest)
throw Exception(tr("Przechwycono sygnał SIGTERM! Koniec wysyłania danych."));
if(!FD_ISSET(mPortDescriptor, &lFileDescriptorSet))
continue;
}
ioctl(mPortDescriptor, TIOCOUTQ, &lFreeSpaceSize);
if(lFreeSpaceSize <= 0)
{
qWarning() << tr("%1 Uwaga: Brak miejsca w bufoerze wyjściowym portu szeregowego!").arg(QTime::currentTime().toString("hh:mm:ss.z"));
continue;
}
lNumberToAquire = qMin(lNumberToAquire, lFreeSpaceSize);
//#ifdef DEBUG
// int lSemaphore(mAvailableData.available());
//#endif
if((lNumberToAquire > 0) && mAvailableData.tryAcquire(lNumberToAquire, 1000))
{
//#ifdef DEBUG
// int lSemaphore2(mAvailableData.available());
//#endif
int lWriten(0), lPartial(0);
while(lWriten < lNumberToAquire)
{
lPartial = ::write(mPortDescriptor, mSendBuffer.data() + mBufferStart, lNumberToAquire);
if(lPartial > 0)
lWriten += lPartial;
else if(lPartial < 0)
{
qCritical() << tr("Błąd zapisu do portu szeregowego!!! Kod błędu: %1, Wiadomość błędu: %2").arg(errno).arg(strerror(errno));
break;
}
}
// qDebug() << QTime::currentTime().toString("hh:mm:ss.z") << "Write to the serial port.";
mBufferStart = (mBufferStart + lNumberToAquire) % mSendBufferSize;
mFreeSpace.release(lNumberToAquire);
}
if(mEnd)
return;
}
}
编辑:
我找到了解决方案!
我在使用pselect时遇到错误:第一次使用后,我没有重新初始化fd_set lFileDescriptorSet;
。每次调用前都需要重新初始化它:
FD_ZERO(&lFileDescriptorSet);
FD_SET(mPortDescriptor, &lFileDescriptorSet);
根据此网站: [How can I determine the amount of write/output buffer space left on a linux serial port? Bogdan-Stefan Mirea写道:
串行端口是字符设备,而不是块设备。它没有 缓冲
但是其他一些消息来源称Linux虽然具有4KB串行端口缓冲区。遵循他的建议,我使用pselect调用循环函数(等待串行端口写就绪),并且每次迭代仅写入1个字节。而且有效!!