Другие журналы

научное издание МГТУ им. Н.Э. Баумана

НАУКА и ОБРАЗОВАНИЕ

Издатель ФГБОУ ВПО "МГТУ им. Н.Э. Баумана". Эл № ФС 77 - 48211.  ISSN 1994-0408

Взаимодействие процессов в OS UNIX

#11 ноябрь 2004

ОПЕРАЦИОННЫЕ СИСТЕМЫ И СРЕДЫ

А.Н. Божко канд. техн. наук, С.В. Родионов, МГТУ им. Н.Э. Баумана

 

Взаимодействие процессов в OS UNIX

 

Рассматриваются вопросы взаимодействия процессов в операционной системе UNIX. Подробно обсуждаются: обработка сигналов, программные каналы, схемы взаимодействия клиент — сервер.

 

Операционная система UNIX существует около тридцати лет. Пользователям серверов и рабочих станций хорошо известны ее привлекательные стороны: высокая устойчивость, вытесняющая многозадачность, переносимость, мощные средства администрирования, инструментальная поддержка разработчика и др. Бурное развитие технических возможностей персональных компьютеров сделало возможным перенос полных версий UNIX на платформу РС. Однако она еще не стала по-настоящему массовой операционной системой. Не в последнюю очередь этому препятствует отсутствие описания внутреннего устройства UNIX.

Данная статья призвана восполнить этот пробел. Она является продолжением цикла работ по внутреннему устройству [2, 3].

 

Средства взаимодействия процессов

Операционная система UNIX является многозадачной системой. Параллельное выполнение нескольких процессов — это обычная практика ее функционирования, поэтому проблема управления взаимодействием процессов является актуальной. Любой процесс в OS UNIX может находиться в двух фазах: “пользователь” и “система”. В фазе “пользователь” процесс выполняет инструкции своего сегмента адресного пространства (подробнее об этом см. [2]). Для управления взаимодействием процессов в фазе “пользователь” OS UNIX предоставляет средства внешней синхронизации и коммуникации процессов.

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

Под коммуникацией процессов в OS UNIX понимается реализация обмена данными между параллельно выполняющимися процессами. Наиболее очевидным способом такого взаимодействия процессов является обмен данными через файл, когда два или более процессов записывают данные в некоторый файл, выбранный по договоренности, а другие процессы читают данные из этого файла.

Способ коммуникации процессов через файл имеет следующие недостатки:

·        ограничения по объему файла обмена;

·        ограничение прав доступа к файлу обмена;

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

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

Из-за отмеченных недостатков коммуникация процессов через файл применяется редко. OS UNIX имеет более совершенные средства межпроцессорной коммуникации, обычно именуемые РС (Inter Process Communication). Средства IPC свободны от недостатков межпроцессорного обмена через файл. Традиционно к средствам IPC в OS UNIX относятся: именованные и неименованные (обычные) программные каналы, разделяемая память, семафоры и очереди сообщений.

 

Обработка сигналов

Сигналы генерируются, когда происходят определенные события, которые вызывают посылку сигнала. Сигнальные события могут быть вызваны программными и аппаратными причинами, прерыванием от терминала, результатом измерения времени и выполнением других процессов. Если инициатор сигнала — ядро, другой процесс или интерактивные действия пользователя, то сигнал считается асинхронным по отношению к процессу-приемнику сигнала. Когда сигнал инициируется выполнением инструкций процесса-приемника, сигнал считается синхронным. В обоих случаях получение сигнала вызывает прерывание работы процесса-приемника сигнала и выполнение заранее определенных действий по его обработке.

Каждый сигнал имеет уникальный номер, однозначно определяющий событие, которым он вызван. Классические версии OS UNIX определяли 16 сигналов с номерами от 1 до 16. В современных версиях OS UNIX список сигналов расширен до 32. Для удобства спецификации сигналов используются символьные мнемоники. Ниже перечислены мнемоники основных сигналов, с кратким описанием событий, инициирующих их появление.

А синхронные терминальные сигналы

1. SIGHUP - разрыв связи с терминалом;

2. SIGINT - прерывание от терминала, генерируется при нажатии Ctrl-C;

3. SIGQUIT - сигнал выхода, генерируется при нажатии Ctrl-\.

Синхронные сигналы ловушки

4. SIGILL - попытка выполнить нелегальную машинную инструкцию;

