Книга: Linux программирование в примерах

15.4.2.2. Используйте специальные переменные окружения

15.4.2.2. Используйте специальные переменные окружения

Другой полезной уловкой является проверка вашим приложением специальных переменных окружения (документированных или иных). Это может быть особенно полезным для тестирования. Вот другой пример из нашего опыта с gawk, но сначала немного основ.

gawk использует функцию с названием optimal_bufsize() для получения оптимального размера буфера для ввода/вывода. Для небольших файлов функция возвращает размер файла. В противном случае, если файловая система определяет размер для использования при вводе/выводе, возвращается это значение (член st_blksize структуры struct stat, см. раздел 5.4.2 «Получение информации о файле»). Если этот член недоступен, optimal_bufsize() возвращает константу BUFSIZ из <stdio.h>. Оригинальная функция (в posix/gawkmisc.c) выглядела следующим образом:

1  /* optimal_bufsize --- определяет оптимальный размер буфера */
2
3  int
4  optimal_bufsize(fd, stb) /* int optimal_bufsize(int fd, struct stat *stb); */
5  int fd;
6  struct stat *stb;
7  {
8   /* инициализировать все члены нулями на случай, если ОС не использует их все. */
9   memset(stb, '', sizeof(struct stat));
10
11 /*
12  * System V.n, n < 4, не имеет в структуре stat размера
13  * системного блока файла. Поэтому нам нужно сделать разумную
14  * догадку. Мы используем BUFSIZ, поскольку именно это имелось
15  * в виду на первом месте.
16  */
17 #ifdef HAVE_ST_BLKSIZE
18 #define DEFBLKSIZE (stb->st_blksize ? stb->st_blksize : BUFSIZ)
19 #else
20 #define DEFBLKSIZE BUFSIZ
21 #endif
22
23  if (isatty(fd))
24   return BUFSIZ;
25  if (fstat(fd, stb) == -1)
26   fatal("can't stat fd %d (%s)", fd, strerror(errno));
27  if (lseek(fd, (off_t)0, 0) == -1) /* не обычный файл */
28   return DEFBLKSIZE;
29  if (stb->st_size > 0 && stb->st_size < DEFBLKSIZE) /* маленький файл */
30   return stb->st_size;
31  return DEFBLKSIZE;
32 }

Константа DEFBLKSIZE является «размером блока по умолчанию»; то есть значением из struct stat или BUFSIZ. Для терминалов (строка 23) или файлов, которые не являются обычными файлами (lseek() завершается неудачей, строка 27) возвращаемое значение также равно BUFSIZ. Для небольших обычных файлов используется размер файла. Во всех других случаях возвращается DEFBLKSIZE. Знание «оптимального» размера буфера особенно полезно в файловых системах, в которых размер блока большеBUFSIZ.

У нас была проблема, когда один из наших контрольных примеров отлично работал на нашей рабочей системе GNU/Linux и на любой другой системе Unix, к которой у нас был доступ. Однако, этот тест последовательно терпел неудачу на других определенных системах.

В течение длительного времени мы не могли получить непосредственный доступ к терпящей неудачу системе, чтобы запустить GDB. В конце концов, мы смогли, однако, ухитриться воспроизвести проблему. Она оказалась связана с размером буфера, который gawk использовал для чтения файлов данных: на терпящих неудачи системах размер буфера был больше, чем на нашей системе разработки.

Нам был нужен способ воспроизведения проблемы на своей машине разработки, система с неудачей находилась в стороне за девять часовых поясов, а интерактивный запуск GDB через Атлантический океан мучителен. Мы воспроизвели проблему, заставив optimal_bufsize() проверять значение специальной переменной окружения AWKBUFSIZE. Когда ее значение равно "exact", optimal_bufsize() всегда возвращает размер файла, каким бы он ни был. Если значением AWKBUFSIZE является какое-нибудь целое число, функция возвращает это число. В противном случае, функция возвращается к прежнему алгоритму. Это дает нам возможность запускать тесты, не требуя постоянной перекомпиляции gawk. Например,

$ AWKBUFSIZE=42 make check

Это запускает тестовый набор gawk с использованием размера буфера в 42 байта. (Тестовый набор проходит.) Вот модифицированная версия optimal_bufsize():

