Вперед Назад Содержание

22. Запуск и Окончание Процесса

Процессы - примитивные модули для распределения ресурсов системы. Каждый процесс имеет собственное адресное пространство. Процесс выполняет программу; Вы можете иметь многократные процессы, выполняющие ту же самую программу, но каждый процесс имеет собственную копию программы внутри собственного адресного пространства и выполняет ее независимо от других копий.

Эта глава объясняет, что ваша программа должна делать, чтобы обработать запуск процесса, завершить процесс, и получить информацию (аргументы и среду) из родительского процесса.

22.1 Аргументы Программы

Система начинает программу C, вызывая функцию main. Вы должны написать функцию, именованную main, иначе Вы не будете способны линковать вашу программу без ошибок.

Вы можете определять main без аргументов, или брать два аргумента, которые представляют аргументы командной строки программы, примерно так:

                int main (int argc, char *argv[])
Аргументы командной строки - отделяемые пропуском лексемы, заданные в команде оболочки, используемой, чтобы вызвать программу; таким образом, в " cat foo bar ", аргументы - " foo " и " bar ". Программа может рассматривать аргументы командной строки единственым способом - через аргументы main.

Значение argc аргумента - число аргументов командной строки. Аргумент argv - вектор строк; элементы - индивидуальные строки аргументов командной строки. Имя файла выполняемой программы также включено в вектор как первый элемент; значение argc учитывает этот элемент. Пустой указатель всегда следует за последним элементом: argv [argc] - это пустой указатель.

Для команды " cat foo bar ", argc - 3, и argv имеет три элемента, " cat ", " foo " и " bar ".

Если синтаксис для аргументов командной строки вашей программы является достаточно простым, Вы может просто выбирать аргументы из argv вручную. Но если ваша программа берет фиксированное число аргументов, или все аргументы интерпретируются одинаковым образом (как имена файлов, например), Вам лучше использовать getopt, чтобы делать синтаксический анализ.

Синтаксические Соглашения Аргументов Программы

POSIX рекомендует эти соглашения для аргументов командной строки. Getopt (см. Раздел 22.1.2 [Опции Синтаксического анализа]) облегчит их реализацию.

Реализация getopt в библиотеке GNU C обычно делает так, как будто все аргументы опции были определены перед всеми аргументами не-опциями для целей синтаксического анализа, даже если пользователь вашей программы смешал опции и аргументы не-опции. Она делает это, переупорядочивая элементы массива argv. Это поведение нестандартно; если Вы хотите подавлять его, определите _POSIX_OPTION_ORDER переменную среды. См. Раздел 22.2.2 [Стандартная Среда]. GNU добавляет длинные опции к этому соглашению. Длинные опции состоят из " -- " сопровождаемых именем, составленным из алфавитно­ цифровых символов, и подчеркивания. Имена опций - обычно от одного до трех слов длинной, с дефисами, чтобы отделить слова. Пользователи могут сокращать имена опций, если только сокращения уникальны.

Пример длинной опции " --name=value ". Этот синтаксис дает возможность длинной опции принять аргумент, который является самостоятельно необязательным.

В конечном счете, система GNU будет обеспечивать длинные имена опций в оболочке.

Опции Программ Синтаксического анализа

Имеются подробности относительно того, как вызвать getopt функцию. Чтобы использовать это средство, ваша программа должна включить заглавный файл " unistd.h ".

       int opterr  (переменная)
Если значение этой переменной является отличным от нуля, то getopt, печатает сообщение об ошибках в стандартный поток ошибки, если она сталкивается с неизвестным символом опции или опцией с отсутствующим требуемым аргументом. Это - заданное по умолчанию поведение. Если Вы обнуляете эту переменную, getopt, не печатает никаких сообщений, но она все еще возвращает символ ? чтобы указывать ошибку.
       int optopt  (переменная)
Когда getopt сталкивается с неизвестным символом опции или опцией с отсутствующим требуемым аргументом, она сохраняет этот символ опции в этой переменной. Вы можете использовать ее для обеспечения ваших собственных диагностических сообщений.
       int optind  (переменная)
Эта переменная будет установлена getopt как индекс следующего элемента массива argv, который будет обработан. Если getopt нашла все аргументы-опции, Вы можете использовать эту переменную, чтобы определить, где начинаются остающиеся аргументы-не-опции. Начальное значение этой переменной 1.

       char * optarg  (переменная)
