Включение многозадачности

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

  • 1. Объявить сегменты и дескрипторы TSS в GDT.
  • 2. Заполнить поля в TSS нужными значениями. Наиболее важными полями являются: EIP, CS, CR3 (если включён механизм страничного преобразования), SS, ESP.
  • 3. Загрузить в регистр TR селектор TSS, который описывает текущий код.

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

Практическая реализация

Теперь пришло время реализовать многозадачность на практике. У нас будет три задачи. Первая задача ничего не будет делать. Л в подтверждение того, что вторая и третья задачи работают, они будут выводить сообщение. Также у нас будут включены прерывания, и на нулевом векторе будет стоять в качестве обработчика шлюз задачи. Именно этот обработчик и будет выполнять переключение между ними (второй и третьей задачей) по очереди. Суть работы обработчика будет заключаться в следующем: при получении управления он выясняет, что находится в Previous Task Link его TSS, и меняет значение селектора: если селектор второй задачи, то идёт замена на селектор третьей, и наоборот. Таким образом, обработчик прерывания будет прерывать одну задачу, а возвращаться уже в другую.

Начнём сначала. В первую очередь надо определить дескрипторы всех используемых нами TSS. Для этого следует добавить в конец GDT новые дескрипторы и обновить поле Limit в регистре GDTR. Но мы поступим иначе: скопируем GDT в другое место и добавим к ней новые дескрипторы, а затем полностью обновим регистр GDTR. Такой способ не является оптимальным - он просто нужен для того, чтобы переместить GDT за пределы первого мегабайта памяти. Не следует считать это обязательным требованием; это делается просто для разнообразия. Для более удобной работы с образом регистра GDTR можно объявить следующую структуру:

