pselect не ждет, пока освободится буферное пространство последовательного порта - PullRequest
0 голосов
/ 05 июля 2018

Я пишу класс C ++ (SerialPort), чтобы использовать его в 2 потоках (один объект для каждого с общим дескриптором). Я хочу отправить данные в первом потоке и получить данные во втором. Работает как положено. Но я хочу сделать это правильно. И я хочу, чтобы мое приложение сопротивлялось большим трафикам (в тех случаях, когда выходной буфер Linux переполнен очень часто). Поэтому я использую pselect в SerialPort::receive(), и он работает как положено. Но когда я использую его в SerialPort::send() , он не блокирует , когда выходной буфер заполнен !!! Я могу обнаружить эту ситуацию с помощью ioctl, но не могу дождаться некоторого свободного места в этом буфере. Так что активный цикл ожидания случается, ведь приложение просто зависает! Примечание: я написал о буфере вывода последовательного порта Linux, а не о буфере последовательного вывода моего приложения (я выделил для него 1 МБ, и он работает как положено). Ниже я вставляю функции SerialPort::receive() и SerialPort::send(), а pease дает мне несколько советов, как избежать активного цикла ожидания в случае, если последовательный порт отупут буфер заполнен ...

спасибо заранее и наилучшими пожеланиями Яцек

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

EDIT: Я нашел решение! У меня ошибка с использованием pselect: я не инициализирую fd_set lFileDescriptorSet; после первого использования. Перед каждым вызовом его нужно повторно инициализировать:

FD_ZERO(&lFileDescriptorSet);
FD_SET(mPortDescriptor, &lFileDescriptorSet);

По данным этого сайта: [ Как определить объем буфера записи / вывода, оставшийся на последовательном порту Linux? Богдан-Стефан Мирея написал:

Последовательный порт является символьным устройством, а не блочным устройством. Не имеет буфер

Но некоторые другие источники утверждают, что Linux имеет буфер последовательного порта 4 КБ. Следуя его совету, я создаю функцию с циклом с вызовом pselect (в ожидании чтения записи последовательного порта) и записываю только 1 байт на итерацию. И это работает !!!

...