Многозада́чность (англ. multitasking) — свойство операционной системы или среды программирования обеспечивать возможность параллельной (или псевдопараллельной) обработки нескольких процессов. Истинная многозадачность операционной системы возможна только в распределённых вычислительных системах.
1 Свойства многозадачной среды
2 Трудности реализации многозадачной среды
3 История многозадачных операционных систем
4 Типы псевдопараллельной многозадачности
4.1 Невытесняющая многозадачность
4.2 Совместная или кооперативная многозадачность
4.3 Вытесняющая или приоритетная многозадачность (режим реального времени)
5 Проблемные ситуации в многозадачных системах
5.1 Голодание (starvation)
5.2 Гонка (race condition)
Работа содержит 1 файл
Содержание
1 Свойства многозадачной среды
2 Трудности реализации многозадачной среды
3 История многозадачных операционных систем
4 Типы псевдопараллельной многозадачности
ПОСТОЯННЫЙ ПРИОРИТЕТ ЛЮБОМУ ПРОЦЕССУ БЕЗ ПРОГРАММ
4.1 Невытесняющая многозадачность
4.2 Совместная или кооперативная многозадачность
4.3 Вытесняющая или приоритетная многозадачность (режим реального времени)
5 Проблемные ситуации в многозадачных системах
5.1 Голодание (starvation)
5.2 Гонка (race condition)
Многозада́чность ( англ. multit asking) — свойство операционной системы или среды программирования обеспечивать возможность параллельной (или псевдопараллельной ) обработки нескольких процессов. Истинная многозадачность операционной системы возможна только в распределённых вычислительных системах .
Существует 2 типа многозадачности:
- Процессная многозадачность (основанная на процессах — одновременно выполняющихся программах). Здесь программа — наименьший элемент кода, которым может управлять планировщик операционной системы. Более известна большинству пользователей (работа в текстовом редакторе и прослушивание музыки).
- Поточная многозадачность (основанная на потоках). Наименьший элемент управляемого кода — поток (одна программа может выполнять 2 и более задачи одновременно).
1 Свойства многозадачной среды
Примитивные многозадачные среды обеспечивают чистое «разделение ресурсов», когда за каждой задачей закрепляется определённый участок памяти, и задача активизируется в строго определённые интервалы времени.
Более развитые многозадачные системы проводят распределение ресурсов динамически, когда задача стартует в памяти или покидает память в зависимости от её приоритета и от стратегии системы. Такая многозадачная среда обладает следующими особенностями:
- Каждая задача имеет свой приоритет, в соответствии с которым получает процессорное время и память
- Система организует очереди задач так, чтобы все задачи получили ресурсы, в зависимости от приоритетов и стратегии системы
- Система организует обработку прерываний , по которым задачи могут активироваться, деактивироваться и удаляться
- По окончании положенного кванта времени ядро временно переводит задачу из состояния выполнения в состояние готовности, отдавая ресурсы другим задачам. При нехватке памяти страницы невыполняющихся задач могут быть вытеснены на диск ( своппинг ), а потом через определённое системой время, восстанавливаться в памяти
- Система обеспечивает защиту адресного пространства задачи от несанкционированного вмешательства других задач
- Система обеспечивает защиту адресного пространства своего ядра от несанкционированного вмешательства задач
- Система распознаёт сбои и зависания отдельных задач и прекращает их
- Система решает конфликты доступа к ресурсам и устройствам, не допуская тупиковых ситуаций общего зависания от ожидания заблокированных ресурсов
- Система гарантирует каждой задаче, что рано или поздно она будет активирована
- Система обрабатывает запросы реального времени
- Система обеспечивает коммуникацию между процессами
2 Трудности реализации многозадачной системы
как направить всю мощность компьютера на игру, или программу.
Основной трудностью реализации многозадачной среды является её надёжность, выраженная в защите памяти, обработке сбоев и прерываний , предохранении от зависаний и тупиковых ситуаций .
Кроме надёжности, многозадачная среда должна быть эффективной. Затраты ресурсов на её поддержание не должны: мешать процессам проходить, замедлять их работу, резко ограничивать память.
3 История многозадачных операционных систем
Поначалу реализация многозадачных операционных систем представляла собой серьёзную техническую трудность, отчего внедрение многозадачных систем затягивалось, а пользователи долгое время после внедрения предпочитали однозадачные.
В дальнейшем, после появления нескольких удачных решений, многозадачные среды стали совершенствоваться, и в настоящее время употребляются повсеместно. Впервые многозадачность операционной системы была реализована в ходе разработки операционной системы Multics ( 1964 год ). Одной из первых многозадачных систем была OS/360 (1966), используемая для компьютеров фирмы IBM и их советских аналогов ЕС ЭВМ . Разработки системы были сильно затянуты, и на начальное время фирма IBM выдвинула однозадачный DOS , чтобы удовлетворить заказчиков до полной сдачи OS/360 в эксплуатацию. Система подвергалась критике по причине малой надёжности и трудности эксплуатации.
В 1969году на основе Multics была разработана система UNIX с достаточно аккуратным алгоритмическим решением проблемы многозадачности. В настоящее время на базе UNIX созданы десятки операционных систем.
На компьютерах PDP-11 и их советских аналогах СМ-4 использовалась многозадачная система RSX-11 (советский аналог — ОСРВ СМ ЭВМ ), и система распределения времени TSX-PLUS, обеспечивающая ограниченные возможности многозадачности и многопользовательский режим разделения времени, эмулируя для каждого пользователя однозадачную RT-11 (советский аналог — РАФОС ). Последнее решение было весьма популярно из-за низкой эффективности и надёжности полноценной многозадачной системы.
Аккуратным решением оказалась операционная система VMS , разработанная первоначально для компьютеров VAX (советский аналог — СМ-1700 ) как развитие RSX-11.
Первый в мире мультимедийный персонал ьный компьютер Amiga 1000 ( 1984 год ) изначально проектировался с расчётом на полную аппаратную поддержку вытесняющей многозадачности реального времени в ОС AmigaOS . В данном случае разработка аппаратной и программной части велась параллельно, это привело к тому, что по показателю квантования планировщика многозадачности (1/50 секунды на переключение контекста) AmigaOS долгое время оставалась непревзойдённой на персональных компьютерах .
Многозадачность обеспечивала также фирма Microsoft в операционных системах Windows . При этом Microsoft выбрала две линии разработок — на базе приобретённой ею Windows 0.9, которая после долгой доработки системы, изначально обладавшей кооперативной многозадачностью, аналогичной Mac OS , вылилась в линейку Windows 3.x, и на основе идей, заложенных в VMS, которые привели к созданию операционных систем Windows NT . Использование опыта VMS обеспечило системам существенно более высокую производительность и надёжность. По времени переключения контекста многозадачности (квантование) только эти операционные системы могут быть сравнимы с AmigaOS и UNIX (а также его потомками, такими, как ядро Linux ).
Интересно, что многозадачность может быть реализована не только в операционной, но и языковой среде. Например, спецификации языков программирования Modula-2 и Ad a требуют поддержки многозадачности вне привязки к какой-либо операционной системе. В результате, популярная в первой половине 1990-х годов реализация языка программирования TopSpeed Модула-2 от JPI / Clario n позволяляла организовывать различные типы многозадачности (кооперативную и вытесняющую — см. ниже) для потоков одной программы в рамках такой принципиально однозадачной операционной системы, как MS-DOS . Это осуществлялось путём включения в модуль программы компактного планировщика задач , содержащего обработчик таймерных прерываний. Языки программирования, обладающие таким свойством, иногда называют языками реального времени .
4 Типы псевдапаралельной многозадачности
4.1 Невытесняющая многозадачность
Тип многозадачности, при котором операционная система одновременно загружает в память два или более приложений, но процессорное время предоставляется только основному приложению. Для выполнения фонового приложения оно должно быть активизировано. Подобная многозадачность может быть реализована не только в операционной системе, но и с помощью программ-переключателей задач. В этой категории известна программа DESQview , работавшая под DOS и выпущенная первый раз в 1985 году.
4.2 Совместная или кооперативная многозадачность
Тип многозадачности, при котором следующая задача выполняется только после того, как текущая задача явно объявит себя готовой отдать процессорное время другим задачам. Как частный случай, такое объявление подразумевается при попытке захвата уже занятого объекта mutex (ядро Linux), а также при ожидании поступления следующего сообщения от подсистемы пользовательского интерфейса (Windows версий до 3.x включительно, а также 16-битные приложения в Windows 9x ).
Кооперативную многозадачность можно назвать многозадачностью «второй ступени» поскольку она использует более передовые методы, чем простое переключение задач, реализованное многими известными программами (например, DOS Shell из MS-DOS 5.0 при простом переключении активная программа получает все процессорное время, а фоновые приложения полностью замораживаются. При кооперативной многозадачности приложение может захватить фактически столько процессорного времени, сколько оно считает нужным. Все приложения делят процессорное время, периодически передавая управление следующей задаче.
Преимущества кооперативной многозадачности: отсутствие необходимости защищать все разделяемые структуры данных объектами типа критических секций и mutex’, что упрощает программирование, особенно перенос кода из однозадачных сред в многозадачные.
Недостатки: неспособность всех приложений работать в случае ошибки в одном из них, приводящей к отсутствию вызова операции «отдать процессорное время». Крайне затрудненная возможность реализации многозадачной архитектуры ввода-вывода в ядре ОС, позволяющей процессору исполнять одну задачу в то время, как другая задача инициировала операцию ввода-вывода и ждет её завершения.
Реализована в пользовательском режиме ОС Windows версий до 3.х включительно, Mac OS версий до Mac OS X, а также внутри ядер многих UNIX-подобных ОС , таких, как FreeBSD, а в течение долгого времени — и Linux.
4.3 Вытесняющая или приоритетная многозадачность ( режим реального времени )
Вид многозадачности, в котором операционная система сама передает управление от одной выполняемой программы другой в случае завершения операций ввода-вывода, возникновения событий в аппаратуре компьютера, истечения таймеров и квантов времени, или же поступлений тех или иных сигналов от одной программы к другой. В этом виде многозадачности процессор может быть переключен с исполнения одной программы на исполнение другой без всякого пожелания первой программы и буквально между любыми двумя инструкциями в её коде. Распределение процессорного времени осуществляется планировщиком процессов. К тому же каждой задаче может быть назначен пользователем или самой операционной системой определенный приоритет, что обеспечивает гибкое управление распределением процессорного времени между задачами (например, можно снизить приоритет ресурсоёмкой программе, снизив тем самым скорость её работы, но повысив производительность фоновых процессов). Этот вид многозадачности обеспечивает более быстрый отклик на действия пользователя.
Источник: www.stud24.ru
7. Многозадачность и многопоточность.
Многозадачность (англ. multitasking) — свойство операционной системы или среды выполнения обеспечивать возможность параллельной (или псевдопараллельной) обработки нескольких задач. Истинная многозадачность операционной системы возможна только в распределённых вычислительных системах.
Существует 2 типа многозадачности:
- Процессная многозадачность (основанная на процессах — одновременно выполняющихся программах). Здесь программа — наименьший элемент управляемого кода, которым может управлять планировщик операционной системы. Более известна большинству пользователей (работа в текстовом редакторе и прослушивание музыки).
- Поточная многозадачность (основанная на потоках). Наименьший элемент управляемого кода — поток (одна программа может выполнять 2 и более задачи одновременно).
Многопоточность — специализированная форма многозадачности.
Свойства многозадачной среды Примитивные многозадачные среды обеспечивают чистое «разделение ресурсов», когда за каждой задачей закрепляется определённый участок памяти, и задача активизируется в строго определённые интервалы времени.
Более развитые многозадачные системы проводят распределение ресурсов динамически, когда задача стартует в памяти или покидает память в зависимости от её приоритета и от стратегии системы. Такая многозадачная среда обладает следующими особенностями:
- Каждая задача имеет свой приоритет, в соответствии с которым получает процессорное время и память
- Система организует очереди задач так, чтобы все задачи получили ресурсы, в зависимости от приоритетов и стратегии системы
- Система организует обработку прерываний, по которым задачи могут активироваться, деактивироваться и удаляться
- По окончании положенного кванта времени ядро временно переводит задачу из состояния выполнения в состояние готовности, отдавая ресурсы другим задачам. При нехватке памяти страницы невыполняющихся задач могут быть вытеснены на диск (своппинг), а потом, через определённое системой время, восстанавливаться в памяти
- Система обеспечивает защиту адресного пространства задачи от несанкционированного вмешательства других задач
- Система обеспечивает защиту адресного пространства своего ядра от несанкционированного вмешательства задач
- Система распознаёт сбои и зависания отдельных задач и прекращает их
- Система решает конфликты доступа к ресурсам и устройствам, не допуская тупиковых ситуаций общего зависания от ожидания заблокированных ресурсов
- Система гарантирует каждой задаче, что рано или поздно она будет активирована
- Система обрабатывает запросы реального времени
- Система обеспечивает коммуникацию между процессами
Типы псевдопараллельной многозадачности
Простое переключение
Тип многозадачности, при котором операционная система одновременно загружает в память два или более приложений, но процессорное время предоставляется только основному приложению.
Преимущества: можно задействовать уже работающие программы, написанные без учёта многозадачности.
Недостатки: невозможно в неинтерактивных системах, работающих без участия человека. Взаимодействие между программами крайне ограничено.
Совместная или кооперативная многозадачность
Тип многозадачности, при котором следующая задача выполняется только после того, как текущая задача явно объявит себя готовой отдать процессорное время другим задачам.
Преимущества кооперативной многозадачности: отсутствие необходимости защищать все разделяемые структуры данных объектами типа критических секций и мьютексов, что упрощает программирование, особенно перенос кода из однозадачных сред в многозадачные.
Недостатки: неспособность всех приложений работать в случае ошибки в одном из них, приводящей к отсутствию вызова операции «отдать процессорное время». Крайне затрудненная возможность реализации многозадачной архитектуры ввода-вывода в ядре ОС, позволяющей процессору исполнять одну задачу в то время, как другая задача инициировала операцию ввода-вывода и ждет её завершения.
Вытесняющая, или приоритетная, многозадачность (режим реального времени)
Вид многозадачности, в котором операционная система сама передает управление от одной выполняемой программы другой в случае завершения операций ввода-вывода, возникновения событий в аппаратуре компьютера, истечения таймеров и квантов времени, или же поступлений тех или иных сигналов от одной программы к другой. В этом виде многозадачности процессор может быть переключен с исполнения одной программы на исполнение другой без всякого пожелания первой программы и буквально между любыми двумя инструкциями в её коде. Распределение процессорного времени осуществляется планировщиком процессов. К тому же каждой задаче может быть назначен пользователем или самой операционной системой определенный приоритет, что обеспечивает гибкое управление распределением процессорного времени между задачами (например, можно снизить приоритет ресурсоёмкой программе, снизив тем самым скорость её работы, но повысив производительность фоновых процессов). Этот вид многозадачности обеспечивает более быстрый отклик на действия пользователя.
возможность полной реализации многозадачного ввода-вывода в ядре ОС, когда ожидание завершения ввода-вывода одной программой позволяет процессору тем временем исполнять другую программу; cильное повышение надежности системы в целом, в сочетании с использованием защиты памяти — идеал в виде «ни одна программа пользовательского режима не может нарушить работу ОС в целом» становится достижимым хотя бы теоретически, вне вытесняющей многозадачности он не достижим даже в теории. возможность полного использования многопроцессорных и многоядерных систем.
необходимость особой дисциплины при написании кода, особые требования к его реентерабельности, к защите всех разделяемых и глобальных данных объектами типа критических секций и мьютексов.
Конспектики
Источник: kvckr.me
Многозадачность и процессы
Как современные компьютеры могут выполнять несколько задач одновременно.
Время чтения: 9 мин
Открыть/закрыть навигацию по статье
Контрибьюторы:
Обновлено 2 февраля 2022
Кратко
Скопировать ссылку «Кратко» Скопировано
Современные операционные системы позволяют нескольким пользователям одновременно работать на одном компьютере. При этом ресурсы компьютера должны правильно распределяться между приложениями, которые запускают пользователи. Для этого в операционной системе используется специальная абстракция — «процесс».
Под процессом чаще всего понимают отдельное работающее приложение, но бывают приложения, которые используют несколько процессов. Например, браузер Chrome под каждую вкладку создаёт отдельный процесс. Процесс — это абстракция операционной системы, которая позволяет выделять процессорное время и память компьютера для работы программы.
Операционная система запускает процессы на короткое время поочерёдно. Диспетчер операционной системы помещает отдельные операции из разных процессов в очередь. Когда очередь подошла, операция выполняется.
Для управления процессами операционная система использует механизм прерываний. Процессы можно останавливать или ставить на паузу, перезапускать различными способами, сообщать процессам об изменении состояния системы и прочее. Прерывания в операционных системах реализованы по-разному. В Unix-подобных системах существует несколько механизмов реализации прерываний. Один из них — управления процессами с помощью сигналов, описанный в стандарте POSIX для Unix-подобных операционных систем.
Как понять
Скопировать ссылку «Как понять» Скопировано
Первые операционные системы были однозадачные. Разработаны они были для компьютеров с одним процессором. В каждый момент времени работать могла только одна программа. Пока она не завершится, нельзя запустить другую программу. Все ресурсы компьютера используются для работы только одной программы:
Вскоре выяснилось, что такой механизм не очень хорошо работает. Программы могут запрашивать данные у внешних устройств или ждать ввода данных от пользователя, и процессор все это время простаивает. Хочется использовать процессор эффективнее, загружая выполнением других программ. Для этого операционная система стала разбивать программы на блоки. Каждый блок выбирался так, чтобы процессор не простаивал.
Например, можно было сделать так:
- выполняется блок программы до тех пор, пока не запросит данные у пользователя;
- далее выполняется блок другой программы, пока данные не будут получены;
- после этого выполняется блок первой программы, пока программа не отправит данные на печать;
- пока печатаются данные, выполняется блок второй программы.
Следующим шагом было внедрение специального диспетчера операционной системы. Главная задача диспетчера — максимально эффективно использовать процессор. Для этого все программы бились уже не на блоки, а на отдельные операции (команды для процессора). Процессор заблокирован, пока команда не выполнится полностью. На схеме показано, как диспетчер использует очередь готовых к запуску программ (процессов в терминах операционной системы):
Диспетчер операционной системы распределяет процессы в очереди в порядке приоритета. Такая очередь называется очередью готовых к запуску. Например, системным процессам назначается наивысший приоритет. Приоритет означает количество процессорного времени, которое будет отдано процессу. В некоторых операционных системах приоритетом процессов можно управлять.
Linux одна из таких операционных систем. Все процессы в Linux имеют свой приоритет, который задаётся числом от -20 (наивысший приоритет) до 19 (наименьший приоритет). Любой пользователь может понижать приоритет процесса, но только суперпользователь может его увеличивать. Управление приоритетом возможно как на этапе запуска, так и на этапе работы процесса.
После создания одной очереди, оказалось, что и это не совсем оптимальный механизм работы с несколькими процессами. Всё дело в том, что разные внешние устройства работают за разное время. Какие-то устройства работают в автоматическом режиме, какие-то в ручном (с участием пользователя). Решили, что было бы здорово сделать несколько очередей, каждая из которых обеспечивает доступ до какого-то определённого ресурса:
Например, существуют очереди для доступа к клавиатуре или принтеру, видеокарте или накопителю (HDD, SSD, Flash-drive и так далее). На рисунке разные устройства обозначены буквами (A, B и так далее).
Процесс для операционной системы — это не просто совокупность операций или код программы. Для того чтобы процессор мог возвращаться к выполнению программы, в операционной системе должны хранится промежуточные результаты выполнения программы, состояние процессора на момент перехода к другому процессу (контекст) и значения переменных (данные программы):
Для операционной системы процесс состоит из трёх частей: программы, контекста и данных. Программа — это последовательность операций над данными, записанная в оперативной памяти. Данные программ располагаются в оперативной памяти и на внешних устройствах. Перед выполнением очередной операции программы все требуемые данные должны быть загружены в оперативную память компьютера.
Контекст позволяет операционной системе отложить процесс на некоторое время, а затем вернуться к нему, когда это будет возможно. Контекст процесса, например, может содержать следующие данные:
- состояние регистров процессора;
- состояние стека программы;
- состояние стека операционной системы;
- состояния зарезервированной памяти.
Контекст необходим для того, чтобы можно было реализовать работу диспетчера операционной системы, обеспечить межпроцессное взаимодействие и обмен информацией между процессами и операционной системой.
У процесса всегда есть приоритет, который позволяет помещать процесс в нужное место очереди. В первую очередь выполняются процессы, имеющие в операционной системе наивысший приоритет. Операционная система использует специальную структуру данных очередь с приоритетом в качестве абстракции для реализации механизма выполнения процессов на процессоре компьютера.
Каждому процессу соответствует набор состояний. В современных операционных системах набор состояний сводится к следующим пяти:
Состояние «блокирован» соответствует случаю, когда процесс, например, ожидает ответа от внешнего устройства: ввод с клавиатуры или ответ от накопителя. Количество состояний увеличивается, если ввести возможность приостанавливать процесс в момент того, когда он, например, заблокирован или готов продолжить свою работу:
Прерывания
Скопировать ссылку «Прерывания» Скопировано
Чтобы можно было управлять процессами на уровне операционной системы, существует механизм прерываний. Когда происходит прерывание, выполнение процесса прерывается и совершается какое-то действие. Например, можно приостановить процесс или переместить из одной очереди в другую. Часть прерываний обрабатываются только операционной системой, другую часть можно слушать и реагировать на них, аналогично событиям в DOM. Прерывания делятся на две группы: прерывания на уровне железа, и программные прерывания.
Механизм прерываний из первой группы реализован на уровне процессора и используется в основном для работы с внешними устройствами. Операционная система может получить доступ к прерываниям, но управлять может только с позиции установки настроек для процессора (разрешать или запрещать определённые прерывания). Программы могут использовать прерывания для взаимодействия с другими программами и для взаимодействия с внешними устройствами на уровне железа. Программе прерывание передаётся как событие, по наступлению которого выполняется заранее заготовленный разработчиками код (обработчик прерывания).
По типу источника «железные» прерывания делятся на:
- внешние, асинхронные, получаемые от внешних устройств: сигнал от аппаратного таймера или сетевой карты, готовность передать информацию с дискового накопителя, нажатие клавиш клавиатуры, движение мыши;
- внутренние, синхронные, возникающие в самом процессоре: переполнение стека, деление на ноль, обращение к недопустимым адресам памяти или недопустимый код операции.
Вторая группа прерываний (программные прерывания) реализуются на уровне операционной системы. Программные прерывания инициируются специальной инструкцией в коде программы. Программные прерывания, например, используются для пересылки данных между процессами или работы драйверами внешних устройств.
Реализация программных прерываний зависит от операционной системы. Например, в Windows в основном используется модель событий. Когда событие наступает программе посылается специальное сообщение. Это может быть использовано для реализации взаимодействия пользователя с интерфейсом окна или для обмена данными между программами.
Однако модель сообщений очень ограничена в функциональности. Например, остановить или перезапустить программу с помощью сообщений не получится.
В Unix-подобных операционных системах используется модель сигналов, которые могут посылаться программам как со стороны пользователя, так и со стороны операционной системы. Когда программа получает сигнал, выполнение программы прерывается, и исполняется заранее заготовленный код (обработчик сигнала).
В рамках стандарта POSIX описан набор стандартных сигналов операционной системы, которые можно обрабатывать в программах. Однако можно устанавливать и свои сигналы, стандарт этого не запрещает.
Вот список наиболее используемых сигналов:
- SIGINT — прервать процесс (тип — управление). Сигнал сообщает процессу, что пользователь собирается остановить процесс из терминала.
- SIGKILL — завершить процесс (тип — исключение). Сигнал сообщает процессу, что операционная система немедленно останавливает процесс. Этот сигнал нельзя проигнорировать или перехватить.
- SIGSTOP — остановить выполнение процесса (тип — управление). Сигнал сообщает процессу, что операционная система принудительно останавливает процесс.
- SIGCONT — продолжить выполнение ранее остановленного процесса (тип — управление). Сигнал сообщает процессу, что операционная система собирается продолжить выполнение процесса.
- SIGTRAP — остановить выполнение процесса в брейкпоинте в программе (тип — отладка). Сигнал сообщает отладчику, что наступило интересующее его событие в программе.
Сигналы можно послать работающему процессу:
— из терминала с помощью сочетания клавиш на клавиатуре;
— с помощью ядра операционной системы;
— из одного процесса другому (или самому себе).
Вот несколько наиболее распространённых сигналов, которые можно послать текущему процессу из терминала:
— SIGINT , нажав на клавиатуре Ctrl C ;
— SIGQUIT , нажав на клавиатуре Ctrl ;
— SIGTSTP , нажав на клавиатуре Ctrl Z .
На практике
Скопировать ссылку «На практике» Скопировано
Источник: doka.guide