Как сделать поток API-оболочки C безопасным? - PullRequest
0 голосов
/ 08 апреля 2020

Редактировать: мы используем C99.

Сначала немного настроек / информации для контекста. Я пишу API в C, который оборачивается вокруг API C с контрольной суммой, который использует функции IUF (Initialize (), Update (), Finalize ()).

Мой API содержит одну функцию, мы Я назову его foo (), который принимает указатель входной структуры в качестве параметра. Он был написан таким образом, что хешируемые данные могут быть представлены в виде блоков / блоков вместо всего буфера данных. Этот код будет работать во встроенной системе с очень ограниченными ресурсами, поэтому возможность чтения файлов по частям до sh данных является конкретной целью c этого API. Использование одной функции - не мой выбор, а требование.

int foo(struct_t*);

И struct_t выглядит следующим образом.

{
   char* data,
   int dataLen,
   bool finalBlock,
   char checksum[CHECKSUM_SIZE]
}

По сути, вызывающая функция foo () заполняет параметры «data» и «dataLen» следующим блоком данных и размером этих данных соответственно каждый раз, когда они вызывают функцию, пока данные был полностью предоставлен foo (). Параметр 'finalBlock', к сожалению, является единственным способом сообщить foo (), что вы предоставляете последний кусок данных для обработки. Это понятно и хорошо для вариантов использования.

Теперь к реальной проблеме. Внутренний API контрольной суммы IUF имеет уникальную собственную структуру данных, которую мне не разрешено открывать для вызывающих функций foo (). Поскольку сейчас мы используем только foo () в одном потоке, текущим решением было сделать структуру IUF API (назовем ее bar) переменной stati c. Это делает функцию foo () не поточно-безопасной.

int foo(struct_t* x)
{
   /*Struct required by IUF API*/
   static bar y = {0};
   int retCode = 0;
   /*rest of code that processes data as needed*/
   ...
   /*after finalizing checksum and storing in x, reset y*/
   return retCode;
}

Я бы хотел сделать foo () потоко-безопасной, если это вообще возможно, без предоставления структуры 'bar' вызывающей стороне foo ( ). Любые предложения?

TL; DR: иметь API с одной функцией, которая требует нескольких вызовов для завершения работы sh. Необходимо скрыть внутреннюю структуру от вызывающей стороны этого API, но структура должна сохраняться до полного завершения API. В настоящее время задаем эту структуру как переменную stati c, чтобы она оставалась до завершения API, но это делает мой API не защищенным от потоков. Помогите пожалуйста.

1 Ответ

0 голосов
/ 10 апреля 2020

Существует два базовых c способа структурирования:

  1. Предоставление монопольного доступа всей подсистеме к одному потоку за раз. Вы можете достичь этого с помощью мьютекса и условной переменной, которая может быть c членом foo(), если хотите. Это невидимо для вызывающих, но предотвращает весь параллелизм, даже между foo() вызовами в случае нескольких блоков.

  2. Предоставьте каждому потоку свои собственные данные. Это то, что вам нужно, если вы не можете приспособиться к несовместимости foo().

Даже без доступа к встроенной поддержке локальных потоков данных C11 может случиться так, что какой-либо API потоков у вас есть механизм TLD или существенный эквивалент. В частности, pthreads делает , если это то, что вы используете. Возможно, вы даже можете свернуть свой собственный с помощью таблицы ha sh.

Но поскольку вы пишете для системы с ограниченными ресурсами, вы, вероятно, заинтересованы в облегченных подходах и абсолютных легких Весовой подход состоит в том, чтобы каждый поток предоставлял свои собственные данные, давая struct_t a bar для члена. Это не только исключает возможность косвенного обращения или поиска при доступе к данным для каждого потока, но также позволяет избежать динамического распределения памяти c. Но это действительно bar для foo вызывающих абонентов, что вы говорите, что вы не должны делать.

Между ними у вас есть альтернатива иметь foo выделить bar с, и передать их отключить непрозрачно для вызывающего абонента, ожидая, что они будут возвращаться при каждом связанном вызове. Это может лучше всего соответствовать вашим потребностям, и именно на этом я сосредоточусь до конца этого ответа.

Чтобы реализовать такой подход, сначала дайте struct_t дополнительный элемент для хранения данных контекста:

struct struct_t {
    char* data;
    int dataLen;
    bool finalBlock;
    char checksum[CHECKSUM_SIZE];
    void *state;  // <-- this
};

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

    struct struct_t checksum_state = { 0 };
    int result;

    checksum_state.data = some_data;
    checksum_state.dataLen = the_length;
    result = foo(&checksum_state);
    // ... check / handle result ...

    checksum_state.data = more_data;
    checksum_state.dataLen = new_length;
    result = foo(&checksum_state);
    // ... check / handle result ...

    checksum_state.data = last_data;
    checksum_state.dataLen = last_length;
    checksum_state.finalBlock = 1;
    result = foo(&checksum_state);
    // ... check / handle result ...
    // ... use checksum_state.checksum ...

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

На стороне foo() это будет структурировано примерно так:

int foo(struct_t* x) {
    int retCode = 0;
    struct bar *y;

    if (!x->state) {
        x->state = calloc(1, sizeof(struct bar));
        if (!x->state) // ... handle allocation error ...
    }
    y = x->state;

    /* Do stuff with x and y */

    if (x->lastBlock) {
        free(x->state);
    }

    return retCode;
}
...