Pull to refresh

Comments 67

то что надо! Но зачем начинать писать ОС в 12-й ночи на понедельник?

буду читать завтра на работе :)
Выложили сразу как дописали, чтобы не забыть. Читайте на здоровье, будем отвечать по возможности.
За интересным делом часто забываешь о времени :)
И не говорите как не сядешь за четвертую цивилизацию, отрываешься не раньше чем часа в 4 утра
Пятая, пятая! Конечно с первой не сравнить, но уууух.
а когда еще есть время? только по выходным.
UFO just landed and posted this here
Количество выпусков даже примерно не знаем. Будем писать, пока есть о чём.
UFO just landed and posted this here
Мне в прошлый раз не ответили, она будет новая-то? Ну, принципиально, в смысле
я, например, не могу понять, что Вы имеете ввиду.
принципиально новая — это, по идее, важная, с важными новшествами.
в данном топике рассматривают пример СОЗДАНИЯ оси. конечно же, это не Windows или Linux. это что-то свое, возможно хуже, возможно лучше существующих систем, но это не важно. важно научится и попробовать сделать. а дальше сам старайся сделать «принципиально новое».
если Вы ко мне, то… я не Денис
Необоснованное заявление.
>> jmp $; зависаем

Блин… я и завис… Assembler для меня пуще магии… О_о
Нихрена не понятно, смещения, сдвиги, но всегда выглядит эффектно!.. )
$ — адрес текущей инструкции.
jmp $
примерно аналогично
while(1) {}
Мне кажется, ближе код
label: 
         goto label;
Только сегодня вспоминал о вашем цикле, куда пропали типа. Завтра читать буду
Мы не пропадаем, нам всегда можно написать)
А я уже перестал надеятся на продолжение. Спасибо огромное! Продолжайте писать!
jmp $ — некрасиво. Раз уж у нас IOPL=0, почему бы не использовать cli-hlt? Или не сделать то, что не умели делать DOS и не выключить компьютер программно?
Можно и cli, hlt сделать, да. В нашем, простейшем, случае не вижу разницы. Выключить компьютер как вы показали ниже не получится, так как прерывания BIOS не будут доступны. Да и не к чему лишние усложнения, мне кажется.
my bad, не то пытался вставить. Выключить компютер вот так:
mov ax, 5307h
mov bx, 1
mov cx, 3
int 15h
На сколько я понял — на данном этапе мы не можем использовать интеррапты (прерывания), а в следующей статье нам раскажут как это сделать, возможно там авторы и используют ваш совет.
«Однако, обратившись, например, по адресу FFFF:FFFF, можно “прыгнуть” немного выше этой планки, и полученный адрес будет иметь длину 21 бит»
Здесь автор обошел такую интересную вещь как HMA, 64кб для вирусов и резидентов реального режима 80286+ процессора )) Это сейчас кажется не важным, а 15 лет назад еще +64Кб памяти считалось искусством.
Вещь, безусловно, интересная, но мы ее умышленно пропустили, так же, как и лирическое отступление о «рваных» базе и лимите в дескрипторе, например. Просто не хотелось нагромождать статью тем, чем мы не пользуемся. Надеюсь, что особо пытливые умы самостоятельно почитают и про A20, и про HMA.
Спасибо, посмотрим.
А при чем здесь своя ОС? Статья-то об адресации памяти процессоров Intel
Читал, но изучение ассемблера и разработка ОС это разные вещи.
разработка ОС напрямую связана со знанием ассемблера
Какая часть, скажем, Линукса написана на ассемблере? Одна тысячная?
Согласитесь, сравнивать нашу учебную ОС с нынешними версиями линукса немного не корректно, ведь правда? И если уж вы заговорили о линуксе, то давайте возьмём раннюю версию ядра и посмотрим. Чтобы не тратить ваше время приведу результаты подсчётов:
строк на ассемблере: 1464
строк на C: 8413
А это далеко не одна тысячная.
Согласитесь, сравнивать нашу учебную ОС с нынешними версиями линукса немного не корректно, ведь правда?


Договорились, разработка учебной ОС связана со знанием ассемблера.
Да ладно вам, ни один курс об операционных системах невозможен без описания адресации памяти.
Меня сильно смутил заголовок статьи. Я ожидал увидеть про написание ОС, а не увидел. Это же сколько надо написать частей чтобы написать свою ОС :)
Много частей. Создание ОС — это вообще дело не скорое :)
Насколько я помню курс из универа, такими темпами части к 80-ой станет понятно как это всё работает. Потом ещё частей 100 на написание чего-нибудь интересного)
Ну тут уж вы преувеличили.
Мы собираемся идти значительно дальше возможностей DOS. Подробнее — в следующих выпусках :)
Несколько слов об особенностях обработки прерываний.

