потоки linux и fopen () fclose () fgets () - PullRequest
4 голосов
/ 28 октября 2009

Я смотрю на какой-то устаревший код Linux, который использует pthreads.

В одной теме файл читается через fgets (). Переменная FILE - это глобальная переменная, общая для всех потоков. (Эй, я не писал это ...)

В другом потоке время от времени ФАЙЛ закрывается и открывается с другим именем файла.

В течение нескольких секунд после того, как это произошло, поток fgets () действует так, как будто он продолжает читать последнюю запись, прочитанную из предыдущего файла: почти как если бы произошла ошибка, но fgets () не возвращал NULL. Затем он сортируется и начинает читать из нового файла.

Код выглядит примерно так (для краткости откорректирован, поэтому я надеюсь, что он все еще понятен):

В одной теме:

while(gRunState != S_EXIT){
  nanosleep(&timer_delay,0);
  flag = fgets(buff, sizeof(buff), gFile);
  if (flag != NULL){
    // do something with buff...
  }
}

В другой теме:

fclose(gFile);
gFile = fopen(newFileName,"r");

Нет блокировки, чтобы убедиться, что fgets () не вызывается одновременно с fclose () / fopen ().

Есть какие-нибудь мысли о режимах сбоев, которые могут привести к сбою функции fgets (), но не возвращать NULL?

Ответы [ 4 ]

4 голосов
/ 28 октября 2009

Как работает описанный код

Библиотека stdio буферизует данные, выделяя память для хранения буферизованных данных. Библиотека GNU C динамически распределяет файловые структуры (некоторые библиотеки, особенно в Solaris, используют указатели для статически размещаемых файловых структур, но буфер по-прежнему распределяется динамически, если вы не установите буферизацию иначе).

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

FILE *global_fp;

void somefunc(FILE *fp, ...)
{
    ...
    while (fgets(buffer, sizeof(buffer), fp) != 0)
        ...
}

void another_function(...)
{
    ...
    /* Pass global file pointer by value */
    somefunc(global_fp, ...);
    ...
}

Код концепции

Протестировано на MacOS X 10.5.8 (Leopard) с GCC 4.0.1:

#include <stdio.h>
#include <stdlib.h>

FILE *global_fp;
const char etc_passwd[] = "/etc/passwd";

static void error(const char *fmt, const char *str)
{
    fprintf(stderr, fmt, str);
    exit(1);
}

static void abuse(FILE *fp, const char *filename)
{
    char buffer1[1024];
    char buffer2[1024];
    if (fgets(buffer1, sizeof(buffer1), fp) == 0)
        error("Failed to read buffer1 from %s\n", filename);
    printf("buffer1: %s", buffer1);

    /* Dangerous!!! */
    fclose(global_fp);
    if ((global_fp = fopen(etc_passwd, "r")) == 0)
        error("Failed to open file %s\n", etc_passwd);

    if (fgets(buffer2, sizeof(buffer2), fp) == 0)
        error("Failed to read buffer2 from %s\n", filename);
    printf("buffer2: %s", buffer2);
}

int main(int argc, char **argv)
{
    if (argc != 2)
        error("Usage: %s file\n", argv[0]);

    if ((global_fp = fopen(argv[1], "r")) == 0)
        error("Failed to open file %s\n", argv[1]);

    abuse(global_fp, argv[1]);

    return(0);
}

При запуске с собственным исходным кодом вывод был:

Osiris JL: ./xx xx.c
buffer1: #include <stdio.h>
buffer2: ##
Osiris JL:

Итак, эмпирическое доказательство того, что в некоторых системах описанный мною сценарий может иметь место.

Как исправить код

Исправление к коду хорошо обсуждается в других ответах. Если вы избежите проблемы, которую я иллюстрировал (например, избегая глобальных файловых указателей), это будет проще всего. Предполагая, что это невозможно, может быть достаточно скомпилировать с соответствующими флагами (во многих Unix-подобных системах флаг компилятора '-D_REENTRANT' выполняет свою работу), и вы в конечном итоге будете использовать поточно-ориентированные версии основных стандартные функции ввода / вывода. В противном случае вам может понадобиться установить явные многопоточные политики управления для доступа к указателям на файлы; мьютекс или что-то подобное (и измените код, чтобы потоки использовали мьютекс перед использованием соответствующего указателя файла).

2 голосов
/ 28 октября 2009

ФАЙЛ * - это просто указатель на различные ресурсы. Если fclose не обнуляет эти ресурсы, возможно, значения имеют достаточный смысл, что fgets не сразу замечает это.

Тем не менее, пока вы не добавите некоторую блокировку, я бы посчитал этот код полностью неработающим.

1 голос
/ 28 октября 2009

Хмм, вам действительно нужно контролировать доступ к потоку FILE с помощью мьютекса, как минимум. Вы не смотрите на какую-то умную реализацию методов без блокировок, вы смотрите на действительно плохой (и пыльный) код.

Использование потоков локальных потоков FILE является очевидным и наиболее элегантным исправлением, просто используйте блокировки соответствующим образом, чтобы убедиться, что два потока не работают с одним и тем же смещением одного и того же файла одновременно. Или, проще, убедитесь, что потоки блокируются (или выполняют другую работу) в ожидании снятия блокировки файла. Консультативные блокировки POSIX лучше всего подходят для этого, или если вы работаете с динамически растущим деревом мьютексов ... или инициализируете мьютекс блокировки файлов для каждого потока и заставляете каждый поток проверять блокировку другого (черт!) ).

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

0 голосов
/ 28 октября 2009

Вы можете также поставить некоторое условие-ожидание (pthread_cond_wait) вместо просто некоторого наносона, который будет сигнализироваться, когда предполагается, например. когда новый файл открывается.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...