5. SIGFPE - десятичное переполнение или деление на 0;

6. SIGBUS - ошибка шины при неверной косвенной адресации;

11. SIGSEGV - нарушение границ сегментов адресного пространства;

12. SIGSYS - неверный параметр при обращении к системному вызову;

13. SIGPIPE - разрыв программного канала.

Сигнал от таймера реального времени

14. SIGARM - ядром при истечении интервала, заданного системным выходом alarm.

Сигналы от событий е других процессах или системе

9. SIGKILL - безусловное завершение процесса;

15. SIGTERM - условное завершение процесса;

16. SIGUSR 1 - сигнал, определенный пользователем;

17. SIGUSR 2 - сигнал, определенный пользователем;

18. SIGCLD - изменение статуса процесса-потомка;

19. SIGPWR - рестарт по сбою питания;

23. SIGSTOP - приостановка выполнения процесса;

24. SIGCONT - возобновление выполнения приостановленного процесса.

Возможность принудительной посылки конкретному процессу или группе процессов определенного сигнала даже при отсутствии реальной причины его возникновения обеспечивают системный вызов и команда kill. Их аргументы задают номер сигнала и идентификатор процесса-приемника сигнала. Значение идентификатора процесса-приемника должно быть больше 0, когда сигнал посылается процессу индивидуально. При распределенной посылке сигнала аргумент идентификации приемника может быть равен 0, когда сигнал посылается группе процессов; равен - 1, когда сигнал посылается всем процессам владельца-источника сигнала; меньше -1, когда сигнал посылается всем процессам группы владельца процесса, инициатора сигнала.

Для организации обработки сигналов используются следующие сигнальные поля структуры struct proc дискрептора процесса-приемника сигнала: p_sig, p_hold, p_cursig, и p_catch.

Поле p_sig используется для регистрации номера принятого сигнала. При этом применяется следующий способ регистрации. Если принят сигнал с номером n, то устанавливается бит (n - 1) поля p_sig который будет сброшен после обработки сигнала. Например, значение поля p_sig равное 5, означает регистрацию сигналов SIGHUP в SIGQUIT с номерами 1 и З соответственно. Следует отметить, что OS UNIX не поддерживает очередь сигналов, поэтому, если процесс принял последовательно несколько одинаковых сигналов до начала обработки первого из них, то будет обработан только результат последнего приема. Очевидно, что разнотипные сигналы могут быть одновременно зарегистрированы в поле p_sig и обработаны в порядке их поступления.

Поле p_cursig сохраняет номер текущего сигнала, который должен быть обработан.

Поле p_hold фиксирует номера сигналов, обработка которых должна быть задержана на период выполнения какого-либо блока инструкций процедурного сегмента программы процесса. Блок программы процесса, когда появление определенных сигналов нежелательно, должен быть ограничен системными вызовами sighold и sigrelse, которые соответственно задерживают и освобождают обработку указанных сигналов.

Поля p_ignore и p_catch определяют форму реакции процесса на получение сигнала. После получения сигнала возможны три альтернативы реакции процесса: обработка по умолчанию, игнорирование и перехват. Вид реакции устанавливает системный вызов signal которому в качестве аргументов передается номер сигнала и адрес функции обработки.

Когда нужно установить стандартную обработку, принятую по умолчанию, в системный вызов signal вместо адреса обработки передается 0 или макрос SIG_DEL. Стандартной реакцией процесса на большинство сигналов является принудительное завершение процесса. Реакция по умолчанию на такие сигналы, как SIGQUIT, SIGFPE, SIGSEGV, SIGBUS приводит к принудительному завершению процесса с образованием файла дампа памяти.

Когда в процессе нужно игнорировать сигнал, в системный вызов signal вместо адреса функции обработки передается 1 или макрос SIG_IGN. Почти все сигналы могут быть принудительно игнорированы. По умолчанию игнорируются сигналы, которые не должны приводить к завершению процесса, например SIGCLD, SIGUSR 1, SIGUSR 2.

