Замена образа процесса

Итак, теперь мы умеем порождать процессы. Научимся теперь заменять образ текущего процесса другой программой. Для этих целей используется системный вызов execve(), который объявлен в заголовочном файле unistd.h вот так:

−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−

int execve (const char * path, char const * argv[], char * const envp[]);

−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−

Все очень просто: системный вызов execve() заменяет текущий образ процесса программой из файла с именем path, набором аргументов argv и окружением envp. Здесь следует только учитывать, что path–это не просто имя программы, а путь к ней. Иными словами, чтобы запустить ls, нужно в первом аргументе указать "/bin/ls".

Массивы строк argv и envp обязательно должны заканчиваться элементом NULL. Кроме того, следует помнить, что первый элемент массива argv (argv[0]) - это имя программы или что-либо иное. Непосредственные аргументы программы отсчитываются от элемента с номером 1.

В случае успешного завершения execve() ничего не возвращает, поскольку новая программа получает полное и безвозвратное управление текущим процессом. Если произошла ошибка, то по традиции возвращается -1.

Рассмотрим теперь пример программы, которая заменяет свой образ другой программой.

−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−

/* execve01.c */

#include <unistd.h>

#include <stdio.h>

 

int main (void)

{

printf ("pid=%dn", getpid ());

execve ("/bin/cat", NULL, NULL);

 

return 0;

}

−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−

И так, данная программа выводит свой PID и передает безвозвратное управление программе cat без аргументов и без окружения.

−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−

$ gcc -o execve01 execve01.c

$ ./execve01

pid=30150

−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−

Программа вывела идентификатор процесса и замерла в ожидании. Откроем теперь другое терминальное окно и проверим, что же творится с нашим процессом:

−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−

$ ps -e | grep 30150

30150 pts/3 00:00:00 cat

−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−

Итак, мы убедились, что теперь процесс 30150 выполняет программа cat. Теперь можно вернуться в исходное окно и нажатием Ctrl+D завершить работу cat.

И, наконец, следующий пример демонстрирует запуск программы в отдельном процессе.

−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−

/* forkexec01.c */

#include <unistd.h>

#include <stdio.h>

 

extern char ** environ;

 

int main (void)

{

char * echo_args[] = { "echo", "child", NULL };

 

if (!fork ()) {

execve ("/bin/echo", echo_args, environ);

fprintf (stderr, "an error occuredn");

return 1;

}

 

printf ("parent");

return 0;

}

−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−

Проверяем:

−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−

$ gcc -o forkexec01 forkexec01.c

$ ./forkexec01

parent

child

−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−

Обратите внимание, что поскольку execve() не может возвращать ничего кроме -1, то для обработки возможной ошибки вовсе необязательно создавать ветвление. Иными словами, если вызов execve() возвратил что-то, то это однозначно ошибка.

 

Функции семейства exec()

Давайте теперь посмотрим на прототип системного вызова execve() критически. Он принимает три аргумента, которые не всегда удобно использовать. Первый аргумент может не устраивать нас из-за того, что требует непременно указывать точный путь к исполняемому файлу, а это не всегда удобно. Второй аргумент - массив строк: тоже не всегда удобно. Хотелось бы иметь возможность передавать строки напрямую в функцию. Третий аргумент зачастую вообще не требуется, поскольку дочерние процессы обычно работают с копией окружения своих родителей.

Чтобы программисту каждый раз при замене образа процесса не приходилось "подгонять" аргументы под прототип execve(), были реализованы библиотечные надстройки над execve(), которые обычно называют семейством exec().

Эти надстройки представляют собой пять функций, объявленных в заголовочном файле unistd.h, которые позволяют манипулировать различными комбинациями аргументов. Хотя функции семейства exec() не являются системными вызовами, но их прототипы жестко прописаны в стандарте POSIX и, следовательно, они совместимы с большинством современных Unix-систем.

Первая функция семейства - execl(). Ниже представлен ее прототип.

−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−

int execl (const char * path, const char * arg, ...);

−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−

В этой функции первый аргумент такой же, как и в execve() –путь к файлу относительно текущего или корневого каталога. А дальше идет список аргументов, передаваемых программе в виде отдельных аргументов. Список должен заканчиваться аргументом NULL. Этот вариант exec() не требует явно задавать окружение: программа просто получает окружение родителя. В следующем примере показана программа из предыдущего раздела (forkexec01.c), но переписанная под использование execl().