Есть 16 аппаратных прерываний IRQ0-IRQ15, которые возникают, когда периферийные устройства типа клавиатуры, таймера или хард-диска пытаются нам что-то сказать. Контроллер прерываний эти сигналы подхватывает и вызывает какие-нибудь INT'ы процессора.

По дефолту IRQ0 — IRQ7 посажены на INT 08h — INT 0Fh, а IRQ8 — IRQ15 на INT 70h — INT 77h. В BIOS'е на них висят обработчики, которые позволяют операционной системе в реальном режиме откликаться на внешние раздражители.

В защищенном режиме INT 08h — INT 0Fh вызываются процессором при возникновении различных исключительных ситуаций (ошибка стека, исключение общей защиты и прочее). Так что контроллер прерываний нужно будет перепрограммировать, чтобы он в ответ на IRQ вызывал другие INT'ы.

Например, если вы хотите живую клавиатуру (IRQ 1), её следует перебросить с INT 09h (который отвечает за Coprocessor Segment Overrun) на какой-нибудь INT в области от 20h до FFh.

Об адресной линии A20.
На современном железе она не обязательно управляется портом 92h, так что приведенный код

in al, 0x92
or al, 2
out 0x92, al

может и сбойнуть.

Как с этим жить
В BIOS есть функция, которая позволяет абстрагироваться от обеих проблем. Это функция 89h прерывания INT 15h, она переключает процессор в защищенный режим, заодно открывает A20 и перепрограммирует контроллер прерываний.

Формат вызова следующий.
Вход:
AH = 89h
BL = номер прерывания для IRQ0, должен быть кратен 8 (IRQ1-7 сядут на следующие 7 прерываний)
BH = номер прерывания для IRQ8, должен быть кратен 8 (IRQ9-F сядут на следующие 7 прерываний)
ES:SI -> адрес GDT для защищенного режима.

Выход:
CF — признак ошибки
CF = 1, AH = FFh — не получилось открыть адресную линию A20
CF = 0, AH = 00h — всё нормально.

Выход из INT 15h будет как обычно, на следующую команду за вызовом. Только мы окажемся уже в защищенном режиме, так что для текущего значения CS должен быть настроен дескриптор. Значение BP не сохраняется, значения всех сегментных регистров будут изменены.
UFO just landed and posted this here
Здесь, например. Список полезной для разработчиков ОС литературы мы привели еще в первой статье, возможно найдете там еще что-нибудь интересное.
Самые широко известные — INT10h для BIOS, INT 21h для DOS — с большой достоверностью гуглятся. А развивающиеся — такие, как дисковый сервис INT 13h, или расширенный AT-сервис INT 15h — по ним информация устаревает быстро. По кускам приходится собирать. Русскоязычные источники вообще, кажется, застряли в 1991-м году.
очень интересно. ждал эту часть и жду следующую
спасибо. хорошая и понятная статья. прямо вспомнил лекции николая ивановича корсунова.
хотя на них я ни черта не мог понять и изучал это по книге о ассемблере зубкова
Я еще искал в сети немного другую инфу, как работать с виртуальным 86 режимом.

Никто толком не знает как можно запустить несколько «реальных» режимов и работать с ними
А еще более непонятно как например виндовс 3х грузит досовские дрова реального режима, например сидирома но потом с ними работает в защищенным режиме и такоже подключает сидюк виртуальным 86 режимам — сеансам мс-дос
Такое даже виндовс 9х умеет, работать с драйверами для дос
Примерно то же самое я читал в учебнике по ассемблеру. У меня сегменты вызывают разрыв шаблона. Я пишу на языках высокого уровня и привык к страничной модели памяти. Я знаю как устроен менеджер памяти в Windows, знаю про адресные пространства процессов, про ошибки станиц и подкачку, про отображаемые в память файлы. Но когда я дизассемблером открываю исполняемый файл и вижу mov cs, ds моё понимание напрочь отказыват… Может поясните?
В общих чертах, оно вот как обстоит.

Сегмент — это произвольный кусок памяти, у него есть начальный адрес. Этот адрес хранится в сегментном регистре. Адреса объектов внутри сегмента задаются относительно этого базового адреса. Теоретически, можно сделать плоскую память, задать все сегментные адреса в 0 и не париться (что в Windows в пользовательском режиме и сделано).

Если память не плоская, тогда код процесса пихается в свой сегмент; данные пихаются в один или два сегмента. Стек тоже в свой сегмент. Хотя можно всё запихнуть в один и тот же.

DS и ES — сегментные регистры. В них адреса сегментов со статическими переменными и строковыми константами. Команды пересылки данных (кроме стековых) берут базовый адрес из этих регистров.

