Процессы могут нормально уничтожать вызовом kill()
только те процессы, которые разделяют тот же эффективный идентификатор пользователя, что и у них самих. Существуют два исключения из этого правила. Во-первых, процессы с эффективным uid, равным 0, могут уничтожать любые процессы в системе. Во-вторых, любой процесс может посылать сигнал SIGCONT
любому процессу в том же сеансе[26].
10.4.7. Дамп ядра
Хотя мы уже упоминали, что передача SIGTERM
и SIGKILL
функции kill()
прерывает процесс, вы также можете использовать несколько других значений (все они описаны в главе 12). Некоторые из них, такие как SIGABRT
, заставляют программу перед уничтожением сбрасывать дамп ядра (dump core).
Дамп ядра программы содержит полную хронологию состояния программы перед ее уничтожением[27]. Большинство отладчиков, включая gdb
, могут анализировать файл дампа и рассказывать, что программа делала непосредственно перед тем, как была уничтожена, а также поможет исследовать образ памяти процесса. Дамп ядра выгружается в файл по имени core, расположенный в текущем каталоге процесса.
Когда процесс нарушает какие-то системные требования (например, пытается обратиться к памяти, доступ к которой запрещен), ядро прерывает процесс, вызывая встроенную версию kill()
с параметром, который заставляет выгрузить дамп ядра. Ядро может уничтожать процессы по разным причинам, включая арифметические ошибки, такие как деление на ноль, либо по причине выполнения программой некорректных инструкций, либо при попытке доступа к запрещенной области памяти. Последняя причина вызывает ошибку сегментации, что выражается в сообщении segmentation fault (core dumped)
(ошибка сегментации (дамп ядра сброшен)). Если вы обладаете хоть каким-нибудь опытом программирования в Linux, то наверняка неоднократно получали это сообщение.
Если для процесса установлен лимит на размер файла дампа, равный 0 (рассматривался ранее в этой главе), то никакой дамп ядра не выгружается.
10.5. Простые дочерние процессы
Хотя функции fork()
, exec()
и wait()
позволяют программам в полной мере использовать модель процессов Linux, многим приложениям не нужен такой контроль дочерних процессов. Существуют две библиотечных функции, которые упрощают создание дочерних процессов: system()
и popen()
.
10.5.1. Запуск и ожидание с помощью system()
Программам часто требуется запускать другие программы и ожидать их завершения, прежде чем продолжать свою работу. Функция system()
позволяет это делать достаточно просто.
int system (const char* cmd);
system()
порождает дочерний процесс, который выполняет exec()
для /bin/sh
, который, в свою очередь, запускает cmd
. Исходный процесс ожидает завершения дочерней оболочки и возвращает тот же код, что wait()
[28]. Если вам не нужно оставлять в памяти оболочку (что случается редко), cmd
должна включать предшествующее слово "exec"
, которое заставляет оболочку вызывать exec()
вместо запуска cmd
как подпроцесса.
Поскольку cmd
запускается из оболочки /bin/sh
, то здесь применимы все обычные правила расширения команд. Ниже показан пример вызова system()
, который отображает исходные тексты С из текущего каталога.
#include
#include
int main() {
int result;
result = system("exec ls *.c");
if (!WIFEXITED(result))
printf("(аварийный выход)\n");
exit(0);
}
Команда system()
должна применяться с большой осторожностью в программах, которые запускаются со специальными полномочиями. Поскольку системная оболочка предоставляет множество мощных средств и сильно зависит от переменных окружения, system()
является уязвимым местом в плане безопасности, которым могут воспользоваться злоумышленники для проникновения в систему. Однако до тех пор, пока приложение не является демоном или программой setuid/setgid
, вызов system()
совершенно безопасен.
10.5.2. Чтение и запись из процесса
Хотя system()
отображает результат работы команды на устройство стандартного вывода и позволяет дочерним программам читать стандартный ввод, это не всегда идеально. Часто процесс желает читать вывод другого процесса либо отправлять текст на стандартный ввод. popen()
облегчает процессам решение этой задачи[29].
FILE * popen(const char *cmd, const char *mode);
cmd
выполняется через оболочку, как и в system()
. Параметр mode
должен быть "r"
, если родительский процесс желает читать командный вывод, и "w"
— для записи в стандартный ввод дочернего процесса. Следует отметить, что с помощью popen()
делать одновременно чтение и запись нельзя.