RDM/2 The Russian Electronic Developer Magazine  
RDM/2 Русский электронный журнал разработчика  
ДомойОт редактораПишите намОбратная связьRU/2

Внутри ядра OS/2.

Copyright (c) 1998, David C. Zimmerli. All rights reserved.
Перевел с английского Денис Батранков, bdv.da.ru
(5 ноября 1998г)

I. Введение

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

Главным инструментом использованным для исследования в этой статье был Kernel Debugger (KDB), любезно предоставленный компанией IBM. А также 4 том книги "OS/2 Debugging Handbook". В статье "Приключения при отладке ядра" (EDM/2, ноябрь 1996г., http://www.edm2.com/0410/kdb.html) даны указания по установке и использованию KDB. Чтобы получить набольшую пользу от сегодняшней статьи, вы должны хорошо разбираться в архитектуре Intel 80x86 и особенностях управления памятью.

Для подробного рассмотрения ядра конечно необходимо основываться на определенной версии OS/2, поскольку код ядра меняется и обновляется вместе с другими системными компонентами в различных версиях и обновлениях. Я выбрал OS/2 2.1 для Windows, версия 6.514, как общий знаменатель систем, которые скорее всего еще используются. Так как большинство пользователей будут использовать более новые версии, то размещение памяти, размер файлов и многое другое будут отличаться от рассмотренных здесь. Но основные структуры данных и компоненты кода не изменятся.

II. Компоненты ядра

Основной объем кода ядра находится в файле OS2KRNL, который занимает в поставке системы около 730K или около 1Мб в отладочной версии. Вспомогательное ядро состоит из нескольких маленьких файлов: OS2LDR (28К родная, 37К отладочная версия), и основные драйвера устройств SCREEN01.SYS, PRINT01.SYS, KBD01.SYS и CLOCK01.SYS (3-30К каждый). Поскольку исходный код для основных драйверов устройств предоставлен в комплекте IBM DDK Developers Connections, мы не будем здесь в них копаться.

Системные DLL DOSCALL1.DLL, KBDCALLS.DLL, QUECALLS.DLL, SESMGR.DLL, OS2CHAR.DLL и так далее не являются частью Presentation Manager, но также, строго говоря, не входят в состав ядра. Большая часть их кода просто перенаправленные точки входа API функций ядра, начинающихся с приставки DOS-. Код SESMGR заслуживает особенного рассмотрения, но я отложу его на будущее с целью сохранить приемлемой длину текущей статьи.

Файл OS2KRNL стандартный сегментированный выполняемый файл формата "LX" и, таким образом, может быть исследован любой утилитой просмотра EXE файлов. Самый легкий и более информативный способ использовать команду "lg" отладчика KDB, которая читает символьный файл поставляемый IBM. Она выводит следующие сегменты:

  ##lg os2krnl
  os2krnl:
  0400:00000000 DOSGROUP
  1100:00000000 DOSCODE
  0120:00000000 DBGCODE
  0128:00000000 DBGDATA
  0030:00000000 TASKAREA
  0138:00000000 DOSGDTDATA
  0140:00000000 DOSINITDATA
  0148:00000000 DOSINITR3CODE
  %00110000 DOSMVDMINSTDATA
  %00120000 DOSSWAPINSTDATA
  %ffeff000 DGROUP
  0150:00000000 DOSHIGH2CODE
  0158:00000000 DOSHIGH3CODE
  0160:00000000 DOSHIGH4CODE
  %fff3f000 DOSHIGH32CODE
  ##

Среди этих сегментов есть 16 разрядные кодовые: DOSINITR3CODE, DOSCODE, DOSHIGH2CODE, DOSHIGH3CODE, DOSHIGH4CODE и DBGCODE. 16 разрядные сегменты данных: DOSGROUP, DOSINITDATA и DBGDATA. (DBGCODE и DBGDATA нужны только для поддержки отладчика KDB и отсутствуют в поставляемом ядре.) 32 разрядный кодовый сегмент DOSHIGH32CODE. 32 разрядный сегмент данных DGROUP.

Сегменты "DOS" относятся, конечно, не к DOS, а к Control Program (программа управления) - преемнику DOS в защищенном режиме. Сегменты "HIGH" будут загружены вверх, то есть, в физическую память выше 1 Мб.

Четыре сегмента требуют специальных комментариев. TASKAREA сегмент данных который представляет собой текущий PTDA (Per-Task Data Area), как описано в Debugging Handbook. DOSGDTDATA содержит системную GDT (Global Descriptor Table), которая содержит шлюзы вызовов к API функциям ядра и точки входа к сегментам содержащим 16 разрядный код ядра. Символьный файл содержит имена многих этих сегментов, дающих путь к их функциям. (Наберите команду "ls dosgdtdata" в отладчике KDB.) Сегменты DOSMVDMINSTDATA и DOSSWAPINSTDATA, как подразумевают их имена, осуществляют поддержку Multiple VDMs (Virtual DOS Machines), обсуждаемых в главе VII.

III. Последовательность загрузки OS/2

В этом разделе мы рассмотрим механизм загрузки ядра. Эти знания необходимы при диагностике отказов оборудования и неверных файлов, которые могут помешать правильной загрузке OS/2. Некоторые детали этой фазы работы системы Вы можете также посмотреть в разделе "Remote IPL/Bootable IFS" файла описания "Installable File Systems" (IFS.INF) на Hobbes.

Каждый школьник знает, что при включении или перезапуске любого IBM совместимого компьютера соответствующий загрузочный сектор операционной системы загружается с диска в память по адресу 07C0:0. Выполнение начинается с этого адреса в реальном режиме процессора. (Этот адрес выбран так, чтобы могли работать ранние версии IBM PC с минимальным объемом оперативной памяти в 32Кб)

Загрузочный сектор операционной системы начинается с 3-х байтовой команды JMP. За ней следует структура, содержащая параметры диска, общая для DOS и OS/2. Ее формат описан во многих книгах, поэтому не будем на ней останавливаться. (См. например Ray Duncan, Advanced MS-DOS Programming, 2d. ed. 1988, Microsoft Press, p. 180 или русское издание Р.Данкан, "Профессиональная работа в MS-DOS", 1993, Москва, "Мир", стр. 143.)

Итак, загрузочный сектор загружается и запускается, он загружает и запускает маленький файл (1Кб) OS2BOOT в 0800:0, который загружает и запускает OS2LDR в 2000:0. Конечно загрузка производится используя функции BIOS, поскольку пока еще нет никакой файловой системы.

Содержимое файла OS2LDR одна из наиболее непонятных и слабо документированных частей ядра. Для него нет символьного файла, его нельзя трассировать в отладчике KDB, поскольку KDB находится в отладочной версии OS2KRNL, который еще не загружен. Однако, поскольку OS2LDR выводит множество данных на терминале отладчика, то анализируя выводимую информацию и дизассемлированный код OS2LDR, можно получить хорошее представление о работе этого модуля.

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

  IODel 000a
Это задержка ввода/вывода - время ожидания между командами IN и OUT - она установлена в 10, основываясь на скорости процессора.
  Int12 st 00000000 end 0009f7ff
  Int1588 st 00100000 end 03ffffff
Здесь BIOS собщает о 640 Кб обычной памяти (приблизительно 9f7ff hex) и 64Мб (03ffffff hex) дополнительной.
  CPUUsable = 00000001
  CPUWeAre = 00000001
У нас 486 процессор. (0 это 386, 1 это 486 и т.д.)
  SLFrm len a342
Длина сегмента OS2LDR, вместе со стеком составляет 0a342 hex.
  cgvi
Вызываем процедуру получения видеорежимов.
  cldr
Наконец мы переходим к действиям, благодаря которым назван загрузчик: загружаем OS2KRNL или, если этот файл не найден, то OS2KRNLI, устанавливаем ядро.

Загрузчик сначала выдает информацию о сегментах OS2KRNL:

  ob     flags    oi-flags   paddr/sel    glp     laddr/fladdr     msz/vsz       Object name
  01  rw--sfTLaA  00005063  004000/0400  0001  ffe00000/ffe00000  009000/00c5b3  DOSGROUP
  02  r-x-sfTLa-  00001065  011000/1100  000a  ffe0d000/ffe0d000  00c000/00bfb0  DOSCODE
  03  r-x-sf-LaA  00005025  01d000/0120  0016  ffe19000/ffe19000  00b000/00aeea  DBGCODE
  04  rw--sf-LaA  00005023  028000/0128  0021  ffe24000/ffe24000  009000/0085c0  DBGDATA
  05  rw--sN-LaA  0000d0a3  031000/0130  002a  ffe2d000/ffe2d000  010000/010000  стек
  06  rw--sN-LaA  0000d023  041000/0138  003a  ffe3d000/ffe3d000  002000/001e50  DOSGDTDATA
  07  rw--sf-LaA  00005023  043000/0140  003c  ffe3f000/ffe3f000  002000/004b4e  DOSINITDATA
  08  r-x-sf-LaA  00005025  048000/0148  003e  ffe44000/ffe44000  002000/001fe8  DOSINITR3CODE
  09  rw-BPf-h--  00002213  100000/0000  0040  ffefc000/00110000  001000/0001ac  DOSMVDMINSTDATA
  0a  rw-BPf-h--  00002013  101000/0000  0041  ffefd000/00120000  002000/001948  DOSSWAPINSTDATA
  0b  rw-Bsf-h-A  00006033  103000/0000  0043  ffeff000/ffeff000  012000/015326  DGROUP
  0c  r-x-sf-ha-  00001035  119000/0150  0055  fff15000/fff15000  010000/00fdcc  DOSHIGH2CODE
  0d  r-x-sf-ha-  00001035  129000/0158  0065  fff25000/fff25000  00a000/009a08  DOSHIGH3CODE
  0e  r-x-sf-ha-  00001035  133000/0160  006f  fff2f000/fff2f000  010000/00f304  DOSHIGH4CODE
  0f  r-xBsf-h--  00002035  143000/0000  007f  fff3f000/fff3f000  081000/080628  DOSHIGH32CODE
В крайнем правом столбце мои комментарии, связывающие список выведенных объектов с сегментами рассмотренными в предыдущем разделе.

Кстати термин "объект" здесь означает 32 разрядный выполняемый модуль эквивалентно термину "сегмент" в 16 разрядном модуле. Объект сложнее чем сегмент, поскольку он содержит страницы, которые могут быть загружены и выгружены в файл подкачки, независимо друг от друга. Вот почему чувствовалась необходимость нового трмина. Но во многих случаях объект это просто сегмент не ограниченный длиной в 64Кб. Я буду использовать эти термины по-необходимости.

Для оценки таблица содержит атрибуты, физические и линейные адреса, селекторы, размеры каждого сегмента OS2KRNL. В действительности загрузка OS2KRNL проходит в два этапа: сначала загружаются в обычную память "верхние" сегменты, а затем перемещаются на положенное место используя функцию BIOS Int 15h/87h "Перенос блока дополнительной памяти". (Помните, что мы пока находимся в реальном режиме!) При втором проходе, загружаются сегменты находящиеся в обычной памяти.

В заключение OS2LDR выдает нам карту памяти:

  pa=00000000 sz=00001000 va=00000000 sel=0000 fl=2000 of=00000003 ow=0000  Real mode IVT
  pa=00001000 sz=00002300 va=ffef9000 sel=0100 fl=2014 of=00001004 ow=ff6d  OS2LDR 32-bit int dispatch
  pa=00004000 sz=0000c5b3 va=ffe00000 sel=0400 fl=2144 of=00005063 ow=ffaa  DOSGROUP
  pa=00011000 sz=0000bfb0 va=ffe0d000 sel=1100 fl=2244 of=00001065 ow=ffaa  DOSCODE
  pa=0001d000 sz=0000aeea va=ffe19000 sel=0120 fl=2344 of=00005025 ow=ffaa  DBGCODE
  pa=00028000 sz=000085c0 va=ffe24000 sel=0128 fl=2444 of=00005023 ow=ffaa  DBGDATA
  pa=00031000 sz=00010000 va=ffe2d000 sel=0130 fl=2544 of=0000d0a3 ow=ffaa  стек
  pa=00041000 sz=00001e50 va=ffe3d000 sel=0138 fl=2644 of=0000d023 ow=ffaa  DOSGDTDATA
  pa=00043000 sz=00004b4e va=ffe3f000 sel=0140 fl=2744 of=00005023 ow=ffaa  DOSINITDATA
  pa=00048000 sz=00001fe8 va=ffe44000 sel=0148 fl=2844 of=00005025 ow=ffaa  DOSINITR3CODE
  pa=0004a000 sz=00000ac8 va=00000000 sel=4a00 fl=2001 of=00000000 ow=0000  OS2DUMP
  pa=0004b000 sz=00049000 va=00000000 sel=0000 fl=2002 of=00000000 ow=0000  не используется
  pa=00094000 sz=0000a762 va=ffeee000 sel=0000 fl=2054 of=00001003 ow=ffab  OS2LDR (перемещен)
  pa=0009f000 sz=00000800 va=00000000 sel=0000 fl=2002 of=00000000 ow=0000  не используется
  pa=0009f800 sz=00000800 va=ffeed800 sel=0000 fl=2004 of=00000000 ow=ff37  romdata
  pa=000a0000 sz=00060000 va=00000000 sel=0000 fl=0001 of=00000000 ow=0000  video/BIOS area
  pa=00100000 sz=000001ac va=ffefc000 sel=0000 fl=0944 of=00002213 ow=ffaa  DOSMVDMINSTDATA
  pa=00101000 sz=00001948 va=ffefd000 sel=0000 fl=0a44 of=00002013 ow=ffaa  DOSSWAPINSTDATA
  pa=00103000 sz=00015326 va=ffeff000 sel=0000 fl=0b44 of=00006033 ow=ffaa  DGROUP
  pa=00119000 sz=0000fdcc va=fff15000 sel=0150 fl=0c44 of=00001035 ow=ffaa  DOSHIGH2CODE
  pa=00129000 sz=00009a08 va=fff25000 sel=0158 fl=0d44 of=00001035 ow=ffaa  DOSHIGH3CODE
  pa=00133000 sz=0000f304 va=fff2f000 sel=0160 fl=0e44 of=00001035 ow=ffaa  DOSHIGH4CODE
  pa=00143000 sz=00080628 va=fff3f000 sel=0000 fl=0f44 of=00002035 ow=ffaa  DOSHIGH32CODE
  pa=001c4000 sz=00e3c000 va=00000000 sel=0000 fl=0002 of=00000000 ow=0000  не используется
  pa=01000000 sz=00000000 va=00000000 sel=0000 fl=0001 of=00000000 ow=0000  не используется
  pa=01000000 sz=03000000 va=00000000 sel=0000 fl=0002 of=00000000 ow=0000  не используется
  pa=04000000 sz=00000000 va=00000000 sel=0000 fl=4000 of=00000000 ow=0000  предел физической памяти
Здесь я снова добавил комментарии в самой правой колонке. Во второй колонке с конца находится "System Object Id" описание которого находится в разделе 4.6 Debugging Handbook, том IV.

Мы настраиваем микросхему 8259 PIC так, чтобы IRQ с 0 по 7 соответствовали прерывания 50h-57h, а IRQ с 8 по 0Fh соответствовали прерывания 70h-77h:

  rPIC
Мы переходим к процедуре syiInitializeOS2 в сегменте DOSCODE файла OS2KRNL:
  j syi
Теперь мы в настоящем ядре и можем просмотреть остальную часть кода инициализации ядра, используя отладчик KDB. Специальные возможности KDB позволяют при нажатии и удержании клавиш "R", "P" или пробел на терминале отладки прервать запущенную программу либо перед переключением в защищенный режим, либо после переключения, но до инициализации загрузчика и менеджера страниц, либо после того и другого, соответственно. (Проверьте установку коэффициента повторения на терминале отладчика в максимальное значение, иначе нажатие клавиши может быть пропущено при передаче по COM порту.)

В основном часть инициализации DOSCODE содержит логику разбора файла CONFIG.SYS. Также в нем содержится компонент называемый "начальная файловая система системы" (sistem init file system - коротко sifs), использемый для чтения CONFIG.SYS, поскольку перед установкой полноценной файловой системы нужны различные BASEDEV и другие файлы. Каждая важная часть ядра (планировщик задач, менеджер страниц, загрузчик и др.) имеет процедуру инициализации, которая вызывается из этой точки, и мы "вернемся" к процедуре syiProcess в сегменте DOSINITR3CODE.

syiProcess теперь загружает и инициализирует обычные (не основные) устанавливаемые драйверы устройств, загружает системные DLL и запускает shell. Это первое место где мы имеем доступ к ADD драйверам, используемым полностью инициализированной системой для доступа к жесткому диску. Поскольку одна из первых процедур вызываемой syiProcess находится в модуле inicp.asm (инициализация кодовой страницы), то здесь мы можем получить печально известное сообщение об ошибке "Не найден COUNTRY.SYS", даже когда нет проблем с COUNTRY.SYS, если основные драйвера устройств не установлены правильно.

IV. Программный доступ к файлам

Я хочу дать краткий обзор последовательности вызовов связанных с вызовом DOS- функций файлового ввода/вывода и непосредственного доступа к аппаратуре. Более детально вы можете ознакомиться в описании "Storage Device Driver Reference" из DDK и файле IFS.INF на Hobbes.

В первом случае мы рассмотрим вызов DOS32READ с файлом, находящимся в FAT разделе SCSI диска. Это точка входа в DOSCALL1.DLL, которая вскоре через шлюз вызовов передается в 32 разрядную часть ядра и вызывает FS32IREAD. Для доступа к FAT, FS32IREAD затем вызывает 16 разрядную процедуру h_DOS_Read в DOSHIGH4CODE, которая, убедившись в том что требуемые данные не находятся в ранее считанном буфере, формирует "список запросов" и передает его в драйвер устройства OS2DASD.DMD. Список запросов создается в процедурах _BufReadExecute и _ReqListExecute из DOSHIGH32CODE и содержит один запрос с расширенным кодом команды 1Eh для чтения.

Запрос указывает начальный блок, число блоков для чтения и адрес буфера для считанных данных. OS2DASD.DMD затем вызывает соответствующий ADD драйвер устройства - для примера, AHA152X.ADD - для доступа к физическому носителю.

Во втором случае все также, только файл для чтения находится в разделе HPFS. В этом случае FS32IREAD обходит 16 разрядную процедуру для FAT в DOSHIGH4CODE, и вызывает вместо нее FS_READ из HPFS.IFS. Файловая система HPFS затем проверяет данные в буфере и взаимодействует с модулем OS2DASD.DMD.

В третьем случае расмотрим чтение фала с гибкого диска. В этом случае ядро работает также как в первом случае, но OS2DASD.DMD передаст запрос в IBM1FLPY.ADD (или IBM2FLPY.ADD на Micro Channel машинах), а не к AHA152X.ADD.

Наконец для чтения файла с SCSI CD-Rom, FS32IREAD вызывает FS_READ из драйвера файловой системы CD-Rom CDFS.IFS. CDFS также проверяет буфер и посылает список запросов к OS2CDROM.DMD, который вызовет соответствующий BASEDEV - например LMS206.ADD для Philips CD-Rom.

V. Механизм переключения контекста

Переключение контекста происходит обычно в конце выполнения API функций ядра. Перед возвратом из режима ядра в режим пользователя система вызовет специальную процедуру названную KMExitKmodeEvents. Эта процедура проверяет глобальную переменную Resched, которая означает достаточно ли приоритета у другой нити, чтобы запуститься. Если Resched ненулевая, следующей будет процедура _tkSchedNext.

_tkSchedNext обращается к планировщику (_SCHGetNextRunner), чтобы решить какая из готовых к выполнению нитей следующей получит управление. Некоторые аспекты планировщика, с его множеством состояний и переходов, очередями приоритетов и так далее изложены в Debugging Handbook. Сейчас мы просто заметим, что _SCHGetNextRunner возвращает в регистре EAX указатель на TCB новой или следующей нити. Этот указатель затем становится единственным аргументом процедуры _PGSwitchContext.

Код _PGSwitchContext занимает 559 байт в DOSHIGH32CODE и полность закрыт от изучения. Мы не можем пройти по шагам его код в отладчике KDB, поскольку таблица страниц и системные структуры находятся в переходном состоянии, которое отладчик не может сделать понятным. Но изучая дизассемблированный код мы можем понять его работу и получить важные знания о происходящем в OS/2 процессе.

Дальше мы действуем в зависимости от того переключаемся ли мы в другой процесс, или просто в другую нить того же самого процесса. Если это переключение процессов, мы должны перезаписать часть таблицы страниц соответствующих памяти пользователя (как правило около 256Мб), чтобы показать новые физические адреса. Переключение процессов также требует изменить значение LDTR, поскольку заполнение LDT может быть другим для другого процеса.

В любом случае при переключении процесса или нити мы должны изменить сегмент TASKAREA (селектор 30), поскольку этот селектор адресует текущий TCB и TSD, также как PTDA. Мы должны также обновить различные системные глобальные переменные: _pPTDACur, _TKSSBase, _TKTCBBias, _pTCBCur, _pTSDCur и указатели стека кольца 0 и кольца 2.

Другие детали механизма переключения контекстов изложены на странице 339 Debugging Handbook, том I.

Конечно существует возможность, что приложение не будет делать никаких системных вызовов долгие периоды времени. Возможно программа решает дифференциальное уравнение, делает сложные поиски текстовых строк, или иначе занимается своим собственным делом без обращения к сервисам ядра и без ввода/вывода. Будет ли процедура KMExitKmodeEvents тогда пропущена и будут ли все остальные нити терять время ожидая когда такая программа завершится?

Ответ, как Вы ожидаете, нет, спасибо программируему таймеру 8254, встроенному в материнскую плату. При загрузке счетчик 0 микросхемы 8254 настраивается на работу в режиме 2 (режим генерации), чтобы возникало IRQ 0 приблизительно 18.2 раз в секунду. Подобно другим IRQ, это прерывание перехватывается процедурой intIRQRouter в DOSHIGH32CODE, и на посылку IRQ 0 intIRQRouter вызывает KMExitKmodeEvents, как описано выше. Это заставляет приложение подвергнуться такому же рассмотрению планировщика, как если бы оно обратилось к ядру непосредственно.

VI. Управление памятью

Когда приложение вызывает DosAllocMem, система создает "объект памяти" резервируя непрерывный участок в собственном виртуальном адресном пространстве процесса. Система размещает записи в таблице страниц для объекта, и поскольку каждая запись в таблице страниц управляет 4Кб памяти, то объект будет реально иметь размер равный требуемому размеру дополненому до ближайшей величины кратной 4Кб. Объект начнется от границы в 64Кб, что позволяет обращаться к нему через селектор из LDT, то есть каждый вызов DosAllocMem расходует минимум 64Кб виртуального адресного пространства.

Однако после вызова DosAllocMem не выделяется физическая память или место в файле подкачки. Этот механизм называется "ленивая фиксация": когда кто-нибудь попытается считать или записать в эту область виртуальной памяти будет сгенерирована ошибка страницы (trap 0eh) и процедура обработки ошибки из DOSHIGH32CODE уже затем разместит физическую память и установит бит присутствия в соответствующей записи таблицы страниц.

Простейший эксперимент показывает состояние "до" и "после" таблицы страниц в результате выполнения DosAllocMem. Здесь программа перед выполнением вызова запроса о выделении 00020000h байт или 128Кб:

  eax=0006eb03 ebx=000a0000 ecx=0006eb88 edx=0006ebb0 esi=00000000 edi=00019010
  eip=000120f7 esp=0006eb7c ebp=0006ebd4 iopl=2 rf -- -- nv up ei pl nz na pe nc
  cs=005b ss=0053 ds=0053 es=0053 fs=150b gs=0000  cr2=00093ffe  cr3=001f6000
  005b:000120f7 e8385a011a     call    DOS32ALLOCMEM (1a027b34)
  ##d ss:esp l 20
  0053:0006eb7c 88 eb 06 00 00 00 02 00-13 00 00 00 03 00 00 00 .k..............
  0053:0006eb8c 74 34 01 00 d0 eb 06 00-08 00 00 00 00 00 00 00 t4..Pk..........
  ##
А здесь записи таблиц страниц для %120000 и %130000:
  ##dp %120000
   linaddr   frame   pteframe  state res Dc Au CD WT Us rW Pn state
  %00120000* 02ec1  frame=02ec1  2    0  D  A        U  W  P  resident
  ##dp %130000
   linaddr   frame   pteframe  state res Dc Au CD WT Us rW Pn state
  %00130000* 02ec1  frame=02ec1  2    0  D  A        U  W  P  resident
  ##
Наберем "p" и изучим стек и таблицу страниц снова:
  ##p
  eax=00000000 ebx=000a0000 ecx=0006eb88 edx=0006ebb0 esi=00000000 edi=00019010
  eip=000120fc esp=0006eb7c ebp=0006ebd4 iopl=2 -- -- -- nv up ei pl nz na pe nc
  cs=005b ss=0053 ds=0053 es=0053 fs=150b gs=0000  cr2=00093ffe  cr3=001f6000
  005b:000120fc 83c40c         add     esp,+0c
  ##d %6eb88 l 20
  %0006eb88 00 00 12 00 74 34 01 00-d0 eb 06 00 08 00 00 00 ....t4..Pk......
  %0006eb98 00 00 00 00 00 00 00 00-00 00 00 00 e0 5d 01 00 ............`]..
  ##dp %120000
   linaddr   frame   pteframe  state res Dc Au CD WT Us rW Pn state
  %00120000* 02ec1  frame=02ec1  2    0  D  A        U  W  P  resident
  %00120000         vp id=01608  0    0  c  u        U  W  n  pageable
  %00121000         vp id=01609  0    0  c  u        U  W  n  pageable
  %00122000         vp id=0160a  0    0  c  u        U  W  n  pageable
  %00123000         vp id=0160b  0    0  c  u        U  W  n  pageable
  %00124000         vp id=0160c  0    0  c  u        U  W  n  pageable
  %00125000         vp id=0160d  0    0  c  u        U  W  n  pageable
  %00126000         vp id=01635  0    0  c  u        U  W  n  pageable
  %00127000         vp id=01636  0    0  c  u        U  W  n  pageable
  %00128000         vp id=01637  0    0  c  u        U  W  n  pageable
  %00129000         vp id=01638  0    0  c  u        U  W  n  pageable
  %0012a000         vp id=01639  0    0  c  u        U  W  n  pageable
  %0012b000         vp id=0163a  0    0  c  u        U  W  n  pageable
  %0012c000         vp id=0163b  0    0  c  u        U  W  n  pageable
  %0012d000         vp id=0163c  0    0  c  u        U  W  n  pageable
  %0012e000         vp id=0163d  0    0  c  u        U  W  n  pageable
  %0012f000         vp id=0163e  0    0  c  u        U  W  n  pageable
  ##dp %130000
   linaddr   frame   pteframe  state res Dc Au CD WT Us rW Pn state
  %00130000* 02ec1  frame=02ec1  2    0  D  A        U  W  P  resident
  %00130000         vp id=0163f  0    0  c  u        U  W  n  pageable
  %00131000         vp id=01640  0    0  c  u        U  W  n  pageable
  %00132000         vp id=01641  0    0  c  u        U  W  n  pageable
  %00133000         vp id=01642  0    0  c  u        U  W  n  pageable
  %00134000         vp id=01643  0    0  c  u        U  W  n  pageable
  %00135000         vp id=01644  0    0  c  u        U  W  n  pageable
  %00136000         vp id=01645  0    0  c  u        U  W  n  pageable
  %00137000         vp id=01646  0    0  c  u        U  W  n  pageable
  %00138000         vp id=01647  0    0  c  u        U  W  n  pageable
  %00139000         vp id=01648  0    0  c  u        U  W  n  pageable
  %0013a000         vp id=01649  0    0  c  u        U  W  n  pageable
  %0013b000         vp id=0164a  0    0  c  u        U  W  n  pageable
  %0013c000         vp id=0164b  0    0  c  u        U  W  n  pageable
  %0013d000         vp id=0164c  0    0  c  u        U  W  n  pageable
  %0013e000         vp id=0164d  0    0  c  u        U  W  n  pageable
  %0013f000         vp id=0164e  0    0  c  u        U  W  n  pageable
  ##
Ядро разместило записи о 128Кб памяти в таблице страниц начинающейся с линейного адреса %120000.

Главной рабчей процедурой ядра, которая делает это является _VMAllocMem, которая вызывает процедуры _VMReserve, _PGAlloc и _SELAlloc.

Мы можем также захотеть увидеть что случается, когда программа действительно пытается обратиться к памяти. По команде "vsp e" KDB будет перехватывать все ошибки страниц, перед их обработкой и это можно использовать совместно с "zs" (изменить команду по-умолчанию), чтобы собрать статистику по механизму обработки ошибок страниц и его влияния на работу системы. В целях трассировки, легче всего задать точку остановки в начале процедуры _PGPageFault, которая обрабатывает это исключение.

VII. Ядро эмуляции DOS

Компонент эмуляции DOS не упоминается ни в одной из Debugging Handbook и стремится быть проигнорированным разработчиками, поскольку он существует только для совместимости со старыми программами. Однако он занимает около 25% кода в OS2KRNL и заслуживает оценки только как иллюстрация многосторонности архитектуры x86 просессоров.

Существует по-существу три части эмуляции DOS в OS/2: менеджер MVDM, настоящий эмулятор DOS и эмулятор x86. Четвертая часть, виртуальные драйвера устройств необходимые для запуска многих программ DOS, существует отдельно от ядра, но использует вызовы Virtual DevHelp API выполняемые в менеджере MVDM.

Давайте посмотрим работу по трассировке в KDB простой программы "Hello, world", написанной на ассемблере. Мы открываем окно DOS, вследствие чего ядро дает нам VDM с копией "части виртуального ядра DOS" - файл C:\OS2\MDOS\DOSKRNL - загруженной в обычную память чтобы обеспечить сервисы int 21h. Мы теперь запускаем программу HELLO.EXE. Это полный дизассемблированный код:

  --u ac2:0 l 7
  0ac2:00000000 b8c30a         mov     ax,0ac3
  0ac2:00000003 8ed8           mov     ds,ax
  0ac2:00000005 b409           mov     ah,09        ; выводим строку из ds:dx
  0ac2:00000007 ba0000         mov     dx,0000
  0ac2:0000000a cd21           int     21
  0ac2:0000000c b44c           mov     ah,4c
  0ac2:0000000e cd21           int     21
  --d ac3:0 l 10
  0ac3:00000000 48 65 6c 6c 6f 2c 20 77-6f 72 6c 64 0d 0a 24 00 Hello, world..$.
Заметьте, что KDB использует приглашение в виде двух тире "-" вместо обычного "##" чтобы показать, что мы работаем в режиме V86.

После набора "t" несколько раз мы достигаем системного вызова DOS:

  --t
  eax=000009c3 ebx=00000000 ecx=000000ff edx=00000000 esi=00000000 edi=00000100
  eip=0000000a esp=00000100 ebp=0000091c iopl=3 -- vm -- nv up ei pl zr na pe nc
  cs=0ac2 ss=0ac4 ds=0ac3 es=0ab2 fs=0000 gs=0000  cr2=01390000  cr3=001f6000
  0ac2:0000000a cd21           int     21
  --
Однако мы не можем выполнить "t" в этот вызов, поскольку эта инструкция вызывает General Protection Exception (Trap 0D) даже если IOPL равен 3, по-видимому потому, что вход IDT для int 21h неправильный или содержит нулевой указатель:
  --di 21
  0021  TrapG   Sel:Off=0000:00000000     DPL=3 P
Мы задаем точку останова у trap0d в 32 разрядном ядре и продолжаем:
  --br e trap0d
  --g
  Debug register hit
  eax=000009c3 ebx=00000000 ecx=000000ff edx=00000000 esi=00000000 edi=00000100
  eip=fff491bc esp=00006708 ebp=0000091c iopl=3 rf -- -- nv up ei pl zr na pe nc
  cs=0170 ss=0030 ds=0000 es=0000 fs=0000 gs=0000  cr2=01390000  cr3=001f6000
  os2krnl:DOSHIGH32CODE:trap0d:
  0170:fff491bc 6a0d           push    +0d              ;br0
  ##
Этот код вызовет em86opINTnn, чтобы эмулировать программное прерывание и мы скоро выполним "iretd" и вернемся в режим V86. Вызов затем будет передан DOSKRNL в обычной памяти.

Мы оставим на следующий день окончание сказки, где DOSKRNL должен еще обрабатывать обращения к BIOS, которые будут снова вызывать General Protection Exception и будут перенаправлены к виртуальным драйверам (VDD), таким как VBIOS.SYS и VVGA.SYS. Они будут взаимодействовать с SESMGR и физическими драйверами (PDD), чтобы закончить вывод приветствия на экран.

Некоторые дополнительные знания о работе DOS эмуляции в OS/2 могут быть найдены в The Design of OS/2, 1992 Addison-Wesley, by H. M. Deitel and M. S. Kogan, pp. 290-300.

VIII. Как делается shutdown

Поскольку все хорошее когда-нибудь кончается, то Control Program API включает процедуру DosShutdown. Рабочий код находится начиная с обозначения w_Shutdown в DOSHIGH4CODE.

Эта функция отключает драйверы IFS подменив все их точки входа адресом процедуры ShutdownBlock в DOSHIGH2CODE. Все нити таким образом при попытке обратиться к FSD будут блокированы. Некоторые процедуры, однако, остаются нетронутыми для использования в коде закрытия системы: FS_COMMIT, FS_DOPAGEIO, FS_FSCTL, FS_FLUSHBUF и FS_SHUTDOWN. Также для драйверов файловых систем использующих программу подкачки некоторые ключевые точки входа сохраняются на месте: FS_SDCHGFILEPTR, FS_SDFSINFO, FS_SDREAD и FS_SDWRITE. Это дает возможность процедурам управления страниц продолжать выполнять заключительные операции, пока все остальные нити заблокированы.

Мы затем посылаем всем установленным драйверам устройств команду "shutdown" (код команды 1Ch) через пакет запроса. Каждый драйвер вызывается дважды с параметрами 0 и 1 для начала и конца закрытия сисемы, соответственно. Также для каждого драйвера IFS процедура FS_SHUTDOWN вызывается дважды с флагами начала и конца закрытия системы. Между этими вызовами процедуры shutdown$FlushAllSFTs и h_FSD_FlushBuf стабилизируют кешируемые части файловых систем.

IX. Заключение

В то время когда поддержка продукта фирмы IBM кажется становится менее восторженной день ото дня, она становится все более важной для пользователей и разработчиков, чтобы понять внутреннюю организацию системы. Это знание поможет при разработке драйверов и приложений, при создании независимых справочных пособий и даже в исправлении кода системы, если необходимо. С активной поддержкой других организаций OS/2 может и будет процветать. Я думаю что эта статья сделает вклад в понимание фундамента этого внушительного сооружения.
  Аббревиатуры:
  TCB - task control block
  TSD - task 
  IVT - interrupt vector table
  IFS - installable file system
  FSD - file system driver
  SFT - 
  PTDA - Per-Task Data Area
  DDK - device driver kit
  GDT - global descriptor table
  PLDT - pointer local descriptor table

---
Интересные ссылки: Юридическое представительство в арбитражном суде в Новосибирске.

---

---
Комментариев к странице: 0 | Добавить комментарий
---
Редактор: Дмитрий Бан
Оформление: Евгений Кулешов


(C) Russian Underground/2