- •Раздел 2. Механизмы последовательного выполнения программ
- •2.1. Классификация методов замены контекста
- •2.2. Процедуры как синхронные методы замены контекста
- •2.3. Сопрограммы
- •2.4. Примеры реализации сопрограмм в Си, в защищенном режиме процессора и в Windows
- •2.4.1. Пример реализации сопрограмм в Си
- •2.4.2. Пример реализации сопрограмм в защищенном режиме
- •2.4.3. Пример реализации сопрограмм средствами Windows api
- •2.4.4. Пример реализации сопрограмм средствами ос Linux
- •2.5. Процедуры ос
- •2.6. Прерывания как асинхронный метод замены контекста
- •2.7. Исключения
- •2.7.1. Самая общая характеристика исключений
- •2.7.2 Исключения на низком уровне
- •2.7.3. Исключения в программных средах
2.4. Примеры реализации сопрограмм в Си, в защищенном режиме процессора и в Windows
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(jmp_buf from, jmp_buf 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, и из этого сегмента устанавливается новое состояние машины. В заключении представим схему переключения задач в защищенном режиме, из схемы видна аналогия с ранее рассмотренными сопрограммами. Если в Си для переключения задач потребовалось написать функцию, то в защищенном режиме переключение задач осуществляется одной инструкцией ассемблера.