Как правильно обрабатывать SIGBUS, чтобы я мог продолжить поиск адреса?

В настоящее время я работаю над проектом, работающим на сильно модифицированной версии Linux, исправленной для доступа к VMEbus. Большая часть обработки шины выполнена, у меня есть класс VMEAccess, который использует mmap для записи по определенному адресу /dev/mem, чтобы драйвер мог получить эти данные и передать их на шину.

Когда программа запускается, она понятия не имеет, где находится искомая ведомая плата на шине, поэтому она должна найти ее, ковыряясь: она пытается прочитать каждый адрес один за другим, если там подключено устройство, метод чтения возвращает некоторые данные, но если ничего не подключено, в программу будет отправлен сигнал SIGBUS.

Я попробовал несколько решений (в основном с использованием обработки сигналов), но через некоторое время решил использовать переходы. Первый вызов longjmp() работает нормально, но второй вызов VMEAccess::readWord() дает мне ошибку шины, хотя мой обработчик должен предотвратить сбой программы.

Вот мой код:

#include <iostream>
#include <string>
#include <sstream>
#include <csignal>
#include <cstdlib>
#include <csignal>
#include <csetjmp>

#include "types.h"
#include "VME_access.h"

VMEAccess *busVME;

int main(int argc, char const *argv[]);
void catch_sigbus (int sig);
void exit_function(int sig);

volatile BOOL bus_error;
volatile UDWORD offset;
jmp_buf env;

int main(int argc, char const *argv[])
{
    sigemptyset(&sigBusHandler.sa_mask);

    struct sigaction sigIntHandler;

    sigIntHandler.sa_handler = exit_function;
    sigemptyset(&sigIntHandler.sa_mask);
    sigIntHandler.sa_flags = 0;

    sigaction(SIGINT, &sigIntHandler, NULL);

    /*   */
    struct sigaction sigBusHandler;

    sigBusHandler.sa_handler = catch_sigbus;
    sigemptyset(&sigBusHandler.sa_mask);
    sigBusHandler.sa_flags = 0;

    sigaction(SIGBUS, &sigBusHandler, NULL);

    busVME = new VMEAccess(VME_SHORT);

    offset = 0x01FE;

    setjmp(env);
    printf("%d\n", sigismember(&sigBusHandler.sa_mask, SIGBUS));

    busVME->readWord(offset);
    sleep(1);

    printf("%#08x\n", offset+0xC1000000);

    return 0;
}

void catch_sigbus (int sig)
{
    offset++;
    printf("%#08x\n", offset);
    longjmp(env, 1);
}

void exit_function(int sig) 
{
    delete busVME;
    exit(0);
}

person Tzig    schedule 18.12.2017    source источник
comment
К сожалению, я не могу, SIGBUS - это определенное поведение в этом случае, поскольку я пытаюсь читать с несуществующей доски, я получаю эту ошибку, ее нельзя избежать, по крайней мере, без создания собственного драйверы/ядро для FPGA   -  person Tzig    schedule 18.12.2017
comment
Не выходите longjmp из обработчика сигнала. Вместо этого используйте sigsetjmp и siglongjmp. См. страницу руководства для получения дополнительной информации.   -  person Some programmer dude    schedule 18.12.2017
comment
Вау! Спасибо, это сработало! На самом деле это было довольно легко решить, большое спасибо, вы можете использовать кнопку фактического ответа, чтобы я мог отметить это как решенное?   -  person Tzig    schedule 18.12.2017


Ответы (1)


Как упоминалось в комментариях, использование longjmp в обработчике сигнала — плохая идея. После выхода из обработчика сигнала ваша программа фактически все еще находится в обработчике сигнала. Таким образом, вызов функций, не безопасных для асинхронных сигналов, приводит, например, к неопределенному поведению. Использование siglongjmp здесь не поможет, цитируя man signal-safety:

Если обработчик сигнала прерывает выполнение небезопасной функции, а обработчик завершается вызовом longjmp(3) или siglongjmp(3), а затем программа вызывает небезопасную функцию, то поведение программы не определено.

И, например, это (siglongjmp) действительно вызывало некоторые проблемы в коде libcurl в прошлом, см. здесь: ошибка: longjmp вызывает неинициализированный кадр стека

Я бы предложил использовать обычный цикл и изменить условие выхода в обработчике сигнала (в любом случае вы измените смещение там). Что-то вроде следующего (псевдокод):

int had_sigbus = 0;

int main(int argc, char const *argv[])
{
    ...
    for (offset = 0x01FE; offset is sane; ++offset) {
        had_sigbus = 0;
        probe(offset);
        if (!had_sigbus) {
            // found
            break;
        }
    }
    ...
}

void catch_sigbus(int)
{
    had_sigbus = 1;
}

Таким образом, сразу видно, что есть петля, и всю логику гораздо легче проследить. И нет никаких переходов, поэтому он должен работать для более чем одного зонда :) Но, очевидно, probe() должен также внутренне обрабатывать неудачный вызов (тот, который был прерван SIGBUS) - и, вероятно, возвращать ошибку. Если он возвращает ошибку, использование функции had_sigbus может вообще не понадобиться.

person dvk    schedule 18.12.2017
comment
Я уже пробовал что-то подобное на самом деле, но по какой-то причине, когда программа проверяет правильный адрес, она не выходит из цикла. Хотя, когда я запускаю программу с правильным значением смещения, она завершается. Странный - person Tzig; 18.12.2017
comment
Вы уверены, что не забыли установить флаг на 0 перед каждым зондом? - person dvk; 18.12.2017
comment
Я да, и когда я пытаюсь увидеть, что происходит, используя cout или printf, ничего не печатается, даже после fflush - person Tzig; 18.12.2017
comment
Похоже, он застрял где-то в функции зондирования. Трудно сказать, что могло пойти не так, но я подозреваю не совсем правильную обработку ошибок, так как здесь разница. Если вы переходите от сигнала, функция, которая исследует шину, просто останавливает выполнение при отправке SIGBUS. При подходе с флагом какой-то системный вызов вернет ошибку, и управление будет передано обратно функции, и она должна правильно обработать эту ошибку. - person dvk; 18.12.2017
comment
Но функция зондирования на самом деле зависит от функции из ядра (и я не могу внести в нее никаких изменений), поэтому я думаю, что застрял с siglongjmp (но, поскольку я кодирую все остальное, и я не планирую использовать какой-либо мьютекс или все остальное должно быть нормально) - person Tzig; 18.12.2017