
Текст книги "Linux программирование в примерах"
Автор книги: Арнольд Роббинс
Жанры:
Программирование
,сообщить о нарушении
Текущая страница: 36 (всего у книги 55 страниц)
memcmp()
Функция memcmp()
сравнивает count
байтов из двух произвольных буферов данных. Возвращаемое ею значение подобно strcmp()
: отрицательное, нулевое или положительное, если первый буфер меньше, равен или больше второго.
Вы можете поинтересоваться: «Почему бы не использовать для такого сравнения strcmp()
?» Разница между двумя функциями в том, что memcmp()
не принимает во внимание нулевые байты (завершающий строку ''.) Таким образом,
memcmp()
является функцией, которая используется, когда вам нужно сравнить произвольные двоичные данные.
Другим преимуществом memcmp()
является то, что она быстрее типичной реализации на C:
/* memcmp – пример реализации на С, НЕ для реального использования */
int memcmp(const void *buf1, const void *buf2, size_t count) {
const unsigned char *cp1 = (const unsigned char*)buf1;
const unsigned char *cp2 = (const unsigned char*)buf2;
int diff;
while (count– != 0) {
diff = *cp1++ – *cp2++;
if (diff != 0)
return diff;
}
return 0;
}
Скорость может быть достигнута в результате использования специальных инструкций «блочного сравнения памяти», которые поддерживают многие архитектуры, или в результате сравнения единиц, превышающих байты. (Эта последняя операция коварна, и лучше оставить ее автору библиотеки.)
По этим причинам всегда следует использовать вашу библиотечную версию memcmp()
вместо прокручивания своей собственной. Велика вероятность, что автор библиотеки знает машину лучше вас
memchr()
Функция memchr()
сходна с функцией strchr()
: она возвращает местоположение определенного значения внутри произвольного буфера. Как и в случае memcmp()
против strcmp()
, основной причиной для использования memchr()
является использование произвольных двоичных данных.
GNU wc
использует memchr()
при подсчете лишь строк и байтов[124]124
См. wс(1). wc
подсчитывает строки, слова и символы – Примеч. автора.
[Закрыть], и это позволяет wс
быть быстрой. Из wc.c
в GNU Coreutils:
257 else if (!count_chars && !count_complicated)
258 {
259 /* Использует отдельный цикл при подсчете лишь строк или строк и байтов -
260 но не символов или слов. */
261 while ((bytes_read = safe_read(fd, buf, BUFFER_SIZE)) > 0)
262 {
263 register char *p = buf;
264
265 if (bytes_read == SAFE_READ_ERROR)
266 {
267 error(0, errno, "%s", file);
268 exit_status = 1;
269 break;
270 }
271
272 while ((p = memchr(p, 'n', (buf + bytes_read) – p)))
273 {
274 ++p;
275 ++lines;
276 }
277 bytes += bytes_read;
278 }
279 }
Внешний цикл (строки 261–278) читает блоки данных из входного файла. Внутренний цикл (строки 272–276) использует memchr()
для поиска и подсчета символов конца строки. Сложное выражение '(buf + bytes_read) – р
' сводится к числу оставшихся байтов между текущим значением p и концом буфера.
Комментарии в строках 259–260 нуждаются в некотором объяснении. Вкратце, современные системы могут использовать символы, занимающие более одного байта в памяти и на диске. (Это несколько более подробно обсуждается в разделе 13.4 «Не могли бы вы произнести это для меня по буквам?».) Таким образом, wc
должна использовать другой код, если она различает байты и символы: этот код имеет дело со случаем подсчета байтов.
Временный файл является в точности тем, что звучит в его названии: файл, в котором при исполнении программы хранятся данные, которые больше не нужны после завершения программы. sort
читает со стандартного ввода, если в командной строке не указаны файлы или вы используете в качестве имени файла '-
'. Тем не менее, sort
должна прочесть все
свои входные данные, прежде чем сможет вывести отсортированные результаты. (Подумайте об этом немного, и вы увидите, что это так.) Когда читается стандартный ввод, данные должны быть где-то сохранены, прежде чем sort
сможет их отсортировать; это отличное применение для временного файла. sort
использует временные файлы также для хранения промежуточных результатов сортировки.
Удивительно, но имеются пять различных функций для создания временных файлов. Три из них работают посредством создания строк, представляющих (предположительно) уникальные имена файлов. Как мы увидим, их обычней следует избегать. Оставшиеся две работают путем создания и открытия временного файла; эти функции предпочтительнее для использования.
Имеются три функции, назначением которых является создание имени уникального, не существующего файла. Получив такое имя, вы можете использовать его для создания временного файла. Поскольку имя уникально, вам «гарантируется» исключительное использование файла. Вот объявления функций:
#include
char *tmpnam(char *s); /* ISO С */
char *tempnam(const char *dir, const char *pfx); /* XSI */
char *mktemp(char *template); /* ISO С */
Все эти функции предусматривают различные вариации одной и той же темы они заполняют или создают буфер с путем уникального временного имени файла. Файл уникален в том, что создаваемое имя не существует во время создания и возвращения его функциями. Функции работают следующим образом:
char *tmpnam(char *s)
Генерирует уникальное имя файла. Если s
не равен NULL
, он должен быть размером по крайней мере L_tmpnam
байтов, и в него копируется уникальное имя. Если s
равен NULL
, имя генерируется во внутреннем статическом буфере, который может быть переписан при последующих вызовах. Префикс каталогов в пути будет P_tmpdir
. Как P_tmpdir
, так и L_tmpnam
определены в
.
char *tempnam(const char *dir, const char *pfx)
Подобно tmpnam()
дает вам возможность указать префикс каталогов. Если dir
равен NULL
, используется P_tmpdir
. Аргумент pfx
, если он не равен NULL
, определяет до пяти символов для использования в качестве начальных символов имени файла tempnam()
выделяет память для имен файлов, которые она генерирует. Возвращенный указатель может впоследствии использоваться с free()
(и это следует сделать, если хотите избежать утечек памяти).
char *mktemp(char *template)
Генерирует уникальные имена файлов на основе шаблона. Последними шестью символами template
должны быть 'ХХХХХХ
'; эти символы замещаются уникальным суффиксом.
ЗАМЕЧАНИЕ. Аргумент
template
функцииmktemp()
переписывается. Поэтому он не должен быть строковой константой. Многие компиляторы, предшествовавшие стандарту С, помещают строковые константы в сегмент данных вместе с обычными глобальными переменными. Хотя в исходном коде они определены как константы, их можно переписать, таким образом, нередко встречался такой код:
/* Код в старом стиле: не используйте его */
char *tfile = mktemp("/tmp/myprogXXXXXX");
/* ...использование файла... */
На современных системах такой код. вероятно, завершится неудачей, в наши дни строковые константы находятся в сегментах памяти только для чтения.
Использование этих функций довольно просто. Файл ch12-mktemp.c
демонстрирует mktemp()
; нетрудно изменить его для использования других функций:
1 /* ch12-mktemp.с – демонстрирует простое использование mktemp().
2 Для краткости проверка ошибок опущена */
3
4 #include
5 #include
6 #include
7
8 int main(void)
9 {
10 static char template[] = "/tmp/myfileXXXXXX";
11 char fname[PATH_MAX];
12 static char mesg[] =
13 "Here's lookin' at you, kid'n"; /* вместо "hello, world" */
14 int fd;
15
16 strcpy(fname, template);
17 mktemp(fname);
18
19 /* ОКНО СОСТОЯНИЯ ГОНКИ ОТКРЫВАЕТСЯ */
20
21 printf("Filename is %sn", fname);
22
23 /* ОКНО СОСТОЯНИЯ ГОНКИ ТЯНЕТСЯ ДОСЮДА */
24
25 fd = open(fname, O_CREAT|O_RDWR|O_TRUNC, 0600);
26 write(fd, mesg, strlen(mesg));
27 close(fd);
28
29 /* unlink(fname); */
30
31 return 0;
32 }
Переменная template
(строка 10) определяет шаблон имени файла; 'ХХХХХХ
' будет заменен уникальным значением. Строка 16 копирует шаблон в fname
, которая не является константой: ее можно изменить. Строка 18 вызывает mktemp()
для генерирования имени файла, а строка 21 выводит ее, так, чтобы мы могли видеть, что это такое. (Вскоре мы объясним комментарии в строках 19 и 23.)
Строка 25 открывает файл, создавая его при необходимости. Строка 26 записывает сообщение в mesg
, а строка 27 закрывает файл. В программе, в которой файл должен быть удален после завершения работы с ним, строка 29 была бы не закомментирована. (Иногда временный файл не следует удалять; например, если файл после полной записи будет переименован.) Мы закомментировали ее, чтобы можно было запустить эту программу и посмотреть на файл впоследствии. Вот что происходит при запуске программы:
$ ch12-mktemp /* Запуск программы */
Filename is /tmp/myfileQES4WA /* Вывод имени файла */
$ cat /tmp/myfileQES4WA
Here's lookin' at you, kid' /* Содержит то, что ожидалось */
$ ls -l /tmp/myfileQES4WA /* To же с владельцем и доступом */
-rw– 1 arnold devel 28 Sep 18 09:27 /tmp/myfileQES4WA
$ rm /tmp/myfileQES4WA /* Удалить его */
$ ch12-mktemp / * Используется ли повторно то же имя? */
Filename is /tmp/myfileic7xCy /* Нет. Это хорошо */
$ cat /tmp/myfileic7xCy /* Снова проверить содержание */
Here's lookin' at you, kid!
$ ls -l /tmp/myfileic7xCy /* Снова проверить владельца и доступ */
-rw– 1 arnold devel 28 Sep 18 09:28 /tmp/myfileic7xCy
Все кажется работающим замечательно, mktemp()
возвращает уникальное имя, ch12-mktemp
создает файл с нужными правами доступа, и содержание то самое, которое ожидалось. Так в чем же проблема со всеми этими функциями?
Исторически mktemp()
использовала простой, предсказуемый алгоритм для создания замещающих символов для 'ХХХХХХ
' в шаблоне. Более того, интервал между временем, когда генерируется имя файла, и временем, когда создается сам файл, создает состояние гонки.
Как? Ну, Linux и Unix являются системами с квантованием времени, методикой, которая разделяет время процессора между всеми исполняющимися процессами. Это означает, что, хотя программа кажется выполняющейся все время, в действительности есть моменты, когда процессы спят, т.е. ожидают выполнения на процессоре.
Теперь рассмотрите программу профессора для отслеживания оценок студентов. Как профессор, так и злоумышленный студент в одно и то же время используют сильно нагруженную многопользовательскую систему. Программа профессора использует для создания временных файлов mktemp()
, видевший в прошлом, как оценивающая программа создает и удаляет временные файлы, выяснил алгоритм, который использует mktemp()
. (В версии GLIBC нет этой проблемы, но не все системы используют GLIBC!) Рис 12.2 иллюстрирует состояние гонки и то, как студент его использует.

