При разработке сервисов или программ, которые работают в фоновом режиме (так называемые демоны) часто требуется проконтролировать была ли запущена программа до этого. К примеру, если Вы разрабатываете сервер принимающий соединения по 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 запущен. Можно вернуться к этой теме позже.