Скачиваний:
97
Добавлен:
03.06.2014
Размер:
5.64 Mб
Скачать

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

463

Виртуальная память и кэширование

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

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

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

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

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

1.Аппаратура диска не смогла выполнить позиционирование.

2.Несуществующий элемент памяти определен как буфер.

3.Процесс ввода-вывода с диска (на диск) начался до того, как закончился предыдущий.

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

4.Ошибка синхронизации при считывании.

5.Обращение к несуществующему диску.

6.Обращение к несуществующему цилиндру.

7.Обращение к несуществующему сектору.

8.Ошибка проверки записи после операции записи.

При наличии одной из этих ошибок устанавливается соответствующий бит в регистре устройства.

Файлы

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

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

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

1.Указание, какой именно открытый файл нужно считывать.

2.Указатель на буфер в памяти, в который нужно поместить данные.

3.Число считываемых байтов.

Данный системный вызов помещает требующиеся данные в буфер. Обычно он возвращает число считанных байтов. Это число может быть меньше того числа, которое запрашивалось изначально (например, нельзя считать 2000 байтов из файла размером 1000 байт).

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

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

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

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

Номер

14

 

 

 

15

 

 

логической•

 

Одна

 

 

 

 

 

{

16

 

 

записи

15

 

логическая

 

 

 

16

Следующая

запись

 

17

Следующая

 

 

логическая

 

 

логическая

 

 

 

 

 

 

 

 

17

запись для чтения

 

 

18

записьдля чтения

 

 

 

 

 

 

 

 

18

 

 

 

19

 

 

 

19

 

 

 

20

 

 

 

20

 

 

 

21

 

 

 

21

Основная

 

 

22

Основная

 

 

память

 

 

память

 

 

22

 

 

23

 

 

 

 

 

 

 

 

23

Логическая

Буфер

 

24

Логическая

Буфер

 

запись 18

 

запись 19

 

 

 

 

 

 

 

24

 

 

 

25

 

 

 

25

 

 

 

26

 

 

Рис. 6.17. Чтениефайла, состоящегоизлогическихзаписей:дочтениязаписи 19(а); после чтения записи 19(6)

Основная виртуальная команда вывода записывает логическую запись из памяти в файл. Последовательные команды write производят последовательные логические записи в файл.

Реализация виртуальных команд ввода-вывода

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

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

Еще одно фундаментальное свойство реализации системы файлов — хранится ли файл в последовательных блоках или нет. На рис. 6.18 изображен простой диск с однойповерхностью, состоящийиз 5дорожекпо 12 секторов каждая. Нарис. 6.18, а файл состоит из последовательных секторов. Последовательное расположение пяти блоков обычно применяется на компакт-дисках. На рис. 6.18, б файл занимает непоследовательные сектора. Такая схема является нормой для жестких дисков.

Сектор 11 Сектор О

Сектор 11 Сектор О

Дорожка 4

Дорожка 0

Направление

Направление

вращения диска

вращения диска

Рис. 6.18. Варианты расположения файла на диске' файл занимает последовательные сектора(а); файл занимаетнепоследовательные сектора (б)

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

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

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

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

467

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

Альтернативный метод нахождения блоков файла — организовать файл в виде связного списка. Каждый единичный блок содержит адрес следующего единичного блока. Для реализации этой схемы нужно в основной памяти иметь таблицу со всеми последующими адресами. Например, длядиска с 64 К блоками операционная система может иметь в памяти таблицу из 64 К элементов, в каждом из которых дается индекс следующего единичного блока. Так, если файл занимает блоки 4,52 и 19, то элемент 4 в таблице будет содержать число 52, элемент 52 будет содержать число 19, а элемент 19 будет содержать специальный код (например, 0 или -1), который указывает на конец файла. Так работают системы файлов в MS DOS, Windows 95 и Windows 98. Windows NT поддерживает эту систему файлов, но кроме этого имеет свою собственную систему файлов, которая больше похожа на UNIX.

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

С другой стороны, если максимальный размер всех файлов известен заранее (например, это имеет место, когда создается компакт-диск), программа может заранее определить серии секторов, точно равных по длине каждому файлу. Если файлы по 1200, 700, 2000 и 900 секторов нужно поместить на компакт-диск, они просто могут начинаться с секторов 0,1200,1900 и 3900 соответственно (оглавление здесь не учитывается). Найти любую часть любого файла легко, поскольку известен первый сектор файла.

Чтобы распределить пространство на диске для файла, операционная система должна следить, какие блоки доступны, а какие уже заняты другими файлами. При записи на компакт-диск вычисление производится один раз и навсегда, а на жестком диске файлы постоянно записываются и удаляются. Один из способов — сохранить список всех «дырок» (неиспользованных пространств), где «дырка» — это любое число смежных единичных блоков. Этот список называется списком свободной памяти. На рис. 6.19, а изображен список свободной памяти для диска с рис. 6.18, б.

Альтернативный подход — сохранить битовое отображение, один бит на единичный блок, как показано на рис. 6.19, б. Бит со значением 1 показывает, что данный блок уже занят, а бит со значением 0 показывает, что данный блок свободен.

468

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

 

 

 

 

 

 

 

 

 

 

 

 

 

Количество

 

 

 

 

 

 

Сектор

 

 

 

 

 

 

 

 

 

секторов

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Дорожка Сектор

в дырке

Дорожка

0

1

0

1

0

1

0

1 0

1

0

 

1

 

0

0

5

0

0

0

0

0

0

1

0

0

0

0

 

0

0

 

1

0

0

0

0

0

0

0

0

0

0

 

1

0

 

0

6

6

 

 

2

1

0

1

0

0

0

1

0

0

0

 

0

0

 

1

0

10

 

 

1

11

1

3

0

0

0

1

1

1

1

1

1

0

 

0

0

 

4

1

1

1

0

0

0

0

0

0

0

 

0

1

 

2

1

1

 

 

2

3

3

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

2

7

5

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

3

0

3

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

3

9

3

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

4

3

8

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Рис. 6.19. Два способа отслеживания свободных секторов: список свободной памяти (а); битовое отображение (б)

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

Перед тем как закончить обсуждение вопроса о реализации системы файлов, нужно сказать пару слов о размере единичного блока. Здесь играют роль несколько факторов. Во-первых, время поиска и время, затрачиваемое на вращение диска, затормаживают доступ к диску. Если на нахождение начала блока тратится 10 млс, то гораздо выгоднее считать 8 Кбайт (это займет примерно 1 млс), чем 1 Кбайт (это займет примерно 0,125 миллисекунды), так как если считывать 8 Кбайт как 8 блоков по 1 Кбайт, нужно будет осуществлять поиск 8 раз. Для повышения производительности нужны большие единичные блоки.

Чем меньше размер единичного блока, тем больше их должно быть. Большое количество единичных блоков, в свою очередь, влечет за собой длинные индексы файлов и большие таблицы в памяти. Системе MS DOS пришлось перейти на многосекторные блоки по той причине, что дисковые адреса хранились в виде 16-бит- ных чисел. Когда размер дисков стал превышать 64 К секторов, представить их можно было, только используя единичные блоки большего размера, поэтому число таких блоков не превышало 64 К. В первом выпуске системы Windows 95 возникла та же проблема, но в последующем выпуске уже использовались 32-битные числа. Система Windows 98 поддерживает оба размера.

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

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

469

больше блок, тем больше остается неиспользованного пространства. Если средний размер файла намного меньше размера единичного блока, большая часть пространства на диске будет неиспользованной. Например, в системе MS DOS или в первой версии Windows 95 с разделом диска в 2 Гбайт размер единичного блока составляет 32 Кбайт, поэтому при записи на диск файла в 100 символов 32 668 байтов дискового пространства пропадут. С точки зрения распределения дискового пространства маленькие единичные блоки имеют преимущество над большими. В настоящее время самым важным фактором считается быстрота передачи данных, поэтому размер блоков постоянно увеличивается.

Команды управления директориями

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

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

Обычно операционная система группирует неавтономные файлы в директории. На рис. 6.20 проиллюстрирован пример такой организации. Обеспечиваются системные вызовы, по крайней мере, для следующих функций:

1.Создание файла и введение его в директорию.

2.Стирание файла из директории.

3.Переименование файла.

4.Изменение статуса защиты файла.

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

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

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

Файл0

Имяфайла:Rubber-ducky

Файл 1

Длина:1840

Файл2

Тип:Anatidaedataram

Файл3

Датасоздания: 16 марта 1066

Файл4

Последнееобращение:1сентября1492

Файл5

Последнее изменение: 4 июля 1776

Файл6

Общеечислообращений: 144

Файл7

Блок 0: Дорожка 4

Сектор 6

Файл8

Блок1: Дорожка 19

Сектор 9

Файл9

Блок 2: Дорожка 11

Сектор 2

Файл10

Блок 3: Дорожка 77 Сектор 0

Рис.6.20.Пользовательскаядиректория(а);содержаниеодногоизэлементовдиректории(б)

Виртуальные команды для параллельной обработки

Некоторые вычисления удобно производить с помощью двух и более параллельных процессов (то есть как будто бы на разных процессорах). Другие вычисления можно поделить на части, которые затем выполняются параллельно, что сокращает общее время вычисления. Чтобы несколько процессов могли происходить параллельно, нужны специальные виртуальные команды. Мы будем их обсуждать в следующих разделах.

Интерес к параллельной обработке обусловлен и некоторыми законами физики. Согласно эйнштейновской теории относительности, скорость передачи электрических сигналов не может превышать скорость света, которая равна примерно 1фут/нс в вакууме, а в медном проводе или оптическом волокне — еще меньше. Важно учитывать этот предел при разработке компьютеров. Например, если процессору нужны данные из основной памяти, которые находятся на расстоянии 1 фута, потребуется по крайней мере 1 не, чтобы запрос дошел до памяти, и еще 1 не, чтобы ответ венулся к центральному процессору. Следовательно, чтобы компьютеры могли передавать сигналы быстрее, они (компьютеры) должны быть совершенно крошечными. Альтернативный способ увеличения скорости компьютера — создание машины с несколькими процессорами. Компьютер, содержащий 1000 процессоров с временем цикла в 1 не, будет иметь такую же мощность, как процессор с временем цикла 0,001 не, но первое осуществить гораздо проще и дешевле.

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

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

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

 

Процесс3 находится

 

 

всостоянииожидания

 

Процесс3

 

 

 

 

 

 

Процесс3

I

 

 

 

 

 

Процесс2

t

 

 

 

i

i

i

 

 

 

 

 

Процесс 2

[

|

 

 

 

| |

 

i

i

i

i

 

 

Процесс1

i

i

 

 

i

i

i

i

i i i i

i

Процесс1

 

 

 

 

 

 

 

Процесс1 работает

 

Время

 

 

 

Время

 

 

а

 

 

 

б

 

 

Рис.6 . 2 1 .Параллельнаяобработкаснесколькимипроцессорами(а);моделирование параллельнойобработкипутемпереключенияодногопроцессорасодногопроцессана другой (вданном случае всеготри процесса) (б)

Формирование процесса

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

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

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

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

Состояние гонок

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

Рассмотрим два независимых процесса, процесс 1 и процесс 2, которые взаимодействуют через общий буфер в основной памяти. Для простоты мы будем называть процесс 1 производителем (producer), а процесс 2 — потребителем (consumer). Производитель выдает простые числа и помещает их в буфер по одному. Потребитель удаляет их из буфера по одному и печатает.

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

В нашем примере для взаимодействия процессов мы будем использовать кольцевой буфер. Указатели in и out будут использоваться следующим образом: in указывает на следующее свободное слово (куда производитель поместит следующее простое число), a out указывает на следующее число, которое должен удалить потребитель.

Если in=out, буфер пуст, как показано на рис. 6.22, а. На рис. 6.22, б показана ситуация после того, как производитель породил несколько простых чисел. На рис. 6.22, в изображен буфер после того, как потребитель удалил из него несколько простых чисел для печати. На рис. 6.22, г — е представлены промежуточные стадии работы буфера. Буфер заполняется по кругу.

Если в буфер было отправлено слишком много чисел и он начал заполняться по второму кругу (снизу), а под out есть только одно слово (например, ш=52, а out=53), буфер заполнится. Последнее слово не используется; если бы оно использовалось, то не было бы возможности сообщить, что именно значит равенство in=out — полный буфер или пустой буфер.

 

 

 

In-

In-*

 

 

 

 

 

Out-»

Out-

 

In-

In-

 

 

 

 

 

Out-

Out-

 

In-

In,

Out-

 

 

 

 

out'

 

 

 

 

б

 

в

г

 

 

 

 

Рис.6.22.Кольцевойбуфер