Linux программирование в примерах - Арнольд Роббинс
Шрифт:
Интервал:
Закладка:
Возвращаемое значение является новым значением относительного приоритета или -1, если возникла ошибка. Поскольку -1 также является действительным значением относительного приоритета, при вызове nice() следует сначала явным образом установить errno в ноль, а затем проверить его насчет имевшихся проблем:
int niceval;
int inc = /* любое значение */;
errno = 0;
if ((niceval = nice(inc)) < 0 && errno != 0) {
fprintf(stderr, "nice(%d) failed: %sn", inc, strerror(errno));
/* другое восстановление */
}
Этот пример может завершиться неудачей, если в inc отрицательное значение, а процесс не запущен как root.
9.1.3.1. POSIX против действительности
Диапазон значений относительного приоритета от -20 до 19, которые использует Linux, имеет исторические корни; он ведет начало по крайней мерее V7. POSIX выражает состояние менее прямым языком, что дает возможность большей гибкости, сохраняя в то же время историческую совместимость. Это также затрудняет чтение и понимание стандарта, вот почему вы и читаете эту книгу. Итак, вот как описывает это POSIX
Во-первых, значение относительного приоритета процесса, поддерживаемое системой, колеблется от 0 до '(2 * NZERO) - 1'. Константа NZERO определена в <limits.h> и должна равняться по крайней мере 20. Это дает диапазон 0–39.
Во-вторых, как мы описывали, сумма текущего значения относительного приоритета и приращение incr загоняются в этот диапазон.
В заключение, возвращаемое nice() значение является значением относительного приоритета процесса минус NZERO. При значении NZERO 20 это дает первоначальный диапазон от -20 до 19, который мы описали вначале.
Результатом является то, что возвращаемое nice() значение в действительности изменяется от '-NZERO' до 'NZERO-1', и лучше всего писать свой код в терминах этой именованной константы. Однако, на практике трудно найти систему, в которой NZERO не было бы равно 20.
9.1.4. Запуск новой программы: семейство exec()
После запуска нового процесса (посредством fork()) следующим шагом является запуск в процессе другой программы. Имеется несколько функций, которые служат различным целям:
#include <unistd.h> /* POSIX */
int execve(const char *filename, /* Системный вызов */
char *const argv[], char *const envp[]);
int execl(const char *path, const char *arg, ...); /* Оболочки */
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ..., char *const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
Мы ссылаемся на эти функции как на «семейство exec()». Функции с именем exec() нет; вместо этого мы используем это имя для обозначения любой из перечисленных выше функций. Как и в случае с fork(), «exec» используется на языке Unix и в качестве глагола, означающего исполнение (запуск) программы, и в качестве существительного.
9.1.4.1. Системный вызов execve()
Простейшей для объяснения функцией является execve(). Она является также лежащим в основе системным вызовом. Другие являются функциями-оболочками, как вскоре будет объяснено.
int execve(const char *filename, char *const argv[],
char* const envp[]);
filename является именем программы для исполнения. Это может быть именем полного или относительного пути. Файл должен иметь формат исполняемого файла, который понимает ядро. Современные системы используют формат исполняемого файла ELF (Extensible Linking Format — открытый формат компоновки). GNU/Linux распознает ELF и несколько других форматов. С помощью execve() можно исполнять интерпретируемые сценарии, если они используют особую первую строку с именем интерпретатора, начинающуюся с '#!'. (Сценарии, которые не начинаются с '#!', потерпят неудачу.) В разделе 1.1.3 «Исполняемые файлы» представлен пример использования '#!'. argv является стандартным списком аргументов С — массив символьных указателей на строки аргументов, включая значение для использования с argv[0][90], завершающийся указателем NULL.
envp является окружением для использования новым процессом, с таким же форматом, как глобальная переменная environ (см. раздел 2.4 «Переменные окружения»). В новой программе это окружение становится начальным значением environ.
Программа не должна возвращаться из вызова exec(). Если она возвращается, возникла проблема. Чаще всего либо не существует затребованная программа, либо она существует, но не является исполняемой (значения для errno ENOENT и EACCESS соответственно). Может быть множество других ошибок; см. справочную страницу execve(2).
В предположении, что вызов был успешным, текущее содержимое адресного пространства процесса сбрасывается. (Ядро сначала сохраняет в безопасном месте данные argv и envp.) Ядро загружает для новой программы исполняемый код вместе со всеми глобальными и статическими переменными. Затем ядро инициализирует переменные окружения переданными execve() данными, а далее вызывает процедуру main() новой программы с переданным функции execve() массивом argv. Подсчитывается число аргументов и это значение передается main() в argc.
К этому моменту новая программа запущена. Она не знает (и не может определить), какая программа была в процессе до нее. Обратите внимание, что ID процесса не меняется. Многие другие атрибуты при вызове exec сохраняются; вскоре мы рассмотрим это более подробно.
exec() для процесса можно сравнить с ролями, которые играют в жизни люди. В различное время в течение дня один человек может быть родителем, супругом, другом, студентом или рабочим, покупателем в магазине и т.д. Это одна и та же личность, исполняющая различные роли. Также и процесс — его PID, открытые файлы, текущий каталог и т.п. — не изменяются, тогда как выполняемая работа - запущенная с помощью exec() программа — может измениться.
9.1.4.2. Функции-оболочки: execl() и др.
Пять дополнительных функций, действующих в качестве оболочек, предоставляют более удобные интерфейсы для execve(). В первой группе все принимают список аргументов, каждый из которых передается в виде явного параметра функции:
int execl(const char *path, const char *arg, ...)
Первый аргумент, path, является путем к исполняемому файлу. Последующие аргументы, начиная с arg, являются отдельными элементами, которые должны быть помещены в argv. Как и ранее, явным образом должен быть включен argv[0]. Вы должны в качестве последнего аргумента передать завершающий указатель NULL, чтобы execl() смогла определить, где заканчивается список аргументов. Новая программа наследует любые переменные окружения, которые находятся в переменной environ.
int execlp(const char *file, const char *arg, ...)
Эта функция подобна execl(), но она имитирует механизм поиска команд оболочки, разыскивая file в каждом каталоге, указанном в переменной окружения PATH. Если file содержит символ /, этот поиск не осуществляется. Если PATH в окружении не присутствует, execlp() использует путь по умолчанию. В GNU/Linux по умолчанию используется ":/bin:/usr/bin", но в других системах может быть другое значение. (Обратите внимание, что ведущее двоеточие в PATH означает, что сначала поиск осуществляется в текущем каталоге.)
Более того, если файл найден и имеет право доступа на исполнение, но не может быть исполнен из-за того, что неизвестен его формат, execlp() считает, что это сценарий оболочки и запускает оболочку с именем файла в качестве аргумента.
int execle(const char *path, const char *arg, ...,
char *const envp[])
Эта функция также подобна execl(), но принимает дополнительный аргумент, envp, который становится окружением новой программы. Как и в случае с execl(), вы должны для завершения списка аргументов поместить перед envp указатель NULL.
Вторая группа функций-оболочек принимает массив в стиле argv:
int execv(const char *path, char *const argv[])
Эта функция подобна execve(), но новая программа наследует любое окружение, которое находится в переменной environ текущей программы.
int execvp(const char *file, char *const argv[])
Эта функция подобна execv(), но она осуществляет такой же поиск в PATH, как и функция execlp(). Она также переходит на исполнение сценария оболочки, если найденный файл не может быть исполнен непосредственно.
В табл. 9.1 подведены итоги для шести функций exec().
Таблица 9.1. Сводка семейства функций exec() по алфавиту