Что такое Protected Mode и с чем его едят

    Для того, чтобы писать операционку, нужно разбираться во многих деталях. Вот давайте я вас немного просвещу, (но давайте договоримся, что маны вы будете читать сами, чтобы было о чём побеседовать).
    Честно говоря, на просторах сети есть туча тучная материалов по PM, да и ileyи pehat несколько рассказали об этом режиме, но меня попросили всё равно описать в общих рамках его. Сейчас кратко выдам теорию (вообще то специально для этого Intel маны писала), потом начнём писать код.


    Введение в защищённый режим.
    Итак, PM значительно отличается от всем привычного ещё со времён DOS’a реального режима (RM). Теперь придётся привыкать: здесь нет статичных, 64 килобайтных сегментов, таблицы прерываний в 1’ом килобайте, адресов баз сегментов в сегментных регистрах, в общем совершенно новый мир.
    Теперь сегменты описываются в Global Descriptor Table (GDT). Сия таблица может быть только в одном экземпляре. Она структура в памяти. Не сегмент! Может располагаться в памяти где угодно, но её адрес и лимит записываются в регистр GDTR. Вот его структура:

    image

    Сама таблица состоит из записей следующей структуры (кстати нулевая запись пустая. Это важно. При обращении к памяти, ‘описываемой’ нулевым дескриптором, получите #GP – General Protection Fault):
    Давайте рассмотрим эту структуру повнимательней.
    image

    1. Segment Limit:
    Назначение этого поля понятно по названию, но есть тонкость. Собака зарыта в бите G (Granularity).
    Если он неустановлен, то память ‘отсчитывается’ в байтах. В таком случае размер сегмента может варьироваться от 1 байта до 1 мегабайта на размер в 1 байт.
    Если установим его в 1, то будет введена страничная адресация памяти. Тогда мы сможем адресовать от 4 килобайт до 4 гигабайт оперативки с изменением размера на 4 килобайта (размер страницы). Вообще страничная адресация предпочтительней (сравните (1Мб+64Кб-16байт) и 4Гб ). Давайте в этом посте поговорим только о сегментной адресации. Paging заслуживает отдельного разговора.

    2. Base Address:
    Здесь указываем физический адрес базы.

    3. Type field:
    Комбинации битов определяют тип сегмента:
    image

    4. S (descriptor type):
    В документации интеловской сказано, что если этот бит не установлен, то этот дескриптор для системного сегмента, иначе – кода или данных. Под системным подразумевается LDT, TSS, Interrupt Gates и иже с ними (о них позже).

    5. DPL (Descriptor Privilege Level):
    Привилегии описываемого сегмента. Всем знакомые Rings.

    6. P (segment present):
    Если этот бит установлен, то процессор ‘знает’, что сегмент в уже памяти (хотя лучше сказать валидный). Если загрузите в сегментный регистр селектор дескриптора с неустановленным битом P, то произойдёт исключение #NP (not present). Вообще смысл этой витиеватой фразы объясню чуть позже.

    7. D/B:
    Для сегментов разного типа по-разному трактуется.
    1. Для сегментов кода:
    32 или 16 битная длина эффективного адреса и размерность операндов.
    (1-32; 0-16);
    2. Для стека:
    Указатель стека 32 или 16 битный. (1-32; 0-16);

    8. G:
    Влияет на то, в каких единицах (байты, страницы) измеряется лимит сегмента. Вообще Paging можно включить при переходе в PM, установив 31 бит регистра CR0.

    Ещё немного информации:
    Догадываемся, что слово Global поставили не напрасно. Значит есть ещё какая-то табличка. Верно, есть также Local Descriptor Table. Их может быть великое множество. К примеру они могут использоваться в реализации задач и.т.д. А вот LDT уже представляет собой сегмент! Так что привыкайте к фразам типа ‘дескриптор сегмента локальной таблички дескрипторов’.

    После того, как мы описали таблицу, нужно ей загрузить в регистр GDTR. Это делается далеко не mov’ом. GDTR заполняется командой lgdt fword (значение). То есть надо сформировать самостоятельно эту структуру и загрузить в вышеупомянутый регистр. Есть ещё команды работы с этим регистром, но мы несёмся галопом по Европам.

    Ещё один момент. В PM в сегментных регистрах хранятся не базовые адреса сегментов (как в RM), а специально обученные штуки, под названием селекторы. Их структура такова:
    image
    Здесь Index – порядковый номер дескриптора в таблице.
    TI показывает где искать дескриптор (в GDT или LDT).

    Теперь, когда уже понятно как строить таблицу, поговорим о том, как перейти в PM (замечу, это можно сделать только из RM). Вообще … нужно всего установить бит 0 управляющего регистра CR0. Хотя вру. Для начала нужно запретить все прерывания (NMI (Non Maskable Interrupts) в том числе), открыть адресную линию A20 (чтобы была доступна 32-битная адресация), загрузить GDTR, и прыгнуть на метку – старт.

    Давайте воспользуемся загрузчиком (можно KOLIBRI’ский взять), который будет грузить наш код по адресу 1000h:0 (RM’овский, замечу, адрес).
    Здесь будет не всё так гладко, как в тех манах, когда в PM переходят прям из бутлоадера. Всё чуточку сложнее. Но сначала давайте разберём код, который бутлоадер будет загружать (всё пишем на FASM'е). Это своеобразный helloworld. Загрузимся, перейдём в PM и напечатаем приветствие. Всё.

    format binary
    xor ax,ax
    cli ;реинициализируем сегментные регистры
    mov ss,ax
    xor sp,sp
    sti
    mov ax,3
    int 10h

    jmp 1000h:r_start

    r_start:

    mov ax,1000h;перенастраиваем регистры
    mov ds,ax
    mov es,ax

    in al, 0x92;включаем A20
    or al, 2
    out 0x92, al

    cli ;запрещаем прерывания
    mov al,8Fh;запрещаем NMI
    out 70h,al
    in al,71h

    lgdt fword [GDTR];загружаем регистр GDTR
    mov eax,cr0
    or al,1;устанавливаем 0-вой бит
    mov cr0,eax;включаем PM

    jmp fword 08h:Startup32; прыгаем в PM

    align 8 ;процессор быстрее обращается с выравненной табличкой
    GDT:
    dq 0 ;пустой
    db 0FFh,0FFh,0,0,0,9Ah,0CFh,0 ;код
    db 0FFh,0FFh,0,0,0,92h,0CFh,0;данные
    db 0FFh,0FFh,0,80h,0Bh,92h,40h,0 ;видеосегмент
    label GDT_SIZE at $-GDT
    GDTR:
    dw GDT_SIZE-1
    dd GDT+10000h
    ; нужно записать 32-битный адрес. Сейчас мы находимся в сегменте 1000h, база которого 1000h*10h (по ;физическому адресу) => физический адрес GDTR (метки!) = 10000h (физический адрес базы сегмента)+offset

    virtual ;теперь, фактически, забиваем пространство до конца сегмента
    rb 10000h-$;
    end virtual
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;PM32 Entry;;;;;;;;;;;;;;;;;;;
    use32
    org $+10000h;вот для чего: в PM мы работаем с Flat-сегментами, и если мы оставим код ;для PM перед org’ом, то ;внутрисегментный адрес не будет совпадать с Flat адресом. Так вот.

    Startup32: ;точка входа в PM
    mov ax,10h ;здесь пихаем селекторы. Зачастую (! не забываем про порядковый номер в
    mov es,ax ;таблице) селектор сегмент кода - 08h. данных - 10h, видеосегмент - 18h
    mov ds,ax
    mov fs,ax
    mov ss,ax
    mov esp,10000h;стек
    mov ax,18h
    mov gs,ax

    mov esi,hi_string ;покажем, что мы удачно перешли
    call print
    jmp $

    ;ESI - адрес строки
    print:
    pushad
    xor ebx,ebx
    mov ah,07h;атрибут
    puts:
    mov al,[esi+ebx]
    mov [gs:(ebx*2)],ax
    inc ebx
    test al,al
    jnz puts
    popad
    ret
    hi_string db ‘Welcome to PM, dude’,0


    Что мы сделали? Загрузчик нас успешно загрузил по адресу 1000h:0, откуда мы и продолжили выполнение. Сначала включили А20, запретили все прерывания, загрузили в GDTR подходящее значение, прыгнули на метку входа. Замечу, что прыгали мы на
    jmp fword 08h:Startup32
    Т.е 08h — селектор дескриптора кода. Привыкайте.

    Теперь как сие чудо запустить. Лично я пользуюсь WinImage и VirtualBox. Запихиваем загрузчик в бутсектор дискеты и кладём .bin’овский файл в корень. Сохраняем в .vfd, прописываем путь к образу дискеты в свойствах виртуальной машины, запускаем и видим результат.

    В следующем выпуске рассмотрим interrupts, faults, traps, aborts и как они работают, ловятся и отлаживаются. Начнём говорить об архитектуре.

    Источники информации.
    1) Сразу хочу выразить благодарность Phantom_84 aka egos за то, что указал на путь истинный и помог мне в самом начале. Без него мне было бы гораздо труднее разобраться.
    2) Статьи BrokenSword’a Статьи BrokenSword’a. На них стоит обратить внимание.
    3) Intel System Programming Guides
    Share post
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 16

      +1
      Оперативно вы. Когда увидел код и ник Phantom_84 сразу вспомнил вашу переписку на wasm'е, которую недавно видел. )
      Пост супер, единственное для удобства можно добавить ссылки на источники информации.
        0
        Отлично. Как раз в тему, нужна была инфа :)
        Может быть еще напишите про реализацию многозадачности в PM?
          +1
          Я сейчас этим вопросом тоже занимаюсь. Как будут наработки — обязательно.
          Вообще многозадачность описана в System Programming Guide Volume 1 в 7-ой главе. Но там рассказано как меняются флаги, стек при переключении, про выполнение эти самых task'ов и.т.д., всё остальное придумывается и пишется самостоятельно. Вообще я думал свою, для начала делать монозадачной, но раз интересует этот вопрос, то я покопаюсь. Будут удовлетворительный результат — обязательно напишу.
            0
            Кстати, для организации многозадачности не обязательно использовать механизм task-ов процессора. Если мне не изменяет память, в ядре Linux на ia32, в частности, этот механизм не используется. Помнится, они делают по одному сегменту TSS на каждый процессор (совсем без него — ну никак), не на каждый процесс. И все процессы, работающие на одном и том же процессоре, разделяют один и тот же TSS.

            Насколько я понимаю, это делается из соображений быстродействия. Для переключения процессов в Linux не обязательно делать такую полную смену контекста, как это делается при переключения task-ов в процессоре.
            Можете попробовать посмотреть, например, здесь. Возможно, найдете для себя какие-нибудь идеи.

            Конечно, совсем не обязательно следовать тому, как это делают другие. Но познакомится, зачастую, бывает интересно.
            –1
            PM — Protected Mode процессора
            В этом режиме работаю большинство операционных систем.

            В защищённом режиме процессор может адресовать до 16 Мбайт виртуальной памяти за счёт изменения механизма адресации. Переключение из реального режима в защищённый происходит программно и относительно просто, однако для обратного перехода необходим аппаратный сброс процессора. Для отслеживания текущего режима работы процессора используется регистр слово состояния машины (MSW). Программы реального режима без модификаций в защищённом режиме исполняться не могут, также как и программы BIOS машины.
            Суть защищённого режима в следующем: программист и разрабатываемые им программы используют логическое адресное пространство, размер которого может составлять 16 мегабайт. Логический адрес преобразуется в физический адрес автоматически с помощью схемы управления памятью (MMU). Благодаря защищённому режиму, в памяти может храниться только та часть программы, которая необходима в данный момент, а остальная часть может храниться во внешней памяти (например, на жёстком диске). В случае обращения к той части программы, которой нет в памяти в данный момент, операционная система может приостановить программу, загрузить требуемую секцию кода из внешней памяти и возобновить выполнение программы. Следовательно, становятся допустимыми программы, размер которых больше объема имеющейся памяти, и пользователю кажется, что он работает с большей памятью, чем на самом деле.
            Физический адрес формируется следующим образом. В сегментных регистрах хранится селектор, содержащий индекс дескриптора в таблице дескрипторов (13 бит), 1 бит, определяющий к какой таблице дескрипторов будет производиться обращение (к локальной или к глобальной) и 2 бита запрашиваемого уровня привилегий. Далее происходит обращение к соответствующей таблице дескрипторов и соответствующему дескриптору, который содержит начальный 24-битный адрес сегмента, размер сегмента и права доступа, после чего вычисляется необходимый физический адрес путём сложения адреса сегмента со смещением из 16-разрядного регистра.

            © Википедия

            З.Ы.: Просто там лучше объяснено
              0
              Могу описать более подробно, если интересно =)
                +1
                Не слишком ли устарела эта информация? Если я ничего не забыл, то 16M можно было адресовать на 80286.
                  +1
                  Очень устарела. Её, видать, с литературы братьев Фроловых писали. Я у них видел подобное.
              0
              Хорошо написано, интересная статья.
              Но, как мне кажется, изучение таких вещей должно быть последовательным по книгам, а не по статьям.
                +1
                Да, потому то я написал: господа, читаем маны. Хотя у Юрова есть в учебнике про PM, но мне сие не понравилось. Я не видел пока ни одной книги, где бы эта тема хорошо раскрывалась.
                0
                по моему — лучше всего это описал broken sword все на том же васме:)
                откуда, кстати, и табличка type field.
                  +2
                  wasm.ru/series.php?sid=20 вот ссылка на цикл, если что.
                    0
                    Несомненно. Но меня попросили, так что не обессудьте.
                    0
                    Когда я этим интересовался (лет 10 назад), то использовал книгу тов. Финогенова «Программируем на языке Ассемблера IBM PC».
                      +2
                      До сих пор считаю лучшим ресурсом по защищенному режиму вот этот древний сайт на народе (понятнее никто не писал):
                      sasm.narod.ru/docs/pm/pm_main.htm

                      Ну и документация от Intel и AMD всегда на видном месте — первоисточники.
                        0
                        Да, сайтец просто класс. Я натыкался на него когда-то, но потерял и не смог найти заново. Спасибо, почитаем.

                      Only users with full accounts can post comments. Log in, please.