Эта переменная будет установлена getopt, чтобы указать число аргументов опций, для тех опций кторые принимают аргументы.
       int getopt (int argc, char **argv, const char *options)   (функция)
Getopt функция получает следующий аргумент-опцию списка параметров, заданного argv и argc аргументами.

Аргумент-опция - строка, которая определяет символы опции, которые являются допустимыми для этой программы. Символ опции в этой строке может сопровождаться двоеточием (": ") чтобы указать, что она берет требуемый аргумент.

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

Getopt функция возвращает символ опции для следующей опции командной строки. Когда нет больше аргументов-опций, она возвращает -1. Может все еще иметься большое количество аргументов-не-опций; Вы должны сравнить внешнюю переменную optind c параметром argc, чтобы проверить это.

Если опция имеет аргумент, getopt возвращает аргумент, сохраняя его в переменной optarg. Вы обычно не должны копировать optarg строку, так как это - указатель в первоначальный массив argv, а не в статическую область, которая могла бы быть перезаписана.

Если getopt находит символ опции в argv, который не был включен в опции, или отсутствующий аргумент некоторой опции, она возвращает "? ", устанавливает внешнюю переменную optopt как фактический символ опции. Если первый символ опции - двоеточие (":"), то getopt возвращает ":" вместо "? " Чтобы указать отсутствующий аргумент опции. Кроме того, если внешняя переменная opterr отлична от нуля (который является значением по умолчанию), getopt печатает сообщение об ошибках.

Пример Синтаксического Анализа Аргументов с getopt

