2. Создание многопроцессных программы на языке Си с использованием системных вызовов Unix
Любая программа в Unix-системах выполняется в виде одного (корневого) процесса или набора процессов. В лабораторной работе №1 мы уже рассмотрели, как происходит запуск программы, становимся теперь на том, как это происходит с точки зрения реализации на Си с использованием системных вызовов Unix.
Программно порождение процессов осуществляется с использованием системного вызова fork() – после выполнения этого вызова в системе появляется процесс, который является точной копией процесса, который выдал данный системный вызов. Но для запуска программ одного вызова fork() не достаточно – необходим механизм позволяющий загрузить бинарный код программы в оперативную память. Такой механизм предоставляет функции группы execl(execl, execlp, execle, execv, execvp). Функция данной группы осуществляет подмену кода процесса ее вызвавшего бинарным кодом загружаемой программы(Пример 2.2).
Когда вы в командном интерпретаторе набираете команду Например $ps - фрагмент кода программы запускающий данную команду будет выглядеть примерно так:
Пример 2.2 Запуск команды ps в командном интерпретаторе
main()
int st;
…
if (fork()==0)
execlp("ps" , "ps", 0);
wait(&st);
Остановимся теперь более подробно на функциях порождения и замещения процессов. Для начала дадим несколько утверждений:
o Процесс, который инициировал системный вызов fork(), принято называть родительским процессом (parent process).
o Вновь порожденный процесс принято называть процессом потомком (child process).
o Процесс потомок является почти полной копией родительского процесса
o Процесс родитель и процесс потомок разделяют один и тот же кодовый сегмент. Системный вызов fork() в случае успеха возвращает родительскому процессу идентификатор потомка, а потомку 0. В ситуации, когда процесс не может быть создан функция fork() возвращает -1.
При программировании процессов вам понадобится следующее:
a) Аргументы функции main:
void main(int argc, char *argv[], char *envp[]);
Если вы наберете команду $ a.out a1 a2 a3,где a.out – имя запускаемой вами программы, то функция main программы из файла a.out вызовется с:
argc = 4 /* количество аргументов */
argv[0] = "a.out" argv[1] = "a1"
argv[2] = "a2" argv[3] = "a3"
argv[4] = NULL
По соглашению argv[0] содержит имя выполняемого файла.
b) Информация о так называемом "окружении" (вспомните переменные окружения описанные в лабораторной работе №1) char *envp[], продублированного также в предопределенной переменной extern char **environ;
Окружение состоит из строк вида
"ИМЯПЕРЕМЕННОЙ=значение"
Массив этих строк завершается NULL (как и argv). Для получения значения переменной с именем ИМЯ существует стандартная функция
char *getenv( char *ИМЯ );
Она выдает либо значение, либо NULL если переменной с таким именем нет.
c) Информация об открытых по умолчанию файлов. По умолчанию (неявно) всегда открыты 3 канала:
ВВОД В Ы В О Д
FILE * stdin stdout stderr
соответствует fd 0 1 2
Эти каналы достаются процессу "в наследство" от запускающего процесса и связаны с дисплеем и клавиатурой, если только не были перенаправлены.
Кроме того, программа может сама явно открывать файлы (при помощи системных вызовов open, creat, pipe, fopen и т.п.). Всего программа может одновременно открыть до 20 файлов (считая стандартные каналы), а в некоторых системах и больше (например, 64).
d) Получение информации об уникальном номере процесса потомка и процесса-родителя и др.
Пример 2.3 Системные вызовы для получения идентификаторов процесса
#include <stream.h>
#include <sys/types.h>
#include <unistd.h>
main ( )
{
cout << " Идентификатор текущего процесса PID: " << getpid() <<” \n”;
cout << " Идентификатор родительского процесса- PPID: " << getppid()<<” \n”;
cout << " Идентификатор группы процесса PGID: " << getpgrp()<<” \n”;
cout << " Идентификатор пользователя –real UID: " << getuid() <<” \n”;
cout << " Реальный идентификатор группы пользователя real -GID:" <<
getgid()<<” \n”;
cout << " Эффектифный идентификатор пользователя - UID: " <<
geteuid()<<” \n”;
cout << " Эффектифный идентификатор группы пользователя GID: " <<
getegid()<<” \n”;
exit(0);
}
е) Текущий каталог - достается в наследство от процесса-"родителя", и может быть затем изменен системным вызовом
chdir(char *имя_нового_каталога);
В командном режиме вы можете получить информацию о текущем каталоге, набрав команду $pwd.
