Всем доброго вечера! Про LDT и LDTR я, вроде бы, разобрался. Продолжаю дальше составлять свой справочник, вот добрался до регистра CR0. Вот формат регистра CR0: Я составил такое вот описание для полей MP, EM, TS, ET и NE: Но вот полный их смысл мне не понятен. Не могли бы уточнить и внести какие-то поправки в то, что я составил из нескольких литератур. Большое спасибо за внимание!
Читайте дальше, а не останавливайтесь после каждой непонятки. Как показывает практика, если не все вопросы, то 90% отпадут сами собой.
s3dworld Берите Intel Manuals уже и читайте И еще один совет: читайте внимательнее всю литературу, посвященную процам. Определений много, а еще когда в головах пейсателей (назовем так "переводчиков" интеловских манов) каша, то идут ошибки сплошняком. Вот и вы, предлагаете вычитывать ваш справочник, в то же время делая по 10 ошибок в простом посте: На счет локальной, не? Нет. Локальная таблица есть простой сегмент данных. А раз она сегмент -- то дескриптор, его описывающий, хранится в глобальной таблице. Тут все правильно, но... я бы не сказал что уж прям доступны. Загрузил туды селектор дескриптора локальной таблицы (который потом загрузится из глобальной таблицы) и забыл в общем-то... LDTR будет указывать на 1) видимая его часть - биты 0-15 - хранит в себе селектор (читай номер) дескриптора LDT, который хранится в GDT; 2) дескрипторный кеш - биты 16-79 - содержат дескриптор, описывающий LDT. Точнее поначалу не содержат, но как только подгрузите селектор в видимую часть -- невидимая обновится аппаратно так сказать. А вывод? -- да ни за что! Говоря проще, LDT просто адресуется через GDT. Но ни разу ни ее часть. Учите матчасть. (стих!) Обыкновенный сегментный дескриптор, описывающий LDT. Потому что художник бредил, видимо, когда интеловские маны переписывал/перерисовывал. Или наоброт, поумничать решил хехе. Сегментные дескрипторы по структуре своей одинаковы, будь то дескриптор из гдт или лдт. Будете забивать на eng и "учиться" по такой ерудне -- далеко пойдете, уверяю. См. выше. Все то же самое. Размеры/лимиты/гранулярности/полярности и т.п. Вывод -- читайте правильную литературу и правильные -- интел/амд -- справочники, а не составляйте свои, тем более такие, ошибка ошибку ошибкой погоняет, сплошь заблуждения, опечатки и т.п. Вообще да, тут и говорить особо не о чем при таких раскладах. baldr Мб вот так более верно ? Видимая часть LDTR содержит селектор дескриптора сегмента данных (дескриптора LDT то есть), который содержится в GDT. (?)
Читать официальные справочники от AMD и Intel это разумеется нужно. Но свой справочник я делаю по двум причинам: 1. При написании текста справочника начинаешь вникать в суть дела (перепись с пониманием материала полезна). 2. Я составляю справочник так, чтобы описываемый элемент помещался целиком на лист формата A4. Потом я это всё распечатаю и у меня будет перед глазами. Будет легче ориентироваться.
Не удастся сделать, чтоб всё влезало на одну страницу и даже лист (с двух сторон). Некоторые вещи слишком объёмные, чтобы их впихнуть на такую площадь. А насчёт п. 1 -- Вы, конечно, правы (именно поэтому изготовление _своих_ шпаргалок полезно -- в отличие от покупки уже готовых). Только вот составлять надо, используя мануалы, а не всякие книжки уважаемых авторов. Заодно и английский будет развиваться.
SII Согласен с Вами полностью. Но я записываю то, что можно поместить: регистры и их описание, структуры и прочее.
s3dworld Да, но тогда надо все брать из нормальных источников и перепроверять еще в 10 местах и на разных языках. Вот тогда толк будет от всего. А если брать один кусок у кривого нетрезвого переводчика, кусок отсюда с васма хз от кого, кусок "сам додумаю" -- тогда есть риск "вникнуть" совсем в другое. А переучиваться труднее будет. А насчет того что информацию вы пропускаете -- имхо зря. На первых порах все должно быть как можно более избыточным, с водицей если хотите, чтобы все разжевывалось по три раза разными фразами и словами. От сжатости опять полезут непонятки.
Miyamoto, Практика — критерий истины. Лучший метод проверить догадку — накорябать тест и запустить его на реальном железе. Хотя адекватно оценить результаты теста — отдельная тема.
Всем доброго дня! Ну почему я пропускаю материал. Я вот читал книгу В.Л. Григорьева "Микропроцессор i486. Архитектура и программирование". Шёл от главы к главе. И то что там было пройдено, на то и составляю справочник, который распечатаю и который будет у меня всегда перед глазами (я люблю документы в текстовом виде, а не в электронном). Прочитал про сегменты и дескрипторы сегментов - составил про них в справочнике. Прочитал про страничную адресацию - составил про неё в справочнике. Теперь составляю про управляющие регистры (про CR0 и CR3 уже составил). CR2 можно вообще не составлять (там и так будет содержаться линейный адрес, при обращении к которому было вызвано исключение отсутствия странички). Далее составлю в справочнике описание про шлюзы вызова: исключений, ловушек и задач. А после про переключение задач (TSS). И вот так вот от шага к шагу - всё о чём узнаю, то опишу. На счёт страничной организации и виртуальной памяти. Я тут размышлял про это всё. Предположим у меня 32 МБ оперативной памяти (ОЗУ). Будем считать что у меня странички будут по 4 МБ. Даже предположим что у меня получится 8 страничных кадров: Вот как я это всё понимаю (адреса взял на бум): Предположим что для самой операционной системы понадобится 8 МБ, то есть 2 странички. А так как код ядра операционной системы не должен изменять адреса (находится в одном и том же месте), то для него я сразу определю страничные кадры: 0x01800000-0x01BFFFFF и 0x01C00000-0x01FFFFFF. А странички с индексами 1022 и 1023 всегда будут ссылаться на страничные кадры, которые содержат код операционной системы. Получается так: И вот пользователь решил запустить программу. Причём программа эта маленькая и не требуется для неё выделять много страниц (для всей программы хватит 3 страниц). Сначала ядро операционной системы выделяет 3 странички: код, данные стек. Странички эти будут содержать следующие индексы: 0, 1 и 2. В соответствующие страничные кадры записывается информация (коды, данные). Получается такая вот настройка: Все остальные странички (все кроме: 0, 1, 2, 1022 и 1023) содержат значение 0 в бите P (Present). Всё настроено и наконец-то передаётся управление по адресу 0x00000000 (то есть на страничку с индексом 0 и со смещением 0). Конечно в совершенстве передавать управление нужно будет с определённым смещением (мало ли где в коде находится стартовая точка), но это уже другая история. Программа работает в своё удовольствие пока вдруг не случится прерывание таймера. Такое прерывание нам говорит о том, что пришло время выполняться другой программе. Пускай следующая программа у нас такая же маленькая как и первая. И табличка уже будет выглядеть так: Все остальные странички (все кроме: 0, 1, 2, 1022 и 1023) содержат значение 0 в бите P (Present). В общем всё тоже самое и снова управление передаётся по адресу 0x00000000. Блин, снова сработал таймер и на очереди очередная маленькая программа. Опа, а свободных страничных кадров у нас уже нет! Тогда придётся что-то выгрузить и скорее всего то, что в списке на управление процессорным временем будет в самом конце. И если вычислить, то это будет только что выполняемая программа (предположим что у нас нет приоритетов). Тогда делаем ядро операционной системы делает следующее: При этом ведь нужно будет вести какую-нибудь табличку по тому, где лежат странички различных программ (возможно для каждой программы устанавливать идентификатор). В общем по этому поводу я ещё не думал. Главное что теоретически у нас уже память освобождена и то что мы сделали, это мы зарегистрировали в системе. Теперь загружаем новую программу: Все остальные странички (все кроме: 0, 1, 2, 1022 и 1023) содержат значение 0 в бите P (Present). И снова передаём управление в 0x00000000. Предположим что снова сработал таймер и на очереди такая программа, что занимает 8 страничек (3 кода, 2 данных, 2 стека). Всё это можно реализовать множеством способом. Например у меня в голове такой... Код, данные и стек - это 3 странички. То есть если у нас нет 3 свободных страничных кадров (ну ведь глупо же для кода, данных и стека через одни страничный кадр всё делать - скорость упадёт), то мы их освобождаем. В данном случае мы их будем освобождать: И теперь на основании всех страничек, которые нужны программе, мы рассчитываем как построить таблицу страниц. Уж алгоритм я тут писать не стал, но конечный вариант такой: Все остальные странички (все кроме: 0, 1, 2, 1022 и 1023) содержат значение 0 в бите P (Present). И снова передаём управление на 0x00000000. И вот вдруг программе нужны данные из второй странички данных (разумеется она ничего про странички не знает, уж так вышло что обратилась ко второй страничке). У нас вызывается исключение отсутствия странички. И тут мы можем сделать разными путями (заменить страничный кадр данных на вторую страницу данных, взять страничный кадр предыдущей программы и туда вставить вторую страничку с данными, стек лучше не менять - я так думаю). Какой вариант лучше, я не думал. Хочу от Вас послушать какой вариант лучше выбирать. Я же пока сделаю так: И следом вот так: В общем сигать, прыгать и изгаляться как хотите можно - но лишь бы дать программе нормально работать. Больше свободных страничных кадров - больше сразу можно загрузить страничек. Собственно хранить информацию о выгруженных на жёсткий диск страничках - я пока понятия не имею. Наверное такую информацию можно где-то хранить (уж хотя бы индекс записать из таблицы) в структуре TSS. Возможно в стеке программы. В общем я не знаю. Хотелось бы услышать Ваше мнение. Ну и собственно сам вариант менеджера памяти у меня дурацкий или вполне нормальный? И ещё хотел спросить: мне сказали что в длинном режиме (L-Mode) работы процессора используется не 64-битная адресация, а 48-битная адресация (младшие 48 битов из 64-битного адреса). В связи с этим у меня несколько вопросов: 1. Старшие 16 бит обязаны быть нулями, единичками или они игнорируются (могут содержать любое значение)? 2. Можно ли переключением какого-нибудь бита в каком-нибудь регистре (я просто не знаю можно ли и где) вместо 48-битной адресации задействовать 64-битную? Большое спасибо за внимание!
Либо сказал какой-то дурак, либо Вы недопоняли. Адресация в любом случае 64-разрядная, просто старшие биты физически не реализованы (сэкономили транзисторы -- всё равно такой объём памяти в обозримом будущем не светит). Насчёт их значений в виртуальных адресах читайте мануалы про каноническую форму адреса.
s3dworld один момент вы упустили. стек растет вниз и если под стек используется больше одной страницы, то первой подготавливать надо последнюю. т.е. ту, что с индексом 6. Вообще и если то абсолютно без разницы что заменять. Главное чтобы это в данный момент не нужно было текущей задаче (программе) обязаны быть прочитайте про cpuid а особенно про функцию 0х80000008
А как лучше хранить информацию о том, что я залил страничку на жёсткий диск? К примеру структура элемента таблицы страниц такая: И если у меня будет бит P (Present) установлен в 0, то мне доступны будут следующие биты: Только вот не пойму для чего я их мог бы использовать. Я вот не пойму следующего, мне может нужно ещё какую-нибудь системную таблицу придумать, которая хранила бы список запущенных программ и описывала бы размещение их страничек?
а почему бы не записать в биты помеченные как u при p=0 смещение в файле подкачке (в секторах), а бит помеченный как r/w при p=1 для такой фишки использовать как присутствие в файле подкачки. т.е. получается страница присутствует в памяти страница присутствует в файле подкачки и находится по смещению Sector*512 для 4 Кб страниц понадобится 8 секторов поэтому для каждой следующей страницы младшие 3 бита смещения в файле будут равны 0 и их можно использовать как флаги (гарантированно), для 2/4 Мб страниц таких битов будет больше, но на это лучше не опираться. страница отсутствует как в памяти, так и на диске Code (Text): PPUR aaaaaaaaaaaaaaaaaaaaUUU00DACW//1 - действительный описатель страницы DTSW sssssssssssssssssssssssssssssu00 - описатель страницы на диске uuuuuuuuuuuuuuuuuuuuuuuuuuuuuu10 - страница отсунствует [add] алгоритм обработки отказа страницы тогда будет примерно такой Code (Text): page_fault_handler: push ebx push esi push edi push eax mov ebx, cr3 mov esi, cr2 shr esi, 12 mov edi, esi and esi, 0x000003FF shr edi, 10 and edi, 0x000003FF mov eax, [ebx+edi*4] test eax, 1 jnz catalog_present test eax, 2 jnz invalid_catalog call load_page catalog_present: mov ebx, eax mov eax, [ebx+esi*4] test eax, 1 jnz page_present test eax, 2 jnz invalid_page call load_page page_present: pop eax pop esi pop edi pop ebx iretd invalid_page: call raise_exception, EXCEPTION_INVALID_PAGE pop eax pop esi pop edi pop ebx iretd invalid_catalog: call raise_exception, EXCEPTION_INVALID_PAGE pop eax pop esi pop edi pop ebx iretd load_page: and eax, 0xFFFFFFF8 call read_page_file retd это самый простой и самый примерный вариант действия в таком случае
max7C4 Спасибо! Правда до меня не совсем всё дошло, видимо нужно несколько раз перечитать или просто ещё не готов к такому. А что касается защищённого режима (P-Mode) работы процессора и страничек размером в 4 КБ, то получается следующее: 1024 записи в каждой таблице второго уровня. Каждая запись занимает 4 байта. Всего таких таблиц второго уровня будет 1024. Ещё 1024 элемента в таблице первого уровня. Каждый элемент занимает по 4 байта. И всего получается 4198400 байт. Это что же, среди кода ядра операционной системы мне придётся выделить размер в 4100 КБ (это более 4 МБ) лишь под описание табличек? И для каждой задачи (программы) его перестраивать? Ну понятное дело что в большинстве случаях задачи будут не все элементы таблицы использовать, но всё же - придётся после каждой задачи подчищать все ненужные элементы в текущей задаче в 0 для бита P (Present). Это ведь сколько процессорного времени потребуется. Как же тогда делают правильно?
s3dworld зачем. если программе надо 64 Кб под стек, 1 Мб под данные и 64 Кб кода, то для это радости достаточно 1 каталог размером 4 Кб, 1 таблица размером 4 Кб и 1.125 Мб под данные и код всего 1160 Кб выглядеть это будет примерно так Code (Text): catalog dd page_table0,1023 dup (2) table dd page_code0,...,page_code15,page_data0,...,page_data255,page_stack0,...,page_stack15,736 dup (2) page_code0: ... page_code15: ... page_data0: ... page_data255: ... page_stack0: ... page_stack15: ... где catalog,table,page_codeXX,page_dataXXX,page_stackXX - символические обозначения адресов соответствующих станиц.
Вообще-то достаточно внимательно почитать описание работы страничного механизма (как он обрабатывает записи таблиц первого уровня, как -- второго и т.д.) и форматы элементов этих таблиц, чтобы понять простую вещь: вовсе не требуется для каждой задачи создавать весь набор таблиц нижних уровней. А чтобы не тратить кучу времени при переключении задач, все необходимые таблицы для каждой задачи создаются при её запуске, а в дальнейшем в них лишь вносятся необходимые коррективы. Книги по принципам работы ОС читайте.