Главная

Популярная публикация

Научная публикация

Случайная публикация

Обратная связь

ТОР 5 статей:

Методические подходы к анализу финансового состояния предприятия

Проблема периодизации русской литературы ХХ века. Краткая характеристика второй половины ХХ века

Ценовые и неценовые факторы

Характеристика шлифовальных кругов и ее маркировка

Служебные части речи. Предлог. Союз. Частицы

КАТЕГОРИИ:






Реализация процессов в UNIX




 

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

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

Информация в таблице процессов подразделяется на следующие категории:

1. Параметры планирования. Приоритеты процессов, процес-сорное время, потребленное за последний учитываемый период, количество времени, про­веденное процессом в режиме ожидания. Вся эта информация использует­ся для выбора процесса, которому будет передано управление следующим.

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

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

4. Прочая информация. Текущее состояние процесса, ожидаемые процессом события (если таковые есть), идентификатор текущего процесса, идентификатор родительского процесса, идентификаторы пользователя и группы, а также некоторая другая информация.

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

Данные, хранящиеся в структуре пользователя, включают в себя следующие пункты:

1. Машинные регистры. Когда происходит прерывание с пере-ключением в ре­жим ядра, машинные регистры (включая регистры с плавающей точкой) сохраняются здесь.

2. Состояние системного вызова. Информация о текущем системном вызове, включая параметры и результаты.

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

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

5. Стек ядра. Фиксированный стек для использования процессом в режиме ядра.

Рассмотрим подробне, как в системе UNIX создаются процессы. Когда выполняется системный вызов fork, вызываю­щий процесс обращается в ядро и ищет свободную ячейку в таблице процессов, в которую можно записать данные о дочернем процессе. Если свободная ячейка находится, системный вызов копирует туда информацию из ячейки родительского процесса. Затем он выделяет память для сегментов данных и для стека дочернего процесса, куда копируются соответствующие сегменты родительского процесса. Структура пользователя (которая часто хранится вместе с сегментом стека) копи­руется вместе со стеком. Программный сегмент может либо копироваться, либо использоваться совместно, если он доступен только для чтения. Начиная с этого момента дочерний процесс может быть запущен.

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

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

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

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

Реализация потоков зависит от того, поддерживаются они ядром или нет. Если потоки ядром не поддерживаются, как, например, в 4BSD, реализация потоков целиком осуществляется в библиотеке, загружающейся в пространстве пользователя.

 

Планирование в системе UNIX

 

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

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

Когда запускается низкоуровневый планировщик, он ищет очередь, начиная с самого высокого приоритета (то есть с наименьшего отрицательного значения), пока не находит очередь, в которой есть хотя бы один процесс. После этого в этой очереди выбирается и запускается первый процесс. Ему разрешается работать в те­чение некоего максимального кванта времени, как правило, 100 мс, или пока он не заблокируется. Если процесс использует весь свой квант времени, он помещается обратно, в конец очереди, а алгоритм планирования запускается снова. Таким об­разом, процессы, входящие в одну группу приоритетов, совместно используют цен­тральный процессор в порядке циклической очереди.

Раз в секунду приоритет каждого процесса пересчитывается по формуле, со­стоящей из суммы трех компонентов. Первой составляющей этой формулы является параметр использования центрального процессора, представляющий собой среднее значение тиков (прерываний) таймера в секунду, которые процесс работал в тече­ние последних нескольких секунд. При каждом тике таймера счет­чик использования центрального процессора в таблице процессов увеличивается на единицу. Этот счетчик в конце концов добавляется к приоритету процесса, увеличивая тем самым числовое значение его приоритета (что соответствует бо­лее низкому приоритету), в результате чего процесс попадает в менее приоритет­ную очередь. Однако в UNIX процесс не находится в конце очереди бесконечно долго, и величина параметра использования центрального процессора со временем уменьша­ется. В различных версиях UNIX это уменьшение выполняется по-разному. Один из способов состоит в том, что к этому параметру прибавляется полученное число тиков, после чего сумма делится на два. Такой алгоритм учитывает самое последнее значение числа тиков с весовым коэффициентом 1/2, предшествующее ему – с весовым коэффициентом 1/4 и т. д. Алгоритм взвешивания очень быстр, так как состоит из всего одной операции сложения и одного сдвига, но также применяются и другие схемы взвешивания.

Второй составляющей формулы является так называемый параметр nice. Его значение по умолчанию рав­но 0, но допустимый диапазон значений, как правило, составляет от -20 до +20. Процесс может установить значение nice в диапазоне от 0 до 20 с помощью систем­ного вызова. Только системный администратор может запросить обслуживание с более высоким приоритетом (то есть значения nice от -20 до -1).

Третьей составляющей формулы является параметр base (база). Когда процесс эмулирует прерывание для выполнения системного вызова в ядре, процесс, вероятно, должен быть блокирован, пока системный вызов не будет вы­полнен и не вернется в режим пользователя. Например, процесс может обратить­ся к системному вызову, ожидая, пока один из его дочерних процессов не закончит работу. Он может также ожидать ввода с терминала или завершения дис­ковой операции ввода-вывода и т. д. Когда процесс блокируется, он удаляется из структуры очереди, пока этот процесс снова не будет готов работать. Однако когда происходит событие, которого ждал процесс, он снова помещает­ся в очередь с отрицательным значением. Выбор очереди определяется событием, которого ждал процесс. Например дисковый ввод-вывод может быть событием с наивысшим приоритетом, так что процесс, только что прочитавший или запи­савший блок диска, вероятно, получит центральный процессор в течение 100 мс. Отрицательные значения приоритета для дискового ввода-вывода, терминаль­ного ввода-вывода и некоторых других операций жестко прошиты в операционной системе и могут быть изменены только путем перекомпиляции самой системы. Эти значения (отрицательные) и представлены параметром base. Их величина достаточно отличается от нуля, чтобы перезапущенный процесс навер­няка попадал в другую очередь.

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

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

