Некоторые программы (в частности, многие демоны) должны следить за тем, чтобы они были запущены в единственном экземпляре. Обычно это достигается следующим образом: демон создает файл в стандартном каталоге и применяет к нему блокировку для записи. Он удерживает ее на протяжении всего своего существования и удаляет прямо перед завершением. Если попытаться запустить другой экземпляр того же демона, то он не сможет получить блокировку для соответствующего файла и автоматически завершится, понимая: один его экземпляр уже выполняется в системе.
Многие сетевые серверы используют другой принцип; чтобы узнать, выполняется ли в системе другой экземпляр сервера, они проверяют, занят ли их стандартный порт (см. раздел 57.10).
Для хранения таких файлов обычно применяется каталог /var/run. Как вариант местоположение файла может определяться конфигурацией демона.
Обычно демон записывает в заблокированный файл идентификатор своего процесса, так что в качестве расширения файла часто используется. pid (например, демон syslogd создает файл /var/run/syslogd.pid). Это удобно, если нужно найти PID демона, а также обеспечивает дополнительную меру предосторожности: можно проверить, существует ли процесс с таким идентификатором, воспользовавшись вызовом kill(pid, 0), как показано в разделе 20.5. (В старых реализациях UNIX, которые не поддерживали блокирование файлов, данный подход применялся в качестве неидеального, но достаточно практичного способа определения, действительно ли предыдущий экземпляр демона все еще работает, или он просто не сумел удалить этот файл перед завершением.)
Процедура создания и блокировки файла с идентификатором процесса может иметь множество мелких вариаций. Листинг 51.4 основан на принципе, описанном в книге [Stevens, 1999], и предоставляет функцию createPidFile(), которая инкапсулирует вышеописанные действия. Вызов данной функции в общем случае выглядит так:
if (createPidFile("mydaemon", "/var/run/mydaemon.pid", 0) == -1)
errExit("createPidFile");
В функции createPidFile() есть один неочевидный нюанс: использование вызова ftruncate() для удаления любой строки, которая могла находиться в файле до того. Это делается на случай, если предыдущий экземпляр демона не смог удалить файл — возможно, в результате системного сбоя. При возникновении такой ситуации, если идентификатор нового процесса слишком маленький, можно не полностью перезаписать старое содержимое файла. Например, если идентификатор равен 789, мы запишем в файл строку 789\n, но предыдущий экземпляр демона мог записать значение 12345\n. Без предварительного усечения файла результат выглядел бы как 789\n5\n. Иногда удаление любой существующей строки может и не понадобиться, но такой подход более аккуратен и исключает любую путаницу.
Аргументу flags можно передать константу CPF_CLOEXEC, заставляющую вызов createPidFile() установить для файлового дескриптора флаг FD_CLOEXEC (см. раздел 27.4). Это может пригодиться в серверных программах, которые перезапускают себя с помощью вызова exec(). Если не закрыть дескриптор во время данного вызова, то перезапущенный сервер будет считать, что в системе уже выполняется один его экземпляр.
Листинг 51.4. Создание PID-файла, который дает запустить только один экземпляр программы
filelock/create_pid_file.c
#include
#include
#include "region_locking.h" /* Для lockRegion() */
#include "create_pid_file.h" /* Объявляем createPidFile() и определяем CPF_CLOEXEC */
#include "tlpi_hdr.h"
#define BUF_SIZE 100 /* Достаточно большой для хранения PID в виде строки */
/* Открываем/создаем файл с именем 'pidFile', блокируем его, при необходимости
устанавливаем флаг FD_CLOEXEC для его дескриптора, записываем в него PID и (если
вызывающий процесс в этом заинтересован) возвращаем дескриптор, ссылающийся
на этот файл. Вызывающий процесс отвечает за удаление файла 'pidFile' прямо
перед завершением работы. Аргумент 'progName' должен содержать имя вызывающей
программы (argv[0] или нечто похожее); используется исключительно в диагностических
целях. Если мы не можем открыть файл 'pidFile' или сталкиваемся с какой-то другой
ошибкой, выводим в терминале соответствующее диагностическое сообщение. */
int
createPidFile(const char *progName, const char *pidFile, int flags)
{
int fd;
char buf[BUF_SIZE];
fd = open(pidFile, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
if (fd == -1)
errExit("Could not open PID file %s", pidFile);
if (flags & CPF_CLOEXEC) {
/* Устанавливаем файловому дескриптору флаг FD_CLOEXEC */
flags = fcntl(fd, F_GETFD); /* Извлекаем флаги */
if (flags == -1)
errExit("Could not get flags for PID file %s", pidFile);
flags |= FD_CLOEXEC; /* Включаем FD_CLOEXEC */
if (fcntl(fd, F_SETFD, flags) == -1) /* Обновляем флаги */
errExit("Could not set flags for PID file %s", pidFile);
}
if (lockRegion(fd, F_WRLCK, SEEK_SET, 0, 0) == -1) {
if (errno == EAGAIN || errno == EACCES)
fatal("PID file '%s' is locked; probably "
"'%s' is already running", pidFile, progName);
else