Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Лекции Сети ЭВМ.doc
Скачиваний:
37
Добавлен:
27.09.2019
Размер:
6 Мб
Скачать
☆

Распределение памяти.

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

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

АРХИТЕКТУРА ОПЕРАЦИОННОЙ СИСТЕМЫ UNIX.

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

Схема на Рис. 58 имеет три уровня: уровень пользователя, уровень ядра и уровень аппаратуры. Обращения к операционной системе и библиотеки составляют границу между

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

На рисунке совокупность обращений к операционной системе разделена на те обращения, которые взаимодействуют с подсистемой управления файлами, и те, которые взаимодействуют с подсистемой управления процессами. Файловая подсистема управляет файлами, размещает записи файлов, управляет свободным пространством, доступом к файлам и поиском данных для пользователей. Процессы взаимодействуют с подсистемой управления файлами, используя при этом совокупность специальных обращений к операционной системе, таких как open (для того, чтобы открыть файл на чтение или запись), close, read, write, stat (запросить атрибуты файла), chown (изменить запись с информаци­ей о владельце файла) и chmod (изменить права доступа к файлу). Подсистема управления файлами обращается к данным, которые хранятся в файле, используя буферный механизм, управляющий потоком данных между ядром и устройствами внешней памяти. Буферный механизм, взаимодействуя с драйверами устройств ввода-вывода блоками, инициирует передачу данных к ядру и обратно. Драйверы устройств являются такими модулями в со­ставе ядра, которые управляют работой периферийных устройств. Устройства ввода-вывода блоками относятся к типу запоминающих устройств с произвольной выборкой; их драйверы построены таким образом, что все остальные компоненты системы восприни­мают эти устройства как запоминающие устройства с произвольной выборкой. Например, драйвер запоминающего устройства на магнитной ленте позволяет ядру системы воспри­нимать это устройство как запоминающее устройство с произвольной выборкой. Подсис­тема управления файлами также непосредственно взаимодействует с драйверами уст­ройств "неструктурированного" ввода-вывода, без вмешательства буферного механизма. К устройствам неструктурированного ввода-вывода, иногда именуемым устройствами посимвольного ввода-вывода (текстовыми), относятся устройства, отличные от устройств ввода-вывода блоками.

Рис. 58. Блок-схема ядра операционной системы

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

Примерами обращений к операционной системе, используемых при управлении про­цессами, могут служить fork (создание нового процесса), exec (наложение образа про­граммы на выполняемый процесс), exit (завершение выполнения процесса), wait (синхро­низация продолжения выполнения основного процесса с моментом выхода из порожденного процесса), brk (управление размером памяти, выделенной процессу) и signal (управление реакцией процесса на возникновение экстраординарных событий).

Модуль распределения памяти контролирует выделение памяти процессам. Если в какой-то момент система испытывает недостаток в физической памяти для запуска всех процессов, ядро пересылает процессы между основной и внешней памятью с тем, чтобы все процессы имели возможность выполняться. Есть два способа управления распределением памяти: выгрузка (подкачка) и замещение страниц. Программу подкачки иногда на­зывают планировщиком, т.к. она "планирует" выделение памяти процессам и оказывает влияние на работу планировщика центрального процессора. Однако в дальнейшем мы будем стараться ссылаться на нее как на "программу подкачки", чтобы избежать путаницы с планировщиком центрального процессора.

Модуль "планировщик" распределяет между процессами время центрального процессора. Он планирует очередность выполнения процессов до тех пор, пока они добровольно не освободят центральный процессор, дождавшись выделения какого-либо ресурса, или до тех пор, пока ядро системы не выгрузит их после того, как их время выполнения пре­высит заранее определенный квант времени. Планировщик выбирает на выполнение гото­вый к запуску процесс с наивысшим приоритетом; выполнение предыдущего процесса (приостановленного) будет продолжено тогда, когда его приоритет будет наивысшим сре­ди приоритетов всех готовых к запуску процессов. Существует несколько форм взаимодействия процессов между собой от асинхронного обмена сигналами о событиях до синхронного обмена сообщениями.

Наконец, аппаратный контроль отвечает за обработку прерываний и за связь с маши­ной. Такие устройства, как диски и терминалы, могут прерывать работу центрального процессора во время выполнения процесса. При этом ядро системы после обработки пре­рывания может возобновить выполнение прерванного процесса. Прерывания обрабаты­ваются не самими процессами, а специальными функциями ядра системы, перечисленны­ми в контексте выполняемого процесса. Модуль распределения памяти контролирует выделение памяти процессам. Если в какой-то момент система испытывает недостаток в физической памяти для запуска всех процессов, ядро пересылает процессы между основной и внешней памятью с тем, чтобы все процессы имели возможность выполняться. Есть два способа управления распределением памяти: выгрузка (подкачка) и замещение страниц. Программу подкачки иногда на­зывают планировщиком, т.к. она "планирует" выделение памяти процессам и оказывает влияние на работу планировщика центрального процессора. Однако в дальнейшем мы будем стараться ссылаться на нее как на "программу подкачки", чтобы избежать путаницы с планировщиком центрального процессора.

