Студопедия.Орг Главная | Случайная страница | Контакты | Мы поможем в написании вашей работы!  
 

Пример 8.3. Функция переключения контекста в ядре Linux/x86 1 страница



/* Фрагмент файла \arch\i386\kernel\process.c.
* Сохранение и восстановление регистров общего назначения
* и сегментных регистров CS, DS и S3 осуществляется при входе в ядре *и при выходе из него соответственно. */
/*
* switch_to(х,у) должна переключать задачи с х на у *
* Мы используем fsave/fwait, поэтому исключения [сопроцессора]
* сбрасываются в нужный момент времени (пока вызов со стороны
* fsave или fwait обрабатывается), и не могут быть посланы
* другому процессу. Отложенное сохранение FP более не имеет
* смысла на современных ЦПУ и это многое упрощает (SMP и UP
* [uniprocessor, однопроцессорная конфигурация] теперь
* обрабатываются одинаково).
Раньше мы использовали аппаратное переключение
* контекста. Причина, по которой мы больше так не делаем
* становится очевидна, когда мы пытаемся аккуратно восстановиться
* из сохраненного состояния, которое стало недопустимым
* (в частности, висящие ссылки в сегментных регистрах).
* При использовании аппаратного переключения контекста нет способа
* разумным образом выйти из плохого состояния [контекста]. *
* То, что Intel документирует аппаратное переключение контекста как
* медленное — откровенная ерунда, этот код не дает заметного ускорения.
* Однако здесь есть некоторое пространство для улучшения, поэтому
* вопросы производительности могут рано или поздно оказаться актуальны.
* [В данном случае], однако, нам важнее, что наша реализация * обеспечивает большую гибкость.
*/ void __switch_to(struct task_struct *prev_p, struct task_struct *next_p)
(
struct thread_struct *prev = &prev_p->thread, Д *next = &next_p->thread;
struct tss_struct *tss = init_tss + smp_processor_id();
unlazy_fpu (prev__p);
/*
* Перезагрузить espO, LOT и указатель на таблицу страниц: */
tss->espO = next->espO;
/*
* Сохранить %fs и %gs. He нужно сохранять %ез и %ds,
* потому что при исполнении в контексте ядра это
* всегда сегменты ядра. */
asm volatile("movl %%fs,%0":"=m" (* (int *)&prev->fs)); asm volatile("movl %%gs,%0":"=m" (*(int *)&prev->gs));
* Восстановить Its и Ч
loadsegment(fs, next->fs); loadsegment(gs, next->gs);
/*
* Если это необходимо, перезагрузить отладочные регистры */ if (next->debugreg[7]){
loaddebug(next, 0};
loaddebug(next, 1);
loaddebug(next, 2);
loaddebug(next, 3);
/* не 4 и 5 */
loaddebug(next, 6);
loaddebug(next, 7);
if (prev->ioperm || next->iopenn) { if (next->ioperm) {
*
/*
* Копирование четырех линий кэша.... не хорошо, но
* и не так уж плохо. У кого-нибудь есть идея лучше?
* Оно воздействует только на процессы, использующие iopermO.
* [Размещение этих TSS в области 4K-tlb и игры с виртуальной
* памятью для переключения битовой маски ввода/вывода на
* самом деле неприемлемы.] */
memcpy(tss->io_bitmap, next->io_bitmap,
IO_BITMAP_SIZE*sizeof(unsigned long)); tss~>bitmap = IO_BITMAP_OFFSET; } else /*
* Смещение битовой маски, указывающее за пределы ограничителя "- - >
* порождает контролируемое SIGSEGV, если процесс пытается
* использовать команды обращения к портам. Первый вызов
* sys_ioperm() устанавливает битовую маску корректно. */
tss->bitmap = INVALID 10 BITMAP OFFSET;

Примечание
Планировщик должен полностью сохранять контекст процесса. Это значительно усложняет жизнь разработчикам процессоров: добавив процессору лишний регистр (как служебный, так и общего назначения), мы рискуем потерять совместимость со всеми ОС для нашего процессора, реализующими вытесняющую многозадачность. Наличие команд сохранения контекста не решает этой проблемы — ведь ОС должна выделять память под сохраняемый контекст, а для этого необходимо знать его размер.
Именно поэтому, в частности, процессоры SPARC и х86 реализуют "мультимедийные" расширения систем команд (групповые операции над 8-битными целыми числами) с использованием уже существовавших регистров арифметического сопроцессора, а не дополнительных регистров.

Как правило, оказывается неудобным сохранять контекст именно в стеке. Тогда его сохраняют в какой-то другой области памяти, чаще всего в дескрипторе процесса. Многие процессоры имеют специальные команды сохранения и загрузки контекста. Для реализации вытеснения достаточно сохранить контекст текущей нити и загрузить контекст следующей активной нити из очереди. Необходимо предоставить также и функцию переключения нитей по их собственной инициативе, аналогичную ThreadSwitch или, точнее, DeactivateThread.
Обычно вместо DeactivateThread система предоставляет высокоуровневые примитивы синхронизации, например семафоры или примитивы гармонического взаимодействия. Вызов DeactivateThread оказывается скрытым внутри таких высокоуровневых функций.
Вытесняющий планировщик с разделением времени ненамного сложнее кооперативного планировщика — и тот, и другой реализуются несколькими десятками строк на ассемблере. В работе (Прохоров 1990| приводится полный ассемблерный текст приоритетного планировщика системы VAX/VMS, занимающий одну страницу (автору неизвестно, не нарушает ли авторские права фирмы DEC публикация этого текста). Впрочем, планировщики, рассчитанные на многопроцессорные машины, часто бывают несколько сложнее (пример 8.4).

Пример 8.4. Планировщик Linux 2.5

/*
* 'schedule!)' — функция планировщика. Это очень простой и
* приятный планировщик: он не совершенен, но несомненно работает
* в большинстве случаев.
* The goto is "interesting".
*
* ЗАМЕЧАНИЕ!! Задача 0 является 'пустой' задачей, которая вызывается
* когда ни одна другая задача не может выполняться. Она не может быть
* "убита" и не может "спать". Информация о состоянии в task[0] никогда
* не используется.
*/
asmlinkage void schedule(void)
{
struct schedule_data * sched_data;
struct task_struct *prev, *next, *p;
struct list_head *tmp;
int this__cpu, c;
if (!current->active_mm) BUG(); need_resched_back: prev = current; this_cpu = prev~>processor;
if (in_interrupt())
goto scheduling_in_interrupt;
release_kernel_lock(prev, this_cpu);
/* Выполнить административную работу здесь, пока мы не держим
* ни одной блокировки.
*/
if (softirq_active(this_cpu) & softirq_mask(this_cpu))
goto handle_softirq; handle_softirq_back:
/*
* 'shed data" защищена неявно, тем фактом, что мы можем исполнять
* только один процесс на одном ЦПУ. */
sched__data = & aligned_data[this_cpu].schedule_data;
spin_lock_irq (&runqueue__lock);
/* Переместить исчерпанный процесс RR в конец [очереди] */ if (prev->policy == SCHED_RR)
goto move_rr_last; pove_rr_back:
switch (prev->state) { case TASKJLNTERRUPTIBLE:
if (signal_pending (prev)) { prev->state = TASK_RUNNING; break; } default:
dei_f rom_runqueue (prev); case TASK_RUNNING:;
}
prev->need_resched = 0;
/*
* это собственно планировщик: */
repeat_schedule: /*
* Выбрать процесс по умолчанию... */
next = idle_task(this_cpu); с = -1000;
if (prev->state == TASK_RUNNING) goto still_running;
still_running_back:
list_for_each (tmp, srunqueue_head) {
p = list_entry (tmp, struct task_struct, run_list); if (can_schedule (p, this_cpu)) {
int weight = goodness (p, this_cpu, prev->active_mm); if (weight > c)
с = weight, next = p;
/* Следует ли перевычислить счетчики? */
if (!c)
goto recalculate; /*
* с этого момента ничто ке может помешать нам
* переключиться на следующую задачу, отметить этот
* факт в sched_data. */
sched_data->curr = next; tifdef CONFIG_SMP
riext->has_cpu = I;
next->processor = this_cpu; lendif
spin_unlock__irq (&runqueue_lock);
if (prev == next) goto same_process;
ttifdef CONFIG_SMP /*
* Поддерживать значение 'last schedule' для каждого процесса
* (его необходимо пересчитать лаже если мы планируем тот же
* процесс). Сейчас это значение используется только в SMP, и оно
* приблизительно, поэтому мы не обязаны поддерживать его,
* пока захвачена блокировка runqueue. */
sched_data->last_schedule = get_cycles();
/*
* Мы снимаем блокировку планировщика рано (это глобальная
* блокировка), поэтому мы должны защитить предыдущий процесс
* от повторного планирования во время switch_to(). */
ttendif /* CONFIG_SMP */
kstat.context_swtch++; /*
* Переключение контекста воздействует на три процесса:
prev
.. ==> (last => next)
* Это 'prev', 'далеко предшествующий' размещенному в стеке 'next1,
* но функция switch_to() устанавливает prev на (только что
* работавший) процесс 'last'.
* Описание несколько запутанно, но не лишено глубокого смысла,
*/
prepare_to_switch ();
{
struct mm struct *mm = next->mm;
struct mm_struct *oldmm = prev->active_mm;
if (!mmi (
if (next->active_mm) BUG ();
next->active_mm = oldmm;
atomic_inc (&oldmm->mra_count);
enter_lazy_tlb (oldmm, next, this_cpu); } else {
if (next->active_mm!= mm) BUG ();
switch_mm(ol<±nm, mm, next, this__cpu);
if (!prev->irin) (
prev->active_mm = NULL; mmdrop (oldmm);
/*
* Этот оператор только переключает состояние регистров
* и стека. */
switch_to(prev, next, prev); __schedule_tail(prev);
same_process:
teacquire_kernel__lock (current); if (current->need_resched) goto need reached back;
recalculate: {
struct task_struct *p;
spin_unlock_irq (&runqueue_lock);
read_lock (&tasklist_lock);
for_each_task (p)
p->counter = (p->counter» 1) + NICE_TO_TICKS (p->nice)
read_unlock (&tasklist__lock);
spin_lock_irq (&runqueue_lock); } goto repeat_schedule;
still_running:
с = goodness (prev, this_cpu, prev->active_mm); next = prev;
goto still_running_back;

handle_sof tirq: do_softirq (); goto handle_softirq_back;
move_rr_last:
if (!prev->counter) (
prev->counter = NICE_TO_TICKS (prev->nice); move_last_runqueue (prev); } goto move_rr_back;
scheduling_in_interrupt:
printk ("Scheduling in interrupt\n");
BUG ();
return;

Контексты современных процессоров
У современных процессоров, имеющих десятки регистров общего назначения и виртуальную память, размер контекста процесса измеряется сотнями байтов. Например, у процессора VAX контекст процессора состоит из 64 32-разрядных слов, т. е. 256 байт. При этом VAX имеет только 16 регистров общего назначения, а большая часть остальных регистров так или иначе относится к системе управления виртуальной памятью.
У микропроцессоров SPARC, имеющих регистровый файл объемом до нескольких килобайтов, контекст, на первый взгляд, должен быть чудовищного размера. Однако программе одновременно доступны лишь 32 регистра общего назначения, 24 из которых образуют скользящее по регистровому файлу окно. Благодаря этому факту, контекст процессора SPARC состоит только из первых восьми регистров общего назначения и служебных регистров. Регистровое окно новой нити выделяется в свободной области регистрового файла, а его передвижение обрабатывается при помощи исключений заполнения и очистки окна.
Если в системе всего несколько активных процессов, может оказаться так, что их регистровые окна постоянно "живут" в регистровом файле, поэтому объем данных, реально копируемых при переключении нитей, у SPARC не больше, чем у CISC-процессоров с небольшим количеством регистров общего назначения. Впрочем, и у SPARC, и у CISC-процессоров основную по объему часть контекста процесса составляют регистры диспетчера памяти.
На этом основано преимущество транспьютера перед процессорами традиционных и RISC-архитектур. Дело в том, что транспьютер не имеет диспетчера памяти, и у него вообще очень мало регистров. В худшем случае, при переключении процессов (в транспьютере, как и в старых ОС, нити называются процессами) должно сохраняться 7 32-разрядных регистров. В лучшем случае сохраняются только два регистра — счетчик команд и статусный регистр. Кроме того, перенастраивается регистр wptr, который выполняет по совместительству функции указателя стека, базового регистра сегмента статических данных процесса и указателя на дескриптор процесса.
Транспьютер имеет три арифметических регистра, образующих регистровый стек. При этом обычное переключение процессов может происходить только, когда этот стек пуст. Такая ситуация возникает довольно часто; например, этот стек обязан быть пустым при вызовах процедур и даже при условных и безусловных переходах, поэтому циклическая программа не может не иметь точек, в которых она может быть прервана. Упомянутые в предыдущем разделе команды обращения к линкам также исполняются при пустом регистровом стеке. Поэтому, оказывается достаточно перезагрузить три управляющих регистра, и мы передадим управление следующему активному процессу.
Операция переключения процессов, а также установка процессов в очередь при их активизации полностью реализованы на микропрограммном уровне.
Деактивизация процесса происходит только по его инициативе, когда он начинает ожидать сигнала от таймера или готовности линка. При этом процесс исполняет специальную команду, которая устанавливает его в очередь ожидающих соответствующего события, и загружает контекст очередного активного процесса. Когда приходит сигнал таймера или данные по линку, то также вызывается микропрограмма, которая устанавливает активизированный процесс в конец очереди активных.
У транспьютера также существует микропрограммно реализованный режим разделения времени, когда по сигналам внутреннего таймера активные про цессы циклически переставляются внутри очереди. Такие переключения ка уже говорилось, могут происходить только, когда регистровый стек текущег процесса пуст, но подобные ситуации возникают довольно часто.
Кроме обычных процессов в системе существуют так называемые высокопри оритетные процессы. Если такой процесс получает управление в результате внешнего события, то текущий низкоприоритетный процесс будет прерван независимо от того, пуст его регистровый стек или нет. Для того чтобы при этом не разрушить прерванный процесс, его стек и весь остальной контекст записываются в быструю память, расположенную на кристалле процессора. Это и есть тот самый худший случай, о котором говорилось ранее. Весь цикл переключения занимает 640нс по сравнению с десятками и, порой, сотнями микросекунд у традиционных процессоров [INMOS 72 TRN 203 02, Харп 1993].
Благодаря такой организации транспьютер не имеет равных себе по времени реакции на внешнее событие. На первый взгляд, микропрограммная реализация такой довольно сложной конструкции, как планировщик, снижает гибкость системы. В действительности, в современных системах планировщики имеют довольно стандартную структуру, и реализация, выполненная в транспьютере, очень близка к этому стандарту, известному как микроядро (microkernel) (см. разд. Монолитные системы или системы с микроядром).

Планировщики с приоритетами

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

Приоритеты процессов в транспьютере
Простейшим случаем такой организации является транспьютер, имеющий две очереди. В транспьютере при этом планировщик не может отобрать управление у высокоприоритетного процесса. В этом смысле низкоприоритетные задачи вынуждены полагаться на "порядочность" высокоприоритетных, т. е. на то, что те освобождают процессор в разумное время.
Отчасти похожим образом организован планировщик системы VAX/VMS. Он имеет 32 приоритетных очереди, из которых старшие 16 называются процессами реального времени, а младшие — разделенного.
При этом процесс реального времени исполняется всегда, когда готов к исполнению, и в системе нет более приоритетных процессов. ОС и процессы разделенного времени также вынуждены полагаться на его порядочность. Поэтому привилегия запускать такие процессы контролируется администратором системы.

Легко понять, что разделение времени обеспечивает более или менее справедливый доступ к процессору для задач с одинаковым приоритетом. В случае транспьютера, который имеет только один приоритет и, соответственно, одну очередь для задач разделенного времени, этого оказывается достаточно. Однако современные ОС как общего назначения, так и реального времени, имеют много уровней приоритета. Для чего это нужно и как достигается в этом случае справедливое распределение времени процессора?
Дело в том, что в системах такого типа приоритет процессов разделенного времени является динамической величиной. Он изменяется в зависимости от того, насколько активно задача использует процессор и другие системные ресурсы.
В системах с пакетной обработкой, когда для задачи указывают верхнюю границу времени процессора, которое она может использовать, часто более короткие задания идут с более высоким приоритетом. Кроме того, более высокий приоритет дают задачам, которые требуют меньше памяти. В системах разделенного времени часто оказывается сложно заранее определить время, в течение которого будет работать задача. Например, вы отлаживаете программу. Для этой цели вы запускаете символьный отладчик и начинаете исполнять вашу программу в пошаговом режиме. Естественно, что вы не можете даже приблизительно предсказать как астрономическое время отладки, так и время центрального процессора, занятое при этом. Поэтому обычно такие системы не ограничивают время исполнения задачи и другие ре-сУрсы и вынуждены прогнозировать поведение программы в будущем на основании ее поведения в прошлом.
Так, если программа начала вычисления, не прерываемые никакими обращениями к внешней памяти или терминалу, мы можем предположить, что она будет заниматься такими вычислениями и дальше. Напротив, если программа сделала несколько запросов на ввод-вывод, следует ожидать, что она и дальше будет активно выдавать такие запросы. Предпочтительными для системы будут те программы, которые захватывают процессор на короткое время и быстро отдают его, переходя в состояние ожидания внешнего или внутреннего события. Таким процессам система стремится присвоить более высокий приоритет. Если программа ожидает завершения запроса на обращение к диску, то это также выгодно для системы — ведь на большинстве машин чтение с диска и запись на него происходят параллельно с работой центрального процессора.
Таким образом, система динамически повышает приоритет тем заданиям, которые освободили процессор в результате запроса на ввод-вывод или ожидание события и, наоборот, снижает тем заданиям, которые были сняты по истечении кванта времени. Однако приоритет не может превысить определенного значения — стартового приоритета задачи.
При этом наиболее высокий приоритет автоматически получают интерактивные задачи и программы, занятые интенсивным вводом-выводом. Во время выполнения таких программ процессор часто оказывается свободен, и управление получают низкоприоритетные вычислительные задания. Поэтому системы семейства UNIX и VAX/VMS даже при очень высокой загрузке обеспечивают как приемлемое время реакции для интерактивных программ, так и приемлемое астрономическое время исполнения для пакетных заданий. Благодаря наличию класса планирования реального времени, эти же ОС можно использовать и в задачах реального времени таких, как управление атомным реактором.
Система VMS повышает приоритет также и тем задачам, которые остановились в ожидании подкачки страницы. Это сделано потому, что если программа несколько раз выскочила за пределы своего рабочего набора (т. е. потребовала еще страницу, когда ее рабочий набор весь занят), то она, скорее всего, будет делать это и далее, а процессор на время подкачки она освобождает.
Нужно отметить, что процесс разделенного времени может повысить свой приоритет до максимального в классе разделения времени, но никогда не сможет стать процессом реального времени. А для процессов реального времени динамическое изменение приоритетов обычно не применяется.

Управление приоритетами во OS-9
Любопытно реализовано динамическое изменение приоритета в OS-9. В этой ОС каждый процесс имеет статически определенный приоритет и возраст
(age) — количество просмотров очереди с того момента, когда этот процесс в последний раз получал управление. Обе эти характеристики представлены 16-разрядными беззнаковыми числами. Процесс может повысить или понизить свой приоритет, исполнив соответствующий системный вызов, но система по собственной инициативе никогда не меняет его. При этом управление каждый раз получает процесс с наибольшей суммой статического приоритета и динамически изменяющегося возраста (рис. 8.1 — в изображенной на рисунке ситуации будет выбран процесс 12). Если у двух процессов такие суммы равны, то берется процесс с большим приоритетом. Если у них равны и приоритеты, то берется тот, который оказался ближе к началу очереди.

Рис. 8.1. Приоритеты и возраст в OS/9

Этот алгоритм гарантирует, что любой низкоприоритетный процесс рано или поздно получит управление. Если же нам нужно, чтобы он получал управление раньше, то мы должны просто повысить его приоритет.
Кроме того, можно запретить исполнение процессов со статическим приоритетом ниже заданного. Это может уменьшить загрузку процессора и, например, позволит высокоприоритетным процессам обработать увеличившийся поток внешних событий. Понятно, что такой запрет можно вводить только на небольшое время, чтобы не нарушить справедливое распределение процессора.
Возможна и более тонкая регулировка — системный вызов, который запрещает увеличивать возраст процесса больше заданного значения. То есть, процесс, стоя в очереди, может достичь этого максимального возраста, после чего он по-прежнему остается в очереди, но его возраст уже не увеличивается.
Получающаяся в результате схема распределения времени процессора отчасти похожа на двухслойную организацию VAX/VMS, когда исполнение процессов со статическим приоритетом, превышающим границу, не может быть прервано низкоприоритетным процессом.

Монолитные системы и системы с микроядром

  Не бывет монолитных программ, бывают плохо структурированные Реплика с семинара по ООП Ходовая часть танка работает в экстремальных условиях и с трудом поддается модернизации Реплика в эхоконференции RU.WEAPON сети ФИДО

Исполняя системный вызов, пользовательская программа передает управление ядру. С понятием ядра — комплекса программ, исполняющихся в привилегированном (системном) режиме процессора, — мы уже сталкивались в разд. Системы с базовой виртуальной адресацией и главе 5. Практически во всех современных системах ядро представляет собой единый процесс — единое адресное пространство. Но организация взаимодействия между нитями этого процесса в различных системах устроена по-разному.
Три основные группы нитей, исполняющихся в режиме ядра, — это, во-первых, обработчики прерываний, во-вторых — обработчики системных вызовов со стороны пользовательских процессов и, в-третьих, — нити, исполняющие различные внутренние по отношению к системе работы, например, управление дисковым кэшем. В документации фирмы Sun нити этих трех групп в ядре Solaris называются, соответственно, исполняющимися в контексте прерывания (interrupt context), в пользовательском контексте (user context) и в контексте ядра (kernel context, или контексте системы) [ docs.sun.com 805-7378-10].
Далее мы будем называть последние две категории нитей, соответственно, пользовательскими и системными, хотя и те, и другие исполняют системный код в системном адресном пространстве.
Обработчики прерываний всегда представляют собой особую статью — система практически никогда не имеет контроля над тем, когда возникают внешние события, зато практически всегда обязана обрабатывать эти события по мере их появления. Поэтому обработчики прерываний получают управление по необходимости.
В то же время, порядок получения управления пользовательскими и системными нитями в ряде ситуаций находится под контролем системы. Планировшик является частью ядра и модули системы вольны включать его и выключать по мере необходимости. Практически всегда его выключают на время работы обработчика прерывания, но некоторые системы делают это и при активизации других нитей ядра.
Полное выключение планировщика на время работы ядра фактически означает что система не реализует внутри ядра вытесняющей многозадачности. Системные и пользовательские нити в этом случае являются сопрограммами а не нитями в полном смысле этого слова.
Эта архитектура, называемая монолитным ядром, привлекательна примерно тем же, чем привлекательна кооперативная многозадачность на пользовательском уровне: любой модуль ядра может потерять управление лишь в двух случаях — при исполнении обработчика прерывания или по собственной инициативе. Благодаря этому разработчик модуля может не беспокоиться о критических секциях и прочих малоприятных аспектах доступа к разделяемым данным (кроме, разумеется, случаев, когда разделяет данные с обработчиком прерывания).
Платить за эти преимущества приходится значительными сложностями при переносе системы на многопроцессорные машины и невозможностью реализовать режим реального времени. Действительно, код ядра, написанный в расчете на кооперативную многозадачность, не может быть реентерабельным, поэтому такая система в то время, когда исполняется какая-то из ее нитей, не может обрабатывать системные вызовы. Следовательно, она не может иметь пользовательские процессы с приоритетом выше, чем у нитей ядра — а именно такие процессы нужны для поддержки приложений реального времени.

Примечание
Те из читателей, кто когда-либо пытался реализовать обработку аппаратных прерываний под MS/DR DOS, должны быть хорошо знакомы с этой проблемой. Вопреки хакерскому фольклору, нереентерабельность ядра DOS связана вовсе не с переустановкой указателя стека при входе в обработчик прерывания 21h, а именно с тем, что ядро работает с разделяемыми данными, но не предоставляет собственных средств для взаимоисключения доступа к ним.

Впрочем, если нам не требуется реальное время и перед нами не стоит задача обеспечить равномерное распределение системных нитей между процессорами симметрично многопроцессорной машины, монолитное ядро вполне приемлемо. Большинство систем семейств Unix (фактически, все широко распространенные системы этого семейства, кроме System V R4) и Win32, OS/2 и ряд других систем общего назначения более или менее успешно ее используют.
Альтернативной монолитным ядрам является микроядро. Микроядерные системы реализуют вытесняющую многозадачность не только между пользовательскими процессами, но и между нитями ядра.

Микроядро QNX
Классическая реализация микроядра, QNX, состоит из вытесняющего планировщика и примитивов гармонического межпоточного взаимодействия, средств для обмена сообщениями send и receive. Эти примитивы, конечно же, сами по себе не могут быть реализованы реентерабельным образом, однако они просты, содержат очень мало кода, исполняются быстро, и на время их исполнения система просто запрещает прерывания.
Все остальные модули системы с точки зрения микроядра представляют соб полностью равноценные нити. То, что некоторые из этих нитей исполняю в пользовательском, а другие — в привилегированном режиме доступа микC" ядру совершенно неинтересно и не влияет на их приоритет и класс планировани QNX разрабатывался для приложений реального времени, в том числе и лл использования во встраиваемых микропроцессорных системах; но, благодап компактности и фантастической производительности, эта ОС иногда заменяв системы общего назначения. Микроядро QNX действительно заслуживает на звания микро, поскольку занимает всего 12 килобайт кода и полностью входит в кэш первого уровня даже старых процессоров архитектуры х86. Все остальные модули ядра — драйверы внешних устройств, файловых систем, сетевых протоколов, имитация API систем семейства Unix — динамически загружаются и выгружаются и не являются обязательными (если, конечно, приложение не требует сервисов, предоставляемых этими модулями). Благодаря этому ОС может использоваться во встраиваемых приложениях с весьма небольшим объемом ПЗУ.





Дата публикования: 2014-11-18; Прочитано: 712 | Нарушение авторского права страницы | Мы поможем в написании вашей работы!



studopedia.org - Студопедия.Орг - 2014-2024 год. Студопедия не является автором материалов, которые размещены. Но предоставляет возможность бесплатного использования (0.009 с)...