strue GDTR32 {

.Limit dw ?

.BaseAddress dd ?

>

Теперь достаточно объявить переменную и использовать её в командах L/SGDT.

GDTR_Image GDTR32

Далее в листинге 2.20 приведён код, обновляющий глобальную дескриптор- ную таблицу.

Листинг 2.20. Обновление GDT

mov esi, messagel mov al, 0 mov ah, 0

mov Ы, "5" call OutText

xor eax, eax mov edi,NewGDT mov ecx, NewGDTSize/4 rep stosd

sgdt [GDTR_Image]

mov esi, [GDTR_Image.BaseAddress]

mov edi, NewGDT

xor ecx, ecx

inc word [GDTR_Image.Limit] mov cx, [GDTR_Image.Limit] shr ecx, 2 rep movsd

mov edi, NewGDT

mov ax, [GDTR_Image.Limit]

add edi, eax

mov esi, TssDescriptors

mov ecx, TssDescriptorsSize/4

rep movsd

Вначале мы выводим сообщение о том, что мы перешли в защищённый режим. Код функции OutText приводиться здесь не будет (это не особенно важно), но следует сказать о параметрах, которые она принимает: ESI - указатель на строку, которая заканчивается символом с кодом О, ЛЬ - номер столбца (нумерация с нуля), ЛН - номер строки (нумерация с нуля), BL - параметры символов (цвет и фон). Далее память, куда будет копироваться СОТ, обнуляется; это необязательно, но для надежности не помешает. Далее выполняется команда SGDT для получения параметров GDT. После этого она копируется по адресу NewGDT. Следует отметить, что GDTR содержит не размер таблицы, а её лимит (т. е. максимальное смещение в таблице), поэтому её лимит увеличивается на единицу. Затем производится копирование дескрипторов TSS в конец новой глобальной дескрнпторной таблицы. Для объявления дескрипторов используется следующий макрос (листинг 2.21).

Листинг 2.21. Макрос, создающий дескриптор TSS

macro DEFINE_TSS_DESCRIPTOR _base_address,_limit

{

dw _limit and OFFFFh dw _base_address and OFFFFh db (_base_address shr 16) and OFFh db TSS_TYPE

db (_limit shr 16) and OFh db _base_address shr 24

}

Этот макрос принимает два параметра: адрес Т58 и его лимит. Теперь, чтобы объявить все четыре дескриптора ТББ, достаточно написать строки, которые вы видите в листинге 2.22.

Листинг 2.22. Объявление необходимых дескрипторов с помощью макроса ОЕЕ1ИЕ_

ТЗЗ_ОЕЗСК1РТОК

ТзБ0езсг1рЬогз:

0ЕР1ЫЕ_ТЗЗ_0ЕЗСК1РТ0К РЮБСТазкТзБ, ЮОЬ ОЕР1ЫЕ_ТЗЗ_ОЕЗСЯ1РТОК ТАЗК_ЫиМВЕР_1_Тзз, ЮОЬ 0ЕР1ЫЕ_ТЗЗ_0ЕЗСР1РТ0Н ТАЗК_ШМВЕК_2_Тзз, ЮОЬ ОЕРШЕ.ТЗЗ.БЕЗаиРТОР! 1гдО_ЬапЬ1ег_Тзз, ЮОЬ ТззОеэсг 1рЬогзЕпс1:

Настало время обновить ОВТЫ и вывести сообщение об этом (листинг 2.23).

Листинг 2.23. Обновление регистра GDTR

mov [GDTR_Image.BaseAddress], NewGDT mov ax, [GDTR_Image.Limit] add ax, TssDescriptorsSize dec ax

mov [GDTR_Image.Limit], ax lgdt [GDTR_Image]

mov esi, message2 mov al, 0 mov ah, 1 mov Ы, "5" call OutText

Теперь очередь заполнения полей каждого TSS. Для облегчения доступа к полям TSS используется структура, приведённая в листинге 2.24.

Листинг 2.24. Структура TSS32

strue TSS32

{

.PreviousTaskLink dw ?

.ReservedO dw ? ; --4

.ESPO dd ?

.SSO dw ?

.Reservedl dw ? ; --12

.ESP1 dd ?

.SSI dw ?

.Reserved2 dw ? ; 20

.ESP2 dd ?

.SS2 dw ?

.Reserved3 dw ? ; 28

.tsCR3 dd ?

.tsEIP dd ? ; 36

.tsEFLAGS dd ?

.tsEAX dd ? ; 44

.tsECX dd ?

.tsEDX dd ? ; 52

.tsEBX dd ?

.tsESP dd ? ; 60

.tsEBP dd ?

.tsESI dd ?

.tsEDI dd ? ; 72

. tsES dw ?

.Reserved4 dw ? ; 76

.tsCS dw ?

.Reserved5 dw ?

. tsSS dw ?

.Reserved6 dw ?

. tsDS dw ?

.Reserved? dw ?

.tsFS dw ?

.Reserved8 dw ?

. tsGS dw ?

.Reserved9 dw ?

.LDTSegmentSelector dw ?

.ReservedlO dw ?

.DebugByte db ?

.Reservedll db ?

.IOMapBaseAddress dw ?

>

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

Листинг 2.25. Функция инициализации TSS

SetJTSS:

; IN

; ЕАХ - CR3

; ЕВХ - EIP

; ЕСХ - ESP

; EDX - EFLAGS

; EDI - TSS base address

pushad

push eax push edi push ecx xor eax, eax mov ecx, 26 cld

rep stosd

pop ecx pop edi pop eax

virtual at edi .edi TSS32 end virtual

mov [.edi.tsCR3], EAX mov [.edi.tsEIP], EBX mov [.edi.tsESP], ECX mov [.edi.tsEFLAGS], EDX

mov [.edi.tsCS], CODE_SELEKTOR mov [.edi.tsDS], DATA_SELEKTOR mov [.edi.tsSS], DATA_SELEKTOR mov [.edi.tsES], DATA_SELEKTOR

popad

ret

Эта функция принимает пять параметров любой задачи (наиболее важных для неё) через регистры общего назначения: ЕЛХ - CR3, EBX - EIP, ECX - ESP, ; EDX - EFLAGS, EDI - адрес TSS. Сначала функция обнуляет первые 104 байта TSS (это нужно для того, чтобы в тех регистрах, которые мы не задали, гарантированно были нулевые значения). Здесь мы использовали директиву компилятора virtual at. Эта директива нужна для облегчения доступа к структурам через метки и регистры, которые на них указывают. Например, mov [.edi.tsCR3], ЕЛХ в результате будет заменена на mov [edi+28], ЕЛХ. Возвращаемся к нашему примеру - надо задать начальные параметры каждой задачи. Используя функцию Set_ TSS, сделать это очень легко (листинг 2.26).

Листинг 2.26. Инициализация задач

mov eax, CR3 ; EAX = CR3 pushfd

pop edx ; EDX = EFLAGS

mov ecx, Main_task_stack mov ebx, FirstTask mov edi, FirstTaskTss call Set_TSS

mov ecx, Task_numberl_Stack mov ebx, TASK_NUMBER_1

mov edi, TASK_NUMBER_l_Tss call Set_TSS

mov ecx, Task_number2_Stack mov ebx, TASK_NUMBER_2 mov edi, TASK_NUMBER_2_Tss call Set_TSS

mov ecx, irqO_handler_Stack mov ebx, irqO_handler mov edi, IrqO_handler_Tss call SetJTSS

Код инициализации контроллеров прерываний и IDT здесь приводиться не будет. Теперь для включения многозадачности достаточно загрузить в регистр TR селектор первой задачи (листинг 2.27).

Листинг 2.27. Включение многозадачности

mov ах, FirstTask_Selektor ltr ах

FirstTask:

mov esi, message3 mov al, 0 mov ah, 2 mov Ы, "5” call OutText

sti jmp $

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

Код обоих задач почти одинаков; в листинге 2.28 приведён код первой задачи.

Листинг 2.28. Код первой задачи

message5 db "Task number 1 message " counterl dd llllllllh db 0

TASK_NUMBER_1: sti

mov esi, message5 mov al, 0

mov ah, б mov Ы, "5"

@@ :

call OutText

inc dword [counterl] jmp @b

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

При каждом повторении значение переменной counterl увеличивается на единицу и при выводе после текста «Task number 1 message» должны стоять четыре мелькающих символа. Метки под названием @@ являются безымянными и используются совместно с директивами @ь и @f ; директива @ь обозначает первую предыдущую метку, a @f - первую последующую метку.

Теперь осталось только привести код обработчика прерывания IRQ0 (листинг 2.29а).

Листинг 2.29а. Обработчик прерывания IRQ0

irqO_handler:

raov edi, IrqO_handler_Tss

virtual at edi .edi TSS32 end virtual

.repeat :

inc dword [counter]

mov esi,interrupt_mes

mov al, 0

mov ah, 4

mov Ы, "5"

call OutText

Начало обработчика должно быть понятно. Заносим в регистр EDI указатель на TSS обработчика. В начале тела цикла мы выводим сообщение о том, что произошёл вызов обработчика (листинг 2.296).

Листинг 2.296. Обработчик прерывания IRQ0 (продолжение) хог еах, еах

mov ах, [.edi.PreviousTaskLink] call ClearBusyFlag

cmp ax, TASK.NUMBER.2.Selektor jz @f

mov ax, TASK_NUMBER_2.Selektor call SetBusyFlag

mov [.edi.PreviousTaskLink], TASK_NUMBER_2_Selektor

jmp .end.dispatch

mov ax, TASK.NUMBER.3.Selektor call SetBusyFlag

mov [.edi.PreviousTaskLink], TASK_NUMBER_3_Selektor Здесь мы производим замену селектора в поле Previous Task Link. Но для начала сбрасываем флаг занятости в дескрипторе предыдущей задачи с помощью функции ClearBusyFlag, она принимает в качестве параметра селектор нужного нам дескриптора в регистре ЛХ. Код этой функции здесь приводиться не будет - его можно посмотреть в полном исходном коде примера, который есть на компакт-диске, прилагающемся к книге. Для установки флага занятости используется аналогичная функция SetBusyFlag. Если В поле Previous Task Link содержится селектор второй задачи, то заносим в него селектор третьей, в противном случае - селектор второй.

Листинг 2.29в. Обработчик прерывания IRQ0 (продолжение)

.end.dispatch: mov al, 2Oh out 020h, al out OaOh, al

iretd

jmp .repeat

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

Полный исходный код программы находится в папке part2 на компакт-диске, прилагающемся к данной книге.

Резюме

В этом разделе нами были изучены механизмы, обеспечивающие многозадачность в защищённом режиме. Тем не менее, как это ни прискорбно, наиболее распространённые операционные системы (Windows и UNIX) по каким-то причинам не используют аппаратный механизм поддержки многозадачности, а вместо этого реализуют многозадачность программно. В разделе 2.5 мы будем изучать механизмы защиты в защищённом режиме.

 
Посмотреть оригинал
< Пред   СОДЕРЖАНИЕ ОРИГИНАЛ   След >