Модуль "планировщик" распределяет между процессами время центрального процессора. Он планирует очередность выполнения процессов до тех пор, пока они добровольно не освободят центральный процессор, дождавшись выделения какого-либо ресурса, или до тех пор, пока ядро системы не выгрузит их после того, как их время выполнения пре­высит заранее определенный квант времени. Планировщик выбирает на выполнение гото­вый к запуску процесс с наивысшим приоритетом; выполнение предыдущего процесса (приостановленного) будет продолжено тогда, когда его приоритет будет наивысшим сре­ди приоритетов всех готовых к запуску процессов. Существует несколько форм взаимодействия процессов между собой от асинхронного обмена сигналами о событиях до синхронного обмена сообщениями.

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

ПРОЦЕССЫ

UNIX является и многопользовательской, и многозадачной системой. Достаточно мощный ЦП может выделить квант времени одной программе, затем переключиться на обслуживание другой, и так далее, что создает иллюзию одновременного выполнения ("квантование времени").

В каждый конкретный момент времени ОЗУ содержит не только объектный код активной программы, но и объектный код определенного числа приостановленных программ, а также различные области данных, содержащие промежуточные результаты этих программ. Большая часть ядра ОС (а иногда и все ядро) также должна быть резидентной в ОЗУ.

Более современный термин "многозадачность" включает в себя понятие мультипрограммирования, но расширяет эту концепцию и предполагает возможность одновременно­го выполнения отдельных частей одной и той же программы (которые называются нитями или потоками). Большинство программ можно интерпретировать как последовательность более мелких элементов, которые называются задачами. Иногда такие задачи должны вы­полняться синхронно, то есть выполняться и завершаться поочередно и по одной, так, например, для выполнения задачи 2 могут потребоваться результаты задачи 1. В других слу­чаях некоторые или все задачи в программе могут выполняться независимо (или асинхронно). Есть, конечно, и смешанные ситуации.

Термины "программа" и "задача" довольно туманны: программный код теоретически и практически можно разбить на фрагменты по-разному. Программист может сформировать программу путем компиляции и компоновки множества мелких программ или моду­лей. При выполнении такие программы могут вызывать другие программы и подпрограм­мы, часть из которых могут представлять собой системные вызовы - предусмотренные в ОС базовые обслуживающие программы. Поэтому с одной стороны программа - это под­готавливаемый программистом исходный код, с другой - скомпилированный двоичный файл на диске, а с третьей - двоичный код, находящийся в ОЗУ и ожидающий планирования.

Процесс и программа не идентичные понятия. Во-первых, существуют процессы, ко­торым не соответствует никакой программы (т. е. исполняемого файла). Во-вторых, программа в ходе своего выполнения может породить более одного процесса. А в-третьих, при запуске программы порождение нового процесса (системный вызов fork) предшествует собственно запуску программы (системный вызов exec).

С технической точки зрения, образ программы представляет собой выполняемую программу в двоичном виде (которую не совсем верно называют сегментом текста), а также различные структуры данных, содержащие контекст программы, и две ее рабочих области - сегмент данных и стек. Упрощенная версия образа программы UNIX показана на рис.59.

Рис. 59. Схема образа программы

Когда вызывается какая-либо команда, командный процессор загружает образ программы (через вызов ядра) и начинает новый процесс с уникальным числовым идентифи­катором процесса (PID). Все последующие ссылки на этот процесс (как пользователей, так и других процессов) осуществляется через идентификатор процесса, а не имя исходной команды. Идентификаторы процессов 0 и 1 резервируются для специальных процессов, создаваемых при загрузке.

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

Существуют, с одной стороны, процессы системные и прикладные, с другой — интерактивные и неинтерактивные. Первые порождаются ядром и не имеют соответствующих им программ (исполнимых файлов). Функция их, в первую очередь — управление виртуальной памятью, свопингом и т. д. К системным относят и процесс init. Это вызвано тем, что, хотя он и не является частью ядра, запускаясь из исполняемого файла, он является прародителем всех остальных процессов.

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

Неинтерактивные процессы именуются обычно демонами (daemon - Disk And Execution MONitor). Они инициируются самой системой при ее загрузке обычным образом (запуском соответствующих программ) и функционируют в фоновом режиме, активизируясь при требовании соответ­ствующей функции (печати, доставки почты и т. д.). Не будучи при этом привязанными к какому-либо терминалу, реальному или виртуальному.

Каждый выполняемый процесс будет выбираться для выполнения согласно своему приоритету, который может устанавливаться и изменяться. Обычно процесс выполняется до завершения, а затем прекращает свое существование. Процесс может также завершить­ся в результате возникновения какой-то ошибки или сигнала (такого как kill) от пользова­теля или другого процесса. При завершении процесса он удаляется из таблицы процессов, а занятая им ОП освобождается для новых процессов.

Когда работает процесс А, он может порождать другой процесс, процесс В. Говорят, что процесс В является дочерним процессом процесса А, а процесс А называется родительским процессом. Помимо собственного идентификатора, каждый процесс имеет атри­бут PPID, то есть идентификатор родительского процесса.

