Вытесняющая многозадачность
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) (см. разд. "Монолитные системы или системы с микроядром").