Где разместить стек и загрузить ядро

Я новичок в разработке операционных систем, и мне любопытна проблема, с которой я столкнулся при разработке собственного загрузчика. Моя операционная система будет написана на ассемблере и будет работать в 16-битном реальном режиме.

Я знаю, что такое стек, и у меня сложилось впечатление, что он растет вниз в память. Поправьте меня, если я ошибаюсь. Я знаю, как загрузить базовое ядро ​​в память с дискеты, и не думаю, что это проблема.

Проблема, с которой я сталкиваюсь, заключается в том, что я не уверен, где разместить стек и загрузить свое ядро ​​​​в память. Я пытался создать свой стек таким образом, и у меня возникли проблемы:

mov ax, 0x0000
mov ss, ax
mov sp, 0xFFFF

Я загружаю свое ядро ​​​​в 0x1000:0x0000. Когда я PUSH, а затем POP, регистры volatile регистрируются в моей функции print, мое ядро ​​просто зависает второй раз, когда я делаю call print. Это моя функция print:

print:
    push ax
    push bx
    push cx

    mov al, [si]
    cmp al, 0
    je p_Done

    cmp al, 9
    je p_Tab

    mov ah, 0xE
    int 0x10

    cmp al, 10
    je p_NewLine
   p_Return:
    inc si
    jmp print
  p_Tab:
    xor cx, cx
   p_Tab_Repeat:
    cmp cx, 8
    je p_Return
    mov ah, 0xE
    mov al, " "
    int 0x10
    inc cx
    jmp p_Tab_Repeat
  p_NewLine:
    xor bx, bx
    mov ah, 0x3
    int 0x10

    mov dl, 0x00
    mov ah, 0x2
    int 0x10
    jmp p_Return
  p_Done:
    pop cx
    pop bx
    pop ax
    ret

Это строки, которые я хочу отобразить:

db "Kernel successfully loaded!", 10, 0
db 9, "Lmao, just a tab test!", 10, 0

Это вывод, который я получаю при запуске моего ядра (_ — это курсор):

Kernel successfully loaded!
_

Он успешно печатает первую строку, но зависает при печати второй. Если я удаляю операторы PUSH и POP, все работает нормально. Почему мое ядро ​​зависает, когда я пытаюсь сохранить и восстановить регистры в моей функции print? Где я должен разместить свой стек и где я должен загрузить свое ядро?


person DrCereal    schedule 05.07.2016    source источник
comment
Вы неправильно выравниваете стек. Вы должны установить SP в 0. Это поместит первое слово в стек по адресу 0000:FFFE. Но это не ваша проблема, которая, вероятно, заключается в коде, который вы не показали. Ваш код должен работать с SS:SP, инициализированным на 0000:FFFF, и вашим кодом на 1000:0000.   -  person Ross Ridge    schedule 05.07.2016
comment
Вы уверены, что вам понадобится только один стек? Как только вы начнете изучать многозадачность, вам понадобится еще несколько из них.   -  person tofro    schedule 05.07.2016
comment
@Росс Ридж О. Слова помещаются в стек? Не байты? Я чувствую, что это то, что я должен был знать, но спасибо, что дали мне знать об этом.   -  person DrCereal    schedule 05.07.2016
comment
@tofro Сейчас я делаю однозадачное ядро.   -  person DrCereal    schedule 05.07.2016
comment
В 16-битном режиме нажатия и выталкивания имеют размер слова (т. е. 16 бит). По сути, push предназначен для отправки содержимого регистра, поэтому push/pop имеют тот же размер, что и регистры. Справочная информация находится здесь: stackoverflow.com/a/15855444/366904   -  person Cody Gray    schedule 06.07.2016


Ответы (1)


Не помогает и то, что это не минимальный полный проверяемый пример, но ваш вопрос предлагает возможные варианты поиска. Обычно, если код работает, удаляя PUSH и POP в прологе и эпилоге функции, это обычно означает, что стек стал несбалансированным во время выполнения тела функции. Несбалансированный стек приведет к тому, что инструкция RET вернется в любое полуслучайное место на вершине стека. Это, вероятно, приведет к очевидным зависаниям и/или перезагрузкам. Поведение будет неопределенным.