Вот пример, показывающий, как getopt обычно используется:

        #include <unistd.h>
        #include <stdio.h>
        int
        main (int argc, char **argv)
        {
                int aflag = 0;
                int bflag = 0;
                char *cvalue = NULL;
                int index;
                int c;
                opterr = 0;
                while ((c = getopt (argc, argv, "abc:")) != -1)
                switch (c)
                {
                        case 'a':
                                aflag = 1;
                                break;
                        case 'b':
                                bflag = 1;
                                break;
                        case 'c':
                                cvalue = optarg;
                                break;
                        case '?':
                                if (isprint (optopt))
                                        fprintf (stderr, "Unknown option
                                                `-%c'.\n", optopt);
                                else
                                        fprintf (stderr, "Unknown option
                                character `\\x%x'.\n", optopt);
                                return 1;
                        default:
                                abort ();
                }
                printf ("aflag = %d, bflag = %d, cvalue = %s\n",
                                aflag, bflag, cvalue);
                for (index = optind; index < argc; index++)
                        printf ("Non-option argument %s\n",
                                argv[index]);
                return 0;
        }
Имеются некоторые примеры, показывающие, что эта программа печатает с различными комбинациями аргументов:
                % testopt
                aflag = 0, bflag = 0, cvalue = (null)

                % testopt -a -b
                aflag = 1, bflag = 1, cvalue = (null)

                % testopt -ab
                aflag = 1, bflag = 1, cvalue = (null)

                % testopt -c foo
                aflag = 0, bflag = 0, cvalue = foo

                % testopt -cfoo
                aflag = 0, bflag = 0, cvalue = foo

                % testopt arg1
                aflag = 0, bflag = 0, cvalue = (null)
                Non-option argument arg1

                % testopt -a arg1
                aflag = 1, bflag = 0, cvalue = (null)
                Non-option argument arg1

                % testopt -c foo arg1
                aflag = 0, bflag = 0, cvalue = foo
                Non-option argument arg1

                % testopt -a -- -b
                aflag = 1, bflag = 0, cvalue = (null)
                Non-option argument -b

                % testopt -a ­
                aflag = 1, bflag = 0, cvalue = (null)
                Non-option argument ­

Синтаксический анализ Длинных Опций

Чтобы воспринимать GNU стиль длинных опций также как одиночно­ символьные опции, используйте getopt_long вместо getopt. Вы должны заставить каждую программу принимать длинные опции, если она использует опции, это занимает немного ресурсов, и помогает новичкам помнить, как использовать программу.

       struct option  (тип данных)
Эта структура описывает одиночное длинное имя опции для getopt_long. Аргумент longopts должен быть массивом этих структур, по одной для каждой длинной опции.

Завершите массив элементом, содержащим все нули.

Структура option имеет поля:

                        const char *name
Это поле - имя опции. Это - строка.
                        int has_arg
Это поле говорит, берет ли опция аргумент. Это - целое число, и имеются три законных значения: no_argument, required_argument и optional_argument.
                        int *flag
                        int val
Эти поля управляют, как сообщать или действовать на опцию, когда она прочитана.

Если flag - пустой указатель, то val - значение, которое идентифицирует эту опцию. Часто эти значения выбраны, чтобы однозначно идентифицировать специфические длинные опции.

Если flag - не пустой указатель, это должен быть адрес int переменной, которая является флагом для этой опции. Значение в val - значение, которое нужно сохранить во флаге, чтобы указать, что опция была замечена.

       int getopt_long (int argc, char **argv, const char *shortopts, struct option *longopts, int *indexptr) (функция)
Декодирует опции из вектора argv (чья длина argc). Аргумент shortopts описывает короткие опции, принимаемые точно так же как это делается в getopt. Аргумент longopts описывает длинные опции (см. выше).

Когда getopt_long сталкивается с короткой опцией, она делает ту же самую вещь, что и getopt: она возвращает символьный код для опции, и сохраняет аргумент этой опции (если он имеется) в optarg.

Когда getopt_long сталкивается с длинной опцией, она действует, основываясь на flag и val полях определения этой опции.

Если flag - пустой указатель, то getopt_long возвращает содержимое val, чтобы указать какую опцию она нашла. Вы должны указывать различные значения в val поле для опций с различными значениями, так что Вы можете декодировать эти значения после того, как getopt_long возвращается. Если длинная опция эквивалентна короткой опции, Вы может использовать код символа короткой опции в val.

Если flag - не пустой указатель, значит эта опция должна только установить флаг в программе. Флаг - переменная типа int, что Вы и определяете. Поместите адрес флага в поле flag. Поместите в val поле значение, которое Вы хотели бы, чтобы эта опция сохранила во флаге. В этом случае, getopt_long возвращает 0.

Для любой длинной опции, getopt_long сообщает Вам индекс в массиве longopts определения опций, сохраняя его в *indexptr. Вы можете получить имя опции через longopts [*indexptr].name . Так что Вы можете различать длинные опции или значениями в их val полях или их индексами.

Когда длинная опция имеет аргумент, getopt_long помещает значение аргумента в переменную optarg перед возвращением. Когда опция не имеет никакого аргумента, значение в optarg - пустой указатель.

Когда getopt_long не имеет больше опций для обработки, она возвращает -1, и оставляет в переменной optind индекс следующего остающегося аргумента в argv.

Пример Синтаксического анализа Длинных Опций

        #include <stdio.h>
        static int verbose_flag;
        int
        main (argc, argv)
                                int argc;
                                char **argv;
        {
                int c;
                while (1)
                {
                        static struct option long_options[] = {
                                {"verbose", 0, &verbose_flag, 1},
                                {"brief", 0, &verbose_flag, 0},
                                {"add", 1, 0, 0},
                                {"append", 0, 0, 0},
                                {"delete", 1, 0, 0},
                                {"create", 0, 0, 0},
                                {"file", 1, 0, 0},
                                {0, 0, 0, 0}
                        };
                        int option_index = 0;
                        c = getopt_long (argc, argv, "abc:d:",
                        long_options, &option_index);
                        if (c == -1)
                                break;
                        switch (c)
                        {
                                case 0:
                                   if (long_options[option_index].flag
                                                                        != 0)
                                        break;
                                   printf ("option %s",
                 long_options[option_index].name);
                                   if (optarg)
                                        printf (" with arg %s", optarg);
                                        printf ("\n");
                                        break;
                                case 'a':
                                        puts ("option -a\n");
                                        break;
                                case 'b':
                                        puts ("option -b\n");
                                        break;
                                case 'c':
                                        printf ("option -c with value
                                                `%s'\n", optarg);
                                        break;
                                case 'd':
                                        printf ("option -d with value
                                                `%s'\n", optarg);
                                        break;
                                case '?':
                /* getopt_long already printed an error message. */
                                        break;
                                default:
                                        abort ();
                        }
                }
                if (verbose_flag)
                        puts ("verbose flag is set");
       /* Печатаем любые остающиеся аргументы командной строки
                                                        (не опции). */
                        if (optind < argc)
                        {
                                printf ("non-option ARGV-elements: ");
                                while (optind < argc)
                                        printf ("%s ", argv[optind++]);
                                putchar ('\n');
                        }
                        exit (0);
                }

22.2 Переменные среды

Когда программа выполняется, она получает информацию относительно контекста, в котором она вызывалась двумя способами. Первый механизм использует argv и argc аргументы функции main, и обсужден в Разделе 22.1 [Аргументы Программы]. Второй механизм использует переменные среды и обсужден в этом разделе.

Механизм argv обычно используется, чтобы передать аргументы командной строки, специфические для специфической вызываемой программы. Среда, с другой стороны, следит за информацией, которая разделена многими программами, и к ней менее часто обращаются.

Переменные среды, обсужденные в этом разделе - те же самые переменные среды, что Вы устанавливаете используя присванивание и команду export в оболочке. Программы, выполненные из оболочки наследуют все переменные среды из оболочки.

Стандартные переменные среды используются для уточнения инфрмации относительно исходного каталога пользователя, типа терминала, текущего стандарта, и так далее; Вы можете определять дополнительные переменные для других целей. Набор всех переменных среды, которые имеют значения, общеизвестен как среда.

Имена переменных среды чувствительны к регистру и не должны содержать символ "=".

Определенные системой переменные среды неизменны относительно верхнего регистра.

Значения переменных среды могут быть чем угодно, что может представляться как строка. Значение не должно содержать внедренный пустой символ, так как им принято завершать строку.

Доступ к Среде

К значению переменной среды можно обращаться getenv функцией. Это объявлено в заглавном файле " stdlib.h ".

       char * getenv (const char *name)  (функция)
Эта функция возвращает строку, которая является значением переменной среды. Вы не должны изменять эту строку. В некоторых системах не-UNIX, не использующих библиотеку GNU, она может быть перезаписана поверх последующими обращениями к getenv (но не к любой другой библиотечной функции). Если имя переменной среды не определено, значение - пустой указатель.

       int putenv (const char *string)  (функция)
Putenv функция добавляет или удаляет определения из среды. Если строка имеет форму " name=value ", определение будет добавлено к среде. Иначе, строка интерпретируется как имя переменной среды, и любое определение для этой переменной в среде будет удалено.

Библиотека GNU обеспечивает эту функцию для совместимости с SVID; она не может быть доступна в других системах.

Вы можете иметь дело непосредственно с основным представлением объектов среды, чтобы добавить большое количество переменных к среде (например, связываться с другой программой, которую Вы собираетесь выполнять; см. Раздел 23.5 [Выполнение Файла]).

       char ** environ  (переменная)
Среда представляется как массив строк. Каждая строка имеет формат " name=value ". Порядок, в котором строки появляются в среде не значителен, но то же самое имя не должно появиться больше чем один раз. Последний элемент массива - пустой указатель.

Эта переменная объявлена в заглавном файле " unistd.h ".

Если Вы хотите только получить значение переменной среды, использует getenv.

Стандартные Переменные среды

Эти переменные среды имеют стандартные значения. Это не означает, что они всегда представляются в среде; но если эти переменные присутствуют, они имеют эти значения, и Вы не должны пробовать использовать эти имена переменных среды для некоторой другой цели.

       HOME
Это - строка представляет исходный каталог пользователя, или начальное значение рабочего каталога по умолчанию.

Пользователь может устанавливать HOME как любое значение. Если, Вы должны получить соответствующий исходный каталог для специфического пользователя, Вы не должен использовать HOME; взамен, найдите имя пользователя в базе данных пользователей (см. Раздел 25.12 [База данных Пользователей]).

       LOGNAME
Это - имя пользователя, используемое для входа в систему. Так как значение в среде может быть произвольно, это - не надежный способ идентифицировать пользователя, который выполняет процесс; функция getlogin (см. Раздел 25.11 [Кто Вошел В Систему] ) лучше для той цели.

Для большинства целей, лучше использовать LOGNAME, потому что она позволяет пользователю определять значение.

       PATH
Путь - последовательность имен каталогов, которая используется для поиска файла. Переменная PATH содержит путь, используемый для поиска программ, которые будут выполнены.

Execlp и execvp функции (см. Раздел 23.5 [Выполнение Файла]) используют эту переменную среды, как и многие оболочки и другие утилиты, которые выполнены в терминах этих функций.

Синтаксис пути - последовательность имен каталогов, отделяемых двоеточиями. Пустая строка вместо имени каталога замещает текущий каталог (см. Раздел 9.1 [Рабочий каталог]).

Типичное значение для этой переменной среды могло бы быть: :/bin:/etc:/usr/bin:/usr/new/X11:/usr/new:/usr/local/bin

Это означает что, если пользователь пробует выполнять программу, именованную foo, система будет искать файлы, именованные " foo ", " /bin/foo ", " /etc/foo ", и так далее. Первый из этих файлов, который существует - будет выполнен.

       TERM
Определяет вид терминала, который получает вывод программы. Некоторые программы могут использовать эту информацию, чтобы пользоваться преимуществом специальных escape-последовательностей или режимов терминала, обеспечиваемых специфическими видами терминалов. Многие программы, которые используют termcap библиотеку (см. раздел " Поиск Описания Терминала " в Библиотечном Руководстве Termcap) использует переменную среды TERM.
       TZ
Определяет часовой пояс. См. Раздел 17.2.5 [Переменная TZ], для уточнения инфрмации относительно формата этой строки и как она используется.
       LANG
Определяет заданный по умолчанию стандарт, используемый для категорий атрибутов, если ни LC_ALL ни специфическая переменная среды для этого класса не установлены. См. Главу 19 [Стандарты], для получения более подробной информации.
       LC_COLLATE
Определяет какой стандарт использовать для строковой сортировки.
       LC_CTYPE
Определяет какой стандарт использовать для символьных наборов и символьной классификации.
       LC_MONETARY
Определяет какой стандарт использовать для форматирования валютных значений.
       LC_NUMERIC
Определяет какой стандарт использовать для форматирования чисел.
       LC_TIME
Определяет то, какой стандарт использовать для форматирования даты/времени.
       _POSIX_OPTION_ORDER
Если эта переменная среды определена, она подавляет обычное переупорядочение аргументов командной строки getopt. См. Раздел 22.1.1 [Синтаксис Аргумента].

22.3 Завершение Программы

Обычный способ завершения программы - просто возврат функции main. Значение состояния выхода, возвращенное из функции main используется, чтобы сообщить информацию обратно родительскому процессу или оболочке.

Программа может также завершаться вызывая функцию exit.

Кроме того, программы могут быть завершены сигналами; это обсуждено более подробно в Главе 21 [Обработка Сигналов]. Функция abort вызывает сигнал, который уничтожает программу.

Нормальное Окончание

Процесс завершается обычно, когда программа вызывает exit. Возвращение из main эквивалентно вызову exit, и значение, которое main возвращает, используется как аргумент exit.

       void exit (int status)  (функция)
Функция exit завершает процесс с состоянием status. Эта функция не возвращается.

Нормальное окончание вызывает следующие действия:

  1. Функции, которые были зарегистрированы с atexit или on_exit функциями, вызываются в обратном порядке их регистрации. Этот механизм позволяет вашему приложению определять собственные действия "очистки", которые нужно выполнить по окончании программы. Обычно, это используется, чтобы делать вещи подобно сохранению информации о состоянии программы в файле, или размыкании блокировок в базах общих данных.
  2. Все открытые потоки будут закрыты. См. Раздел 7.4 [Закрытие Потоков]. Кроме того, временные файлы, открытые с tmpfile функцией будут удалены; см. Раздел 9.10 [Временные Файлы].
  3. _exit вызывается, завершая программу. См. Раздел 22.3.5 [Внутренняя организация Окончания].

Состояние Выхода

Когда программа выходит, она может возвращать родительскому процессу малое количество информации относительно причины окончания, используя состояние exit. Это - значение между 0 и 255, которое выходящий процесс передает как аргумент exit.

Обычно Вы должны использовать состояние exit, чтобы сообщить очень широкую информацию относительно успеха или отказа. Вы не можете обеспечивать множество подробностей относительно причин для отказа, да и большинство родительских процессов не требуют много подробностей.

Имеются соглашения для того, что некоторые программы должны возвратить. Наиболее общее соглашение - просто 0 для успеха и 1 для отказа. Программы, которые выполняют сравнение, используют другое соглашение: они используют состояние, 1, чтобы указать несоответствие, и состояние 2, чтобы указать неспособность сравнить. Ваша программа должна следовать за существующим соглашением, если существующее соглашение имеет смысл для нее.

Общее соглашение резервирует значения 128 состояний и более для специальных целей. В частности значение 128 используется, чтобы указать отказ выполнить другую программу в подпроцессе. Это соглашение не удовлетворяет унивверсальным условиям, но неплохо следовать за ним в ваших программах.

Предупреждение: Не пробуйте использовать число ошибок как состояние exit. Это фактически не очень полезно; родительский процесс вообще не должен заботиться, сколько ошибок произошло. К тому же значение состояния усекается до восьми бит. Таким образом, если программа пробовала передать 256, родитель получит 0 шибок т.е. успех.

По той же самой причине не работает использование значения errno как состояния exit.

Примечание Переносимости: Некоторые не-posix системы используют различные соглашения для значений состояния exit. Для большей переносимости, Вы можете использовать макрокоманды EXIT_SUCCESS и EXIT_FAILURE для стандартного значения состояния успеха и отказа, соответственно. Они объявлены в файле " stdlib.h ".

       int EXIT_SUCCESS  (макрос)
Эта макрокоманда может использоваться с функцией exit, чтобы указать успешное завершение программы.

На системах POSIX, значение этой макрокоманды - 0. В других системах, значение может быть другим (возможно не-константа) целочисленным выражением.

       int EXIT_FAILURE  (макрос)
Эта макрокоманда может использоваться с функцией exit, чтобы указать неудачное завершение программы в общем смысле.

На системах POSIX, значение этой макрокоманды 1. На других системах, значение может быть другим.

Очистки на Выходе

Ваша программа может выполнить собственные функции очистки при нормальное окончании. Ненадежно вызывать функции очистки явно перед выходом. Намного лучше делать очистку невидимой для приложения, устанавливая функцию очистки используя atexit или on_exit.

       int atexit (void (*function) (void))  (функция)
Atexit функция регистрирует функцию function, которую нужно вызвать при нормальном окончании программы. Функция вызывается без аргументов.

Возвращаемое значение из atexit - нуль при успехе и отличное от нуля, если функция не может быть зарегистрирована.

       int on_exit (void (*function)(int status, void *arg), void *arg)
Эта функция - несколько более мощный вариант atexit. Она принимает два аргумента, функцию и произвольный указатель. При нормальном окончании программы, функция вызывается с двумя аргументами: значением состояния, переданным exit, и параметром arg.

Эта функция включена в библиотеку GNU C только для совместимости с SunOS, и может не обеспечиваться другими реализациями.

Имеется тривиальная программа, которая иллюстрирует использование exit и atexit:

                        #include <stdio.h>
                        #include <stdlib.h>
                        void
                        bye (void)
                        {
                                puts ("Goodbye, cruel world....");
                        }
                        int
                        main (void)
                        {
                                atexit (bye);
                                exit (EXIT_SUCCESS);
                        }
Когда эта программа выполнена, она печатает сообщение и выходит.

Прерывание выполнения Программы

Вы можете прервать вашу программу, используя функцию abort. Прототип для этой функции находится в " stdlib.h ".

       void abort (void)  (функция)
Функция abort вызывает аварийное окончание программы. Она не выполняет функции очистки, зарегистрированные с atexit или on_exit.

Эта функция фактически завершает процесс, вызывая сигнал SIGABRT, и ваша программа может включать обработчик, чтобы прервать этот сигнал; см. Главу 21 [Обработка Сигнала].

Внутренняя организация Окончания

Функция _exit - примитив, используемый для окончания процесса exit. Она объявлена в заглавном файле " unistd.h ".

       void _exit (int status)  (функция)
_exit - функция для завершения процесса с состоянием status. Вызов этой функции не выполняет функции очистки, зарегистрированные с atexit или on_exit.

Когда процесс завершается по любой причине либо явным запросом окончания, либо окончанием в результате сигнала, производятся следующие действия:


Вперед Назад Содержание