При разработке сервисов или программ, которые работают в фоновом режиме (так называемые демоны) часто требуется проконтролировать была ли запущена программа до этого. К примеру, если Вы разрабатываете сервер принимающий соединения по tcp протоколу, то попытка занять тот же сокет завершится неудачей, в заголовочном файле /usr/include/asm-generic/errno.h этот код ошибки:
#define EADDRINUSE 98 /* Address already in use */
Конечно в случае tcp-сервиса упасть с таким кодом это нормально. Но часто может быть, что демон является обработчиком какой-нибудь очереди заданий и обрабатывает файлы или директории. Запускать несколько экземпляров программы в таком случае может быть противопоказано, так как могут возникнуть конфликты в работе или вообще не предсказуемые результаты.
Нужно отдельно уметь проанализировать эту ситуацию. В попытке упростить этот процесс многие применяют возможности shell окружения, запуская команду скриптами типа:
#!/bin/sh
if pgrep prog_name > /dev/null; then
echo "Error: Already running"
else
./prog_name
fi
Но можно ли полагаться на такую защиту? Случайный запуск "вручную" не через этот "запускающий" скрипт приведет к неприятным последствиям. В поисках какого-то готового решения я решил поискать, и нашел stackoverflow.com: Determine programmatically if a program is running. Все предложенные там варианты рабочие, но не до конца.
Конечно же анализировать придется все через /proc файловую систему, которая обеспечивает доступ к данным от ядра системы Linux, но существует несколько мест, откуда мы может взять имя процесса:
Пусть не сочтут за плагиат, но эти два ответа я тут приведу, чтобы не потерялось, первая программа анализирует cmdline:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <dirent.h>
#include <sys/types.h>
pid_t proc_find(const char* name)
{
DIR* dir;
struct dirent* ent;
char* endptr;
char buf[512];
if (!(dir = opendir("/proc"))) {
perror("can't open /proc");
return -1;
}
while((ent = readdir(dir)) != NULL) {
/* if endptr is not a null character, the directory is not
* entirely numeric, so ignore it */
long lpid = strtol(ent->d_name, &endptr, 10);
if (*endptr != '\0') {
continue;
}
/* try to open the cmdline file */
snprintf(buf, sizeof(buf), "/proc/%ld/cmdline", lpid);
FILE* fp = fopen(buf, "r");
if (fp) {
if (fgets(buf, sizeof(buf), fp) != NULL) {
/* check the first token in the file, the program name */
char* first = strtok(buf, " ");
if (!strcmp(first, name)) {
fclose(fp);
closedir(dir);
return (pid_t)lpid;
}
}
fclose(fp);
}
}
closedir(dir);
return -1;
}
int main(int argc, char* argv[])
{
if (argc == 1) {
fprintf("usage: %s name1 name2 ...\n", argv[0]);
return 1;
}
int i;
for(int i = 1; i < argc; ++i) {
pid_t pid = proc_find(argv[i]);
if (pid == -1) {
printf("%s: not found\n", argv[i]);
} else {
printf("%s: %d\n", argv[i], pid);
}
}
return 0;
}
И второй вариант, по мнению автора более правильный, который анализирует stat:
#include <sys/types.h>
#include <dirent.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
pid_t proc_find(const char* name)
{
DIR* dir;
struct dirent* ent;
char buf[512];
long pid;
char pname[100] = {0,};
char state;
FILE *fp=NULL;
if (!(dir = opendir("/proc"))) {
perror("can't open /proc");
return -1;
}
while((ent = readdir(dir)) != NULL) {
long lpid = atol(ent->d_name);
if(lpid < 0)
continue;
snprintf(buf, sizeof(buf), "/proc/%ld/stat", lpid);
fp = fopen(buf, "r");
if (fp) {
if ( (fscanf(fp, "%ld (%[^)]) %c", &pid, pname, &state)) != 3 ){
printf("fscanf failed \n");
fclose(fp);
closedir(dir);
return -1;
}
if (!strcmp(pname, name)) {
fclose(fp);
closedir(dir);
return (pid_t)lpid;
}
fclose(fp);
}
}
closedir(dir);
return -1;
}
int main(int argc, char* argv[])
{
int i;
if (argc == 1) {
printf("usage: %s name1 name2 ...\n", argv[0]);
return 1;
}
for( i = 1; i < argc; ++i) {
pid_t pid = proc_find(argv[i]);
if (pid == -1) {
printf("%s: not found\n", argv[i]);
} else {
printf("%s: %d\n", argv[i], pid);
}
}
return 0;
}
Объясняю почему все эти популярные ответы не будут работать должным образом.
Давайте разработаем небольшого демона ничего не делающего daemon.c:
#include <unistd.h>
int main() {
daemon(1,1);
while (1) sleep(5);
return 0;
}
Скомпилируем эту программу командой:
$ gcc daemon.c -o nothing_doing_daemon_programm
Получаем экземпляр программы. Я полагаю, что если я положу эту программу в разные папки, то это разные прораммы. Например у вас есть несколько версий проекта в разных каталогах /home/user/daemon-ver0.1 и /home/user/daemon-ver0.2, в обеих этих папках содержатся разные версии программ и могут работать по разному, но компилируются в одно имя nothing_doing_daemon_programm. Сделаем приготовления:
$ mkdir /home/user/daemon-ver0.1 $ mkdir /home/user/daemon-ver0.2 $ cp ./nothing_doing_daemon_programm /home/user/daemon-ver0.1 $ cp ./nothing_doing_daemon_programm /home/user/daemon-ver0.2
Теперь запустим эти два демона следующим образом:
$ cd /home/user/daemon-ver0.1 && ./nothing_doing_daemon_programm $ cd /home/user/daemon-ver0.2 && ./nothing_doing_daemon_programm
Теперь смотрим, что нам покажет программа ps:
$ ps aux | grep nothing_doing user 47642 0.0 0.0 2356 80 ? Ss 15:09 0:00 ./nothing_doing_daemon_programm user 47644 0.0 0.0 2356 76 ? Ss 15:09 0:00 ./nothing_doing_daemon_programm user 47647 0.0 0.0 9064 736 pts/2 S+ 15:10 0:00 grep --color=auto nothing_doing
Т.е. имеем два процесса, запущенных из разных директорий, но с одним названием. Для первого 47642 имеем:
$ cat /proc/47642/cmdline ./nothing_doing_daemon_programm $ cat /proc/47642/stat 47642 (nothing_doing_d) S 1506 47642 47642 0 -1 1077936192 8 0 0 0 0 0 0 0 20 0 1 0 3298232 2412544 20 18446744073709551615 94775205928960 94775205933589 140732498649552 0 0 0 0 0 0 1 0 0 17 2 0 0 0 0 0 94775205944752 94775205945360 94775235919872 140732498653907 140732498653939 140732498653939 140732498657240 0 $ cat /proc/47642/comm nothing_doing_d
А также для второго процесса 47644 имеем:
$ cat /proc/47644/cmdline ./nothing_doing_daemon_programm $ cat /proc/47644/stat 47644 (nothing_doing_d) S 1506 47644 47644 0 -1 1077936192 8 0 0 0 0 0 0 0 20 0 1 0 3298232 2412544 19 18446744073709551615 94610351038464 94610351043093 140737311360992 0 0 0 0 0 0 1 0 0 17 2 0 0 0 0 0 94610351054256 94610351054864 94610382802944 140737311367910 140737311367942 140737311367942 140737311371224 0 $ cat /proc/47644/comm nothing_doing_d
Вы видите проблемы с этими файлами ? Мы никаким образом не можем отличить откуда были запущены эти программы, более того, в файлах stat и comm от названия процесса у нас остались рожки да ножки, всего лишь 16 байт (вместе с нулевым).
$ pgrep nothing_doing 47642 47644 $ pgrep nothing_doing_daemon_programm пусто
Из-за того, что process grep - pgrep анализирует названия программ по comm, то он не может найти программы во втором случае, а это очень плохо. Программа запущена и работает, но мы не можем об этом узнать...
К счастью остался последний вариант, это символьная ссылка exe:
$ ls -l /proc/47642 | grep exe lrwxrwxrwx 1 user user 0 jul 9 15:25 exe -> /home/user/daemon-ver0.1/nothing_doing_daemon_programm $ ls -l /proc/47644 | grep exe lrwxrwxrwx 1 user user 0 jul 9 15:25 exe -> /home/user/daemon-ver0.2/nothing_doing_daemon_programm
Почувствовали разницу ?! Это то, что нам надо... И путь полный есть, откуда прозводился запуск и название программы есть. Даже если название совпадает, но запущено из разных мест, то это разные программы, думаю не стоит это отрицать.
Вот и будем это анализировать, программа uniq.c:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <dirent.h>
int is_already_running() {
char proc_path[100];
char proc_own_exe[1024];
ssize_t own_exe_len;
char proc_cmp_exe[1024];
ssize_t cmp_exe_len;
DIR *proc_dir;
struct dirent *proc_dir_entry;
pid_t mypid = getpid();
sprintf(proc_path,"/proc/%d/exe", mypid);
own_exe_len = readlink(proc_path, proc_own_exe, sizeof(proc_own_exe) - 1);
if (own_exe_len == -1) return -1;
proc_own_exe[own_exe_len] = '\0';
int ret = 0;
proc_dir = opendir("/proc");
if (!proc_dir) return -1;
while ( (proc_dir_entry = readdir(proc_dir)) != NULL) {
char *end;
pid_t cmp_pid;
cmp_pid = strtol(proc_dir_entry->d_name, &end, 10);
if (*end != '\0') continue;
if (cmp_pid == mypid) continue;
sprintf(proc_path,"/proc/%d/exe", cmp_pid);
cmp_exe_len = readlink(proc_path, proc_cmp_exe, sizeof(proc_cmp_exe) - 1);
if (cmp_exe_len == -1) continue;
proc_cmp_exe[cmp_exe_len] = '\0';
if (own_exe_len != cmp_exe_len) continue;
if (strcmp(proc_own_exe, proc_cmp_exe) == 0) { ret = 1; break; }
}
closedir(proc_dir);
return ret;
}
int main() {
pid_t temppid = getpid();
printf("sizeof(pid_t) = %ld, pid = %d \n", sizeof(pid_t), temppid);
if (is_already_running()) {
printf("already running\n");
return 1;
} else {
printf("not running yet\n");
daemon(0,0);
while (1) sleep(5);
}
return 0;
}
Помоему код даже меньше получился и делает более правильные вещи. Мы сравниваем не только название процесса, которое больше не имеет ограничение по длине названия в 15 символов, но еще и путь где программа располагается. Конечно же надо подумать над размерами буферов или даже делать malloc/free, но это уже тонкости, думаю в реальной жизни 1024 байт должно хватить для любого случая.
$ gcc uniq.c -o uniqproc $ ./uniqproc sizeof(pid_t) = 4, pid = 48031 not running yet $ ./uniqproc sizeof(pid_t) = 4, pid = 48035 already running $ ./uniqproc sizeof(pid_t) = 4, pid = 48039 already running $ ./uniqproc sizeof(pid_t) = 4, pid = 48040 already running
Все. Можете пользоваться.
Еще одним из методов проверки повторного запуска программы это создание PID файла, в который вы после запуска записываете свой идентификатор процесса. Повторно запускаемый процесс должен прочитать из этого файла этот идентификатор и удостовериться, что данный pid запущен. Можно вернуться к этой теме позже.