Я не следовал логике в вашем коде, но это выделяется:

print:
    push ax
    push bx
    push cx

    ... snip out code for brevity  

    jmp print

В какой-то момент ваша функция print может быть перезапущена в момент, предшествующий всем нажатиям. Это приведет к увеличению количества PUSH в стеке без соответствующих POP в конце. Я думаю, вы, возможно, пытались добиться такого поведения:

print:
    push ax
    push bx
    push cx

.prloop:
    ... snip out code for brevity  

    jmp .prloop

Метка .prloop появляется в верхней части функции, но после нажатия. Это предотвращает размещение избыточных значений в стеке. .prloop может быть любой допустимой меткой по вашему выбору.


Стек можно разместить в любом месте памяти, которое не используется системой и не мешает вашему загрузчику и/или коду ядра. Как указывает @RossRidge, использование SP 0xFFFF приводит к смещению стека, поскольку это нечетный адрес (0xFFFF=-1). x86 не будет жаловаться (отсутствует флаг проверки выравнивания), но это может снизить производительность стека на некоторых архитектурах x86.

Примечание. Установка для SS:SP значения 0x1000:0x0000 приведет к тому, что стек будет работать с 0x1000:0xFFFF до 0x1000:0x0000. Первое переданное 16-битное значение будет иметь адрес 0x1000:0xFFFE.


Ваше ядро ​​и стек, как правило, безопасны в любом месте между физическими адресами 0x00520 и 0x90000, если они не конфликтуют друг с другом. В некоторых системах верхняя часть области памяти между 0x90000 и 0xA0000 может быть недоступна. Если вы хотите использовать эту область памяти, я бы избегал области между 0x9C000 и 0xA0000. Эта область может использоваться BIOS как часть расширенной области данных BIOS (EBDA). .

Точный объем используемого пространства в области низкой памяти (LMA) можно узнать, вызвав службу прерывания ROM-BIOS 12h или непосредственно прочитав слово по адресу 0x00413. В любом случае результатом будет объем используемой памяти в килобайтах. Если фактической памяти меньше 640 КиБ и/или часть памяти в верхней части LMA используется EBDA или другим программным обеспечением, тогда результат будет ниже 640 (то есть 0x0280). Технически результат может быть и выше 640. Умножая или сдвигая влево количество в КиБ, можно рассчитать эквивалентное количество в абзацах или байтах.

Область между 0x00000 и 0x00520 не следует использовать, так как она содержит таблицу векторов прерываний реального режима, BIOS. Область данных (BDA) и 32 байта памяти, которые считаются зарезервированными.

person Michael Petch    schedule 05.07.2016
comment
Да, я не знаю, почему я не заметил этого действительно вопиющего бага. Что ж, спасибо за ответ, это очень помогает. :) - person DrCereal; 05.07.2016
comment
Подождите, будет ли мой стек работать с 0x1000:0xFFFF, если он установлен на 0x1000:0x0000? Это кажется немного странным. - person DrCereal; 05.07.2016
comment
@DrCereal: Да. Это особенность архитектуры смещения сегментов в 16-битном коде. Когда вы выполняете PUSH в 16-битном формате, смещение (SP) уменьшается на два, прежде чем данные будут помещены в стек. Сегмент никак не меняется. Если вы возьмете 0x0000 и вычтете два, вы получите 0xFFFE. Поскольку сегмент не изменен, вы получите 0x1000:0xFFFE. Если вы хотите иметь стек, который работает с физического адреса 0x00000 до 0x0FFFF, вы можете установить SS:SP на 0x0000:0x0000. В этом случае 1-й толчок уменьшит SP на 2 и поместит туда значение. Это будет адрес памяти 0x0000:0xfffe. - person Michael Petch; 05.07.2016