SS — сегментный регистр, в нем адрес стека. На него ориентируются команды PUSH, POP и команды пересылки, в которых адрес операнда задан в регистре BP или SP.

FS — сегментный регистр. В его сегменте под Windows лежит инфа про SEH-chain, а вообще его можно как угодно использовать.

CS — сегментный регистр, в нем адрес сегмента кода. Адрес CS:IP указывает на текущую выполняемую команду (значение CS своё для каждого потока).

Значения DS и ES задаются свои для каждого процесса. Это позволяет данным от разных процессов лежать рядышком в памяти без использования страничной адресации.
Значение SS — своё для каждого потока. Это позволяет иметь каждому потоку свой стек.

mov cs, ds — если б так можно было делать, привело бы к спонтанной передаче управления неведомо куда. Но в cs вообще нельзя напрямую писать, только командами перехода.
Спасибо. Ну cs ds я от балды написал, просто для примера. Сейчас попробую осмыслить, меня вообще сегментный вопрос давно интересовал, но т.к. с ассемблером я не работаю и были более насущные проблемы, я так для себя его и не прояснил.
Как я понимаю, сегменты появились изначально, до страниц, как способ сэкономить на размерах операндов команд работы с памятью, при помощи замены абсолютных адресов на короткие относительные смещения. А сегменты это по сути псевдонимы каких-то линейных областей памяти, которые по особому используются определенными инструкциями процессора. Ну и естественно с введением страниц для совместимости сегменты оставили. Ну кажется начинаю понимать.
Но по моему в защищенном режиме существует еще хитрая «ядерная» таблица, которая, если правильно помню, называется VAD. Вернее не таблица, а дерево, в котором задаётся соответствие физических страниц и виртуальных страниц из адресного пространства процесса. Я так понял, что это последний шаг для получения адреса, посылаемого контроллеру памяти, из линейного адреса, вычисляемого описанным вами образом, при обращении к сегменту? Наверное тогда значения сегментных регистров совсем не равны 0, специфичны для потока и сохраняются/восстанавливаются при переключении контекста потока, как и остальные? Вот это было бы интересно узнать.
>сегменты появились изначально, до страниц, как способ сэкономить на размерах операндов…
Скорее, на разрядности регистров сэкономили. В 8086 регистры 16-разрядные были, а шина адреса — 20 разрядов, так что без сегментов там никак.

В 386 уже 32-разрядная шина, и регистры такие же ( как раз хватит, чтоб адресовать 4 Гб). Поэтому в сегментах особого смысла нет, и остались они в основном из-за совместимости. В AMD x86-64 их вообще выпилили, в нативном режиме там сегментные регистры недоступны.

>по сути псевдонимы каких-то линейных областей памяти…
В общем, да.

>Но по моему в защищенном режиме существует еще хитрая «ядерная» таблица…

При включенной линейной адресации там получаются два уровня. Сначала адрес сегмента складывается со смещением, получается линейный адрес.

Потом линейный адрес смотрится в трёхуровневом каталоге, который так и называется «Page Directory», оттуда берется физический адрес, и по этому адресу процессор лезет в память. Сам каталог лежит в оперативке, а его физический адрес в регистре CR3.

В каталоге каждой странице тупо проставлен бит — присутствует она в памяти или нет. Этот бит аппаратно проверяется. Если страницы нет в памяти, вызывается исключение Page Fault (INT 0Eh). Операционка его отлавливает и может делать что хочет — например, выключить линейную адресацию, загрузить эту страницу в память по любому физическому адресу, апдейтить физический адрес в каталоге, включить линейную адресацию обратно и сделать вид, что ничего не произошло.

Грубо говоря, любой линейный адрес можно мапить на любой физический. При переключении задачи просто подсовывается новый каталог страниц. Таким образом, один и тот же линейный адрес мапится на разные физические, в зависимости от того, из какого потока идет обращение.

Для прикладной программы это без разницы, она не заметит подмены. В Windows вообще у всех пользовательских процессов один и тот же базовый адрес всегда, и сегментные регистры одинаково настроены. Только на уровне каталог страниц у каждого потока свой, так что физические адреса разные получаются.

И да, при переключении задач (т.е. потоков) сегментные регистры аппаратно сохраняются. Специальное место есть для этого, сегмент состояния задачи (TSS).
Ага, спасибо. Ну теперь я имею представление о том как оно всё устроено.
первое впечатление — джордейн чайными ложечками...)
Вы дальнейший код хотите писать на Си или до конца на асме?
На Си. Уже через выпуск планируем перейти. Пока по времени не очень получается(
Only those users with full accounts are able to leave comments. Log in, please.