Операционные системы(теория)
Многозадачность(multitasking) — свойство операционной системы или среды выполнения обеспечивать возможность параллельной (или псевдопараллельной)) обработки нескольких задач. Истинная многозадачность операционной системы возможна только в распределённых вычислительных системах.
Есть несколько разных понятий, связанных с областью параллельных вычислений.
Конкурентность (concurrency)
Конкурентность (concurrency) — это наиболее общий термин, который говорит, что одновременно выполняется более одной задачи. Например, вы можете одновременно смотреть телевизор и комментить фоточки в фейсбуке. К сожалению, вменяемого русскоязычного термина я не знаю. Википедия говорит, что concurrent computing — это параллельные вычисления, но как тогда будет parallel computing по русски?
Конкурентное исполнение — это самый общий термин, который не говорит о том, каким образом эта конкурентность будет получена: путем приостановки некоторых вычислительных элементов и их переключение на другую задачу, путем действительно одновременного исполнения, путем делегации работы другим устройствам или еще как-то. Это не важно.
Конкурентное исполнение говорит о том, что за определенный промежуток времени будет решена более, чем одна задача. Точка.
Параллельное исполнение
Параллельное исполнение (parallel computing) подразумевает наличие более одного вычислительного устройства (например, процессора), которые будут одновременно выполнять несколько задач.
Параллельное исполнение — это строгое подмножество конкурентного исполнения. Это значит, что на компьютере с одним процессором параллельное программирование — невозможно;)
Многопоточность
Многопоточность — это один из способов реализации конкурентного исполнения путем выделения абстракции "рабочего потока" (worker thread).
Потоки "абстрагируют" от пользователя низкоуровневые детали и позволяют выполнять более чем одну работу "параллельно". Операционная система, среда исполнения или библиотека прячет подробности того, будет многопоточное исполнение конкурентным (когда потоков больше чем физических процессоров), или параллельным (когда число потоков меньше или равно числу процессоров и несколько задач физически выполняются одновременно).
Асинхронное исполнение
Асинхронность (asynchrony) подразумевает, что операция может быть выполнена кем-то на стороне: удаленным веб-узлом, сервером или другим устройством за пределами текущего вычислительного устройства.
Основное свойство таких операций в том, что начало такой операции требует значительно меньшего времени, чем основная работа. Что позволяет выполнять множество асинхронных операций одновременно даже на устройстве с небольшим числом вычислительных устройств.
CPU-bound и IO-Bound операции
Еще один важный момент, с точки зрения разработчика — разница между CPU-bound и IO-bound операциями. CPU-Bound операции нагружают вычислительные мощности текущего устройства, а IO-Bound позволяют выполнить задачу вне текущей железки.
Разница важна тем, что число одновременных операций зависит от того, к какой категории они относятся. Вполне нормально запустить параллельно сотни IO-Bound операций, и надеяться, что хватит ресурсов обработать все результаты. Запускать же параллельно слишком большое число CPU-bound операций (больше, чем число вычислительных устройств) бессмысленно.
Процессы и потоки
Существует 2 типа многозадачности:
- Процессная многозадачность (основанная на процессах — одновременно выполняющихся программах). Здесь программа — наименьший элемент управляемого кода, которым может управлять планировщик операционной системы. Более известна большинству пользователей (работа в текстовом редакторе и прослушивание музыки).
- Поточная многозадачность (основанная на потоках). Наименьший элемент управляемого кода — поток (одна программа может выполнять 2 и более задачи одновременно).
Процесс — экземпляр программы во время выполнения, независимый объект, которому выделены системные ресурсы (например, процессорное время и память). Каждый процесс выполняется в отдельном адресном пространстве: один процесс не может получить доступ к переменным и структурам данных другого. Если процесс хочет получить доступ к чужим ресурсам, необходимо использовать межпроцессное взаимодействие. Это могут быть конвейеры, файлы, каналы связи между компьютерами и многое другое.
Поток использует то же самое пространства стека, что и процесс, а множество потоков совместно используют данные своих состояний. Как правило, каждый поток может работать (читать и писать) с одной и той же областью памяти, в отличие от процессов, которые не могут просто так получить доступ к памяти другого процесса. У каждого потока есть собственные регистры и собственный стек, но другие потоки могут их использовать.
В Linux (в ядре) понятие «threads» отсутствует. С точки зрения ядра все потоки — это полноценные, отдельные, процессы (просто с общим адресным пространством)
Поток — определенный способ выполнения процесса. Когда один поток изменяет ресурс процесса, это изменение сразу же становится видно другим потокам этого процесса. Программный поток (thread): в русскоязычной литературе часто употребляют термин «нить» (в программистском жаргоне — «нитка»).
Рассказ о жизни процессов естественно начать с самого начала — с их появления на свет. Так вот, процессы размножаются. почкованием: системный вызов Linux, создающий новый процесс, называется clone, а дочерний процесс представляет собой почти точную копию родительского. Только далее он выполняет назначенную ему функцию, а исходный процесс — то, что написано в программе после вызова clone. Потом отличий может стать больше, так что пути-дороги процессов способны разойтись достаточно далеко. Но если нам нужно этому воспрепятствовать, вызов clone позволит задать флаги, указывающие, что порожденный процесс будет иметь со своим предком общие моменты.
Если родительский процесс по какой-то причине завершится раньше дочернего, последний становится сиротой (orphaned process). "Сироты" автоматически "усыновляются" программой init, выполняющейся в процессе с номером 1, которая и принимает сигнал об их завершении.
Если же потомок уже завершил работу, а предок не готов принять от системы сигнал об этом событии, то потомок не исчезает полностью, а превращается в зомби (zombie); в поле Stat такие процессы помечаются буквой Z. Зомби не занимает процессорного времени, но строка в таблице процессов остается, и соответствующие структуры ядра не освобождаются. После завершения родительского процесса "осиротевший" зомби на короткое время также становится потомком init, после чего уже "окончательно умирает".
Асинхронность
- Асинхронный буквально означает “не синхронный”. Например, отправка email асинхронная, потому что отправитель не ожидает получить ответ сразу же. В программировании “асинхронным” называют код, в котором компоненты посылают друг другу сообщения, не ожидая немедленного ответа.
- Неблокирующий — термин, чаще всего касающийся ввода-вывода. Он означает, что при вызове “неблокирующего” системного API управление сразу же будет возвращено программе, и она продолжит использовать свой квант процессорного времени. Обычные, простые в использовании системные вызовы блокирующие: они усыпляют вызывающий поток до тех пор, пока данные для ответа не будут готовы.
- Событийный означает, что компонент программы обрабатывает очередь событий с помощью цикла, а тем временем кто-либо добавляет события в очередь, формируя входные данные компонента, и забирает у него выходные данные.
Синхронные операции — операции, при которых мы получаем результат в результате блокирования потока выполнения. Для простых вычислительных операций (сложение/умножение чисел) — это единственный вариант их совершения, для операций ввода/вывода — один из, при этом мы говорим, к примеру, «попытайся прочитать из файла что-нибудь за 100мс», и если для чтения ничего нет — поток выполнения будет заблокирован на эти 100мс.
В некоторых случаях это допустимо (например, если мы делаем простое консольное приложение, либо какую-либо утилиту, цель которой — отработать и всё), но в некоторых — нет. К примеру, если мы так застрянем в потоке, в котором обрабатывается UI — наше приложение зависнет. За примерами далеко ходить не нужно — если javascript на сайте сделает while(true); , то перестанут вызываться какие-либо другие обработчики событий страницы и её придётся закрыть. Те же дела, если начать что-нибудь вычислять под Android’ом в обработчиках UI-событий (код которых вызывается в UI-потоке), это приведёт к появлению окна «приложение не отвечает, закрыть?» (подобные окна вызываются по watchdog-таймеру, который сбрасывается, когда выполнение возвращается обратно к системе UI).
Асинхронные операции — операции, при которых мы просим совершить некоторую операцию и можем каким-либо образом отслеживать процесс/результат её выполнения. Когда она будет выполнена — неизвестно, но мы можем продолжить заниматься другими делами.
Блокировки — это проблема для всех программ, требующих конкурентного выполнения, поскольку заблокированные потоки процесса засыпают и не получают процессорное время. Существует два различных, но взаимодополняющих способа устранить блокировки:
- неблокирующий режим ввода-вывода
- мультиплексирование с помощью системного API, такого как select либо epoll
Event loop
Бесконечный цикл, который берёт события из очереди и как-то их обрабатывает. А в некоторых промежутках — смотрит, не произошло ли каких-нибудь IO-событий, либо не просрочились ли какие-либо таймеры — тогда добавляет в очередь событие об этом, чтобы потом обработать.
Race condition
Состояние гонки (race condition), также конкуренция — ошибка проектирования многопоточной системы или приложения, при которой работа системы или приложения зависит от того, в каком порядке выполняются части кода. Своё название ошибка получила от похожей ошибки проектирования электронных схем (см. Гонки сигналов).
Термин состояние гонки относится к инженерному жаргону и появился вследствие неаккуратного дословного перевода английского эквивалента. В более строгой академической среде принято использовать термин неопределённость параллелизма.
Состояние гонки — «плавающая» ошибка (гейзенбаг), проявляющаяся в случайные моменты времени и «пропадающая» при попытке её локализовать.
Вытесняющая и кооперативная многозадачность
Совместная или кооперативная многозадачность
Тип многозадачности, при котором следующая задача выполняется только после того, как текущая задача явно объявит себя готовой отдать процессорное время другим задачам. Как частный случай такое объявление подразумевается при попытке захвата уже занятого объекта мьютекс (ядро Linux), а также при ожидании поступления следующего сообщения от подсистемы пользовательского интерфейса (Windows версий до 3.x включительно, а также 16-битные приложения в Windows 9x).
Кооперативную многозадачность можно назвать многозадачностью «второй ступени», поскольку она использует более передовые методы, чем простое переключение задач, реализованное многими известными программами (например, DOS Shell из MS-DOS 5.0). При простом переключении активная программа получает все процессорное время, а фоновые приложения полностью замораживаются. При кооперативной многозадачности приложение может захватить фактически столько процессорного времени, сколько оно считает нужным. Все приложения делят процессорное время, периодически передавая управление следующей задаче.
Преимущества кооперативной многозадачности: отсутствие необходимости защищать все разделяемые структуры данных объектами типа критических секций и мьютексов, что упрощает программирование, особенно перенос кода из однозадачных сред в многозадачные.
Недостатки: неспособность всех приложений работать в случае ошибки в одном из них, приводящей к отсутствию вызова операции «отдать процессорное время». Крайне затрудненная возможность реализации многозадачной архитектуры ввода-вывода в ядре ОС, позволяющей процессору исполнять одну задачу, в то время как другая задача инициировала операцию ввода-вывода и ждет её завершения.
Вытесняющая, или приоритетная, многозадачность (режим реального времени)
Вид многозадачности, в котором операционная система сама передает управление от одной выполняемой программы другой в случае завершения операций ввода-вывода, возникновения событий в аппаратуре компьютера, истечения таймеров и квантов времени, или же поступлений тех или иных сигналов от одной программы к другой. В этом виде многозадачности процессор может быть переключен с исполнения одной программы на исполнение другой без всякого пожелания первой программы и буквально между любыми двумя инструкциями в её коде. Распределение процессорного времени осуществляется планировщиком процессов. К тому же каждой задаче может быть назначен пользователем или самой операционной системой определенный приоритет, что обеспечивает гибкое управление распределением процессорного времени между задачами (например, можно снизить приоритет ресурсоёмкой программе, снизив тем самым скорость её работы, но повысив производительность фоновых процессов). Этот вид многозадачности обеспечивает более быстрый отклик на действия пользователя.
- возможность полной реализации многозадачного ввода-вывода в ядре ОС, когда ожидание завершения ввода-вывода одной программой позволяет процессору тем временем исполнять другую программу;
- cильное повышение надежности системы в целом, в сочетании с использованием защиты памяти — идеал в виде «ни одна программа пользовательского режима не может нарушить работу ОС в целом» становится достижимым хотя бы теоретически, вне вытесняющей многозадачности он не достижим даже в теории.
- возможность полного использования многопроцессорных и многоядерных систем.
- необходимость особой дисциплины при написании кода, особые требования к его реентерабельности, к защите всех разделяемых и глобальных данных объектами типа критических секций и мьютексов.
Системный вызов
Системный вызов(system call) — обращение прикладной программы к ядру операционной системы для выполнения какой-либо операции.
Современные операционные системы (ОС) предусматривают разделение времени между выполняющимися вычислительными процессами(многозадачность) и разделение полномочий, препятствующее обращению исполняемых программ к данным других программ и оборудованию. Ядро ОС исполняется в привилегированном режиме работы процессора. Для выполнения межпроцессорной операции или операции, требующей доступа к оборудованию, программа обращается к ядру, которое, в зависимости от полномочий вызывающего процесса, исполняет либо отказывает в исполнении такого вызова.
С точки зрения программиста, системный вызов обычно выглядит как вызов подпрограммы или функции из системной библиотеки. Однако системный вызов, как частный случай вызова такой функции или подпрограммы, следует отличать от более общего обращения к системной библиотеке, поскольку последнее может и не требовать выполнения привилегированных операций.
Чем отличается системный вызов от (программного) прерывания?
Вызвать прерывание можно оператором int в ассемблере, при этом будет передано управление коду в ячейке с указанным номером из таблицы прерываний. При этом в linux возможен вызов прерывания intx80, именуемого syscall. Со стороны программиста это будет выглядеть как вызов библиотечной функции, которая, тем не менее, будет обработана в пространстве ядра. Так в чём разница между системным вызовом и софтверным прерыванием, если первый всё также вызывается через intx80?
На самом деле это разные, несравнимые, вещи. По определениям википедии:
- Системный вызов — обращение прикладной программы к ядру операционной системы для выполнения какой-либо операции.
- Прерывание — сигнал от программного или аппаратного обеспечения, сообщающий процессору о наступлении какого-либо события, требующего немедленного внимания.
- Программные прерывания (в русской терминологии) — прерывания, которые инициируются исполнением специальной инструкции в коде программы.
Т.е. системный вызов — это абстракция ОС, в которой есть разделение на пространство пользователя и пространство ядра, а программное прерывание — это механизм взаимодействия программного кода с процессором.
Действительно, зачастую системные вызовы реализуются с помощью программных прерываний, но это не обязательно так, например, на amd64 поддерживается по меньшей мере 3 способа:
Системный вызов
Систе́мный вы́зов (англ. system call ) в программировании и вычислительной технике — обращение прикладной программы к ядру операционной системы для выполнения какой-либо операции.
Современные операционные системы (ОС) предусматривают разделение времени между выполняющимися вычислительными процессами (многозадачность) и разделение полномочий, препятствующее исполняемым программам обращаться к данным других программ и оборудованию. Ядро ОС исполняется в привилегированном режиме работы процессора. Для выполнения межпроцессной операции или операции, требующей доступа к оборудованию, программа обращается к ядру, которое, в зависимости от полномочий вызывающего процесса, исполняет либо отказывает в исполнении такого вызова.
С точки зрения программиста системный вызов обычно выглядит как вызов подпрограммы или функции из системной библиотеки. Однако системный вызов как частный случай вызова такой функции или подпрограммы следует отличать от более общего обращения к системной библиотеке, поскольку последнее может и не требовать выполнения привилегированных операций.
Ссылки
- Технологии в операционных системах
Wikimedia Foundation . 2010 .
Полезное
Смотреть что такое «Системный вызов» в других словарях:
МИРО-СИСТЕМНЫЙ АНАЛИЗ — текст И. Валлерстайна , опубликованный в 1987. Согласно Валлерстайну, миро системный анализ это не теория о социальном мире или его части. Это протест против способов, которыми было структурировано социальное научное исследование при его… … Социология: Энциклопедия
санкционированный вызов — Вызов системы, программы или данных, разрешенный данному пользователю. Как правило, реализуется путем ввода и проверки пароля. [Домарев В.В. Безопасность информационных технологий. Системный подход.] Тематики защита информации EN authorized call … Справочник технического переводчика
Интерфейс системный — Интерфейс (от англ. interface поверхность раздела, перегородка) совокупность средств и методов взаимодействия между элементами системы. В зависимости от контекста, понятие применимо как к отдельному элементу (интерфейс элемента), так и к… … Википедия
GNU Assembler — (GAS) Тип ассемблер Разработчик Фонд свободного ПО Написана на C Операционная система кроссплатформенное ПО Аппаратная платформа кроссплатформенное ПО Последняя версия 2.20.1 (3 ма … Википедия
TTY-абстракция — Стиль этой статьи неэнциклопедичен или нарушает нормы русского языка. Статью следует исправить согласно стилистическим правилам Википедии. У этого термина существуют и другие значения, см. Tty. Подсистема TTY, или TTY абстракция это одна из … Википедия
Библиотека потоков POSIX — (англ. Native POSIX Thread Library, англ. NPTL) набор программного кода, позволяющего ядру операционной системы Linux достаточно эффективно выполнять программы, использующие потоки POSIX. В настоящее время интегрирована в библиотеку… … Википедия
SIGSYS — Описание: Неправильный аргумент системного вызова По умолчанию: завершение процесса коды SA SIGINFO (не имеет специфических кодов) В POSIX системах, SIGSYS сигнал, предназначенный для посылки программе, при попытке передать… … Википедия
Fork — У этого термина существуют и другие значения, см. Форк (значения). В Unix системах, fork() системный вызов, создающий новый процесс( потомок), который является почти точной копией процесса( родителя), выполняющего этот вызов. Между… … Википедия
fork — У этого термина существуют и другие значения, см. Форк (значения). В Unix системах, fork() системный вызов, создающий новый процесс (потомок), который является практически полной копией процесса родителя, выполняющего этот вызов. Между… … Википедия
Chroot — операция изменения корневого каталога в Unix подобных операционных системах. Программа, запущенная с изменённым корневым каталогом, будет иметь доступ только к файлам, содержащимся в данном каталоге. Поэтому, если нужно обеспечить программе… … Википедия
PhantomEx: журнал разработки
Необходимость в реализации внутри системы доступа к операциям ввода/вывода для пользовательских приложений, и одновременная невозможность выполнения привилегированных команд из пользовательского режима поднимает вопрос — а как же производится, например файловый ввод/вывод в пользовательских программах больших ОС? Для этого применяются так называемые системные вызовы.
Системный вызов — обращение прикладной программы к ядру операционной системы, для выполнения какой либо операции. В зависимости от полномочий вызывающей программы ОС может либо выполнить запрашиваемую операцию, либо возвратить процессу информацию о невозможности данного действия.
Если мы внимательно посмотрим, как работает наша система, то хоть часть потоков и работает в пользовательском режиме, но есть потоки работающие на уровне ядра, а так же постоянно происходит переключение задач по прерыванию системного таймера. Системные вызовы организуются как раз с помощью специально настроенного прерывания, в обработчике которого вызывается необходимая пользователю функция. Рассмотрим это подробнее
1.Программное прерывание, как способ выхода в Ring 0
Итак, мы говорим о том, что необходимо организовать специально настроенное прерывание, для реализации на его основе системных вызовов.
Выберем для системных вызовов номер прерывания из свободных номеров в пределах от 0x20 до 0xFF. Скажем, пусть это будет 0x50. Для этого прерывания в модуле interrupts.s создадим специальный обработчик
Листинг 111. Обработчик прерывания INT 50h (interrupts.s)
push $0 /* Помещаем в стек фиктивный код ошибки */
push $80 /* и номер прерывания (для обработчика на C) */
/* Проталкиваем в стек все РОН */
pusha
/* Сохраняем текущий селектор сегмента данных */
push %ds
/* Настраиваемся на сегмент данных ядра */
mov $0x10, %ax
mov %ax, %ds
/* Вызываем обработчик на C */
call isr_handler
/* Восстанавливаем селектор сегмента данных */
pop %ds
/* Выполняем действия, аналогичные команде popa */
/* за исключением восстановления регистра EAX */
/* для сохранения значения, возвращенного системным вызовом */
pop %edi
pop %esi
pop %ebp
add $4, %esp
pop %ebx
pop %edx
pop %ecx
add $12, %esp /* Очищаем стек */
Создаем прототип функции isr80() в модуле работы с таблицами дескриптором, и регистрируем в IDT обработчик прерывания, создав для него дескриптор шлюза ловушки
Листинг 112. Прототип обработчика прерывания INT 50h (descriptor_tables.h)
Листинг 113. Регистрируем шлюз ловушки в IDT (descriptor_tables.c)
memset(&idt_entries, 0, sizeof(idt_entry_t)*256);
/* Перепрограммируем контролер прерываний */
outb(0x20, 0x11);
outb(0xA0, 0x11);
outb(0x21, 0x20);
outb(0xA1, 0x28);
outb(0x21, 0x04);
outb(0xA1, 0x02);
outb(0x21, 0x01);
outb(0xA1, 0x01);
outb(0x21, 0x0);
outb(0xA1, 0x0);
/* Устанавливаем обработчики на прерывания */
idt_set_gate(0, (u32int)isr0, 0x08, 0x8E);
.
.
.
idt_set_gate(7, (u32int)isr7, 0x08, 0x8E);
idt_set_gate(8, (u32int)isr8, 0x08, 0x8E);
.
.
.
idt_set_gate(15, (u32int)isr15, 0x08, 0x8E);
idt_set_gate(16, (u32int)isr16, 0x08, 0x8E);
.
.
.
idt_set_gate(31, (u32int)isr31, 0x08, 0x8E);
idt_set_gate(32, (u32int)irq0, 0x08, 0x8E);
.
.
.
idt_set_gate(47, (u32int)irq15, 0x08, 0x8E);
/* Шлюз ловушки для прерывания INT 50h */
idt_set_gate(0x50, (u32int)isr80, 0x08, 0xEF);
добавленный код выделен полужирным шрифтом. Для того чтобы прерывание вызывалось из 3-го кольца защиты, дескриптор должен иметь DPL = 3, тип шлюза ловушки имеет значение 0xF, таким образом мы получаем значение байта доступа дескриптора 0xEF — шлюз ловушки с DPL = 3 (подробнее о формате дескриптора IDT написано вот тут). Селектор сегмента кода, в котором выполняется обработчик этого прерывания — 0x8 — это селектор кода ядра, то есть данный шлюз обеспечит нам переход из CPL = 3 в CPL = 0, при вызове обработчика прерывания INT 50h.
2. Организация системного вызова
За основу возьмем механизм реализации системных вызовов в ядре Linux. Там для обращения к ядру используется прерывание INT 80h, передача параметров происходит через регистры общего назначения: EAX — номер функции в таблице системных вызовов, EBX — 1-й параметр, ECX — 2-й параметр, EDX — 3-й параметр и так далее. Возвращаемое из системного вызова значение передается через регистр EAX.
Воспользуемся аналогичной с точностью до номера прерывания схемой — номер функции и параметры будем передавать в РОН, возвращаемое значение будем забирать из EAX.
Всё хозяйство, связанное с системными вызовами соберем в отдельном модуле. Для примера реализуем два системных вызова, выполняющих синхронизируемый ядром ОС доступ к портам ввода-вывода.
Листинг 114. Модуль организации системных вызовов (syscalls.h)
#include «common.h»
#include «isr.h»
#include «text_framebuffer.h»
#include «io_disp.h» /* Диспетчер ввода-вывода */
#define NUM_CALLS 2 /* Число системных функции в таблице */
extern u32int syscall_entry_call(void* entry_point); / * Передача управления
системной функции */
void syscall_handler(registers_t regs); /* Обработчик прерывания */
#include «common.h»
#include «sync.h»
#define PORTS_NUM 0x10000 /* Число портов ввода-вывода */
/* Инициализация диспетчера */
void init_io_dispatcher(void);
/* Синхронизированное чтение из порта */
u8int in_byte(u16int port);
/* Синхронизированная запись в порт */
void out_byte(u16int port, u8int value);
/* Выделяем память под массив мьютексов портов и освобождаем их все */
port_mutex = (mutex_t*) kmalloc(sizeof(mutex_t)*PORTS_NUM);
for (i = 0; i < PORTS_NUM; i++)
<
mutex_release(&port_mutex[i]);
>
>
/*——————————————————————
* Синхронизированное чтение байта из порта
*—————————————————————-*/
u8int in_byte(u16int port)
<
u8int value = 0;
/* Захватываем мьютекс данного порта */
mutex_get(&port_mutex[port], true);
/* Читаем этот порт */
value = inb(port);
/* Отдаем мьютекс */
mutex_release(&port_mutex[port]);
/* Возвращаем прочитанное значение */
return value;
>
/*——————————————————————
* Синхронизированная запись в порт
*—————————————————————-*/
void out_byte(u16int port, u8int value)
<
/* Захватываем мьютекс порта */
mutex_get(&port_mutex[port], true);
/* Выводим значение в порт */
outb(port, value);
/* Освобождаем мьютекс */
mutex_release(&port_mutex[port]);
>
void init_syscalls(void)
<
/* Регистрируем обработчик прерывания */
register_interrupt_handler(0x50, &syscall_handler);
>
syscall_entry_call:
push %edx /* Проталкиваем в стек параметры */
push %ecx /* в обратном порядке (конвенция C) */
push %ebx
mov 16(%esp), %edx /* Сохраняем точку входа в EDX */
call *%edx /* Вызываем системную функцию */
add $12, %esp /* Освобождаем стек после вызова */
mov $0, %eax /* Номер функции записываем в EAX */
mov $0x60, %ebx /* Номер порта — в EBX */
int $0x50 /* Выполняем программное прерывание INT 50h */
После этого прочтенный байт можно было бы взять из регистра EAX. Однако на C нам бы хотелось иметь нормальный прототип для данного системного вызова, поэтому добавим в файл syscalls.h некоторые макроопределения для прототипов и реализации системных вызовов
Листинг 118. Макроопределения для системных вызовов (syscalls.h)
#define DEFN_SYSCALL1(func, num, P1)\
\
int syscall_##func(P1 p1)\
< int ret = 0;\
asm volatile («int $0x50»:»=a»(ret):»a»(num),»b»(p1));\
return ret;\
>
/*—- Два параметра ———————————————*/
#define DECL_SYSCALL2(func, p1, p2) int syscall_##func(p1, p2)
#define DEFN_SYSCALL2(func, num, P1, P2)\
\
int syscall_##func(P1 p1, P2 p2)\
< int ret = 0;\
asm volatile («int $0x50»:»=a»(ret):»a»(num),»b»(p1),»c»(p2));\
return ret;\
>
/*—- Три параметра ———————————————*/
#define DECL_SYSCALL3(func, p1, p2, p3) int syscall_##func(p1, p2, p3)
#define DEFN_SYSCALL3(func, num, P1, P2, P3)\
\
int syscall_##func(P1 p1, P2 p2, P3 p3)\
< int ret = 0;\
asm volatile («int $0x50»:»=a»(ret):»a»(num),»b»(p1),»c»(p2), «d»(p3));\
return ret;\
>
vs04 = (vscreen_t*) get_vscreen();
while (1)
<
vs04->cur_x = 0;
vs04->cur_y = start_y + 2;
vprint_text(vs04, «I’m user thread #4: «);
/* Читаем скан-код последней нажатой клавиши */
u8int keyb = (u8int) syscall_in_byte(0x60);
vprint_text(vs04, «Last key scan code: «);
init_esp = initial_esp;
.
.
.
/* Инициализируем дистпетчер памяти */
init_memory_manager(init_esp);
init_timer(BASE_FREQ);
asm volatile («sti»);
/* Инициализируем планировщик */
init_task_manager();
/* Инициализируем системные вызовы */
init_syscalls();
process_t* proc = get_current_proc();
thread01 = thread_create(proc,
&task01,
0x4000,
true,
false);
thread02 = thread_create(proc,
&task02,
0x4000,
true,
false);
/* Инициализируем диспетчер ввода-вывода */
init_io_dispatcher();