
- •Раздел 2. Механизмы последовательного выполнения программ
- •2.1. Классификация методов замены контекста
- •2.2. Процедуры как синхронные методы замены контекста
- •2.3. Сопрограммы
- •2.4. Примеры реализации сопрограмм в Си, в защищенном режиме процессора и в Win32
- •2.4.1. Пример реализации сопрограмм в Си
- •2.4.2. Пример реализации сопрограмм в защищенном режиме
- •2.4.3. Пример реализации сопрограмм в Win32
- •2.5. Процедуры ос
- •2.6. Прерывания как асинхронный метод замены контекста
- •2.7. Исключения
- •2.7.1. Самая общая характеристика исключений
- •2.7.2 Исключения на низком уровне
- •2.7.3. Исключения в программных средах
- •2.7.4. Обработка исключений в Delphi
- •2.7.4.1. Защита ресурсов
- •2.7.4.2. Обработка исключений
2.4. Примеры реализации сопрограмм в Си, в защищенном режиме процессора и в Win32
2.4.1. Пример реализации сопрограмм в Си
В языке Си существует предопределенная структура, имеющая следующее описание.
Имя типа jmp_buf. Это по сути дела запись, включающая 10 полей типа word. Имена полей имеют следующий вид:
j_sp, j_cs, j_bp, j_si,
j_ss, j_ip, j_di, j_ds.
j_flag, j_es,
Чтобы сразу провести параллель с сопрограммами, заметим, что структура подобного типа может выступить в качестве дескриптора сопрограммы, причем даже более информативного, чем тот, который мы рассмотрели ранее.
Для работы со структурой jmp_buf существует пара функций, имеющая следующее описание:
int setjmp(jmp_buf jmpb) - пишет состояние текущей задачи в буфер jmpb и возвращает 0;
void longjmp(jmp_buf jmpb, int retval) – восстанавливает состояние задачи из jmpb так, что задача продолжает свое выполнение с той точки, в которую бы она пришла, если бы функция setjmp вернула не 0, а значение, равное retval.
Рассмотрим структуру сопрограмм и функции Transfer для данного случая.
void cor1(void) { void cor2(void) {
while (1) { while (1) {
... ...
transfer(jmpc1,jmpc2); transfer(jmpc2,jmpc1);
} }
} }
void transfer(from, to)
jmp_buf from,to; {
if (0 == setjmp(from)) {//setjmp пишет такое состояние в буфер
longjmp(to,1); //from, что когда будет вызов longjmp
} //с этим буфером, управление передастся
***** //в точку *****
}
Инициализация буфера (на примере cor1)
jmp_buf jmpc1;
unsigned stack1[1000];
struct SREGS segs;
segread(&segs);
jmpc1[0].j_sp = FP_OFF(stack1) + 1982;
jmpc1[0].j_ss = FP_SEG(stack1);
jmpc1[0].j_flag = 0x200; //прерывания разрешены
jmpc1[0].j_cs = FP_SEG(cor1);
jmpc1[0].j_ip = FP_OFF(cor1);
jmpc1[0].j_bp = jmpc1[0].j_sp;
jmpc1[0].j_di = 0;
jmpc1[0].j_es = segs.es;
jmpc1[0].j_si = 0;
jmpc1[0].j_ds = segs.ds;
Функции setjmp и longjmp вместе со структурой jmp_buf являются чрезвычайно удобным средством реализации сопрограмм. Во-первых, мы не спускаемся на уровень ассемблера, а во-вторых, запись состояния регистров в буфер и восстановление их из буфера происходит в режиме запрета прерываний, что обеспечивает высокую надежность переключения задач.
2.4.2. Пример реализации сопрограмм в защищенном режиме
Защищенный режим процессора архитектурно создан для организации многозадачности. У защищенного режима много аспектов, здесь мы рассмотрим именно вопросы создания и переключения задач, которые по сути дела эквивалентны нашему понятию сопрограмм.
Пример, который реализует переключение задач в защищенном режиме, полностью написан на ассемблере. Я буду приводить здесь только фрагменты примера. Если кому-то нужен полный текст примера, то с ним можно также ознакомиться.
Доступ к памяти в защищенном режиме осуществляется не непосредственно по адресу, а через специальные таблицы - таблицы дескрипторов. В таблице дескрипторов хранятся строки, описывающие отдельные участки памяти - страницы, если размер участка постоянен и известен, или сегменты, размер которых может меняться.
Для описания задач существует структура, которая называется «сегмент состояния задачи» TSS. Она имеет следующий вид:
Селектор LTD |
Селектор DS |
Селектор SS |
Селектор CS |
Селектор ES |
Регистры AX, BX, CX, DX, SP, BP, SI, DI |
Регистр флагов |
IP |
SS, SP для уровня привилегий 2 |
SS, SP для уровня привилегий 1 |
SS, SP для уровня привилегий 0 |
Указатель на следующий TSS |
Как видно, это опять наш дескриптор, только еще более информативный, даже по сравнению со структурой jmp_buf из Си.
Для выполнения программы в защищенном режиме создается глобальная таблица дескрипторов GDT, строки которой включают дескрипторы TSS:
не используется |
описание самой таблицы дескрипторов |
дескриптор сегмента данных |
дескриптор сегмента стека |
дескриптор кодового сегмента |
дескриптор задачи - main |
дескриптор задачи 1 |
дескриптор задачи 2 |
Строка таблицы дескрипторов содержит следующие данные:
Размер сегмента |
Адрес сегмента |
Признак сегмента |
В программу, реализующую переключение задач в защищенном режиме, вводятся:
селекторы задач TASK1_SEL, TASK2_SEL, MAIN_TSK – смещения соответствующих дескрипторов в таблице GDT;
выделяется память под стеки, например, так, tsk1_stack db 1024d (0);
инициализируется таблица дескрипторов GDT - т.е. корректно заполняются все ее строки;
инициализируются сегменты состояния задач, например, в поле ip пишется OFFSET Имя процедуры-задачи; в поле sp пишется OFFSET tsk1_stack + Size_Of_Stack;
в регистр GDTR грузится адрес таблицы дескрипторов GDT;
в регистр задач TR грузится селектор задачи - MAIN_TSK.
Таким образом, происходит выполнение задачи Main. Переключение на другую задачу производится инструкцией jmp, например, jmp TASK1_SEL.
По этой инструкции машина видит по признаку сегмента, что селектор указывает на дескриптор задачи. По селектору MAIN_TSK, находящемуся в регистре задач TR, через дескриптор задачи main, находится сегмент состояния задачи main и в него списывается состояние машины в соответствие со структурой сегмента.
Селектор новой задачи TASK1_SEL грузится в регистр задач TR, по селектору также через дескриптор находится сегмент состояния задачи 1, и из этого сегмента устанавливается новое состояние машины. В заключении представим схему переключения задач в защищенном режиме, из схемы видна аналогия с ранее рассмотренными сопрограммами. Если в Си для переключения задач потребовалось написать функцию, то в защищенном режиме переключение задач осуществляется одной инструкцией ассемблера.