Атрибутами процесса являются:

- идентификатор процесса и его родителя;

- его приоритет;

- реальный и эффективный идентификаторы пользователя;

- реальный и эффективный идентификаторы группы пользователей.

Для получения информации о протекающих в системе процессах и их атри­бутах служит команда ps.

ПРИОРИТЕТ И ЗНАЧЕНИЕ NICE.

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

Непосредственно установить внутренний приоритет невозможно, но можно установить так называемое значение nice, которое существенно влияет на внутренний приори­тет. Кроме того, внутренний приоритет зависит от того, сколько времени центрального процессора уже использовал процесс и от времени ожидания своей очереди на выполнение.

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

Если пользователь не предпринимает особых мер, то вновь созданный процесс наследует приоритет своего родительского процесса. Владелец процесса может увеличить зна­чение nice, но не может уменьшить его. Это не дает возможности процессам с низким приоритетом производить на свет "высокоприоритетных детей". Но привилегированный пользователь имеет полную свободу в установке значений nice.

Для создания нового процесса существующий процесс копирует самого себя с помощью системного вызова fork (ветвь - отсюда "ветвление процессов"), который создает копию исходного процесса, идентичную родителю (например, у них идентичные копии первоначального ОЗУ), но имеются следующие отличия:

  • у нового процесса свой РID;

  • PРID нового процесса равен РID родителя;

  • учетная информация нового процесса обнулена;

  • у нового процесса имеется свой собственный экземпляр дескрипторов файлов.

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

exec обновляет образ дочернего процесса так, что дочерний процесс начинает выполнение наследуемой команды. Он изменяет текст программы, которую выполняет процесс, и устанавливает сегменты данных и стека в исходное состояние. Когда вызывается одна из подпрограмм exec, она записывает в адресное пространство процесса содержимое но­вого программного файла, а затем возобновляет выполнение с означенной точки входа нового текста. После этого дочерний и родительский процессы могут идти своими путями и выполнять собственные операции (теперь это различные образы).

Когда система загружается, ядро самостоятельно создает несколько процессов. Наиболее важный из них - процесс init, PID которого всегда равен 1. Этот процесс отвечает за вызов shell для выполнения сценариев запуска (если они есть). Все процессы, кроме тех, которые создает ядро, являются потомками init.

Процесс init играет важную роль и в управлении процессами. Когда процесс завершается, он вызывает подпрограмму _exit, чтобы уведомить ядро о своей готовности "уме­реть". В качестве параметра подпрограмме _exit передается код завершения - целое число, указывающее на причину завершения процесса (нулевой код завершения означает, что процесс был "успешным").

Код завершения необходим родительскому процессу, поэтому ядро хранит его, пока родительский процесс не запросит его системным вызовом wait. Дело в том, что когда процесс завершается, его адресное пространство освобождается, время центрального процессора этому процессу не выделяется, однако в таблице процессов ядра запись о нем сохраняется. Процесс в этом состоянии называется "зомби".

Этот механизм работает нормально, если родительский процесс завершается позже порожденных им процессов и добросовестно вызывает wait для того, чтобы все процессы-зомби умерли. Если же родительский процесс умирает первым, то ядро понимает, что вызова wait не последует, и передает всех зомби процессу init. Тот обязан принять эти процессы и выполнить wait. Иногда init срабатывает некорректно, и зомби остаются в системе.

В ОС UNIX определено свыше тридцати различных видов сигналов. Сигнал - это способ сообщить процессу: "Произошло какое-то событие, поэтому брось делать то, что де­лаешь, и займись этим событием".

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

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

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

Сигналы kill (разрушение принимающего процесса) и stop (приостановление принимающего процесса до получения сигнала cont) нельзя ни перехватить, ни блокировать. Сигнал cont можно перехватить и игнорировать, но нельзя блокировать.

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

Пять основных состояний процесса перечислены в таблице 6.

Состояние

Смысл

Выполнимый

процесс можно выполнять

Ожидающий

процесс ждет выделения какого-либо ресурса

Свопированный

процесс отсутствует в памяти

Зомби

процесс пытается умереть

Остановленный

процесс приостановлен (не имеет разрешения на выполнение)

Таблица 6. Основные состояния процесса.

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

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

Свопированный процесс - это процесс, переписанный на диск и удаленный из ОП компьютера. Большинство систем свопируют процессы на диск, когда конкуренция за ОП столь сильна, что замещение страниц (paging) "захлебывается" и вместо того, чтобы делать полезную работу, система тратит неоправданно много времени на обработку преры­ваний, вызванную нехваткой страниц памяти. Строго говоря, свопированный процесс - не состояние процесса. Свопированный процесс может быть ожидающим, остановленным и даже номинально выполнимым; он оста­ется замороженным в том состоянии, в котором он находился перед записью на диск, вплоть до возвращения в ОП.

Остановленный процесс - это процесс, выполнение которого запрещено. Процессы останавливаются при получении сигнала stop или tstp и перезапускаются сигналом cont. Это состояние аналогично ожиданию, но выйти из него можно только с помощью другого процесса.