1  /* optimal_bufsize --- определение оптимального размера буфера */
2
3  /*
4   * В целях отладки усовершенствуйте это следующим образом:
5   *
6   * Всегда используйте stat для файла, буфер stat используется кодом
7   * более высокого уровня.
8   * if (AWKBUFSIZE == "exact")
9   *  return the file size
10  * else if (AWKBUFSIZE == число)
11  *  всегда возвращать это число
12  * else
13  *  if размер < default_blocksize
14  *   return размер
15  *  else
16  *   return default_blocksize
17  *  end if
18  * end if
19  *
20  * Приходится повозиться, чтобы иметь дело с AWKBUFSIZE лишь
21  * однажды, при первом вызове этой процедуры, а не при каждом
22  * ее вызове. Производительность, знаете ли.
23  */
24
25 size_t
26 optimal_bufsize(fd, stb)
27 int fd;
28 struct stat *stb;
29 {
30  char *val;
31  static size_t env_val = 0;
32  static short first = TRUE;
33  static short exact = FALSE;
34
35  /* обнулить все члены, на случай, если ОС их не использует. */
36  memset(stb, '', sizeof(struct stat));
37
38  /* всегда использовать stat на случай, если stb используется кодом более высокого уровня */
39  if (fstat(fd, stb) == -1)
40   fatal("can't stat fd %d (%s)", fd, strerror(errno));
41
42  if (first) {
43   first = FALSE;
44
45   if ((val = getenv("AWKBUFSIZE")) != NULL) {
46    if (strcmp(val, "exact") == 0)
47     exact = TRUE;
48    else if (ISDIGIT(*val)) {
49     for (; *val && ISDIGIT(*val); val++)
50     env_val = (env_val * 10) + *val - '0';
51
52     return env_val;
53    }
54   }
55  } else if (!exact && env_val > 0)
56   return env_val;
57  /* else
58     обрабатывать дальше */
59
60  /*
61   * System V.n, n < 4, не имеет в структуре stat размера системного
62   * блока файла. Поэтому нам нужно осуществить разумную догадку.
63   * Мы используем BUFSIZ из stdio, поскольку именно это имелось
64   * в виду прежде всего.
65   */
66 #ifdef HAVE_ST_BLKSIZE
67 #define DEFBLKSIZE (stb->st_blksize > 0 ? stb->st_blksize : BUFSIZ)
68 #else
69 #define DEFBLKSIZE BUFSIZ
70 #endif
71
72  if (S_ISREG(stb->st_mode) /* обычный файл */
73   && 0 < stb->st_size /* ненулевой размер */
74   && (stb->st_size < DEFBLKSIZE /* маленький файл */
75   || exact)) /* или отладка */
76   return stb->st_size; /* использовать размер файла*/
77
78  return DEFBLKSIZE;
79 }

Комментарий в строках 3–23 объясняет алгоритм. Поскольку поиск переменных окружения может быть затратным и его нужно осуществить лишь однажды, функция использует для сбора соответствующих сведений в первый раз несколько статических переменных.

Строки 42–54 выполняются лишь при первом вызове функции. Строка 43 обеспечивает это условие, устанавливая в first значение false. Строки 45–54 обрабатывают переменную окружения, разыскивая либо строку "exact", либо число. В последнем случае оно преобразуется из строкового значения в десятичное, сохраняясь в env_val. (Возможно, нам следовало бы использовать здесь strtoul(); в свое время это не пришло нам на ум.)

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

Строки 60–70 определяют DEFBLKSIZE; эта часть не изменилась. Наконец, строки 72–76 возвращают размер файла, если это приемлемо. Если нет (строка 78), возвращается DEGBLKSIZE.

Мы действительно устранили проблему[174], но между тем оставили на месте новую версию optimal_bufsize(), чтобы можно было убедиться, что проблема не возникнет вновь.

Незначительное увеличение размера кода и его сложности более чем компенсируется возросшей гибкостью, которая есть теперь у нас для тестирования. Более того, поскольку это код изделия, пользователь в полевых условиях может с легкостью использовать эту особенность для тестирования, чтобы определить, не появилась ли сходная проблема. (До сих пор нам не приходилось просить проделать этот тест, но приятно осознавать, что мы могли бы это сделать, если бы пришлось.)

Оглавление книги


Генерация: 0.056. Запросов К БД/Cache: 0 / 0
поделиться
Вверх Вниз