Под перехватом сигнала понимается передача в системный вызов signal адреса функции, которая реализует желаемую реакцию процесса на данный сигнал. Следует отметить, что после перехвата сигнала автоматически восстанавливается стандартная реакция на него. Поэтому целесообразно переустанавливать перехват сигнала системным вызовом . когда нужно сохранить его нестандартную обработку. Когда необходимость в перехвате сигнала исчерпана, следует восстановить обработку по умолчанию, используя системный вызов signal с аргументом 0 или SIG_DEL. Следует отметить, что сигнал безусловного завершения SIGKILL с номером 9 не может быть игнорирован или перехвачен никаким процессом. Приводимый ниже фрагмент С-кода демонстрирует перехват сигнала прерывания от терминала:

# include <signal.h>

/* Функция обработки сигнала SIGINT*/

voit handler () {

puts (“Сигнал SIGINT перехвачен”);

return;

} /*handler*/

/*Основная функция программы процесса*/

main (){

/*Установка перехвата сигнала SIGINT*/

signal (SIGINT, handler);

/*Код программы процесса, где перехвачен SIGINT*/

………………………………………………

/*Восстановление стандартной обработки сигнала SIGINT*/

signal (SIGINT, SIG_DEL);

}/*main*/

Установленная реакция процесса на сигналы отражается контекстом процесса в форме таблицы сигналов u_signal. Записи этой таблицы упорядочены по номерам сигналов в виде массива адресов их функций обработки. Формально каждый элемент таблицы сигналов может принимать одно из трех значений в соответствии с выбранной альтернативой обработки:

u_signal [i]=0 - принята обработка по умолчанию сигнала i + 1;

u_signal [i]=1 - установлено игнорирование сигнала i+1;

u_signal [i]=n адрес функции обработки в адресном пространстве процесса при перехвате сигнала i+1.

Порожденный процесс наследует весь контекст обработки сигналов от процесса-предка. После смены программы выполнения процесса системным вызовом семейства exec установленная нестандартная обработка сигналов будет игнорирована. Если сигнал был принят во время реализации системного вызова, то выполнение последнего будет прервано для обработки сигнала. После завершения обработки сигнала прерванный системный вызов перезапускается автоматически. В заключение следует отметить, что механизм обработки сигналов в OS UNIX представляет собой реализацию эффективной методологии событийного программирования.

 

Программные каналы

Программный канал есть средство коммуникации процессов, которое можно рассматривать как программно реализованную линию связи для передачи данных между двумя или более процессами. Каналы реализованы в виде снабженных специальным механизмом управления буферов ввода-вывода, которые рассматриваются как псевдофайлы. Формальное сходство с файлами позволяет использовать одинаковый аппарат для доступа к данным в канале в файле. Такой подход соответствует базовой концепции унификации ввода-вывода в OS UNIX. Наличие каналов дает возможность исключить недостатки организации межпроцессорного обмена через файлы.

Каналы принято использовать следующим образом. Один процесс направляет (записывает) данные в канал. Другой процесс получает (читает) данные из канала. Этот простейший канальный вариант взаимодействия процессов иллюстрирует рис. 1.

 

Рис. 1. Одноканальная схема обмена двух процессов

 

Следует отметить, что в OS UNIX нет принципиальных препятствий для организации обмена нескольких процессов через один или более каналов. Из одноканальных схем наиболее целесообразной в практическом плане является схема с одним читающим процессом-сервером и несколькими пигвущими процессами-клиентами, которая показана на рис. 2.

 

Рис. 2. Одноканальная схема обмена клиент-сервер

 

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

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

 

Рас. З. двухканальный двунаправленный обмен двух процессов

 

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

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

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

 

Рис. 4. Формальная модель канала

 

OS UNIX поддерживает два типа каналов: неименованные (или обычные) и именованные (или FIFO-файлы). Неименованные каналы поддерживают обмен данными только между процессами-родственниками, например, между предком и потомком или между потомками одного предка. Именованные каналы могут быть использованы для коммуникации любых необязательно родственных, процессов. Каналы указанных типов создаются и открываются по-разному, но используются одинаково.

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

Полученные канальные дескрипторы будут наследоваться всеми потомками, которые порождены данным предком, после реализации системного вызова pipe вместе с таблицей открытых файлов контекста предка. Потомки и предок могут освободить (закрыть) в своих контекстах тот канальный дескриптор, который не будет использован для обмена. Если обычный канал создается только для коммуникации потомков и не предполагается реализация их обмена с предком, последний может освободить оба канальных дескриптора в своем контексте. Во всех перечисленных случаях нужно использовать системный вызов close, чтобы освободить лишние канальные дескрипторы.

