Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Архитектура компьютера - Таненбаум Э

..pdf
Скачиваний:
493
Добавлен:
24.05.2014
Размер:
5.67 Mб
Скачать

Виртуальные команды ввода-вывода

473

В листинге 6.1 на языке Java показано решение задачи с производителем и потребителем.

Здесь используются три класса: m,produser и consumer. Класс т содержит некоторые константы, указатели буфера in и out и сам буфер, который в нашем примере вмещает 100 простых чисел (от buffer[O] до buffer[99]).

Для моделирования параллельных процессов в данном случае используются потоки (threads). У нас есть классproducerи класс consumer, которым приписываются значения переменных р и с соответственно. Каждый из этих классов образуется из базового класса Threadс процедурой run. Класс run содержит код для thread. Когда вызывается процедура start для объекта, образованного из Thread, запускается новый поток.

Каждый поток похож на процесс. Единственным различием является то, что все потоки в пределах одной программы на языкеJava работают в одном адресном пространстве. Это позволяет им разделять один общий буфер. Если в компьютере имеется два и более процессоров, каждый поток может выполняться на другом процессоре, поэтому в данном случае имеет место реальный параллелизм. Если компьютер содержит только один процессор, потоки разделяются во времени на одном процессоре. Мы будем продолжать называть производителя и потребителя процессами (поскольку нас в данный момент интересуют параллельные процессы), хотя Java поддерживает только параллельные потоки, а не реальные параллельные процессы.

Функция next позволяет увеличивать значения in и out, при этом не нужно каждый раз записывать код, чтобы проверить условие циклического возврата. Если параметр в next равен 98 или указывает на более низкое значение, то возвращается следующее по порядку целое число. Если параметр равен 99, это значит, что мы наткнулись на конец буфера, поэтому возвращается 0.

Должен быть способ «усыплять» любой из процессов, если он не может продолжаться. Для этого разработчики Java включили в класс Thread специальные процедуры suspend (отключение) и resume (возобновление). Они используются

влистинге 6.1.

Атеперь рассмотрим саму программу для производителя и потребителя. Сначала производитель порождает новое простое число (шаг Р1). Обратите внимание на строку m.MAX_PRIME. Префикс т. здесь указывает, что имеется в виду MAXPRIME, определенный в классе т. По той же причине этот префикс нужен для in, out, buffer и next.

Затем производитель проверяет (шаг Р2), не находится ли in ниже out. Если да (например, ш=62 и out=63), то буфер заполнен и производитель вызывает процедуру suspend в Р2. Если буфер не заполнен, туда помещается новое простое число (шаг РЗ) и значение in увеличивается (шаг Р4). Если новое значение in на 1 больше значения out (шаг Р5) (например, ш=17, out=l6), значит, in и out были равны перед тем, как увеличилось значение in. Производитель делает вывод, что буфер был пуст и что потребитель не функционировал (находился в режиме ожидания) и в данный момент тоже не функционирует. Поэтому производитель вызывает процедуру resume, чтобы возобновить работу потребителя (шаг Р5). Наконец, производитель начинает искать следующее простое число.

4 7 4 Глава 6. Уровень операционной системы

Листинг 6 . 1 . Параллельная обработка с состоянием гонок

public class m {

final public static int BUF_SIZE = 100;

final public static long MAX_PRIME=100000000000L; public static int in = 0, out = 0.

public static long buffer[ ] - new long[BUF_SIZE];

public

static

producer

p.

public

static

consumer

c;

public

static void mam(Stnng args[ ]){

 

p = new producer );

с= new consumer ), p startO:

сstartO .

//буфер от 0 до 99 //остановиться здесь

//указатели на данные //простые числа хранятся здесь //имя производителя //имя потребителя

//основной класс

//создание производителя //создание потребителя //запуск производителя //запуск потребителя

//Это утилита для циклического увеличения m и out

 

public