−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−

/* forkexec02.c */

#include <unistd.h>

#include <stdio.h>

 

int main (void)

{

if (!fork ()) {

execl ("/bin/echo", "echo", "child", NULL);

fprintf (stderr, "an error occuredn");

return 1;

}

 

return 0;

}

−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−

Следующая функция семейства - execlp(). Она работает аналогично функции execl(), но в качестве первого аргумента может принимать не только путь к исполняемому файлу, но так же и просто имя программы из каталога, обозначенного в переменной окружения PATH. Иными словами, теперь вместо /bin/echo мы можем писать просто echo. Ниже показан прототип функции execlp().

−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−

int execlp (constchar * file, constchar * arg, ...);

−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−

Теперь рассмотрим версию программы forkexec02.c, в которой используется execlp().

−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−

/* forkexec03.c */

#include <unistd.h>

#include <stdio.h>

 

int main (void)

{

if (!fork ()) {

execlp ("echo", "echo", "child", NULL);

fprintf (stderr, "an error occuredn");

return 1;

}

 

return 0;

}

−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−

Еще одна функция семейства - execle(). Эта функция, подобноexecl() и execve(), не читает переменную PATH и использует в качестве первого аргумента путь к исполняемому файлу. Как и в execl() здесь аргументы задаются не массивом, а списком, оканчивающимся элементом NULL, однако после этого списка идет еще один аргумент, задающий окружение будущей программы. Ниже представлен прототип функции execle().

−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−

int execle (const char * path, const char * arg, ..., char * const envp[]);

−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−

Ниже показана модифицированная версия программы forkexec03.c, использующая execle() для замены образа процесса.

−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−

/* forkexec04.c */

#include <unistd.h>

#include <stdio.h>

 

extern char ** environ;

 

int main (void)

{

if (!fork ()) {

execle ("/bin/echo", "echo", "child", NULL, environ);

fprintf (stderr, "an error occuredn");

return 1;

}

 

return 0;

}

−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−

Следующая функция - execv(). Она работает также, как и execve(), но только без последнего аргумента, задающего окружение. Ниже представлен прототип execv().

−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−

int execv (const char * path, char * const argv[]);

−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−

Ниже показан пример, демонстрирующий работу execv().

−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−

/* forkexec05.c */

#include <unistd.h>

#include <stdio.h>

 

int main (void)

{

char * echo_args[] = { "echo", "child", NULL };

if (!fork ()) {

execv ("/bin/echo", echo_args);

fprintf (stderr, "an error occuredn");

return 1;

}

 

return 0;

}

−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−

И осталась последняя функция семейства - execvp(). Эта функция работает аналогично execv(), но в качестве первого аргумента может принимать не только путь, но и имя файла, находящегося в одном из каталогов, перечисленных в переменной окружения PATH. Вот прототип этой функции.

−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−

int execvp (const char * file, char * const argv[]);

−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−

А вот пример работы execvp().

−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−

/* forkexec06.c */

#include <unistd.h>

#include <stdio.h>

 

int main (void)

{

char * echo_args[] = { "echo", "child", NULL };

if (!fork ()) {

execvp ("echo", echo_args);

fprintf (stderr, "an error occuredn");

return 1;

}

 

return 0;

}

−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−

Не запутаться в названиях функций семейства exec() позволяет простое правило. Нужно лишь запомнить, какой постфикс из набора "l, v, e, p" за что отвечает. Итак, наличие "l" говорит о том, что набор аргументов запускаемой программы представлен не массивом, а отдельными строками. Противоположность "l" - постфикс "v", который сообщает, что список аргументов запускаемой программы передается в виде массива. Постфикс "e" говорит о том, что через последний аргумент функции запускаемой программе передается массив окружения. Постфикс "p" говорит о том, что в первом аргументе функции можно использовать не только путь к исполняемому файлу, а просто имя этого файла, при условии, что файл находится в одном из каталогов, обозначенных в переменной окружения PATH. Отсутствие "p" сообщает о том, что поиск по PATH не производится. Как видите, системный вызов execve() тоже отвечает этому правилу.