В отличие от обычного канала каждому именованному каналу должен соответствовать оригинальный по маршрутному имени канальный файл, который может быть расположен в произвольном каталоге файловой системы OS UNIX. Для создания канального файла в прикладной программе можно использовать системные вызовы mkfifo или mknod. Системный вызов mkfifo ориентирован исключительно на создание FIFO-файлов. Системный вызов mknod может быть использован для создания новых файлов любых типов. Аналогично в командном режиме именованный канал может быть создан специальной командой /etc/mkfofo или универсальной командой /etc/mknod.

Для начала работы с именованным каналом он должен быть открыт во всех процессах, которые будут использовать его как средство обмена. Чтобы открыть именованный канал, каждый заинтересованный в обмене процесс должен использовать системный вызов open, которому передается имя канального файла и желаемый режим обмена — чтение или запись. Системный вызов open является универсальным средством открытия любых файлов, но для FIFO-файлов его реализация имеет свои особенности. В частности, когда именованный канал открывается по чтению (записи), системный вызов open переводит реализующий его процесс в состояние ожидания, пока канал не будет открыт по записи (чтению) каким-либо другим процессом. При успешном завершении системный вызов open возвращает канальный дескриптор чтения или записи, который может быть использован для работы с каналом в контексте данного процесса и его потенциальных потомков.

Обмен данными через обычный и именованный каналы реализован одинаково через системные вызовы read и write. Для работы с каналом им передаются соответствующий канальный дескриптор чтения или записи, адрес и размер объекта обмена. Так же, как системный вызов open, системные вызовы read и write являются универсальным средством ввода-вывода данных через заданный дескриптор в контексте процесса, которое с равным успехом применимо для работы с каналом, каталогом, обычным и специальным файлами. Однако канальная реализация этих универсальных системных вызовов ввода-вывода имеет специфические особенности.

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

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

Блокировка чтения пустого и записи полного канала может быть исключена переводом канала в режим неблокированного ввода-вывода путем установки режимных флагов O_NDELAY или O_NONBLOCK с помощью системного вызова fcntl. Установка режимных флагов обеспечивает немедленный возврат кода 0 или -1 системными вызовами ввода-вывода read и write при попытке чтения пустого канала или записи в полный канал, для именованного канала режим работы неблокированного ввода-вывода может быть изначально заказан при открытии канала системным вызовом open.

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

Приводимый ниже фрагмент С-кода демонстрирует передачу содержимого текстовой строки между двумя процессами через существующий именованный канал chanel. Первый процесс выполняет программу writer, обеспечивая запись строки в канал.

/*Программа процесса записи строки в канал chanel*/

main ( ) {

static char *str=”Hello”; /*передаваемая строка*/

int fd; /*дескриптор канала*/

fd=open (chanel, 1”); /*открыть канал по записи*/

write(fd, str, strlen (str)); /*записать данные в канал*/

close (fd); /*освободить дескриптор канала*/

exit (0); /*завершить процесс write*/

}/*main writer*/

Второй процесс, выполняя программу reader, обеспечивает чтение данных из канального файла chanel и распечатывает их на стандартный вывод.

/*Программа процесса чтения данных из канала chanel*/

main ( ) {

char c; /*принимаемый символ*/

int fd; /*канальный дескриптор*/

fd=open (chanel”, 0); /*открыть канал по чтению*/

while (read (fd, &c, 1)>0) /*чтение данных из канала*/

close (fd); /*освободить дескриптор канала*/

exit (0); /*завершить процесс reader*/

}/*main reader*/

Обмен данными через именованный канал chanel может быть реализован путем запуска процессов writer и reader с отдельных экранов, причем последовательность запуска значения не имеет.

В практике работы с OS UNIX обычные каналы используются для организации конвейера команд. Конвейер образует цепочка параллельных процессов, реализующая последовательную обработку данных. Процессы выполнения соседних команд конвейера взаимодействуют через обычный канал. Для обозначения каналов в конвейере используется символ “|” который понимает командный процессор при разборе командной строки. Стандартный вывод каждого конвейерного процесса, кроме последнего в цепочке команд, перенаправляется на вывод в канал. Стандартный ввод каждого конвейерного процесса, кроме первого в цепочке команд, перенаправляется на ввод из канала. Например, следующий конвейер обеспечивает постраничный просмотр содержания текущего каталога в длинном формате:

ls-l\more

Для программной реализации перенаправления ввода- вывода при конвейерной обработке могут быть использованы системные вызовы dup, dup2 и fcntl (режим F_DUPFD) в сочетании с системным вызовом close. Эти средства позволяют ассоциировать заданный дескриптор канала с минимальным по номеру свободным элементом таблицы открытых файлов контекста процесса. Например, приводимый ниже фрагмент С-кода освобождает файловый дескриптор стандартного вывода (по умолчанию равный 1) и делает его синонимом дескриптора fd, который соответствует ранее открытому файлу или каналу:

close(1); /*освобождает дескриптор стандартного вывода*/

dup (fd); /*дублирует дескриптор fd*/

/*освобождает дубликат дескриптора fd*/

Аналогичная последовательность действий выполняется для перенаправления стандартного ввода в канал или файл. Следующий, более представительный, фрагмент С-кода моделирует интерпретацию приведенного выше конвейера командным процессором Shell с помощью обычного канала и рассмотренных средств перенаправления ввода-вывода:

/*Модель конвейера команд: ls-l|more*/

main ( ) {

int fd [2]; /*массив канальных дескрипторов*/

pipe (fd); /*инициализация канальных дескрипторов*/

/*Создание 1-го процесса потомка*/

If (fork( )= =0) {

close (1); /*Стандартный вывод*/

dup (fd[1]); /*перенаправляется*/

close (fd[0]); /*на вывод в канал*/

close (fd[1]); /*по дескриптору в fd[1].*/

excels (“/bin/ls”, “ls”,”-l”, 0); /*Замены программы 1-го*/

exit (1); /*потомка*/

} /*if*/

/*Создание 2-го процесса потомка*/

if (fork ( )= =0) {

close (0); /*Стандартный ввод*/

dup (fd [0]); /*перенаправляется*/

close (fd[0]); /*на ввод из канала*/

close (fd[1]); /*по дескриптору fd[0].*/

execl (“/bin/more”, “more”, 0); /*Замены программы 2-го*/

exit (1); /*потомка*/

} /*if*/

/*Закрытие канала в процессе-предке*/

close (fd [0]);

close (fd [1]);

/*Ожидание завершения потомков*/

while (wait (0)!=(-1));

exit (0);

} /*main*/

 

Список литературы

1. Беляков М.И., Ливеровский А.Ю., Семик В.П., Шяудкулис В.И. Инструментальная мобильная операционная система ИНМОС. М.: Финансы и статистика, 1985.

2. Божко А.Н., Родионов С.В. Внутреннее устройство UNIX. Организация процессов // Информационные технологии. 1996. № 1. С. 38—42.

3. Божко А.Н., Родионов С.В. Внутреннее устройство UNIX. Синхронизация и диспетчеризация процессов // Информационные технологии. 1997. № 3. С. 36—40.

4. Дансмур М., Дейвис Г. Операционная система UNIX и программирование на языке Си. М.: Радио и связь, 1989, 192 с.

5. Дунаев С. UNIX System V Release 4.2. Общее руководство. М.: Диалог-МИФИ, 1995, 287 с.

6. Руководство по внутреннему устройству UNIX SVR4: Учебно-методический комплекс. Зеленоград: Институт Операционных Систем, 1995, 356 с.

ИНФОРМАЦИОННЫЕ ТЕХНОЛОГИИ, № 8. 1997.

ОПЕРАЦИОННЫЕ СИСТЕМЫ И СРЕДЫ

Поделиться:
 
ПОИСК
 
elibrary crossref ulrichsweb neicon rusycon
 
ЮБИЛЕИ
ФОТОРЕПОРТАЖИ
 
СОБЫТИЯ
 
НОВОСТНАЯ ЛЕНТА



Авторы
Пресс-релизы
Библиотека
Конференции
Выставки
О проекте
Rambler's Top100
Телефон: +7 (915) 336-07-65 (строго: среда; пятница c 11-00 до 17-00)
  RSS
© 2003-2024 «Наука и образование»
Перепечатка материалов журнала без согласования с редакцией запрещена
 Тел.: +7 (915) 336-07-65 (строго: среда; пятница c 11-00 до 17-00)