Рис. 12.2. Состояние гонки с mktemp()
Вот что случилось.
1. Оценивающая программа использует mktemp()
для создания имени файла. По возвращении из mktemp()
открыто окно состояния гонки (строка 19 в ch12-.mktemp.c
).
2. Ядро останавливает оценивающую программу, чтобы могли поработать другие программы в системе. Это происходит до вызова open()
.
Пока оценивающая программа остановлена, студент создает файл с тем же самым именем, которое вернула mktemp()
. (Помните, выяснить алгоритм было просто.) Студент создает файл с дополнительной ссылкой, так что когда оценивающая программа удаляет файл, он все еще будет доступен для прочтения.
3. Теперь оценивающая программа открывает файл и записывает в него данные. Студент создал файл с правами доступа -rw-rw-rw-
, поэтому это не представляет проблему.
4. Когда оценивающая программа завершает работу, она удаляет временный файл. Однако, у студента все еще есть копия. Например, может быть возможность получения прибыли при заблаговременной продаже своим товарищам их оценок.
Наш пример упрощенный; помимо простого похищения данных с оценками умный (хотя и аморальный) студент мог бы вместо этого изменить данные. Если бы профессор не проверял результаты работы своей программы дважды, никто бы ничего не узнал.
ЗАМЕЧАНИЕ. Мы не рекомендуем делать что-либо из этого! Если вы студент, не пытайтесь сделать что-либо подобное. Первое и самое главное, это неэтично. Во-вторых, вас могут выгнать из школы. В-третьих, ваши профессора, наверное, не сталь наивны, чтобы использовать
mktemp()
в своих программах. Этот пример лишь для иллюстрации!
По приведенным и другим причинам, все три описанные в данном разделе функции не следует никогда использовать. Они существуют в POSIX и GLIBC лишь для поддержки старых программ, которые были написаны до того, как была осознана опасность этих процедур С этой целью системы GNU/Linux генерируют во время компоновки сообщение:
$ cc ch12-mktemp.c -о ch12-mktemp /* Компилировать программу */
/tmp/cc1XCvD9.о(.text+0x35): In function 'main':
: the use of 'mktemp' is dangerous, better use 'mkstemp'
(Мы рассмотрим mkstemp()
в следующем подразделе.)
Если бы в вашей системе не было mkstemp()
, подумайте, как вы могли бы использовать эти интерфейсы для ее эмулирования. (См. также «Упражнения» для главы 12 в конце.)
Есть две функции, не имеющие проблем состояния гонки. Одна из них предназначена для использования с библиотекой
:
#include
FILE *tmpfile(void);
Другая функция для использования с системными вызовами на основе дескрипторов файлов:
#include
int mkstemp(char* template);
tmpfile()
возвращает значение FILE*
, представляющее уникальный открытый временный файл. Файл открывается в режиме "w+b
". w+
означает «открыть для чтения и записи, сначала урезав файл», a b означает двоичный, а не текстовый режим. (На системах GNU/Linux или Unix нет разницы, но на других системах есть.) Файл автоматически удаляется, когда закрывается указатель файла; нет способа получить имя файла, чтобы сохранить его содержимое. Программа в ch12-tmpfile.c
демонстрирует tmpfile()
:
/* ch12-tmpfile.с – демонстрация tmpfile().
Проверка ошибок для краткости опущена */
#include
int main(void) {
static char mesg[] =
"Here's lookin' at you, kid!"; /* заменяет "hello, world" */
FILE *fp;
char buf[BUFSIZ];
fp = tmpfile(); /* Получить временный файл */
fprintf(fp, "%s", mesg); /* Записать s него */
fflush(fp); /* Сбросить на диск */
rewind(fp); /* Перейти в начало */
fgets(buf, sizeof buf, fp); /* Прочесть содержимое */
printf("Got back <%s>n", buf); /* Вывести полученные данные */
fclose(fp); /* Закрыть файл, закончить */
return 0; /* Все сделано */
}
Возвращенное значение FILE*
не отличается от любого другого FILE*
, возвращенного fopen()
. При запуске получаем ожидавшиеся результаты:
$ ch12-tmpfile
Got back
Ранее мы видели, что авторы GLIBC рекомендуют использование функции mkstemp()
:
$ cc ch12-mktemp.с -о ch12-mktemp /* Компилировать программу */
/tmp/cc1XCvD9.о(.text+0x35): In function "main':
: the use of 'mktemp' is dangerous, better use 'mkstemp'
Эта функция похожа на mktemp()
в том, что она принимает имя файла, оканчивающееся на 'ХХХХХХ
', и заменяет эти символы уникальным суффиксом для создания уникального имени файла. Однако, она идет на один шаг дальше. Она создает и открывает файл. Файл создается с доступом 0600 (т.е. -rw–
). Таким образом, доступ к файлу может получить только пользователь, запустивший программу.
Более того, и это то, что делает mkstemp()
более безопасной, файл создается с флагом O_EXCL
, который гарантирует, что файл не существует, и не дает никому больше открыть файл.
Возвращаемое значение является дескриптором открытого файла, который может использоваться для чтения и записи. Для удаления файла после завершения работы с ним должно использоваться имя пути, сохраненное теперь в переданном mkstemp()
буферу. Все это демонстрируется в ch12-mkstemp.c
, который является простой модификацией ch12-tmpfile.с
:
/* ch12-mkstemp.с – демонстрирует mkstemp().
Проверка ошибок для краткости опущена */
#include
#include
#include
int main(void) {
static char template[] = "/tmp/myfileXXXXXX";
char fname[PATH_MAX];
static char mesg[] =
"Here's lookin' at you, kid!n"; /* заменяет "hello, world" */
int fd;
char buf[BUFSIZ];
int n;
strcpy(fname, template); /* Копировать шаблон */
fd = mkstemp(fname); /* Создать и открыть временный файл */
printf("Filename is %sn", fname); /* Вывести его для сведений */
write(fd, mesg, strlen(mesg)); /* Что-нибудь записать в файл */
lseek(fd, 0L, SEEK_SET); /* Перейти в начало */
n = read(fd, buf, sizeof(buf));
/* Снова прочесть данные; НЕ завышается ''! */
printf("Got back: %.*s", n, buf); /* Вывести его для проверки */
close(fd); /* Закрыть файл */
unlink(fname); /* Удалить его */
return 0;
}
При запуске получаем ожидавшиеся результаты:
$ ch12-mkstemp
Filename is /tmp/myfileuXFWIN
Got back: Here's lookin' at you, kid!
TMPDIR
Многие стандартные утилиты обращают внимание на переменную окружения TMPDIR
, используя обозначенный в ней каталог в качестве места для помещения временных файлов. Если TMPDIR
не установлена, каталогом по умолчанию для временных файлов обычно является /tmp
, хотя на многих современных системах есть также и каталог /var/tmp
. /tmp
обычно очищается от всех файлов и каталогов административными сценариями оболочки при запуске.
Многие системы GNU/Linux предоставляют каталог /dev/shm
, использующий файловую систему типа tmpfs:
$ df
Filesystem 1K-blocks Used Available Use% Mounted on
/dev/hda2 6198436 5136020 747544 88% /
/dev/hda5 61431520 27720248 30590648 48% /d
none 256616 0 256616 0% /dev/shm
Тип файловой системы tmpfs
предоставляет электронный (RAM) диск: часть памяти, которая используется, как если бы она была диском. Более того, файловая система tmpfs
использует механизмы виртуальной памяти ядра Linux для его увеличения сверх фиксированного размера. Если на вашей системе уйма оперативной памяти, этот подход может обеспечить заметное ускорение. Чтобы протестировать производительность, мы начали с файла /usr/share/dict/linux.words
, который является отсортированным списком правильно написанных слов, по одному в строке. Затем мы перемешали этот файл, так что он больше не был сортированным, и создали больший файл, содержащий 500 копий спутанной версии файла:
$ ls -l /tmp/randwords.big /* Показать размер */
-rw-r–r– 1 arnold devel 204652500 Sep 18 16:02 /tmp/randwords.big
$ wc -l /tmp/randwords.big /* Сколько слов? */
22713500 /tmp/randwords.big /* Свыше 22 миллионов! */
Затем мы отсортировали файл, используя сначала каталог /tmp
, а затем с TMPDIR
, установленным в /dev/shm
[125]125
Такое использование /dev/shm
на самом деле является злоупотреблением,, он предназначен для использования в реализации разделяемой памяти, а не в качестве электронного диска. Тем не менее, это полезно для иллюстрации нашей мысли – Примеч. автора.
[Закрыть]:
$ time sort /tmp/randwords.big > /dev/null
/* Использование реальных файлов */
real 1m32.566s
user 1m23.137s
sys 0m1.740s
$ time TMPDIR=/dev/shm sort /tmp/randwords.big > /dev/null
/* Использование электронного диска */
real 1m28.257s
user 1m18.469s
sys 0m1.602s
Интересно, использование электронного диска было лишь незначительно быстрее, чем использование обычных файлов. (В некоторых дальнейших тестах оно было даже в действительности медленнее!) Мы предполагаем, что в игру вступил буферный кэш ядра (см. раздел 4.6.2 «Создание файлов с помощью creat()
»), весьма эффективно ускоряя файловый ввод/вывод[126]126
На нашей системе 512 мегабайтов оперативной памяти, что для старых чудаков вроде автора кажется порядочным. Однако цены на память упали, и вполне обычны системы с одним или более гигабайтами оперативной памяти, по крайней мере, для разработчиков программного обеспечения – Примеч. автора.
[Закрыть].
У электронного диска есть важный недостаток: он ограничен сконфигурированным для вашей системы размером пространства для подкачки.[127]127
Пространство для подкачки состоит из одного или более выделенных участков диска, используемых для хранения частей исполняющегося процесса, который не находится в настоящее время в памяти – Примеч. автора.
[Закрыть] Когда мы попытались отсортировать файл, содержащий 1000 копий файла с перемешанными словами, место на электронном диске закончилось, тогда как обычный sort завершился благополучно.
Использовать TMPDIR для своих программ просто. Мы предлагаем следующую схему.
const char template[] = "myprog.XXXXXX";
char *tmpdir, *tfile;
size_t count;
int fd;
if ((tmpdir = getenv("TMPDIR")) == NULL)
/* Использовать значение TMPDIR, если имеется */
tmpdir = "/tmp"; /* В противном случае, /tmp по умолчанию */
count = strlen(tmpdir) + strlen(template) + 2;
/* Вычислить размер имени файла */
tfile = (char *)malloc(count); /* Выделить для него память */
if (tfile == NULL) /* Проверка ошибок */
/* восстановить */
sprintf(tfile, "%s/%s", tmpdir, template);
/* Создать завершающий шаблон */
fd = mkstemp(tfile); /* Создать и открыть файл */
/* ...использование tempfile через fd... */
close(fd); /* Очистка */
unlink(tfile);
free(tfile);
В зависимости от потребностей вашего приложения, вы можете захотеть немедленно удалить файл после его открытия, вместо его удаления как части завершающей очистки.