1. Потоки реального времени, обслуживаемые по алгоритму FIFO.

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

3. Потоки разделения времени.

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

У каждого потока есть приоритет планирования. Значение по умолчанию рав­но 20, но оно может быть изменено при помощи специального системного вызова, вычитающего некоторое значение в диапа­зоне от -20 до +19 из 20. Поэтому возможные значения приоритетов попадают в промежуток от 1 до 40. Цель алгоритма планирования состоит в том, чтобы обеспечить грубое пропорциональ­ное соответствие качества обслуживания приоритету (то есть чем выше приори­тет, тем меньше должно быть время отклика и тем большая доля процессорного времени достанется процессу).

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

7.3. Управление памятью в UNIX

 

Основные понятия

 

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

Сегмент данных содержит переменные, строки, массивы и другие данные про­граммы. Он состоит из двух частей: инициализированных данных и неинициали­зированных данных. По историческим причинам вторая часть называется BSS (Bulk Storage System – запоминающее устройство большой емкости или массовое запоминающее устройств). Инициализированная часть сегмента данных содержит переменные и константы компилятора, значения которых должны быть заданы при запуске программы. Например, на языке С можно объявить символьную строку и в то же время за­дать ее значение, то есть проинициализировать ее. Когда программа запускается, она предполагает, что в этой строке уже содержится некий осмысленный текст. Чтобы реализовать это, компилятор назначает строке определенное место в адрес­ном пространстве и гарантирует, что в момент запуска программы по этому адресу будет располагаться соответствующая строка. С точки зрения операционной сис­темы, инициализированные данные не отличаются от текста программы – тот и другой сегменты содержат сформированные компилятором последовательности битов, загружаемые в память при запуске программы.

Неинициализированные данные необходимы лишь с точки зрения оптимиза­ции. Когда начальное значение глобальной переменной явно не указано, то, соглас­но семантике языка С, ее значение устанавливается равным 0. На практике боль­шинство глобальных переменных не инициализируются, и, таким образом, их начальное значение равно 0. Это можно реализовать следующим образом: создать целый сегмент исполняемого двоичного файла, точно равного по размеру числу байтов данных, и проинициализировать весь этот сегмент нулями. Однако с целью экономии места на диске этого не делается. Файл содержит только те переменные, начальные значения которых явно заданы. Вместо неинициализи­рованных переменных компилятор помещает в исполняемый файл просто одно слово, содержащее размер области неинициализированных данных в байтах. При запуске программы операционная система считывает это слово, выделяет нужное число байтов и обнуляет их.

В отличие от текстового сегмента, который не может изменяться, сегмент дан­ных может модифицироваться. Программы изменяют свои переменные постоян­но. Более того, многим программам требуется выделение дополнительной памяти динамически, во время выполнения. Чтобы реализовать это, операционная систе­ма UNIX разрешает сегменту данных расти при динамическом выделении памяти программам и уменьшаться при освобождении памяти программами. Програм­ма может установить размер своего сегмента данных с помощью системного вызо­ва brk. Таким образом, чтобы получить больше памяти, программа может увеличить размер своего сегмента данных. Этим системным вызовом пользуется библиотеч­ная процедура, используемая для выделения памяти.

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

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

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

На некоторых вычислительных машинах аппаратное обеспечение поддерживает раздельные адресные пространства для команд и для данных. Если такая возможность есть, система UNIX может ею воспользоваться. Например, на компьютере с 32-разряд­ными адресами при возможности использования раздельных адресных пространств можно получить 4 Гбайт адресного пространства для команд и еще 4 Гбайт адрес­ного пространства для данных. Передача управления по адресу 0 будет восприни­маться как передача управления по адресу 0 в текстовом пространстве, тогда как при обращении к данным по адресу 0 будет использоваться адрес 0 в пространстве данных. Таким образом, это свойство удваивает доступное адресное пространство.

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

Дополнительное преимущество отображения файла на память заключается в том, что два или более процессов могут одновременно отобразить на свое адрес­ное пространство один и тот же файл. Запись в этот файл одним из процессов мгновенно становится видимой всем остальным. Таким образом, отображение на адресное пространство памяти временного файла (который будет удален после завершения работы процессов) представляет собой механизм реализации общей памяти для нескольких процессов, причем у такого механизма будет высокая про­пускная способность. В предельном случае два или более процессов могут отобра­зить на память файл, покрывающий все адресное пространство, получая, таким образом, форму совместного использования памяти – нечто среднее между процес­сами и потоками. В этом случае, как и у потоков, все адресное пространство ис­пользуется совместно, но каждый процесс может управлять собственными файла­ми и сигналами, что отличает этот вариант от потоков.

 






Не нашли, что искали? Воспользуйтесь поиском:

vikidalka.ru - 2015-2024 год. Все права принадлежат их авторам! Нарушение авторских прав | Нарушение персональных данных