Собственно, ввожу в свою поделку многозадачность. Переключение происходит по тику таймера. Чтоб не засорять стек между функциями перехожу jmp'ами и получаю джигурду с селекторами. Что имею ввиду: в обработчике прыгаю на функцию переключения, потом, после выполнения, обратно в хэндлер (к посылке EOI) и получаю превышение лимита по обращению к памяти (во всяком случае Bochs так говорит)... Что самое странное, селекторы при возврате я восстанавливаю. Код приводить смысла нет. Просто интересная ситуация с jmp'ом.
Допустим. Вот просто сохраним текущее состояние и прыгаем обратно. Замечу, что пытаюсь софтверную многозадачность сделать. Структурка, в которой храню всю необходимую инфу весит 100h. Код (Text): timer: ......... jmp near task_switch .switch_task: push ax mov al,20h;EOI out 20h,al out 0a0h,al pop ax iretd Код (Text): task_switch: mov [temp_1],eax mov [temp_2],es xor eax,eax mov ax,20h mov es,ax ;calculate address push ebx mov eax,[cur_task_num];вычисление адреса, где будет хранится ;состояние задачи mov ebx,100h mul ebx pop ebx mov edi,eax;EDI - start of TSS_struct jmp store_context .cont: mov ax,10h mov es,ax jmp timer.switch_task; хватит пока что. Попробуем просто вернуться. store_context: ;ESP,EIP,CS,ES,EAX - later ;mov eax,cr3 ;потом ;mov [es:edi+5],eax ;pop eax ;mov [ret_addr],eax pushfd pop eax mov [es:edi+13],eax;EFLAGS mov [es:edi+21],ecx mov [es:edi+25],edx mov [es:edi+29],ebx mov [es:edi+37],ebx mov [es:edi+41],esi mov [es:edi+45],edi mov ax,[temp_2] mov [es:edi+49],ax;ES mov [es:edi+53],ss mov [es:edi+55],ds mov [es:edi+57],fs mov [es:edi+59],gs sldt ax mov [es:edi+61],ax mov eax,[temp_1] mov [es:edi+17],eax;EAX pop eax mov [es:edi+9],eax;EIP pop ax mov [es:edi+51],ax;CS popfd ;EFLAGS jmp task_switch.cont
Да, проблема где-то в коде. Сейчас повнимательней потестил, оказалось, что и с call'ом такая же проблема. Ладно, буду отлаживать.
Как-то сложно... Ты мой пост видел? Нужно делать именно call SwitchTask, потому что контекст меняется внутри обработчика, а не при выходе из него (кроме того, переключение может выполняться не только в обработчике) - инструкция выхода из SwitchTask актуализирует eip новой задачи. По этой же причине EOI нужно делать до вызова SwitchTask, т.к. код обработчика, расположенный после SwitchTask доработает только тогда, когда исходной задаче будет передано управление повторно (т.е. не скоро ). Формально обработка прерывания закончится, когда ты сделаешь sti в контексте новой задачи. Я описал отличный прием, используемый у меня в ядре, когда адрес структуры потока совпадает с вершиной стека ядра данного потока. Если ввести ряд упрощений: выполнять переключение при каждой вызове таймерного обработчика, не переустанавливать CR0.TS и CR3 (работать в контексте одного процесса и не следить за состоянием регистров FPU и т.п.), то получится примерно следующее. Код (Text): struc TS ; ThreadStructure { .pred dd ? .next dd ? .stackpointer dd ? } proc SwitchToNext ; cs=KCODE, ds=es=ss=KDATA, df=if=0 push fs push gs mov eax,[TSS.sp0] mov [eax+TS.stackpointer],esp mov eax,[eax+TS.next] mov esp,[eax+TS.stackpointer] mov [TSS.sp0],eax pop gs pop fs ret proc IRQ0Handler pusha push ds push es mov al,20h out 20h,al push ss pop ds ; ds <- KDATA ; push ss ; pop es ; es <- KDATA ; cld call SwitchToNext pop es pop ds popa iret
Phantom_84, Вы меня опять спасаете Подробно я не рассказал. У меня переключение делается так: сначала сохраняем контекст текущей задачи, потом по битмэпу ищем следующую занятую структуру, загружаем её значения, возвращаемся в обработчик и делаем iretd. Сейчас переварю, что Вы сказали, и отпишусь.
OK, переваривай. runqueue практически всегда делается как динамический двунаправленный кольцевой список (я даже не удержался и поле pred упомянул).
Да, чтобы упростить тебе задачу, нужно сказать об инициализации стека ядра создаваемого потока. Перед тем, как поток в первый раз получит управление, в его стеке ядра нужно сформировать кадр определенного формата. Например, для потока ядра можно сделать так: Код (Text): ; все элементы ниже указаны в порядке, обратном их расположению в памяти ; - stackpointer ; указывает на StartFrame ; - next ; - pred ; ^^TS ; - start ; - InitKernelThread ; - fs ; - gs ; ^^StartFrame proc InitKernelThread sti ... ; инициализация потока, например, можно проинициализировать регистры общего назначения ret ; go to start Если не хочешь использовать "доводчик" (InitKernelThread) или лишний раз заморачиваться с флагами, то можно в SwitchTask сделать как-то так. Код (Text): pushf cli ... popf ret Хотя "доводчики" - традиционная практика - например, для прикладных потоков доводчик выполняет очень много служебной работы, особенно для стартовых потоков приложений. Он же обычно формирует стековый кадр для перехода в user mode, хотя это можно сделать заранее. Код (Text): ; - stackpointer ; указывает на StartFrame ; - next ; - pred ; ^^TS ; - user ss ; - user esp ; - user eflags ; - user cs ; - start ; - InitUserThread ; - fs ; - gs ; ^^StartFrame
Чтоб у меня меньше вопросов возникало:где же про такие тонкости в переключении контекстов сказано в интеловских мануалах?
Так, всё. Я проснулся и со свежими силами ринулся в бой. Я переключаюсь на задачу не в обработчике. Просто перед выходом из него пихаю 'координаты' новой задачи в стек и iretd делаю. И прерывание закончено, и на задачу переключился. Со стеком у Вас хитро сделано, ничего не скажешь. Хотя в принципе без разницы что использовать - битовый массив или кольцевой список. Как минимум битовый массив меньше места занимает. Сделал через call'ы всё, но валится с fetch_raw_descriptor на этих строках: Код (Text): pop eax mov [es:edi+9],eax;EIP pop ax mov [es:edi+51],ax;CS popfd ;EFLAGS
Phantom_84, спасибо за идеи. Если у меня их получится реализовать, тогда прям очень хорошо. Вы, видать, в плане осьдева поднаторели очень хорошо, ведь занимаетесь очень давно этим. Спасибо ещё раз. Всё. Нашёл свой баг... Кстати, проблема вся крылась в стеке. Также выяснил, что спокойно можно на task_switch jmp'ать. Тема закрыта.