Странности с jmp'ами и interrupt handler'ами

Тема в разделе "WASM.BEGINNERS", создана пользователем Igor1024, 31 май 2011.

  1. Igor1024

    Igor1024 Васил Троянов Боянов (Azis)

    Публикаций:
    0
    Регистрация:
    15 окт 2010
    Сообщения:
    345
    Адрес:
    Sliven, Bulgaria
    Собственно, ввожу в свою поделку многозадачность. Переключение происходит по тику таймера. Чтоб не засорять стек между функциями перехожу jmp'ами и получаю джигурду с селекторами. Что имею ввиду: в обработчике прыгаю на функцию переключения, потом, после выполнения, обратно в хэндлер (к посылке EOI) и получаю превышение лимита по обращению к памяти (во всяком случае Bochs так говорит)... Что самое странное, селекторы при возврате я восстанавливаю. Код приводить смысла нет. Просто интересная ситуация с jmp'ом.
     
  2. NoName

    NoName New Member

    Публикаций:
    0
    Регистрация:
    1 авг 2004
    Сообщения:
    1.229
    1. Ошибка именно в коде.
    2. Про селекторы ничего непонятно.
     
  3. Igor1024

    Igor1024 Васил Троянов Боянов (Azis)

    Публикаций:
    0
    Регистрация:
    15 окт 2010
    Сообщения:
    345
    Адрес:
    Sliven, Bulgaria
    Допустим. Вот просто сохраним текущее состояние и прыгаем обратно. Замечу, что пытаюсь софтверную многозадачность сделать. Структурка, в которой храню всю необходимую инфу весит 100h.
    Код (Text):
    1. timer:
    2. .........
    3.         jmp near task_switch
    4. .switch_task:        
    5.         push ax
    6.         mov al,20h;EOI
    7.         out 20h,al
    8.         out 0a0h,al
    9.         pop ax
    10.  
    11.         iretd
    Код (Text):
    1. task_switch:
    2.  
    3.         mov [temp_1],eax
    4.         mov [temp_2],es
    5.  
    6.         xor eax,eax
    7.         mov ax,20h
    8.         mov es,ax
    9.         ;calculate address
    10.         push ebx
    11.         mov eax,[cur_task_num];вычисление адреса, где будет хранится ;состояние задачи
    12.         mov ebx,100h
    13.         mul ebx
    14.         pop ebx
    15.         mov edi,eax;EDI - start of TSS_struct
    16.  
    17.         jmp store_context
    18.  
    19. .cont:
    20.         mov ax,10h
    21.         mov es,ax
    22.         jmp timer.switch_task; хватит пока что. Попробуем просто вернуться.
    23.  
    24.  
    25.  
    26. store_context:
    27.         ;ESP,EIP,CS,ES,EAX - later
    28.  
    29.         ;mov eax,cr3               ;потом
    30.         ;mov [es:edi+5],eax
    31.  
    32.         ;pop eax
    33.         ;mov [ret_addr],eax
    34.  
    35.         pushfd
    36.         pop eax
    37.         mov [es:edi+13],eax;EFLAGS
    38.  
    39.         mov [es:edi+21],ecx
    40.         mov [es:edi+25],edx
    41.         mov [es:edi+29],ebx
    42.         mov [es:edi+37],ebx
    43.         mov [es:edi+41],esi
    44.         mov [es:edi+45],edi
    45.  
    46.         mov ax,[temp_2]
    47.         mov [es:edi+49],ax;ES
    48.  
    49.         mov [es:edi+53],ss
    50.         mov [es:edi+55],ds
    51.         mov [es:edi+57],fs
    52.         mov [es:edi+59],gs
    53.         sldt ax
    54.         mov [es:edi+61],ax
    55.         mov eax,[temp_1]
    56.         mov [es:edi+17],eax;EAX
    57.  
    58.         pop eax
    59.         mov [es:edi+9],eax;EIP
    60.  
    61.         pop ax
    62.         mov [es:edi+51],ax;CS
    63.  
    64.         popfd             ;EFLAGS
    65.  
    66.  
    67.         jmp task_switch.cont
     
  4. Igor1024

    Igor1024 Васил Троянов Боянов (Azis)

    Публикаций:
    0
    Регистрация:
    15 окт 2010
    Сообщения:
    345
    Адрес:
    Sliven, Bulgaria
    Да, проблема где-то в коде. Сейчас повнимательней потестил, оказалось, что и с call'ом такая же проблема. Ладно, буду отлаживать.
     
  5. Phantom_84

    Phantom_84 New Member

    Публикаций:
    0
    Регистрация:
    6 июн 2007
    Сообщения:
    820
    Как-то сложно... Ты мой пост видел? Нужно делать именно call SwitchTask, потому что контекст меняется внутри обработчика, а не при выходе из него (кроме того, переключение может выполняться не только в обработчике) - инструкция выхода из SwitchTask актуализирует eip новой задачи. По этой же причине EOI нужно делать до вызова SwitchTask, т.к. код обработчика, расположенный после SwitchTask доработает только тогда, когда исходной задаче будет передано управление повторно (т.е. не скоро :) ). Формально обработка прерывания закончится, когда ты сделаешь sti в контексте новой задачи. Я описал отличный прием, используемый у меня в ядре, когда адрес структуры потока совпадает с вершиной стека ядра данного потока.

    Если ввести ряд упрощений: выполнять переключение при каждой вызове таймерного обработчика, не переустанавливать CR0.TS и CR3 (работать в контексте одного процесса и не следить за состоянием регистров FPU и т.п.), то получится примерно следующее.

    Код (Text):
    1.   struc TS ; ThreadStructure
    2.   {
    3.   .pred dd ?
    4.   .next dd ?
    5.   .stackpointer dd ?
    6.   }
    7.  
    8.   proc SwitchToNext ; cs=KCODE, ds=es=ss=KDATA, df=if=0
    9.   push fs
    10.   push gs
    11.   mov eax,[TSS.sp0]
    12.   mov [eax+TS.stackpointer],esp
    13.   mov eax,[eax+TS.next]
    14.   mov esp,[eax+TS.stackpointer]
    15.   mov [TSS.sp0],eax
    16.   pop gs
    17.   pop fs
    18.   ret
    19.  
    20.   proc IRQ0Handler
    21.   pusha
    22.   push ds
    23.   push es
    24.  
    25.   mov al,20h
    26.   out 20h,al
    27.  
    28.   push ss
    29.   pop ds ; ds <- KDATA
    30. ; push ss
    31. ; pop es ; es <- KDATA
    32. ; cld
    33.  
    34.   call SwitchToNext
    35.  
    36.   pop es
    37.   pop ds
    38.   popa
    39.   iret
     
  6. Igor1024

    Igor1024 Васил Троянов Боянов (Azis)

    Публикаций:
    0
    Регистрация:
    15 окт 2010
    Сообщения:
    345
    Адрес:
    Sliven, Bulgaria
    Phantom_84, Вы меня опять спасаете :)
    Подробно я не рассказал. У меня переключение делается так: сначала сохраняем контекст текущей задачи, потом по битмэпу ищем следующую занятую структуру, загружаем её значения, возвращаемся в обработчик и делаем iretd.
    Сейчас переварю, что Вы сказали, и отпишусь.
     
  7. Phantom_84

    Phantom_84 New Member

    Публикаций:
    0
    Регистрация:
    6 июн 2007
    Сообщения:
    820
    OK, переваривай.

    runqueue практически всегда делается как динамический двунаправленный кольцевой список (я даже не удержался и поле pred упомянул).
     
  8. Phantom_84

    Phantom_84 New Member

    Публикаций:
    0
    Регистрация:
    6 июн 2007
    Сообщения:
    820
    Да, чтобы упростить тебе задачу, нужно сказать об инициализации стека ядра создаваемого потока. Перед тем, как поток в первый раз получит управление, в его стеке ядра нужно сформировать кадр определенного формата. Например, для потока ядра можно сделать так:

    Код (Text):
    1. ; все элементы ниже указаны в порядке, обратном их расположению в памяти
    2. ; - stackpointer ; указывает на StartFrame
    3. ; - next
    4. ; - pred
    5. ;   ^^TS
    6. ; - start
    7. ; - InitKernelThread
    8. ; - fs
    9. ; - gs
    10. ;   ^^StartFrame
    11.  
    12.   proc InitKernelThread
    13.   sti
    14.   ... ; инициализация потока, например, можно проинициализировать регистры общего назначения
    15.   ret ; go to start
    Если не хочешь использовать "доводчик" (InitKernelThread) или лишний раз заморачиваться с флагами, то можно в SwitchTask сделать как-то так.
    Код (Text):
    1. pushf
    2. cli
    3. ...
    4. popf
    5. ret
    Хотя "доводчики" - традиционная практика - например, для прикладных потоков доводчик выполняет очень много служебной работы, особенно для стартовых потоков приложений. Он же обычно формирует стековый кадр для перехода в user mode, хотя это можно сделать заранее.
    Код (Text):
    1. ; - stackpointer ; указывает на StartFrame
    2. ; - next
    3. ; - pred
    4. ;   ^^TS
    5. ; - user ss
    6. ; - user esp
    7. ; - user eflags
    8. ; - user cs
    9. ; - start
    10. ; - InitUserThread
    11. ; - fs
    12. ; - gs
    13. ;   ^^StartFrame
     
  9. Igor1024

    Igor1024 Васил Троянов Боянов (Azis)

    Публикаций:
    0
    Регистрация:
    15 окт 2010
    Сообщения:
    345
    Адрес:
    Sliven, Bulgaria
    Чтоб у меня меньше вопросов возникало:где же про такие тонкости в переключении контекстов сказано в интеловских мануалах?
     
  10. Igor1024

    Igor1024 Васил Троянов Боянов (Azis)

    Публикаций:
    0
    Регистрация:
    15 окт 2010
    Сообщения:
    345
    Адрес:
    Sliven, Bulgaria
    Так, всё. Я проснулся и со свежими силами ринулся в бой.
    Я переключаюсь на задачу не в обработчике. Просто перед выходом из него пихаю 'координаты' новой задачи в стек и iretd делаю. И прерывание закончено, и на задачу переключился.
    Со стеком у Вас хитро сделано, ничего не скажешь. Хотя в принципе без разницы что использовать - битовый массив или кольцевой список. Как минимум битовый массив меньше места занимает.
    Сделал через call'ы всё, но валится с fetch_raw_descriptor на этих строках:
    Код (Text):
    1.         pop eax
    2.         mov [es:edi+9],eax;EIP
    3.  
    4.         pop ax
    5.         mov [es:edi+51],ax;CS
    6.  
    7.         popfd             ;EFLAGS
     
  11. Igor1024

    Igor1024 Васил Троянов Боянов (Azis)

    Публикаций:
    0
    Регистрация:
    15 окт 2010
    Сообщения:
    345
    Адрес:
    Sliven, Bulgaria
    Тьфу... Вот я... адрес возврата для iretd в стек не положил...
     
  12. Igor1024

    Igor1024 Васил Троянов Боянов (Azis)

    Публикаций:
    0
    Регистрация:
    15 окт 2010
    Сообщения:
    345
    Адрес:
    Sliven, Bulgaria
    Phantom_84, спасибо за идеи. Если у меня их получится реализовать, тогда прям очень хорошо. Вы, видать, в плане осьдева поднаторели очень хорошо, ведь занимаетесь очень давно этим. Спасибо ещё раз. Всё. Нашёл свой баг...
    Кстати, проблема вся крылась в стеке. Также выяснил, что спокойно можно на task_switch jmp'ать.
    Тема закрыта.