static int next(int k) { i f

(k < BUF_SIZE

- 1) return(k+l). else return(O):

class producer extends Thread {

 

//класс производителя

public

void runO {

 

//код производителя

long prime = 2;

 

// временная переменная

while

(prime < m.MAX_PRIME)

{

 

 

 

prime = next_pnme(pnme):

//шаг Р1

 

if (m next(m.m) == m.out) suspendO:

//шаг Р2

 

m

buffer[m. in] = prime;

 

//шаг

РЗ

 

m in = m.next(m in).

 

//шаг

Р4

 

if

(m next(m out) = m in)

m c.resumeO.

//шаг

Р5

private long next_pnme(long pnme){ ..}

 

//функция, вычисляющая следующее число

class consumer extends Thread {

//класс

потребителя

public void run() {

//

код

потребителя

long emirp = 2;

//

временная переменная

while (emirp < m MAX_PRIME) {

 

 

 

if (m in == m out) suspendO:

 

 

//шагС1

emirp = m buffer[m.out]:

 

 

//шагС2

m.out = m next(m out);

 

 

//шагСЗ

if (m.out — m.next(m next(m.in))) m

p.resumeO; //шагС4

System out print!n(emirp).

 

 

//шагС5

Программа потребителя по структуре очень проста. Сначала производится проверка (шаг С1), чтобы узнать, пуст ли буфер. Если он пуст, то потребителю ничего не нужно делать, поэтому он отключается. Если буфер не пуст, то потребитель удаляет из него следующее число для печати (шаг С2) и увеличивает значение out. Если после этого out стало на две позиции выше in, значит, до этого out было на одну позицию выше in. Так как in=out-\ — это условие заполненности буфера, значит, производитель не работает и потребитель должен вызвать процедуру resume. После этого число выводится на печать, и весь цикл повторяется снова.

Виртуальныекомандыввода-вывода

4 7 5

К сожалению, такая программа содержит ошибку (рис. 6.23). Напомним, что эти два процесса работают асинхронно и с разными скоростями, которые, к тому же, могут меняться. Рассмотрим случай, когда в буфере осталось только одно число в элементе 21, и m=22, a out=2i (см. рис. 6.23, а). Производитель на шаге Р1 ищет простое число, а потребитель на шаге С5 печатает число из позиции 20. Потребитель заканчивает печатать число, совершает проверку на шаге С1 и забирает последнее число из буфера на шаге С2. Затем он увеличивает значение out. В этот момент и in и ом? равны 22. Потребитель печатает число, а затем переходит к шагу С1, на котором он вызывает in и out из памяти, чтобы сравнить их (рис. 6.23, б).

 

 

Процесс-производитель

Процесс-производитель

Процесс-производитель

на шаге Р5 посылает

на шаге Р1

на шаге Р1

сигнал пробуждения

Процесс-потребитель

Процесс-потребитель

процессу-потребителю

на шаге С5

на шаге С5

на шаге С1

100

100

100

 

 

In = 23

 

In = 22

In = Out = 22

Out = 22

Простое

Out = 21

Простое

 

число

 

 

 

число

 

 

 

1 число

 

1 число

 

 

в буфере

 

в буфере

 

 

Рис. 6.23. Ситуация, при которой механизм взаимодействия производителя wпотребителя не работает

Вэтот момент, после того как потребитель вызвал in и out, но еще до того как он сравнил их, производитель находит следующее простое число. Он помещает это простое число в буфер на шаге РЗ и увеличивает in на шаге Р4. Теперь щ=23,

аом£=22. На шаге Р5 производитель обнаруживает, что in=*next(out). Иными словами, in на единицу больше out, а это значит, что в буфере в данный момент находится один элемент.

Исходя из этого, производитель делает неверный вывод, что потребитель отключен, и вызывает процедуру resume (рис. 6.23, в). На самом деле потребитель все это время продолжал работать, поэтому вызов процедуры resume оказался ложным. Затем производитель начинает искать следующее простое число.

Вэтот момент потребитель продолжает работать. Он уже вызвал in и out из памяти, перед тем как производитель поместил последнее число в буфер. Так как ш=22 и out-22, потребитель отключается. К этому моменту производитель находит следующее простое число. Он проверяет указатели и обнаруживает, что ш=24, a out=22. Из этого он делает заключение, что в буфере находится 2 числа (что соответствует действительности) и что потребитель функционирует (что неверно).

4 7 6 Глава 6. Уровень операционной системы

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

Сложность здесь в том, что между моментом, когда потребитель вызывает in и out, и моментом, когда он отключается, производитель, обнаружив, что in=out+\, и предположив, что потребитель отключен (хотя на самом деле он еще продолжает функционировать), вызывает процедуру resume, чего не нужно делать, поскольку потребитель функционирует. Такая ситуация называется состоянием гонок, поскольку успех процедуры зависит от того, кто выиграет гонку по проверке in и out, после того как значение out увеличилось.

Проблема состояния гонок хорошо известна. Она была настолько серьезна, что через несколько лет после появления Java компания Sun изменила класс Thread и убрала вызовы процедур suspend и resume, поскольку они очень часто приводили к состоянию гонок. Предложенное решение было основано на изменении языка, но поскольку мы изучаем операционные системы, мы обсудим другое решение, которое используется во многих операционных системах, в том числе UNIX и NT.

Синхронизация процесса с использованием семафоров

Проблему состояния гонок можно разрешить по крайней мере двумя способами. Первый способ — снабдить каждый процесс специальным битом ожидания пробуждения. Если процесс, который функционирует в данный момент, получает сигнал «пробуждения», то этот бит устанавливается. Если процесс отключается в тот момент, когда этот бит установлен, он немедленно перезапускается, а бит сбрасывается. Данный бит сохраняет сигнал пробуждения для будущего использования.

Этот метод решает проблему состояния гонок только в том случае, если у нас есть всего 2 процесса. В общем случае при наличии п процессов он не работает. Конечно, каждому процессу можно приписать п-1 таких битов ожидания пробуждения, но это неудобно.

Дейкстра [31] предложил другое решение этой проблемы. Где-то в памяти находятся две переменные, которые могут содержать неотрицательные целые числа. Эти переменные называются семафорами. Операционная система предоставляет два системных вызова, up и down, которые оперируют семафорами. Up прибавляет 1 к семафору, a down отнимает 1 от семафора. Если операция down совершается над семафором, значение которого больше 0, этот семафор уменьшается на 1 и процесс продолжается. Если значение семафора равно 0, то операция down не может завершиться. Тогда данный процесс отключается до тех пор, пока другой процесс не выполнит операцию up над этим семафором.

Команда up проверяет, не равен ли семафор нулю. Если он равен 0 и другой процесс находится в режиме ожидания, то семафор увеличивается на 1. После этого процесс, который «спит», может завершить операцию down, установив семафор на 0. Теперь оба процесса могут продолжать работу. Если семафор не равен 0, команда up просто увеличивает его на 1. Семафор позволяет сохранять сигналы пробуждения, так что они не пропадут зря. У семафорных команд есть одно важное свойство: если один из процессов начал выполнять команду над семафором, то

Виртуальные команды ввода-вывода

4 7 7

другой процесс не может получить доступ к этому семафору до тех пор, пока первый не завершит выполнение команды или не будет приостановлен при попытке выполнить команду down над 0. В таблице 6.5 изложены важные свойства системных вызовов up и down.

Таблица 6.5. Результаты операций над семафором

 

Команда

Семафор = 0

Семафор > О

Up

Семафор = семафор + 1; если другой процесс пытается

Семафор = семафор +1

 

совершить команду down над этим семафором, теперь

 

 

он сможет это сделать и продолжить свою работу

 

Down

Процесс останавливается до тех пор, пока другой

Семафор = семафор -1

 

процесс не выполнит операцию up над этим

 

 

семафором

 

Как мы уже сказали, в языке Java предусмотрено свое решение проблемы состояния гонок, но мы сейчас обсуждаем операционные системы. Следовательно, нам нужно каким-либо образом выразить использование семафоров в языке Java. Мы предположим, что были написаны две процедуры, up и down, которые совершают системные вызовы up и down соответственно. Используя в качестве параметров обычные целые числа, мы сможем выразить применение семафоров в программах на языке Java.

В листинге 6.2 показано, как можно устранить состояние гонок с помощью семафоров. В класс т добавляются два семафора: available, который изначально равен 100 (это размер буфера), w.filled, который изначально равен 0. Производитель начинает работус шага Р1, а потребитель — с шага С1. Выполнение процедуры down над семафором filled сразу же приостанавливает работу потребителя. Когда производитель находит первое простоечисло, он вызывает процедуруdown сavailable в качестве параметра, устанавливая available на 99. На шаге Р5 он вызывает процедуру up с параметромfilled, устанавливаяfilled на 1. Это действие освобождает потребителя, который теперь может завершить вызов процедуры down. После этогоfilled принимает значение 0, и оба процесса продолжают работу.

А теперь давайте еще раз рассмотрим состояние гонок. В определенный момент ш=22, a out=2\, производитель находится на шаге Р1, а потребитель — на шаге С5. Потребитель завершает действие и переходит к шагу С1, который вызывает процедуру down, чтобы выполнить ее над семафором filled, который до вызова имел значение 1, а после вызова принял значение 0. Затем потребитель берет последнее число из буфера и выполняет процедуру up над available, после чего available принимает значение 100. Потребитель печатает это число и переходит к шагу С1.

Как раз перед тем, как потребитель может вызвать процедуру down, производитель находит следующее простое число и быстро выполняет шаги Р2, РЗ и Р4.

В этот MOMemfilled=0. Производитель собирается выполнить над ним команду up, а потребитель собирается вызвать процедуру up. Если потребитель выполнит команду первым, то он будет приостановлен до тех пор, пока производитель не освободит его (вызвав процедуру up). Если же первым будет производитель, то семафор примет значение 1 и потребитель вообще не будет приостановлен. В обоих случаях сигнал пробуждения не пропадет. Именно для этого мы и ввели в программу семафоры.

Ч/О

Глава Ь. Уровень операционной системы

Операции над семафорами неделимы. Если операция над семафором уже началась, то никакой другой процесс не может использовать этот семафор до тех пор, пока первый процесс не завершит операцию или пока он не будет приостановлен. Более того, при наличии семафоров сигналы пробуждения не пропадают. А вот операторы if в листинге 6.1 делимы. Между проверкой условия и выполнением нужного действия другой процесс может послать сигнал пробуждения.

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

Технология с использованием семафоров работает для произвольного количества процессов. Несколько процессов могут «спать», не завершив системный вызов down на одном и том же семафоре. Когда какой-нибудь другой процесс выполнит процедуру up на том же семафоре, один из ждущих процессов может завершить вызов down и продолжить работу. Семафор сохраняет значение 0, и другие процессы продолжают ждать.

Поясним это на другом примере. Представьте себе 20 баскетбольных команд. Они играют 10 партий (процессов). Каждая игра происходит на отдельном поле. Имеется большая корзина (семафор) для баскетбольных мячей. К сожалению, есть только 7 мячей. В каждый момент в корзине находится от 0 до 7 мячей (семафор принимает значение от 0 до 7). Помещение мяча в корзину — это операция up, поскольку она увеличивает значение семафора. Извлечение мяча из корзины — это операция down, поскольку она уменьшает значение семафора.

Всамом начале один игрок от каждого поля посылается к корзине за мячом. Семерым из них удается получить мяч (завершить операцию down); оставшиеся трое вынуждены ждать мяч. Их игры временно приостановлены. В конце концов одна из партий заканчивается и мяч возвращается в корзину (выполняется операция up). Эта операция позволяет одному из трех оставшихся игроков получить мяч (закончить незавершенную операцию down) и продолжить игру. Оставшиеся две партии остаются приостановленными до тех пор, пока еще два мяча не возвратятся в корзину. Когда эти два мяча положат в корзину (то есть будет выполнено еще две операции up), можно будут продолжить последние две партии.

Листинг 6.2. Параллельная обработка с использованием семафоров

 

public class m {

 

 

final public static int BUF_SIZE = 100.

//буфер от 0 до 99

final public static long MAX_PRIME=100000000000L.

//остановиться здесь

public static int in = 0. out = 0.

//указатели

на данные

public static long buffer[ ] = new long[BUF_SIZE].

//здесь хранятся простые числа

public static producer p

//имя

произво/щеля

public static consumer с

//имя

потребитшя

public static int filled = 0, available = 100.

//семафоры

 

 

Примеры операционных систем

479

public static vend mainCStnng args[ ]) {

//основной

клласс

 

p = new producerO;

//создание

производителя

 

с = new consumerO;

//создание

потребителя

 

p.startO:

//запуск

производителя

 

с startO:

//запуск

потребителя

 

// Это утилита для циклического увеличения in и out

public static int nextdnt k) {if (k < BUF_SIZE - 1) return(k+l): else return(O).

class producer extends Thread {

native void updnt s); native void downdnt s); public void run() {

long prime - 2;

while (prime < m.MAX_PRIME) { prime = next_pnme(pnme): downtm.available);

m buffer[m in] = prime; m.in = m next(m.in); up(m.filled);

private long next_pnme(long pnme){ ...}

class consumer extends Thread {

native void updnt s); native void downdnt s); public void runC) {

long emirp = 2,

while (emirp < m MAX_PRIME) { down(m.filled),

emirp = m.buffer[m.out]; m.out = m next(m out); up(m. available): System.out.print!n(emirp);

//класс производителя //процедуры над семафорами //код производителя //временная переменная

//шагР1

//шагР2

//шагРЗ

//шагР4

//шагР5

//функция, которая выдает следующее число

//класс потребителя //процедуры над семафорами //код потребителя //временная переменная

//шагС1

//шагС2

//шагСЗ //шаг С4 //шагС5

Примеры операционных систем

В этом разделе мы продолжим обсуждать Pentium II и Ultra SPARC П. Мы рассмотрим операционные системы, которые используются на этих процессорах.

Для Pentium II мы возьмем Windows NT (для краткости мы будем называть эту операционную систему NT); для UltraSPARC II — UNIX. Мы начнем наш разговор с UNIX, поскольку эта система гораздо проще NT. Кроме того, операционная система UNIX была разработана раньше и сильно повлияла на NT, поэтому такой порядок изложения будет более осмысленным.

4 8 0 Глава 6. Уровень операционной системы

Введение

В этом разделе мы дадим краткий обзор двух операционных систем (UNIX и NT). При этом мы обратим особое внимание на историю, структуру и системные вызовы.

UNIX

Операционная система UNIX была разработана в компании Bell Labs в начале 70-х годов. Первая версия была написана Кеном Томпсоном (Ken Thompson) на ассемблере для мини-компьютера PDP-7. Затем была написана вторая версия для компьютера PDP-11, уже на языке С. Ее автором был Деннис Ритчи (Dennis Ritchie). В 1974 году Ритчи и Томпсон опубликовали очень важную работу о системе UNIX [120]. За эту работу они были награждены престижной премией Тьюринга Ассоциации вычислительной техники (Ritchie, 1984; Thompson, 1984). После публикации этой работы многие университеты попросили у Bell Labs копию UNIX. Поскольку материнская компания Bell Labs, AT&T была в тот момент регулируемой монополией и ей не разрешалось принимать участие в компьютерном бизнесе, университеты смогли приобрести операционную систему UNIX за небольшую плату.

PDP-11 использовались практически во всех компьютерных научных отделах университетов, и операционные системы, которые пришли туда вместе с PDP-11, не нравились ни профессорам, ни студентам. UNIX быстро заполнил эту нишу. Эта операционная система была снабжена исходными текстами, поэтому люди могли до бесконечности исправлять ее.

Одним из первых университетов, которые приобрели систему UNIX, был Калифорнийский университет в Беркли. Поскольку имелась в наличии полная исходная программа, в Беркли сумели существенно преобразовать эту систему. Среди изменений было портирование этой системы на мини-компьютер VAX, создание виртуальной памяти со страничной организацией, расширение имен файлов с 14 символов до 255, а также включение сетевого протокола TCP/IP, который сейчас используется в Интернете (во многом благодря тому факту, что он был в системе Berkeley UNIX).

Пока в Беркли производились все эти изменения, компания AT&T самостоятельно продолжала разработку UNIX, в результате чего в 1982 году появилась System III, а в 1984 — System V. В конце 80-х годов широко использовались две разные и совершенно несовместимые версии UNIX: Berkeley UNIX и System V. Такое положение, да еще и отсутствие стандартов на форматы программ в двоичном коде сильно препятствовало коммерческому успеху системы UNIX. Поставщики программного обеспечения не могли писать программы для UNIX, ведь не было никакой гарантии, что эти программы будут работать на любой версии UNIX (как было сделано с MS DOS). После долгих споров комиссия стандартов в Институте инженеров по электротехнике и электронике выпустила стандарт POSIX (Portable Operating System-IX — интерфейс переносной операционной системы1). POSIX также известен как стандарт Р1003. Позднее он стал международным стандартом.

1POSIX — Portable Operating System Interface for Computer Environments — платформенно-независи- мый системный интерфейс. — Примеч. научн. ред.

Примеры операционных систем

481

Стандарт POSIX разделен на несколько частей, каждая из которых покрывает отдельную область системы UNIX. Первая часть Р 1003.1 определяет системные вызовы; вторая часть Р1003.2 определяет основные обслуживающие программы и т. д. Стандарт Р1003.1 определяет около 60 системных вызовов, которыедолжны поддерживаться всеми соответствующими системами. Это вызовы для чтения и записи файлов, создания новых процессов и т. д. Сейчас практически все системы UNIX поддерживают системные вызовы Р1003.1. Однако многие системы UNIX поддерживают и дополнительные системные вызовы, в частности те, которые определены в System V или в Berkeley UNIX. Обычно к набору POSIX добавляется до 100 системных вызовов. Операционная система для машины UltraSPARC II основана на System V. Она называется Solaris. Она поддерживает и многие вызовы из системы Berkeley.

В табл. 6.6 приведены категории системных вызовов. Системные вызовы управления файлами и директориями — это самые большие и самые важные категории. Большинство из них относятся к стандарту Р1003.1. Остальные происходят из системы System V.

Таблица 6.6. Системные вызовы UNIX

Категория

Примеры

Управлениефайлами

Открытие, чтение, запись, закрытие и блокировка файлов

Управлениедиректориями

Создание и удалениедиректорий; перемещение файлов

 

по директориям

Управление процессами

Порождение, завершение, отслеживание процессов

 

ипередачасигналов

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

Разделение общей памяти между процессами; защита страниц

Вызов/установкапараметров Идентификацияпользователя,группы,процесса;установка

 

приоритетов

Даты и периодывремени

Указание на время доступа к файлам; использование датчика

 

временных интервалов; рабочий профиль программы

Работа в сети

Установка/принятие соединения; отправка/получение

 

сообщения

Прочее

Учет использования ресурсов; ограничение на доступный

 

объем памяти; перезагрузка системы

Сфера использования сетей в большей степени относится к Berkeley UNIX, а не к System V. В Беркли было введено понятие сокет (конечный пункт сетевой связи). Четырехпроводные стенные розетки, к которым можно подсоединять телефоны, послужили в качестве модели этого понятия. Процесс в системе UNIX может создать сокет, присоединиться к нему и установить связь с сокетом на удаленном компьютере. По этой связи можно пересылать данные в обоих направлениях, обычносиспользованиемпротоколаTCP/IP. Посколькутехнологиясетевойсвязидесятилетиями применялась в системе UNIX, значительное число серверов в Интернете используют именно UNIX.

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

4 8 2 Глава 6. Уровень операционной системы

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

Оболочка

Пользовательская

Пользовательский

программа

 

 

 

режим

 

 

 

Интерфейссистемныхвызовов

 

 

Система файлов

Управление процессами

Привилегированный

 

Межпроцессорная

 

Кэш блоков

Планирование

режим

 

связь

 

 

Драйверы устройств

 

Управление

 

Сигналы

памятью

Аппаратное обеспечение

Рис. 6.24. Структура типичной системы UNIX

Над драйверами устройств находится система управления файлами. Она управляет именами файлов, директориями, расположением блоков на диске, защитой и выполняет многие другие функции. В системе файлов имеется так называемый кэш блоков для хранения недавно считанных с диска блоков, на случай если они понадобятся еще раз. Некоторые системы файлов использовались на протяжении многих лет. Среди них можно назвать быструю файловую систему Berkeley [95] и журналирующие файловые системы [121].

Еще одна часть ядра системы UNIX — структура управления процессами. Она выполняет различные функции, в том числе управляет межпроцессорной связью, которая позволяет разным процессам взаимодействовать друг с другом и синхронизирует работу процессов, чтобы избежать состояния гонок. Для этого существует ряд механизмов. Код управления процессами также управляет планированием работы процессов, которое основано на приоритетах. Кроме того, здесь обрабатываются сигналы прерываний. Наконец, здесь же происходит управление памятью. Большинство систем UNIX поддерживают виртуальную память с подкачкой страницпотребованию,иногдаснекоторымидополнительнымиособенностями(например, несколько процессов могут разделять общие области адресного пространства).