Мультизадачность.

Глава 9. Реализация мультизадачности.

        Для начала вашему вниманию предлагается пример, в котором определяется три задачи - "Main", "2" и "3". Задача Main производит переключения на задачи 2 и 3 командой FAR CALL, а задачи 2 и 3 возвращают управление в Main командой IRET. Этот пример демонстрирует инициализацию задач и простейший способ управления ими. Исключения и прерывания здесь не реализованы в виде отдельных задач, поэтому при возникновении исключения или прерывания, обработчик будет выполняться в контексте текущей задачи.
        Для того, чтобы увидеть мультизадачность в действии, три задачи выполняют простейшие действия:
1. Main_TSS: Выводит строку "First task switches other tasks:"
2. TSS_1: Выводит строку "Second Task: Increment:" и число, которое увеличивается на 1 при каждом переключении на задачу.
3. TSS_2: Выводит строку "Third Task: Decrement:" и число, которое уменьшается на 1 при каждом переключении на эту задачу.

        Для работы мультизадачности необходимо следующее:
1. Подготовить все необходимые дескрипторы.
2. Установить контексты всех задач в их сегментах TSS.
3. Запустить мультизадачность и обеспечить переключение задач.

        В примере мы определим следующие 4 дескриптора:
  •  
  • TSS_area - сегмент данных, в котором определены TSS всех задач.
  •  
  • Три дескриптора TSS: Main_TSS, TSS_2 и TSS_3.

            Вот так эти дескрипторы определяются (перед переходом в защищённый режим):


    ; Устанавливаем сегмент TSS_area размером 4Кб для описаний TSS. mov bx,offset GDT + TSS_area mov eax,cr3 add eax,3 * 4096 ; EAX - базовый адрес сегмента, ему ; будет выделена не используемая до сих ; пор страница памяти, 3-я после страницы ; для каталога страниц. mov ebp,eax ; Временно сохраняем этот адрес. mov edx,4096 ; Предел сегмента mov cx,92h ; CL - байт прав доступа, ; CH - биты GDXU и старшие 4 бита предела. call set_descriptor ; TSS_area ; Устанавливаем дескрипторы трёх TSS: mov bx,offset GDT + Main_TSS mov eax,ebp mov edx,67h mov cx,10001001b call set_descriptor ; Main_TSS add eax,104 call set_descriptor ; TSS_2 add eax,104 call set_descriptor ; TSS_3

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


    set_TSS proc near ; Создаёт 32-разрядный TSS минимального размера (104 байта - предел 67h). ; DI = указатель на TSS внутри сегмента TSS_area ; Параметры новой задачи: ; EAX = CR3 ; EBX = EIP ; ECX = EFLAGS ; EDX = ESP push eax push bx push di push es push eax ; Сохраняем регистры для очистки TSS. push ecx push di mov ax,TSS_area ; ES:DI указывают на начало TSS. mov es,ax xor eax,eax ; Очищаем 26 двойных слов (т.е. 104 байта) mov cx,26 ; в TSS. Это делается для потому, что cld ; большинство полей TSS будут содержать нули. rep stosd pop di pop ecx pop eax mov es:[ di + 28 ],eax ; Записали CR3 mov es:[ di + 32 ],ebx ; EIP mov es:[ di + 36 ],ecx ; EFLAGS mov es:[ di + 56 ],edx ; ESP mov al,8 mov es:[ di + 76 ],al ; CS ( = 8 = Code_selector ) add al,8 mov es:[ di + 80 ],al ; SS ( = 16 = Stack_selector ) add al,8 mov es:[ di + 84 ],al ; DS ( = 24 = Data_selector ) pop es pop di pop bx pop eax ret endp

            Вот так выглядит инциализация задач:


    ; Установка мультизадачности xor di,di ; Это будет первая задача (Main_TSS). call set_TSS ; Для задачи Main можно вообще не ; определять TSS. Почему - см. далее. add di,104 ; Это будет вторая задача (TSS_2). lea ebx,second_task_entry_point ; EIP задачи. mov edx,esp ; ESP 2-й задачи. sub edx,256 call set_TSS add di,104 ; Это будет третья задача (TSS_3). lea ebx,third_task_entry_point ; EIP задачи. mov edx,esp ; ESP 3-й задачи. sub edx,512 call set_TSS

            Сразу после такого определения контекстов задач можно запускать мультизадачность. Посмотрите, как это делается в этом примере:


    ; Запускаем первую задачу: mov ax,Main_TSS ltr ax ; Загружаем в регистр TR селектор ; дескриптора TSS задачи Main. Теперь текущая ; задача - это Main_TSS. ; Чистим Busy flag Main_TSS - он установился после загрузки селектора в TR mov bx,offset GDT + Main_TSS and byte ptr [ bx + 5 ],11111101b ; Переход на Main_TSS db 0eah ; Этот код эквивалентен команде FAR JMP Main_TSS:00 dw 00 dw Main_TSS ; Вот здесь процессор сохранил контекст текущей задачи, т.е. заполнил ; поля TSS текущими значениями.
    ; Main_TSS: ; А теперь процессор загрузил те же самые значения из TSS в регистры. ; Вот почему можно не определять TSS для первой задачи - процессор всё ; равно перезапишет в него текущие значения, однако тогда нужно быть ; уверенным, что поля для SSi:ESPi будут содержать нули. ; Установка Busy flag Main_TSS - она нужна, потому что был переход с ; Main_TSS на Main_TSS командой JMP - эта команда сбрасывает флаг занятости ; старой задачи, т.е. Main_TSS. mov bx,offset GDT + Main_TSS or byte ptr [ bx + 5 ],10b ; Вывод строки первой задачи. mov text_color,1fh lea bx,fst_task_msg mov dx,0600h call put_zs mtss_1: db 9ah ; far call TSS_1:00 dw 00 dw TSS_2 db 9ah ; far call TSS_1:00 dw 00 dw TSS_3 jmp mtss_1
    second_task_entry_point: mov text_color,1ah lea bx,snd_task_msg mov dx,0700h call put_zs xor eax,eax step_1: mov text_color,1ah call put_dd_num inc eax sub dl,8 iret jmp step_1
    third_task_entry_point: mov text_color,1eh lea bx,trd_task_msg mov dx,0800h call put_zs xor eax,eax ttep_1: mov text_color,1eh call put_dd_num dec eax sub dl,8 iret jmp ttep_1

            Рабочий пример доступен здесь: файлы examp_9.asm, examp_9.com, pmode_9.lib и init.lib в архиве examp_9.zip (19'586 байт).

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

            Из-за обилия подключаемых процедур, строки макросов, их подключающих, теперь будут вынесены в отдельный файл "init.lib", а код инициализации перед переходом в защищённый режим и после перехода вынесены в две отдельные процедуры. Файл examp.asm теперь содержит только те процедуры, которые обсуждались в данном разделе - это упрощает понимание исходного текста.

    Следующая глава Оглавление Вопросы? Замечания? Пишите: sasm@narod.ru

      Copyright © Александр Семенко.
    TopList

    Hosted by uCoz